summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java1
-rw-r--r--core/java/android/alsa/AlsaCardsParser.java116
-rw-r--r--core/java/android/animation/AnimatorInflater.java401
-rw-r--r--core/java/android/animation/AnimatorSet.java12
-rw-r--r--core/java/android/animation/FloatKeyframeSet.java1
-rw-r--r--core/java/android/animation/IntKeyframeSet.java1
-rw-r--r--core/java/android/animation/KeyframeSet.java1
-rw-r--r--core/java/android/animation/Keyframes.java1
-rw-r--r--core/java/android/animation/ObjectAnimator.java25
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java2
-rw-r--r--core/java/android/animation/ValueAnimator.java19
-rw-r--r--core/java/android/annotation/CallSuper.java38
-rw-r--r--core/java/android/annotation/CheckResult.java58
-rw-r--r--core/java/android/annotation/ColorInt.java42
-rw-r--r--core/java/android/annotation/FloatRange.java55
-rw-r--r--core/java/android/annotation/IntRange.java47
-rw-r--r--core/java/android/annotation/Size.java52
-rw-r--r--core/java/android/annotation/TransitionRes.java37
-rw-r--r--core/java/android/app/ActionBar.java29
-rw-r--r--core/java/android/app/Activity.java122
-rw-r--r--core/java/android/app/ActivityManager.java41
-rw-r--r--core/java/android/app/ActivityManagerNative.java256
-rw-r--r--core/java/android/app/ActivityOptions.java31
-rw-r--r--core/java/android/app/ActivityThread.java142
-rw-r--r--core/java/android/app/ActivityTransitionCoordinator.java48
-rw-r--r--core/java/android/app/ActivityTransitionState.java1
-rw-r--r--core/java/android/app/ActivityView.java2
-rw-r--r--core/java/android/app/AlarmManager.java1
-rw-r--r--core/java/android/app/AlertDialog.java465
-rw-r--r--core/java/android/app/AppImportanceMonitor.java2
-rw-r--r--core/java/android/app/AppOpsManager.java12
-rw-r--r--core/java/android/app/ApplicationPackageManager.java14
-rw-r--r--core/java/android/app/ApplicationThreadNative.java59
-rw-r--r--core/java/android/app/AssistContent.aidl19
-rw-r--r--core/java/android/app/AssistContent.java124
-rw-r--r--core/java/android/app/AssistStructure.aidl19
-rw-r--r--core/java/android/app/AssistStructure.java618
-rw-r--r--core/java/android/app/BackStackRecord.java1
-rw-r--r--core/java/android/app/ContextImpl.java692
-rw-r--r--core/java/android/app/DatePickerDialog.java3
-rw-r--r--core/java/android/app/Dialog.java58
-rw-r--r--core/java/android/app/Fragment.java12
-rw-r--r--core/java/android/app/FragmentManager.java5
-rw-r--r--core/java/android/app/IActivityContainer.aidl1
-rw-r--r--core/java/android/app/IActivityManager.java37
-rw-r--r--core/java/android/app/IApplicationThread.java18
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/ISearchManager.aidl2
-rw-r--r--core/java/android/app/IWallpaperManager.aidl20
-rw-r--r--core/java/android/app/Instrumentation.java6
-rw-r--r--core/java/android/app/Notification.java887
-rw-r--r--core/java/android/app/NotificationManager.java51
-rw-r--r--core/java/android/app/ResourcesManager.java162
-rw-r--r--core/java/android/app/SearchDialog.java6
-rw-r--r--core/java/android/app/SearchManager.java6
-rw-r--r--core/java/android/app/SearchableInfo.java4
-rw-r--r--core/java/android/app/Service.java2
-rw-r--r--core/java/android/app/SharedPreferencesImpl.java11
-rw-r--r--core/java/android/app/SystemServiceRegistry.java784
-rw-r--r--core/java/android/app/TimePickerDialog.java80
-rw-r--r--core/java/android/app/UiModeManager.java7
-rw-r--r--core/java/android/app/VoiceInteractor.aidl19
-rw-r--r--core/java/android/app/VoiceInteractor.java301
-rw-r--r--core/java/android/app/WallpaperManager.java61
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java37
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java123
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java383
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl50
-rw-r--r--core/java/android/app/backup/BackupAgent.java11
-rw-r--r--core/java/android/app/backup/BackupDataOutput.java2
-rw-r--r--core/java/android/app/job/JobParameters.java1
-rw-r--r--core/java/android/app/job/JobScheduler.java2
-rw-r--r--core/java/android/app/usage/UsageEvents.java5
-rw-r--r--core/java/android/app/usage/UsageStatsManagerInternal.java10
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java50
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java7
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java12
-rw-r--r--core/java/android/bluetooth/BluetoothActivityEnergyInfo.java21
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java3
-rw-r--r--core/java/android/bluetooth/BluetoothHeadsetClientCall.java25
-rw-r--r--core/java/android/bluetooth/le/TruncatedFilter.java3
-rw-r--r--core/java/android/content/ContentProvider.java6
-rw-r--r--core/java/android/content/ContentProviderOperation.java16
-rw-r--r--core/java/android/content/ContentProviderResult.java1
-rw-r--r--core/java/android/content/ContentResolver.java4
-rw-r--r--core/java/android/content/Context.java186
-rw-r--r--core/java/android/content/ContextWrapper.java19
-rw-r--r--core/java/android/content/IContentProvider.java4
-rw-r--r--core/java/android/content/Intent.java53
-rw-r--r--core/java/android/content/RestrictionEntry.java5
-rw-r--r--core/java/android/content/SharedPreferences.java12
-rw-r--r--core/java/android/content/UndoManager.java44
-rw-r--r--core/java/android/content/UndoOwner.java22
-rw-r--r--core/java/android/content/pm/ActivityInfo.java10
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java9
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/content/pm/LauncherActivityInfo.java40
-rw-r--r--core/java/android/content/pm/LauncherApps.java1
-rw-r--r--core/java/android/content/pm/PackageManager.java27
-rw-r--r--core/java/android/content/pm/PackageParser.java155
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java271
-rw-r--r--core/java/android/content/res/ColorStateList.java440
-rw-r--r--core/java/android/content/res/Resources.java432
-rw-r--r--core/java/android/content/res/ResourcesKey.java29
-rw-r--r--core/java/android/content/res/StringBlock.java2
-rw-r--r--core/java/android/content/res/TypedArray.java372
-rw-r--r--core/java/android/database/DatabaseUtils.java28
-rw-r--r--core/java/android/gesture/GestureLibraries.java3
-rw-r--r--core/java/android/gesture/GestureOverlayView.java7
-rw-r--r--core/java/android/hardware/Camera.java1
-rw-r--r--core/java/android/hardware/ICameraService.aidl2
-rw-r--r--core/java/android/hardware/ICameraServiceListener.aidl2
-rw-r--r--core/java/android/hardware/Sensor.java92
-rw-r--r--core/java/android/hardware/camera2/CameraAccessException.java10
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java230
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java16
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java327
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java148
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java160
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java214
-rw-r--r--core/java/android/hardware/camera2/ICameraDeviceUser.aidl4
-rw-r--r--core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java2
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java59
-rw-r--r--core/java/android/hardware/camera2/legacy/BurstHolder.java4
-rw-r--r--core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java9
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java6
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java2
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java1
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java59
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java1
-rw-r--r--core/java/android/hardware/camera2/legacy/ParameterUtils.java2
-rw-r--r--core/java/android/hardware/camera2/legacy/PerfMeasurement.java1
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestHandlerThread.java8
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestHolder.java1
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestThreadManager.java9
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java3
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java3
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java2
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java3
-rw-r--r--core/java/android/hardware/camera2/params/LensShadingMap.java46
-rw-r--r--core/java/android/hardware/camera2/params/OutputConfiguration.aidl20
-rw-r--r--core/java/android/hardware/camera2/params/OutputConfiguration.java165
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfigurationMap.java1
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java8
-rw-r--r--core/java/android/hardware/display/VirtualDisplay.java1
-rw-r--r--core/java/android/hardware/input/InputManager.java2
-rw-r--r--core/java/android/hardware/radio/RadioManager.java1308
-rw-r--r--core/java/android/hardware/radio/RadioMetadata.java449
-rw-r--r--core/java/android/hardware/radio/RadioModule.java218
-rw-r--r--core/java/android/hardware/radio/RadioTuner.java302
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTriggerModule.java3
-rw-r--r--core/java/android/hardware/usb/UsbDevice.java1
-rw-r--r--core/java/android/hardware/usb/UsbManager.java10
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java3
-rw-r--r--core/java/android/inputmethodservice/Keyboard.java6
-rw-r--r--core/java/android/net/ConnectivityManager.java4
-rw-r--r--core/java/android/net/DhcpResults.java1
-rw-r--r--core/java/android/net/IConnectivityManager.aidl3
-rw-r--r--core/java/android/net/Network.java1
-rw-r--r--core/java/android/net/NetworkAgent.java3
-rw-r--r--core/java/android/net/NetworkFactory.java3
-rw-r--r--core/java/android/net/NetworkRequest.java2
-rw-r--r--core/java/android/net/NetworkStats.java160
-rw-r--r--core/java/android/net/NetworkUtils.java44
-rw-r--r--core/java/android/net/PacProxySelector.java5
-rw-r--r--core/java/android/net/ProxyInfo.java7
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java2
-rw-r--r--core/java/android/net/StaticIpConfiguration.java1
-rw-r--r--core/java/android/net/Uri.java9
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java527
-rw-r--r--core/java/android/net/http/AndroidHttpClientConnection.java460
-rw-r--r--core/java/android/net/http/CertificateChainValidator.java279
-rw-r--r--core/java/android/net/http/Connection.java575
-rw-r--r--core/java/android/net/http/ConnectionThread.java137
-rw-r--r--core/java/android/net/http/DelegatingSSLSession.java158
-rw-r--r--core/java/android/net/http/EventHandler.java131
-rw-r--r--core/java/android/net/http/Headers.java521
-rw-r--r--core/java/android/net/http/HttpAuthHeader.java424
-rw-r--r--core/java/android/net/http/HttpConnection.java93
-rw-r--r--core/java/android/net/http/HttpResponseCache.java4
-rw-r--r--core/java/android/net/http/HttpsConnection.java433
-rw-r--r--core/java/android/net/http/IdleCache.java175
-rw-r--r--core/java/android/net/http/LoggingEventHandler.java92
-rw-r--r--core/java/android/net/http/Request.java526
-rw-r--r--core/java/android/net/http/RequestHandle.java466
-rw-r--r--core/java/android/net/http/RequestQueue.java542
-rw-r--r--core/java/android/net/http/X509TrustManagerExtensions.java2
-rw-r--r--core/java/android/os/AsyncTask.java2
-rw-r--r--core/java/android/os/BaseBundle.java113
-rw-r--r--core/java/android/os/BatteryManager.java17
-rw-r--r--core/java/android/os/BatteryStats.java896
-rw-r--r--core/java/android/os/Build.java79
-rw-r--r--core/java/android/os/Bundle.java137
-rw-r--r--core/java/android/os/CommonClock.java1
-rw-r--r--core/java/android/os/Debug.java71
-rw-r--r--core/java/android/os/INetworkManagementService.aidl12
-rw-r--r--core/java/android/os/IProcessInfoService.aidl (renamed from core/java/android/net/http/RequestFeeder.java)33
-rw-r--r--core/java/android/os/IUserManager.aidl3
-rw-r--r--core/java/android/os/Looper.java115
-rw-r--r--core/java/android/os/MessageQueue.java413
-rw-r--r--core/java/android/os/Parcel.java34
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java91
-rw-r--r--core/java/android/os/PersistableBundle.java46
-rw-r--r--core/java/android/os/PooledStringReader.java50
-rw-r--r--core/java/android/os/PooledStringWriter.java76
-rw-r--r--core/java/android/os/StrictMode.java74
-rw-r--r--core/java/android/os/UserHandle.java1
-rw-r--r--core/java/android/os/UserManager.java38
-rw-r--r--core/java/android/preference/DialogPreference.java10
-rw-r--r--core/java/android/preference/GenericInflater.java5
-rw-r--r--core/java/android/preference/ListPreference.java5
-rw-r--r--core/java/android/preference/MultiCheckPreference.java5
-rw-r--r--core/java/android/preference/MultiSelectListPreference.java5
-rw-r--r--core/java/android/preference/Preference.java20
-rw-r--r--core/java/android/preference/PreferenceActivity.java20
-rw-r--r--core/java/android/preference/PreferenceFragment.java12
-rw-r--r--core/java/android/preference/PreferenceGroup.java3
-rw-r--r--core/java/android/preference/PreferenceManager.java5
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java6
-rw-r--r--core/java/android/preference/SwitchPreference.java5
-rw-r--r--core/java/android/preference/TwoStatePreference.java5
-rw-r--r--core/java/android/print/PrintAttributes.java109
-rw-r--r--core/java/android/print/PrinterCapabilitiesInfo.java91
-rw-r--r--core/java/android/printservice/PrintService.java5
-rw-r--r--core/java/android/provider/Browser.java1
-rw-r--r--core/java/android/provider/CallLog.java66
-rw-r--r--core/java/android/provider/Contacts.java49
-rw-r--r--core/java/android/provider/ContactsContract.java425
-rw-r--r--core/java/android/provider/DocumentsProvider.java3
-rw-r--r--core/java/android/provider/Settings.java656
-rw-r--r--core/java/android/provider/VoicemailContract.java161
-rw-r--r--core/java/android/security/keymaster/KeyCharacteristics.java2
-rw-r--r--core/java/android/security/keymaster/KeymasterBlobArgument.java1
-rw-r--r--core/java/android/security/keymaster/KeymasterBooleanArgument.java1
-rw-r--r--core/java/android/security/keymaster/KeymasterDateArgument.java2
-rw-r--r--core/java/android/security/keymaster/KeymasterIntArgument.java1
-rw-r--r--core/java/android/security/keymaster/KeymasterLongArgument.java1
-rw-r--r--core/java/android/security/keymaster/OperationResult.java2
-rw-r--r--core/java/android/service/carrier/CarrierMessagingService.java3
-rw-r--r--core/java/android/service/chooser/ChooserTarget.aidl19
-rw-r--r--core/java/android/service/chooser/ChooserTarget.java248
-rw-r--r--core/java/android/service/chooser/ChooserTargetService.java130
-rw-r--r--core/java/android/service/chooser/IChooserTargetResult.aidl (renamed from core/java/android/net/http/HttpLog.java)30
-rw-r--r--core/java/android/service/chooser/IChooserTargetService.aidl30
-rw-r--r--core/java/android/service/dreams/DreamService.java23
-rw-r--r--core/java/android/service/fingerprint/FingerprintManager.java68
-rw-r--r--core/java/android/service/fingerprint/FingerprintUtils.java60
-rw-r--r--core/java/android/service/fingerprint/IFingerprintService.aidl10
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java15
-rw-r--r--core/java/android/service/restrictions/RestrictionsReceiver.java1
-rw-r--r--core/java/android/service/trust/TrustAgentService.java17
-rw-r--r--core/java/android/service/voice/IVoiceInteractionSession.aidl6
-rw-r--r--core/java/android/service/voice/IVoiceInteractionSessionService.aidl2
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java41
-rw-r--r--core/java/android/service/voice/VoiceInteractionServiceInfo.java4
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java370
-rw-r--r--core/java/android/service/voice/VoiceInteractionSessionService.java12
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java2
-rw-r--r--core/java/android/speech/tts/AudioPlaybackQueueItem.java1
-rw-r--r--core/java/android/speech/tts/BlockingAudioTrack.java1
-rw-r--r--core/java/android/speech/tts/ITextToSpeechCallback.aidl2
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java12
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java81
-rw-r--r--core/java/android/speech/tts/TtsEngines.java1
-rw-r--r--core/java/android/speech/tts/UtteranceProgressListener.java19
-rw-r--r--core/java/android/text/DynamicLayout.java30
-rw-r--r--core/java/android/text/Html.java2
-rw-r--r--core/java/android/text/Layout.java2
-rw-r--r--core/java/android/text/MeasuredText.java72
-rw-r--r--core/java/android/text/SpannableStringBuilder.java569
-rw-r--r--core/java/android/text/StaticLayout.java641
-rw-r--r--core/java/android/text/TextPaint.java4
-rw-r--r--core/java/android/text/TextUtils.java7
-rwxr-xr-xcore/java/android/text/format/DateFormat.java2
-rw-r--r--core/java/android/text/style/ForegroundColorSpan.java4
-rw-r--r--core/java/android/text/style/ImageSpan.java5
-rw-r--r--core/java/android/text/style/QuoteSpan.java4
-rw-r--r--core/java/android/text/util/Linkify.java5
-rw-r--r--core/java/android/text/util/Rfc822Token.java14
-rw-r--r--core/java/android/transition/ChangeScroll.java2
-rw-r--r--core/java/android/transition/CircularPropagation.java1
-rw-r--r--core/java/android/transition/SidePropagation.java1
-rw-r--r--core/java/android/transition/Transition.java12
-rw-r--r--core/java/android/transition/TransitionInflater.java8
-rw-r--r--core/java/android/transition/TransitionManager.java56
-rw-r--r--core/java/android/transition/TransitionPropagation.java1
-rw-r--r--core/java/android/transition/Visibility.java7
-rw-r--r--core/java/android/util/AtomicFile.java2
-rw-r--r--core/java/android/util/AttributeSet.java2
-rw-r--r--core/java/android/util/DebugUtils.java80
-rw-r--r--core/java/android/util/LocalLog.java2
-rw-r--r--core/java/android/util/StateSet.java83
-rw-r--r--core/java/android/util/TypedValue.java3
-rw-r--r--core/java/android/view/ActionMode.java77
-rw-r--r--core/java/android/view/Choreographer.java20
-rw-r--r--core/java/android/view/ContextMenu.java6
-rw-r--r--core/java/android/view/ContextThemeWrapper.java15
-rw-r--r--core/java/android/view/Display.java2
-rw-r--r--core/java/android/view/DisplayAdjustments.java30
-rw-r--r--core/java/android/view/DisplayInfo.java28
-rw-r--r--core/java/android/view/DisplayListCanvas.java344
-rw-r--r--core/java/android/view/FrameInfo.java118
-rw-r--r--core/java/android/view/FrameStats.java3
-rw-r--r--core/java/android/view/GLES20Canvas.java999
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java66
-rw-r--r--core/java/android/view/GhostView.java10
-rw-r--r--core/java/android/view/HardwareCanvas.java121
-rw-r--r--core/java/android/view/HardwareLayer.java2
-rw-r--r--core/java/android/view/HardwareRenderer.java11
-rw-r--r--core/java/android/view/IWindowManager.aidl13
-rw-r--r--core/java/android/view/IWindowSession.aidl3
-rw-r--r--core/java/android/view/KeyEvent.java1
-rw-r--r--core/java/android/view/LayoutInflater.java105
-rw-r--r--core/java/android/view/Menu.java9
-rw-r--r--core/java/android/view/MenuInflater.java35
-rw-r--r--core/java/android/view/MenuItem.java33
-rw-r--r--core/java/android/view/PhoneFallbackEventHandler.java288
-rw-r--r--core/java/android/view/PhoneLayoutInflater.java73
-rw-r--r--core/java/android/view/PhoneWindow.java4818
-rw-r--r--core/java/android/view/PointerIcon.java5
-rw-r--r--core/java/android/view/RenderNode.java44
-rw-r--r--core/java/android/view/RenderNodeAnimator.java4
-rw-r--r--core/java/android/view/SubMenu.java8
-rw-r--r--core/java/android/view/Surface.java3
-rw-r--r--core/java/android/view/ThreadedRenderer.java84
-rw-r--r--core/java/android/view/View.java621
-rw-r--r--core/java/android/view/ViewAssistStructure.java35
-rw-r--r--core/java/android/view/ViewGroup.java144
-rw-r--r--core/java/android/view/ViewParent.java23
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java113
-rw-r--r--core/java/android/view/ViewStub.java35
-rw-r--r--core/java/android/view/Window.java57
-rw-r--r--core/java/android/view/WindowCallbackWrapper.java5
-rw-r--r--core/java/android/view/WindowManager.java29
-rw-r--r--core/java/android/view/WindowManagerGlobal.java45
-rw-r--r--core/java/android/view/WindowManagerInternal.java44
-rw-r--r--core/java/android/view/WindowManagerPolicy.java12
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java1
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java2
-rw-r--r--core/java/android/view/animation/Animation.java7
-rw-r--r--core/java/android/view/animation/AnimationUtils.java9
-rw-r--r--core/java/android/view/animation/ClipRectAnimation.java13
-rw-r--r--core/java/android/view/animation/ClipRectLRAnimation.java49
-rw-r--r--core/java/android/view/animation/ClipRectTBAnimation.java49
-rw-r--r--core/java/android/view/animation/LayoutAnimationController.java6
-rw-r--r--core/java/android/view/animation/Transformation.java19
-rw-r--r--core/java/android/view/animation/TranslateAnimation.java34
-rw-r--r--core/java/android/view/animation/TranslateXAnimation.java55
-rw-r--r--core/java/android/view/animation/TranslateYAnimation.java55
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java1
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java20
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtypeArray.java64
-rw-r--r--core/java/android/webkit/CookieManager.java5
-rw-r--r--core/java/android/webkit/CookieSyncManager.java1
-rw-r--r--core/java/android/webkit/LegacyErrorStrings.java33
-rw-r--r--core/java/android/webkit/WebBackForwardList.java1
-rw-r--r--core/java/android/webkit/WebChromeClient.java24
-rw-r--r--core/java/android/webkit/WebMessage.java62
-rw-r--r--core/java/android/webkit/WebMessagePort.java86
-rw-r--r--core/java/android/webkit/WebResourceError.java39
-rw-r--r--core/java/android/webkit/WebResourceRequest.java3
-rw-r--r--core/java/android/webkit/WebResourceResponse.java59
-rw-r--r--core/java/android/webkit/WebResourceResponseBase.java68
-rw-r--r--core/java/android/webkit/WebSettings.java25
-rw-r--r--core/java/android/webkit/WebSyncManager.java5
-rw-r--r--core/java/android/webkit/WebView.java167
-rw-r--r--core/java/android/webkit/WebViewClient.java67
-rw-r--r--core/java/android/webkit/WebViewDatabase.java1
-rw-r--r--core/java/android/webkit/WebViewDelegate.java10
-rw-r--r--core/java/android/webkit/WebViewFactory.java8
-rw-r--r--core/java/android/webkit/WebViewProvider.java10
-rw-r--r--core/java/android/widget/AbsListView.java52
-rw-r--r--core/java/android/widget/AbsSeekBar.java39
-rw-r--r--core/java/android/widget/AbsSpinner.java22
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java56
-rw-r--r--core/java/android/widget/ActionMenuView.java44
-rw-r--r--core/java/android/widget/ActivityChooserView.java5
-rw-r--r--core/java/android/widget/AdapterView.java32
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java13
-rw-r--r--core/java/android/widget/AdapterViewFlipper.java13
-rw-r--r--core/java/android/widget/ArrayAdapter.java93
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java3
-rw-r--r--core/java/android/widget/Button.java13
-rw-r--r--core/java/android/widget/CalendarView.java31
-rw-r--r--core/java/android/widget/CheckBox.java13
-rw-r--r--core/java/android/widget/CheckedTextView.java66
-rw-r--r--core/java/android/widget/Chronometer.java13
-rw-r--r--core/java/android/widget/CompoundButton.java79
-rw-r--r--core/java/android/widget/CursorAdapter.java45
-rw-r--r--core/java/android/widget/DatePicker.java32
-rwxr-xr-xcore/java/android/widget/DatePickerCalendarDelegate.java63
-rw-r--r--core/java/android/widget/DateTimeView.java3
-rw-r--r--core/java/android/widget/DayPickerView.java21
-rw-r--r--core/java/android/widget/DigitalClock.java15
-rw-r--r--core/java/android/widget/EdgeEffect.java4
-rw-r--r--core/java/android/widget/EditText.java17
-rw-r--r--core/java/android/widget/Editor.java1111
-rw-r--r--core/java/android/widget/ExpandableListView.java13
-rw-r--r--core/java/android/widget/FastScroller.java9
-rw-r--r--core/java/android/widget/FrameLayout.java37
-rw-r--r--core/java/android/widget/Gallery.java17
-rw-r--r--core/java/android/widget/GridLayout.java79
-rw-r--r--core/java/android/widget/GridView.java12
-rw-r--r--core/java/android/widget/HorizontalScrollView.java23
-rw-r--r--core/java/android/widget/ImageButton.java13
-rw-r--r--core/java/android/widget/ImageSwitcher.java17
-rw-r--r--core/java/android/widget/ImageView.java27
-rw-r--r--core/java/android/widget/LinearLayout.java18
-rw-r--r--core/java/android/widget/ListView.java15
-rw-r--r--core/java/android/widget/MediaController.java18
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java13
-rw-r--r--core/java/android/widget/NumberPicker.java5
-rw-r--r--core/java/android/widget/PopupMenu.java26
-rw-r--r--core/java/android/widget/PopupWindow.java787
-rw-r--r--core/java/android/widget/ProgressBar.java197
-rw-r--r--core/java/android/widget/QuickContactBadge.java31
-rw-r--r--core/java/android/widget/RadialTimePickerView.java741
-rw-r--r--core/java/android/widget/RadioButton.java13
-rw-r--r--core/java/android/widget/RadioGroup.java21
-rw-r--r--core/java/android/widget/RatingBar.java84
-rw-r--r--core/java/android/widget/RelativeLayout.java145
-rw-r--r--core/java/android/widget/RemoteViews.java3
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java2
-rw-r--r--core/java/android/widget/ResourceCursorAdapter.java41
-rw-r--r--core/java/android/widget/ScrollBarDrawable.java239
-rw-r--r--core/java/android/widget/ScrollView.java22
-rw-r--r--core/java/android/widget/SearchView.java13
-rw-r--r--core/java/android/widget/SeekBar.java45
-rw-r--r--core/java/android/widget/SimpleAdapter.java59
-rw-r--r--core/java/android/widget/SimpleMonthAdapter.java13
-rw-r--r--core/java/android/widget/SimpleMonthView.java247
-rw-r--r--core/java/android/widget/SlidingDrawer.java12
-rw-r--r--core/java/android/widget/SpellChecker.java6
-rw-r--r--core/java/android/widget/Spinner.java234
-rw-r--r--core/java/android/widget/SpinnerAdapter.java13
-rw-r--r--core/java/android/widget/StackView.java17
-rw-r--r--core/java/android/widget/SuggestionsAdapter.java39
-rw-r--r--core/java/android/widget/Switch.java215
-rw-r--r--core/java/android/widget/TabHost.java21
-rw-r--r--core/java/android/widget/TabWidget.java34
-rw-r--r--core/java/android/widget/TableLayout.java14
-rw-r--r--core/java/android/widget/TableRow.java13
-rw-r--r--core/java/android/widget/TextSwitcher.java13
-rw-r--r--core/java/android/widget/TextView.java772
-rw-r--r--core/java/android/widget/TextViewWithCircularIndicator.java43
-rw-r--r--core/java/android/widget/TimePicker.java26
-rw-r--r--core/java/android/widget/TimePickerClockDelegate.java68
-rw-r--r--core/java/android/widget/TimePickerSpinnerDelegate.java12
-rw-r--r--core/java/android/widget/Toast.java5
-rw-r--r--core/java/android/widget/ToggleButton.java13
-rw-r--r--core/java/android/widget/Toolbar.java179
-rw-r--r--core/java/android/widget/TwoLineListItem.java13
-rw-r--r--core/java/android/widget/VideoView.java13
-rw-r--r--core/java/android/widget/ViewAnimator.java18
-rw-r--r--core/java/android/widget/ViewFlipper.java13
-rw-r--r--core/java/android/widget/ViewSwitcher.java13
-rw-r--r--core/java/android/widget/YearPickerView.java45
-rw-r--r--core/java/android/widget/ZoomButton.java13
-rw-r--r--core/java/android/widget/ZoomControls.java13
-rw-r--r--core/java/com/android/internal/alsa/AlsaCardsParser.java225
-rw-r--r--core/java/com/android/internal/alsa/AlsaDevicesParser.java (renamed from core/java/android/alsa/AlsaDevicesParser.java)175
-rw-r--r--core/java/com/android/internal/alsa/LineTokenizer.java (renamed from core/java/android/alsa/LineTokenizer.java)2
-rw-r--r--core/java/com/android/internal/app/AlertActivity.java2
-rw-r--r--core/java/com/android/internal/app/AlertController.java157
-rw-r--r--core/java/com/android/internal/app/DumpHeapActivity.java106
-rw-r--r--core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl24
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl5
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractor.aidl4
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractorCallback.aidl5
-rw-r--r--core/java/com/android/internal/app/IntentForwarderActivity.java1
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java78
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java23
-rw-r--r--core/java/com/android/internal/app/WindowDecorActionBar.java15
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java42
-rw-r--r--core/java/com/android/internal/http/multipart/ByteArrayPartSource.java86
-rw-r--r--core/java/com/android/internal/http/multipart/FilePart.java259
-rw-r--r--core/java/com/android/internal/http/multipart/FilePartSource.java131
-rw-r--r--core/java/com/android/internal/http/multipart/MultipartEntity.java236
-rw-r--r--core/java/com/android/internal/http/multipart/Part.java445
-rw-r--r--core/java/com/android/internal/http/multipart/PartBase.java150
-rw-r--r--core/java/com/android/internal/http/multipart/PartSource.java72
-rw-r--r--core/java/com/android/internal/http/multipart/StringPart.java156
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodUtils.java97
-rw-r--r--core/java/com/android/internal/net/VpnInfo.aidl19
-rw-r--r--core/java/com/android/internal/net/VpnInfo.java69
-rw-r--r--core/java/com/android/internal/os/BatterySipper.java38
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHelper.java538
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java858
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java9
-rw-r--r--core/java/com/android/internal/os/PowerProfile.java27
-rw-r--r--core/java/com/android/internal/os/ProcessCpuTracker.java113
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java1
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java6
-rw-r--r--core/java/com/android/internal/policy/IPolicy.java39
-rw-r--r--core/java/com/android/internal/policy/PolicyManager.java71
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl39
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl37
-rw-r--r--core/java/com/android/internal/transition/EpicenterClipReveal.java136
-rw-r--r--core/java/com/android/internal/util/DumpUtils.java7
-rw-r--r--core/java/com/android/internal/util/UserIcons.java13
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java58
-rw-r--r--core/java/com/android/internal/view/StandaloneActionMode.java6
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItem.java49
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java6
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java4
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java58
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java21
-rw-r--r--core/java/com/android/internal/widget/AccessibleDateAnimator.java4
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java14
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java8
-rw-r--r--core/java/com/android/internal/widget/ActionBarOverlayLayout.java1
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java6
-rw-r--r--core/java/com/android/internal/widget/ButtonBarLayout.java91
-rw-r--r--core/java/com/android/internal/widget/ExploreByTouchHelper.java8
-rw-r--r--core/java/com/android/internal/widget/FaceUnlockView.java67
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl1
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java946
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java47
-rw-r--r--core/java/com/android/internal/widget/PagerAdapter.java320
-rw-r--r--core/java/com/android/internal/widget/ResolverDrawerLayout.java6
-rw-r--r--core/java/com/android/internal/widget/ScrollingTabContainerView.java48
-rw-r--r--core/java/com/android/internal/widget/SizeAdaptiveLayout.java442
-rw-r--r--core/java/com/android/internal/widget/SubtitleView.java1
-rw-r--r--core/java/com/android/internal/widget/ToolbarWidgetWrapper.java2
-rw-r--r--core/java/com/android/internal/widget/ViewPager.java2866
-rw-r--r--core/java/com/android/internal/widget/WaveView.java663
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/Ease.java132
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/GlowPadView.java1383
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/PointCloud.java225
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java229
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/Tweener.java177
-rw-r--r--core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java380
-rw-r--r--core/java/com/android/server/backup/SystemBackupAgent.java4
-rw-r--r--core/java/com/android/server/net/NetlinkTracker.java2
-rw-r--r--core/java/org/apache/http/conn/ConnectTimeoutException.java69
-rw-r--r--core/java/org/apache/http/conn/scheme/HostNameResolver.java47
-rw-r--r--core/java/org/apache/http/conn/scheme/LayeredSocketFactory.java77
-rw-r--r--core/java/org/apache/http/conn/scheme/SocketFactory.java143
-rw-r--r--core/java/org/apache/http/conn/ssl/AbstractVerifier.java288
-rw-r--r--core/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java59
-rw-r--r--core/java/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java67
-rw-r--r--core/java/org/apache/http/conn/ssl/SSLSocketFactory.java408
-rw-r--r--core/java/org/apache/http/conn/ssl/StrictHostnameVerifier.java74
-rw-r--r--core/java/org/apache/http/conn/ssl/X509HostnameVerifier.java91
-rw-r--r--core/java/org/apache/http/conn/ssl/package.html40
-rw-r--r--core/java/org/apache/http/params/CoreConnectionPNames.java136
-rw-r--r--core/java/org/apache/http/params/HttpConnectionParams.java229
-rw-r--r--core/java/org/apache/http/params/HttpParams.java192
549 files changed, 36112 insertions, 20658 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 3f1845a..9d6aa13 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -27,7 +27,6 @@ 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;
diff --git a/core/java/android/alsa/AlsaCardsParser.java b/core/java/android/alsa/AlsaCardsParser.java
deleted file mode 100644
index 8b44881..0000000
--- a/core/java/android/alsa/AlsaCardsParser.java
+++ /dev/null
@@ -1,116 +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.alsa;
-
-import android.util.Slog;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.Vector;
-
-/**
- * @hide Retrieves information from an ALSA "cards" file.
- */
-public class AlsaCardsParser {
- private static final String TAG = "AlsaCardsParser";
-
- private static LineTokenizer tokenizer_ = new LineTokenizer(" :[]");
-
- public class AlsaCardRecord {
- public int mCardNum = -1;
- public String mField1 = "";
- public String mCardName = "";
- public String mCardDescription = "";
-
- public AlsaCardRecord() {}
-
- public boolean parse(String line, int lineIndex) {
- int tokenIndex = 0;
- int delimIndex = 0;
- if (lineIndex == 0) {
- // line # (skip)
- tokenIndex = tokenizer_.nextToken(line, tokenIndex);
- delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
-
- // mField1
- tokenIndex = tokenizer_.nextToken(line, delimIndex);
- delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
- mField1 = line.substring(tokenIndex, delimIndex);
-
- // mCardName
- tokenIndex = tokenizer_.nextToken(line, delimIndex);
- // delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
- mCardName = line.substring(tokenIndex);
- // done
- } else if (lineIndex == 1) {
- tokenIndex = tokenizer_.nextToken(line, 0);
- if (tokenIndex != -1) {
- mCardDescription = line.substring(tokenIndex);
- }
- }
-
- return true;
- }
-
- public String textFormat() {
- return mCardName + " : " + mCardDescription;
- }
- }
-
- private Vector<AlsaCardRecord> cardRecords_ = new Vector<AlsaCardRecord>();
-
- public void scan() {
- cardRecords_.clear();
- final String cardsFilePath = "/proc/asound/cards";
- File cardsFile = new File(cardsFilePath);
- try {
- FileReader reader = new FileReader(cardsFile);
- BufferedReader bufferedReader = new BufferedReader(reader);
- String line = "";
- while ((line = bufferedReader.readLine()) != null) {
- AlsaCardRecord cardRecord = new AlsaCardRecord();
- cardRecord.parse(line, 0);
- cardRecord.parse(line = bufferedReader.readLine(), 1);
- cardRecords_.add(cardRecord);
- }
- reader.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- public AlsaCardRecord getCardRecordAt(int index) {
- return cardRecords_.get(index);
- }
-
- public int getNumCardRecords() {
- return cardRecords_.size();
- }
-
- public void Log() {
- int numCardRecs = getNumCardRecords();
- for (int index = 0; index < numCardRecs; ++index) {
- Slog.w(TAG, "usb:" + getCardRecordAt(index).textFormat());
- }
- }
-
- public AlsaCardsParser() {}
-}
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 688d7e4..021194c 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -15,6 +15,7 @@
*/
package android.animation;
+import android.annotation.AnimatorRes;
import android.content.Context;
import android.content.res.ConfigurationBoundResourceCache;
import android.content.res.ConstantState;
@@ -66,8 +67,7 @@ public class AnimatorInflater {
private static final int VALUE_TYPE_FLOAT = 0;
private static final int VALUE_TYPE_INT = 1;
private static final int VALUE_TYPE_PATH = 2;
- private static final int VALUE_TYPE_COLOR = 4;
- private static final int VALUE_TYPE_CUSTOM = 5;
+ private static final int VALUE_TYPE_COLOR = 3;
private static final boolean DBG_ANIMATOR_INFLATER = false;
@@ -82,7 +82,7 @@ public class AnimatorInflater {
* @return The animator object reference by the specified id
* @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
*/
- public static Animator loadAnimator(Context context, int id)
+ public static Animator loadAnimator(Context context, @AnimatorRes int id)
throws NotFoundException {
return loadAnimator(context.getResources(), context.getTheme(), id);
}
@@ -296,39 +296,50 @@ public class AnimatorInflater {
}
}
- /**
- * @param anim The animator, must not be null
- * @param arrayAnimator Incoming typed array for Animator's attributes.
- * @param arrayObjectAnimator Incoming typed array for Object Animator's
- * attributes.
- * @param pixelSize The relative pixel size, used to calculate the
- * maximum error for path animations.
- */
- private static void parseAnimatorFromTypeArray(ValueAnimator anim,
- TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) {
- long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
-
- long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
-
- int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType,
- VALUE_TYPE_FLOAT);
-
- TypeEvaluator evaluator = null;
+ private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
+ int valueFromId, int valueToId, String propertyName) {
boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
- TypedValue tvFrom = arrayAnimator.peekValue(R.styleable.Animator_valueFrom);
+ TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
boolean hasFrom = (tvFrom != null);
int fromType = hasFrom ? tvFrom.type : 0;
- TypedValue tvTo = arrayAnimator.peekValue(R.styleable.Animator_valueTo);
+ TypedValue tvTo = styledAttributes.peekValue(valueToId);
boolean hasTo = (tvTo != null);
int toType = hasTo ? tvTo.type : 0;
- // TODO: Further clean up this part of code into 4 types : path, color,
- // integer and float.
+ PropertyValuesHolder returnValue = null;
+
if (valueType == VALUE_TYPE_PATH) {
- evaluator = setupAnimatorForPath(anim, arrayAnimator);
+ String fromString = styledAttributes.getString(valueFromId);
+ String toString = styledAttributes.getString(valueToId);
+ PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
+ PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+
+ if (nodesFrom != null || nodesTo != null) {
+ if (nodesFrom != null) {
+ TypeEvaluator evaluator =
+ new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
+ if (nodesTo != null) {
+ if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+ throw new InflateException(" Can't morph from " + fromString + " to " +
+ toString);
+ }
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ nodesFrom, nodesTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ (Object) nodesFrom);
+ }
+ } else if (nodesTo != null) {
+ TypeEvaluator evaluator =
+ new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ (Object) nodesTo);
+ }
+ }
} else {
+ TypeEvaluator evaluator = null;
// Integer and float value types are handled here.
if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
@@ -338,7 +349,101 @@ public class AnimatorInflater {
getFloats = false;
evaluator = ArgbEvaluator.getInstance();
}
- setupValues(anim, arrayAnimator, getFloats, hasFrom, fromType, hasTo, toType);
+ if (getFloats) {
+ float valueFrom;
+ float valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = styledAttributes.getDimension(valueFromId, 0f);
+ } else {
+ valueFrom = styledAttributes.getFloat(valueFromId, 0f);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = styledAttributes.getDimension(valueToId, 0f);
+ } else {
+ valueTo = styledAttributes.getFloat(valueToId, 0f);
+ }
+ returnValue = PropertyValuesHolder.ofFloat(propertyName,
+ valueFrom, valueTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofFloat(propertyName, valueFrom);
+ }
+ } else {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = styledAttributes.getDimension(valueToId, 0f);
+ } else {
+ valueTo = styledAttributes.getFloat(valueToId, 0f);
+ }
+ returnValue = PropertyValuesHolder.ofFloat(propertyName, valueTo);
+ }
+ } else {
+ int valueFrom;
+ int valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
+ } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ valueFrom = styledAttributes.getColor(valueFromId, 0);
+ } else {
+ valueFrom = styledAttributes.getInt(valueFromId, 0);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
+ } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ valueTo = styledAttributes.getColor(valueToId, 0);
+ } else {
+ valueTo = styledAttributes.getInt(valueToId, 0);
+ }
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom, valueTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom);
+ }
+ } else {
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
+ } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ valueTo = styledAttributes.getColor(valueToId, 0);
+ } else {
+ valueTo = styledAttributes.getInt(valueToId, 0);
+ }
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueTo);
+ }
+ }
+ }
+ if (returnValue != null && evaluator != null) {
+ returnValue.setEvaluator(evaluator);
+ }
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * @param anim The animator, must not be null
+ * @param arrayAnimator Incoming typed array for Animator's attributes.
+ * @param arrayObjectAnimator Incoming typed array for Object Animator's
+ * attributes.
+ * @param pixelSize The relative pixel size, used to calculate the
+ * maximum error for path animations.
+ */
+ private static void parseAnimatorFromTypeArray(ValueAnimator anim,
+ TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) {
+ long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
+
+ long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
+
+ int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_FLOAT);
+
+ PropertyValuesHolder pvh = getPVH(arrayAnimator, valueType,
+ R.styleable.Animator_valueFrom, R.styleable.Animator_valueTo, "");
+ if (pvh != null) {
+ anim.setValues(pvh);
}
anim.setDuration(duration);
@@ -353,12 +458,10 @@ public class AnimatorInflater {
arrayAnimator.getInt(R.styleable.Animator_repeatMode,
ValueAnimator.RESTART));
}
- if (evaluator != null) {
- anim.setEvaluator(evaluator);
- }
if (arrayObjectAnimator != null) {
- setupObjectAnimator(anim, arrayObjectAnimator, getFloats, pixelSize);
+ setupObjectAnimator(anim, arrayObjectAnimator, valueType == VALUE_TYPE_FLOAT,
+ pixelSize);
}
}
@@ -570,6 +673,7 @@ public class AnimatorInflater {
}
String name = parser.getName();
+ boolean gotValues = false;
if (name.equals("objectAnimator")) {
anim = loadObjectAnimator(res, theme, attrs, pixelSize);
@@ -588,11 +692,18 @@ public class AnimatorInflater {
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
pixelSize);
a.recycle();
+ } else if (name.equals("propertyValuesHolder")) {
+ PropertyValuesHolder[] values = loadValues(res, theme, parser,
+ Xml.asAttributeSet(parser));
+ if (values != null && anim != null && (anim instanceof ValueAnimator)) {
+ ((ValueAnimator) anim).setValues(values);
+ }
+ gotValues = true;
} else {
throw new RuntimeException("Unknown animator name: " + parser.getName());
}
- if (parent != null) {
+ if (parent != null && !gotValues) {
if (childAnims == null) {
childAnims = new ArrayList<Animator>();
}
@@ -612,7 +723,233 @@ public class AnimatorInflater {
}
}
return anim;
+ }
+
+ private static PropertyValuesHolder[] loadValues(Resources res, Theme theme,
+ XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
+ ArrayList<PropertyValuesHolder> values = null;
+
+ int type;
+ while ((type = parser.getEventType()) != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ parser.next();
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (name.equals("propertyValuesHolder")) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyValuesHolder, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.PropertyValuesHolder);
+ }
+ String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
+ int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
+ VALUE_TYPE_FLOAT);
+ PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
+ if (pvh == null) {
+ pvh = getPVH(a, valueType,
+ R.styleable.PropertyValuesHolder_valueFrom,
+ R.styleable.PropertyValuesHolder_valueTo, propertyName);
+ }
+ if (pvh != null) {
+ if (values == null) {
+ values = new ArrayList<PropertyValuesHolder>();
+ }
+ values.add(pvh);
+ }
+ a.recycle();
+ }
+
+ parser.next();
+ }
+
+ PropertyValuesHolder[] valuesArray = null;
+ if (values != null) {
+ int count = values.size();
+ valuesArray = new PropertyValuesHolder[count];
+ for (int i = 0; i < count; ++i) {
+ valuesArray[i] = values.get(i);
+ }
+ }
+ return valuesArray;
+ }
+
+ private static void dumpKeyframes(Object[] keyframes, String header) {
+ if (keyframes == null || keyframes.length == 0) {
+ return;
+ }
+ Log.d(TAG, header);
+ int count = keyframes.length;
+ for (int i = 0; i < count; ++i) {
+ Keyframe keyframe = (Keyframe) keyframes[i];
+ Log.d(TAG, "Keyframe " + i + ": fraction " +
+ (keyframe.getFraction() < 0 ? "null" : keyframe.getFraction()) + ", " +
+ ", value : " + ((keyframe.hasValue()) ? keyframe.getValue() : "null"));
+ }
+ }
+
+ private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
+ String propertyName, int valueType)
+ throws XmlPullParserException, IOException {
+
+ PropertyValuesHolder value = null;
+ ArrayList<Keyframe> keyframes = null;
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ String name = parser.getName();
+ if (name.equals("keyframe")) {
+ Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType);
+ if (keyframe != null) {
+ if (keyframes == null) {
+ keyframes = new ArrayList<Keyframe>();
+ }
+ keyframes.add(keyframe);
+ }
+ parser.next();
+ }
+ }
+
+ int count;
+ if (keyframes != null && (count = keyframes.size()) > 0) {
+ // make sure we have keyframes at 0 and 1
+ // If we have keyframes with set fractions, add keyframes at start/end
+ // appropriately. If start/end have no set fractions:
+ // if there's only one keyframe, set its fraction to 1 and add one at 0
+ // if >1 keyframe, set the last fraction to 1, the first fraction to 0
+ Keyframe firstKeyframe = keyframes.get(0);
+ Keyframe lastKeyframe = keyframes.get(count - 1);
+ float endFraction = lastKeyframe.getFraction();
+ if (endFraction < 1) {
+ if (endFraction < 0) {
+ lastKeyframe.setFraction(1);
+ } else {
+ keyframes.add(keyframes.size(), createNewKeyframe(lastKeyframe, 1));
+ ++count;
+ }
+ }
+ float startFraction = firstKeyframe.getFraction();
+ if (startFraction != 0) {
+ if (startFraction < 0) {
+ firstKeyframe.setFraction(0);
+ } else {
+ keyframes.add(0, createNewKeyframe(firstKeyframe, 0));
+ ++count;
+ }
+ }
+ Keyframe[] keyframeArray = new Keyframe[count];
+ keyframes.toArray(keyframeArray);
+ for (int i = 0; i < count; ++i) {
+ Keyframe keyframe = keyframeArray[i];
+ if (keyframe.getFraction() < 0) {
+ if (i == 0) {
+ keyframe.setFraction(0);
+ } else if (i == count - 1) {
+ keyframe.setFraction(1);
+ } else {
+ // figure out the start/end parameters of the current gap
+ // in fractions and distribute the gap among those keyframes
+ int startIndex = i;
+ int endIndex = i;
+ for (int j = startIndex + 1; j < count - 1; ++j) {
+ if (keyframeArray[j].getFraction() >= 0) {
+ break;
+ }
+ endIndex = j;
+ }
+ float gap = keyframeArray[endIndex + 1].getFraction() -
+ keyframeArray[startIndex - 1].getFraction();
+ distributeKeyframes(keyframeArray, gap, startIndex, endIndex);
+ }
+ }
+ }
+ value = PropertyValuesHolder.ofKeyframe(propertyName, keyframeArray);
+ if (valueType == VALUE_TYPE_COLOR) {
+ value.setEvaluator(ArgbEvaluator.getInstance());
+ }
+ }
+
+ return value;
+ }
+
+ private static Keyframe createNewKeyframe(Keyframe sampleKeyframe, float fraction) {
+ return sampleKeyframe.getType() == float.class ?
+ Keyframe.ofFloat(fraction) :
+ (sampleKeyframe.getType() == int.class) ?
+ Keyframe.ofInt(fraction) :
+ Keyframe.ofObject(fraction);
+ }
+
+ /**
+ * Utility function to set fractions on keyframes to cover a gap in which the
+ * fractions are not currently set. Keyframe fractions will be distributed evenly
+ * in this gap. For example, a gap of 1 keyframe in the range 0-1 will be at .5, a gap
+ * of .6 spread between two keyframes will be at .2 and .4 beyond the fraction at the
+ * keyframe before startIndex.
+ * Assumptions:
+ * - First and last keyframe fractions (bounding this spread) are already set. So,
+ * for example, if no fractions are set, we will already set first and last keyframe
+ * fraction values to 0 and 1.
+ * - startIndex must be >0 (which follows from first assumption).
+ * - endIndex must be >= startIndex.
+ *
+ * @param keyframes the array of keyframes
+ * @param gap The total gap we need to distribute
+ * @param startIndex The index of the first keyframe whose fraction must be set
+ * @param endIndex The index of the last keyframe whose fraction must be set
+ */
+ private static void distributeKeyframes(Keyframe[] keyframes, float gap,
+ int startIndex, int endIndex) {
+ int count = endIndex - startIndex + 2;
+ float increment = gap / count;
+ for (int i = startIndex; i <= endIndex; ++i) {
+ keyframes[i].setFraction(keyframes[i-1].getFraction() + increment);
+ }
+ }
+
+ private static Keyframe loadKeyframe(Resources res, Theme theme, AttributeSet attrs,
+ int valueType)
+ throws XmlPullParserException, IOException {
+
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.Keyframe);
+ }
+
+ Keyframe keyframe = null;
+
+ float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
+
+ boolean hasValue = a.peekValue(R.styleable.Keyframe_value) != null;
+
+ if (hasValue) {
+ switch (valueType) {
+ case VALUE_TYPE_FLOAT:
+ float value = a.getFloat(R.styleable.Keyframe_value, 0);
+ keyframe = Keyframe.ofFloat(fraction, value);
+ break;
+ case VALUE_TYPE_COLOR:
+ case VALUE_TYPE_INT:
+ int intValue = a.getInt(R.styleable.Keyframe_value, 0);
+ keyframe = Keyframe.ofInt(fraction, intValue);
+ break;
+ }
+ } else {
+ keyframe = (valueType == VALUE_TYPE_FLOAT) ? Keyframe.ofFloat(fraction) :
+ Keyframe.ofInt(fraction);
+ }
+
+ a.recycle();
+ return keyframe;
}
private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs,
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 92762c3..53d5237 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -972,6 +972,18 @@ public final class AnimatorSet extends Animator {
}
}
+ @Override
+ public String toString() {
+ String returnVal = "AnimatorSet@" + Integer.toHexString(hashCode()) + "{";
+ boolean prevNeedsSort = mNeedsSort;
+ sortNodes();
+ mNeedsSort = prevNeedsSort;
+ for (Node node : mSortedNodes) {
+ returnVal += "\n " + node.animation.toString();
+ }
+ return returnVal + "\n}";
+ }
+
/**
* Dependency holds information about the node that some other node is
* dependent upon and the nature of that dependency.
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index abac246..56da940 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -18,7 +18,6 @@ package android.animation;
import android.animation.Keyframe.FloatKeyframe;
-import java.util.ArrayList;
import java.util.List;
/**
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index 0ec5138..12a4bf9 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -18,7 +18,6 @@ package android.animation;
import android.animation.Keyframe.IntKeyframe;
-import java.util.ArrayList;
import java.util.List;
/**
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 0e99bff..c80e162 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -16,7 +16,6 @@
package android.animation;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
diff --git a/core/java/android/animation/Keyframes.java b/core/java/android/animation/Keyframes.java
index c921466..c149bed 100644
--- a/core/java/android/animation/Keyframes.java
+++ b/core/java/android/animation/Keyframes.java
@@ -15,7 +15,6 @@
*/
package android.animation;
-import java.util.ArrayList;
import java.util.List;
/**
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 59daaab..3f71d51 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -16,6 +16,7 @@
package android.animation;
+import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Path;
@@ -24,7 +25,6 @@ import android.util.Log;
import android.util.Property;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
/**
* This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
@@ -33,6 +33,27 @@ import java.util.ArrayList;
* are then determined internally and the animation will call these functions as necessary to
* animate the property.
*
+ * <p>Animators can be created from either code or resource files, as shown here:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator.xml ObjectAnimatorResources}
+ *
+ * <p>When using resource files, it is possible to use {@link PropertyValuesHolder} and
+ * {@link Keyframe} to create more complex animations. Using PropertyValuesHolders
+ * allows animators to animate several properties in parallel, as shown in this sample:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh.xml
+ * PropertyValuesHolderResources}
+ *
+ * <p>Using Keyframes allows animations to follow more complex paths from the start
+ * to the end values. Note that you can specify explicit fractional values (from 0 to 1) for
+ * each keyframe to determine when, in the overall duration, the animation should arrive at that
+ * value. Alternatively, you can leave the fractions off and the keyframes will be equally
+ * distributed within the total duration. Also, a keyframe with no value will derive its value
+ * from the target object when the animator starts, just like animators with only one
+ * value specified.</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf.xml KeyframeResources}
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about animating with {@code ObjectAnimator}, read the
@@ -841,6 +862,7 @@ public final class ObjectAnimator extends ValueAnimator {
* <p>Overriders of this method should call the superclass method to cause
* internal mechanisms to be set up correctly.</p>
*/
+ @CallSuper
@Override
void initAnimation() {
if (!mInitialized) {
@@ -941,6 +963,7 @@ public final class ObjectAnimator extends ValueAnimator {
*
* @param fraction The elapsed fraction of the animation.
*/
+ @CallSuper
@Override
void animateValue(float fraction) {
final Object target = getTarget();
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index bd7bca0..8928e99 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -25,10 +25,8 @@ import android.util.Property;
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;
/**
* This class holds information about a property and the values that that property
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 9709555..85dc832 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -16,7 +16,7 @@
package android.animation;
-import android.content.res.ConfigurationBoundResourceCache;
+import android.annotation.CallSuper;
import android.os.Looper;
import android.os.Trace;
import android.util.AndroidRuntimeException;
@@ -40,6 +40,21 @@ import java.util.HashMap;
* out of an animation. This behavior can be changed by calling
* {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p>
*
+ * <p>Animators can be created from either code or resource files. Here is an example
+ * of a ValueAnimator resource file:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/animator.xml ValueAnimatorResources}
+ *
+ * <p>It is also possible to use a combination of {@link PropertyValuesHolder} and
+ * {@link Keyframe} resource tags to create a multi-step animation.
+ * Note that you can specify explicit fractional values (from 0 to 1) for
+ * each keyframe to determine when, in the overall duration, the animation should arrive at that
+ * value. Alternatively, you can leave the fractions off and the keyframes will be equally
+ * distributed within the total duration:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/value_animator_pvh_kf.xml
+ * ValueAnimatorKeyframeResources}
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about animating with {@code ValueAnimator}, read the
@@ -492,6 +507,7 @@ public class ValueAnimator extends Animator {
* <p>Overrides of this method should call the superclass method to ensure
* that internal mechanisms for the animation are set up correctly.</p>
*/
+ @CallSuper
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
@@ -1361,6 +1377,7 @@ public class ValueAnimator extends Animator {
*
* @param fraction The elapsed fraction of the animation.
*/
+ @CallSuper
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
diff --git a/core/java/android/annotation/CallSuper.java b/core/java/android/annotation/CallSuper.java
new file mode 100644
index 0000000..82e2723
--- /dev/null
+++ b/core/java/android/annotation/CallSuper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that any overriding methods should invoke this method as well.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * &#64;CallSuper
+ * public abstract void onFocusLost();
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD})
+public @interface CallSuper {
+} \ No newline at end of file
diff --git a/core/java/android/annotation/CheckResult.java b/core/java/android/annotation/CheckResult.java
new file mode 100644
index 0000000..787514e
--- /dev/null
+++ b/core/java/android/annotation/CheckResult.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated method returns a result that it typically is
+ * an error to ignore. This is usually used for methods that have no side effect,
+ * so calling it without actually looking at the result usually means the developer
+ * has misunderstood what the method does.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * public &#64;CheckResult String trim(String s) { return s.trim(); }
+ * ...
+ * s.trim(); // this is probably an error
+ * s = s.trim(); // ok
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD})
+public @interface CheckResult {
+ /** Defines the name of the suggested method to use instead, if applicable (using
+ * the same signature format as javadoc.) If there is more than one possibility,
+ * list them all separated by commas.
+ * <p>
+ * For example, ProcessBuilder has a method named {@code redirectErrorStream()}
+ * which sounds like it might redirect the error stream. It does not. It's just
+ * a getter which returns whether the process builder will redirect the error stream,
+ * and to actually set it, you must call {@code redirectErrorStream(boolean)}.
+ * In that case, the method should be defined like this:
+ * <pre>
+ * &#64;CheckResult(suggest="#redirectErrorStream(boolean)")
+ * public boolean redirectErrorStream() { ... }
+ * </pre>
+ */
+ String suggest() default "";
+} \ No newline at end of file
diff --git a/core/java/android/annotation/ColorInt.java b/core/java/android/annotation/ColorInt.java
new file mode 100644
index 0000000..69d196c
--- /dev/null
+++ b/core/java/android/annotation/ColorInt.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element represents a packed color
+ * int, {@code AARRGGBB}. If applied to an int array, every element
+ * in the array represents a color integer.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * public abstract void setTextColor(&#64;ColorInt int color);
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
+public @interface ColorInt {
+} \ No newline at end of file
diff --git a/core/java/android/annotation/FloatRange.java b/core/java/android/annotation/FloatRange.java
new file mode 100644
index 0000000..3a7c150
--- /dev/null
+++ b/core/java/android/annotation/FloatRange.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element should be a float or double in the given range
+ * <p>
+ * Example:
+ * <pre>{@code
+ * &#64;FloatRange(from=0.0,to=1.0)
+ * public float getAlpha() {
+ * ...
+ * }
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+public @interface FloatRange {
+ /** Smallest value. Whether it is inclusive or not is determined
+ * by {@link #fromInclusive} */
+ double from() default Double.NEGATIVE_INFINITY;
+ /** Largest value. Whether it is inclusive or not is determined
+ * by {@link #toInclusive} */
+ double to() default Double.POSITIVE_INFINITY;
+
+ /** Whether the from value is included in the range */
+ boolean fromInclusive() default true;
+
+ /** Whether the to value is included in the range */
+ boolean toInclusive() default true;
+} \ No newline at end of file
diff --git a/core/java/android/annotation/IntRange.java b/core/java/android/annotation/IntRange.java
new file mode 100644
index 0000000..1e3c072
--- /dev/null
+++ b/core/java/android/annotation/IntRange.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element should be an int or long in the given range
+ * <p>
+ * Example:
+ * <pre>{@code
+ * &#64;IntRange(from=0,to=255)
+ * public int getAlpha() {
+ * ...
+ * }
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+public @interface IntRange {
+ /** Smallest value, inclusive */
+ long from() default Long.MIN_VALUE;
+ /** Largest value, inclusive */
+ long to() default Long.MAX_VALUE;
+} \ No newline at end of file
diff --git a/core/java/android/annotation/Size.java b/core/java/android/annotation/Size.java
new file mode 100644
index 0000000..389b819
--- /dev/null
+++ b/core/java/android/annotation/Size.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element should have a given size or length.
+ * Note that "-1" means "unset". Typically used with a parameter or
+ * return value of type array or collection.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * public void getLocationInWindow(&#64;Size(2) int[] location) {
+ * ...
+ * }
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,LOCAL_VARIABLE,METHOD,FIELD})
+public @interface Size {
+ /** An exact size (or -1 if not specified) */
+ long value() default -1;
+ /** A minimum size, inclusive */
+ long min() default Long.MIN_VALUE;
+ /** A maximum size, inclusive */
+ long max() default Long.MAX_VALUE;
+ /** The size must be a multiple of this factor */
+ long multiple() default 1;
+} \ No newline at end of file
diff --git a/core/java/android/annotation/TransitionRes.java b/core/java/android/annotation/TransitionRes.java
new file mode 100644
index 0000000..06bac74
--- /dev/null
+++ b/core/java/android/annotation/TransitionRes.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a transition resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface TransitionRes {
+}
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 014a7af..94e3b66 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -16,9 +16,12 @@
package android.app;
+import android.annotation.DrawableRes;
import android.annotation.IntDef;
+import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -30,14 +33,10 @@ import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
import android.view.Window;
import android.widget.SpinnerAdapter;
-import android.widget.Toolbar;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
/**
* A primary toolbar within the activity that may display the activity title, application-level
@@ -256,7 +255,7 @@ public abstract class ActionBar {
*
* @see #setDisplayOptions(int, int)
*/
- public abstract void setCustomView(int resId);
+ public abstract void setCustomView(@LayoutRes int resId);
/**
* Set the icon to display in the 'home' section of the action bar.
@@ -271,7 +270,7 @@ public abstract class ActionBar {
* @see #setDisplayUseLogoEnabled(boolean)
* @see #setDisplayShowHomeEnabled(boolean)
*/
- public abstract void setIcon(int resId);
+ public abstract void setIcon(@DrawableRes int resId);
/**
* Set the icon to display in the 'home' section of the action bar.
@@ -301,7 +300,7 @@ public abstract class ActionBar {
* @see #setDisplayUseLogoEnabled(boolean)
* @see #setDisplayShowHomeEnabled(boolean)
*/
- public abstract void setLogo(int resId);
+ public abstract void setLogo(@DrawableRes int resId);
/**
* Set the logo to display in the 'home' section of the action bar.
@@ -397,7 +396,7 @@ public abstract class ActionBar {
* @see #setTitle(CharSequence)
* @see #setDisplayOptions(int, int)
*/
- public abstract void setTitle(int resId);
+ public abstract void setTitle(@StringRes int resId);
/**
* Set the action bar's subtitle. This will only be displayed if
@@ -420,7 +419,7 @@ public abstract class ActionBar {
* @see #setSubtitle(CharSequence)
* @see #setDisplayOptions(int, int)
*/
- public abstract void setSubtitle(int resId);
+ public abstract void setSubtitle(@StringRes int resId);
/**
* Set display options. This changes all display option bits at once. To change
@@ -892,7 +891,7 @@ public abstract class ActionBar {
* @see #setDisplayHomeAsUpEnabled(boolean)
* @see #setHomeActionContentDescription(int)
*/
- public void setHomeAsUpIndicator(int resId) { }
+ public void setHomeAsUpIndicator(@DrawableRes int resId) { }
/**
* Set an alternate description for the Home/Up action, when enabled.
@@ -931,7 +930,7 @@ public abstract class ActionBar {
* @see #setHomeAsUpIndicator(int)
* @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
*/
- public void setHomeActionContentDescription(int resId) { }
+ public void setHomeActionContentDescription(@StringRes int resId) { }
/**
* Enable hiding the action bar on content scroll.
@@ -1154,7 +1153,7 @@ public abstract class ActionBar {
* @param resId Resource ID referring to the drawable to use as an icon
* @return The current instance for call chaining
*/
- public abstract Tab setIcon(int resId);
+ public abstract Tab setIcon(@DrawableRes int resId);
/**
* Set the text displayed on this tab. Text may be truncated if there is not
@@ -1172,7 +1171,7 @@ public abstract class ActionBar {
* @param resId A resource ID referring to the text that should be displayed
* @return The current instance for call chaining
*/
- public abstract Tab setText(int resId);
+ public abstract Tab setText(@StringRes int resId);
/**
* Set a custom view to be used for this tab. This overrides values set by
@@ -1190,7 +1189,7 @@ public abstract class ActionBar {
* @param layoutResId A layout resource to inflate and use as a custom tab view
* @return The current instance for call chaining
*/
- public abstract Tab setCustomView(int layoutResId);
+ public abstract Tab setCustomView(@LayoutRes int layoutResId);
/**
* Retrieve a previously set custom view for this tab.
@@ -1235,7 +1234,7 @@ public abstract class ActionBar {
* @see #setContentDescription(CharSequence)
* @see #getContentDescription()
*/
- public abstract Tab setContentDescription(int resId);
+ public abstract Tab setContentDescription(@StringRes int resId);
/**
* Set a description of this tab's content for use in accessibility support.
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9568897..7fcbe35 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,7 +16,14 @@
package android.app;
+import android.annotation.CallSuper;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.annotation.IntDef;
+import android.annotation.LayoutRes;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
import android.os.PersistableBundle;
import android.transition.Scene;
import android.transition.TransitionManager;
@@ -27,10 +34,7 @@ import android.widget.Toolbar;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.app.ToolbarActionBar;
-import com.android.internal.policy.PolicyManager;
-import android.annotation.IntDef;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentCallbacks2;
@@ -84,6 +88,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.PhoneWindow;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
@@ -362,7 +367,7 @@ import java.util.HashMap;
*
* <p>Note the "Killable" column in the above table -- for those methods that
* are marked as being killable, after that method returns the process hosting the
- * activity may killed by the system <em>at any time</em> without another line
+ * activity may be killed by the system <em>at any time</em> without another line
* of its code being executed. Because of this, you should use the
* {@link #onPause} method to write any persistent data (such as user edits)
* to storage. In addition, the method
@@ -743,6 +748,7 @@ public class Activity extends ContextThemeWrapper
final FragmentManagerImpl mFragments = new FragmentManagerImpl();
final FragmentContainer mContainer = new FragmentContainer() {
@Override
+ @Nullable
public View findViewById(int id) {
return Activity.this.findViewById(id);
}
@@ -781,6 +787,7 @@ public class Activity extends ContextThemeWrapper
private boolean mChangeCanvasToTranslucent;
private boolean mTitleReady = false;
+ private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE;
private SpannableStringBuilder mDefaultKeySsb = null;
@@ -916,6 +923,7 @@ public class Activity extends ContextThemeWrapper
* @see #onRestoreInstanceState
* @see #onPostCreate
*/
+ @CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
@@ -1117,6 +1125,7 @@ public class Activity extends ContextThemeWrapper
* recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b>
* @see #onCreate
*/
+ @CallSuper
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
if (!isChild()) {
mTitleReady = true;
@@ -1154,6 +1163,7 @@ public class Activity extends ContextThemeWrapper
* @see #onStop
* @see #onResume
*/
+ @CallSuper
protected void onStart() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
mCalled = true;
@@ -1191,6 +1201,7 @@ public class Activity extends ContextThemeWrapper
* @see #onStart
* @see #onResume
*/
+ @CallSuper
protected void onRestart() {
mCalled = true;
}
@@ -1215,6 +1226,7 @@ public class Activity extends ContextThemeWrapper
* @see #onPostResume
* @see #onPause
*/
+ @CallSuper
protected void onResume() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
@@ -1234,6 +1246,7 @@ public class Activity extends ContextThemeWrapper
*
* @see #onResume
*/
+ @CallSuper
protected void onPostResume() {
final Window win = getWindow();
if (win != null) win.makeActive();
@@ -1242,22 +1255,18 @@ public class Activity extends ContextThemeWrapper
}
/**
- * @hide
* Check whether this activity is running as part of a voice interaction with the user.
* If true, it should perform its interaction with the user through the
* {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
*/
- @SystemApi
public boolean isVoiceInteraction() {
return mVoiceInteractor != null;
}
/**
- * @hide
* Retrieve the active {@link VoiceInteractor} that the user is going through to
* interact with this activity.
*/
- @SystemApi
public VoiceInteractor getVoiceInteractor() {
return mVoiceInteractor;
}
@@ -1463,6 +1472,7 @@ public class Activity extends ContextThemeWrapper
* @see #onSaveInstanceState
* @see #onStop
*/
+ @CallSuper
protected void onPause() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
getApplication().dispatchActivityPaused(this);
@@ -1538,7 +1548,7 @@ public class Activity extends ContextThemeWrapper
* {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
* application. You can override this method to place into the bundle anything
* you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
- * of the assist Intent. The default implementation does nothing.
+ * of the assist Intent.
*
* <p>This function will be called after any global assist callbacks that had
* been registered with {@link Application#registerOnProvideAssistDataListener
@@ -1548,6 +1558,28 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * This is called when the user is requesting an assist, to provide references
+ * to content related to the current activity. Before being called, the
+ * {@code outContent} Intent is filled with the base Intent of the activity (the Intent
+ * returned by {@link #getIntent()}). The Intent's extras are stripped of any types
+ * that are not valid for {@link PersistableBundle} or non-framework Parcelables, and
+ * the flags {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} and
+ * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} are cleared from the Intent.
+ *
+ * <p>Custom implementation may adjust the content intent to better reflect the top-level
+ * context of the activity, and fill in its ClipData with additional content of
+ * interest that the user is currently viewing. For example, an image gallery application
+ * that has launched in to an activity allowing the user to swipe through pictures should
+ * modify the intent to reference the current image they are looking it; such an
+ * application when showing a list of pictures should add a ClipData that has
+ * references to all of the pictures currently visible on screen.</p>
+ *
+ * @param outContent The assist content to return.
+ */
+ public void onProvideAssistContent(AssistContent outContent) {
+ }
+
+ /**
* Called when you are no longer visible to the user. You will next
* receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
* depending on later user activity.
@@ -1565,6 +1597,7 @@ public class Activity extends ContextThemeWrapper
* @see #onSaveInstanceState
* @see #onDestroy
*/
+ @CallSuper
protected void onStop() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
@@ -1602,6 +1635,7 @@ public class Activity extends ContextThemeWrapper
* @see #finish
* @see #isFinishing
*/
+ @CallSuper
protected void onDestroy() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
mCalled = true;
@@ -2068,7 +2102,8 @@ public class Activity extends ContextThemeWrapper
*
* @return The view if found or null otherwise.
*/
- public View findViewById(int id) {
+ @Nullable
+ public View findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
@@ -2141,7 +2176,7 @@ public class Activity extends ContextThemeWrapper
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
- public void setContentView(int layoutResID) {
+ public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
@@ -3609,7 +3644,7 @@ public class Activity extends ContextThemeWrapper
* Convenience for calling
* {@link android.view.Window#setFeatureDrawableResource}.
*/
- public final void setFeatureDrawableResource(int featureId, int resId) {
+ public final void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
getWindow().setFeatureDrawableResource(featureId, resId);
}
@@ -3664,7 +3699,7 @@ public class Activity extends ContextThemeWrapper
}
@Override
- protected void onApplyThemeResource(Resources.Theme theme, int resid,
+ protected void onApplyThemeResource(Resources.Theme theme, @StyleRes int resid,
boolean first) {
if (mParent == null) {
super.onApplyThemeResource(theme, resid, first);
@@ -4619,7 +4654,7 @@ public class Activity extends ContextThemeWrapper
if (Looper.myLooper() != mMainThread.getLooper()) {
throw new IllegalStateException("Must be called from main thread");
}
- mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, false);
+ mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, null, false);
}
/**
@@ -5581,6 +5616,7 @@ public class Activity extends ContextThemeWrapper
* @see #requestVisibleBehind(boolean)
* @see #onBackgroundVisibleBehindChanged(boolean)
*/
+ @CallSuper
public void onVisibleBehindCanceled() {
mCalled = true;
}
@@ -5664,10 +5700,10 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Start an action mode.
+ * Start an action mode of the default type {@link ActionMode#TYPE_PRIMARY}.
*
- * @param callback Callback that will manage lifecycle events for this context mode
- * @return The ContextMode that was started, or null if it was canceled
+ * @param callback Callback that will manage lifecycle events for this action mode
+ * @return The ActionMode that was started, or null if it was canceled
*
* @see ActionMode
*/
@@ -5677,6 +5713,20 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Start an action mode of the given type.
+ *
+ * @param callback Callback that will manage lifecycle events for this action mode
+ * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+ * @return The ActionMode that was started, or null if it was canceled
+ *
+ * @see ActionMode
+ */
+ @Nullable
+ public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+ return mWindow.getDecorView().startActionMode(callback, type);
+ }
+
+ /**
* Give the Activity a chance to control the UI for an action mode requested
* by the system.
*
@@ -5690,19 +5740,37 @@ public class Activity extends ContextThemeWrapper
@Nullable
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
- initWindowDecorActionBar();
- if (mActionBar != null) {
- return mActionBar.startActionMode(callback);
+ // Only Primary ActionModes are represented in the ActionBar.
+ if (mActionModeTypeStarting == ActionMode.TYPE_PRIMARY) {
+ initWindowDecorActionBar();
+ if (mActionBar != null) {
+ return mActionBar.startActionMode(callback);
+ }
}
return null;
}
/**
+ * {@inheritDoc}
+ */
+ @Nullable
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+ try {
+ mActionModeTypeStarting = type;
+ return onWindowStartingActionMode(callback);
+ } finally {
+ mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
+ }
+ }
+
+ /**
* Notifies the Activity that an action mode has been started.
* Activity subclasses overriding this method should call the superclass implementation.
*
* @param mode The new action mode.
*/
+ @CallSuper
@Override
public void onActionModeStarted(ActionMode mode) {
}
@@ -5713,6 +5781,7 @@ public class Activity extends ContextThemeWrapper
*
* @param mode The action mode that just finished.
*/
+ @CallSuper
@Override
public void onActionModeFinished(ActionMode mode) {
}
@@ -5929,7 +5998,7 @@ public class Activity extends ContextThemeWrapper
mFragments.attachActivity(this, mContainer, null);
- mWindow = PolicyManager.makeNewWindow(this);
+ mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
@@ -6080,6 +6149,17 @@ public class Activity extends ContextThemeWrapper
" did not call through to super.onResume()");
}
+ // invisible activities must be finished before onResume() completes
+ if (!mVisibleFromClient && !mFinished) {
+ Log.w(TAG, "An activity without a UI must call finish() before onResume() completes");
+ if (getApplicationInfo().targetSdkVersion
+ > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
+ throw new IllegalStateException(
+ "Activity " + mComponent.toShortString() +
+ " did not call finish() prior to onResume() completing");
+ }
+ }
+
// Now really resume, and install the current status bar and menu.
mCalled = false;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7a636db..29b024ac 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -256,6 +256,9 @@ public class ActivityManager {
/** @hide User operation call: given user id is the current user, can't be stopped. */
public static final int USER_OP_IS_CURRENT = -2;
+ /** @hide Process does not exist. */
+ public static final int PROCESS_STATE_NONEXISTENT = -1;
+
/** @hide Process is a persistent system process. */
public static final int PROCESS_STATE_PERSISTENT = 0;
@@ -306,6 +309,27 @@ public class ActivityManager {
/** @hide Process is being cached for later use and is empty. */
public static final int PROCESS_STATE_CACHED_EMPTY = 13;
+ /** @hide requestType for assist context: only basic information. */
+ public static final int ASSIST_CONTEXT_BASIC = 0;
+
+ /** @hide requestType for assist context: generate full AssistStructure. */
+ public static final int ASSIST_CONTEXT_FULL = 1;
+
+ /**
+ * Lock task mode is not active.
+ */
+ public static final int LOCK_TASK_MODE_NONE = 0;
+
+ /**
+ * Full lock task mode is active.
+ */
+ public static final int LOCK_TASK_MODE_LOCKED = 1;
+
+ /**
+ * App pinning mode is active.
+ */
+ public static final int LOCK_TASK_MODE_PINNED = 2;
+
Point mAppTaskThumbnailSize;
/*package*/ ActivityManager(Context context, Handler handler) {
@@ -2681,12 +2705,25 @@ public class ActivityManager {
* no new tasks can be created or switched to.
*
* @see Activity#startLockTask()
+ *
+ * @deprecated Use {@link #getLockTaskModeState} instead.
*/
public boolean isInLockTaskMode() {
+ return getLockTaskModeState() != LOCK_TASK_MODE_NONE;
+ }
+
+ /**
+ * Return the current state of task locking. The three possible outcomes
+ * are {@link #LOCK_TASK_MODE_NONE}, {@link #LOCK_TASK_MODE_LOCKED}
+ * and {@link #LOCK_TASK_MODE_PINNED}.
+ *
+ * @see Activity#startLockTask()
+ */
+ public int getLockTaskModeState() {
try {
- return ActivityManagerNative.getDefault().isInLockTaskMode();
+ return ActivityManagerNative.getDefault().getLockTaskModeState();
} catch (RemoteException e) {
- return false;
+ return ActivityManager.LOCK_TASK_MODE_NONE;
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index e94cdae..1484af8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -50,6 +50,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Singleton;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.os.IResultReceiver;
import java.util.ArrayList;
import java.util.List;
@@ -689,14 +690,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case MOVE_TASK_TO_BACK_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
- int task = data.readInt();
- moveTaskToBack(task);
- reply.writeNoException();
- return true;
- }
-
case MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -728,7 +721,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case RESIZE_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int stackId = data.readInt();
- float weight = data.readFloat();
Rect r = Rect.CREATOR.createFromParcel(data);
resizeStack(stackId, r);
reply.writeNoException();
@@ -774,6 +766,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_FOCUSED_STACK_ID_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int focusedStackId = getFocusedStackId();
+ reply.writeNoException();
+ reply.writeInt(focusedStackId);
+ return true;
+ }
+
case REGISTER_TASK_STACK_LISTENER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -2114,6 +2114,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int requestType = data.readInt();
+ IResultReceiver receiver = IResultReceiver.Stub.asInterface(data.readStrongBinder());
+ requestAssistContextExtras(requestType, receiver);
+ reply.writeNoException();
+ return true;
+ }
+
case REPORT_ASSIST_CONTEXT_EXTRAS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -2183,13 +2192,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case CREATE_ACTIVITY_CONTAINER_TRANSACTION: {
+ case CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder parentActivityToken = data.readStrongBinder();
IActivityContainerCallback callback =
IActivityContainerCallback.Stub.asInterface(data.readStrongBinder());
IActivityContainer activityContainer =
- createActivityContainer(parentActivityToken, callback);
+ createVirtualActivityContainer(parentActivityToken, callback);
reply.writeNoException();
if (activityContainer != null) {
reply.writeInt(1);
@@ -2209,6 +2218,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case CREATE_STACK_ON_DISPLAY: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int displayId = data.readInt();
+ IActivityContainer activityContainer = createStackOnDisplay(displayId);
+ reply.writeNoException();
+ if (activityContainer != null) {
+ reply.writeInt(1);
+ reply.writeStrongBinder(activityContainer.asBinder());
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
case GET_ACTIVITY_DISPLAY_ID_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder activityToken = data.readStrongBinder();
@@ -2271,6 +2294,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_LOCK_TASK_MODE_STATE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final int lockTaskModeState = getLockTaskModeState();
+ reply.writeNoException();
+ reply.writeInt(lockTaskModeState);
+ return true;
+ }
+
case SET_TASK_DESCRIPTION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -2281,6 +2312,24 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case SET_TASK_RESIZEABLE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ boolean resizeable = (data.readInt() == 1) ? true : false;
+ setTaskResizeable(taskId, resizeable);
+ reply.writeNoException();
+ return true;
+ }
+
+ case RESIZE_TASK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ Rect r = Rect.CREATOR.createFromParcel(data);
+ resizeTask(taskId, r);
+ reply.writeNoException();
+ return true;
+ }
+
case GET_TASK_DESCRIPTION_ICON_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String filename = data.readString();
@@ -2374,6 +2423,33 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
+
+ case SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String procName = data.readString();
+ long maxMemSize = data.readLong();
+ setDumpHeapDebugLimit(procName, maxMemSize);
+ reply.writeNoException();
+ return true;
+ }
+
+ case DUMP_HEAP_FINISHED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String path = data.readString();
+ dumpHeapFinished(path);
+ reply.writeNoException();
+ return true;
+ }
+
+ case SET_VOICE_KEEP_AWAKE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IVoiceInteractionSession session = IVoiceInteractionSession.Stub.asInterface(
+ data.readStrongBinder());
+ boolean keepAwake = data.readInt() != 0;
+ setVoiceKeepAwake(session, keepAwake);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -2994,7 +3070,7 @@ class ActivityManagerProxy implements IActivityManager
ArrayList<IAppTask> list = null;
int N = reply.readInt();
if (N >= 0) {
- list = new ArrayList<IAppTask>();
+ list = new ArrayList<>();
while (N > 0) {
IAppTask task = IAppTask.Stub.asInterface(reply.readStrongBinder());
list.add(task);
@@ -3032,7 +3108,8 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return size;
}
- public List getTasks(int maxNum, int flags) throws RemoteException {
+ public List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, int flags)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3040,10 +3117,10 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(flags);
mRemote.transact(GET_TASKS_TRANSACTION, data, reply, 0);
reply.readException();
- ArrayList list = null;
+ ArrayList<ActivityManager.RunningTaskInfo> list = null;
int N = reply.readInt();
if (N >= 0) {
- list = new ArrayList();
+ list = new ArrayList<>();
while (N > 0) {
ActivityManager.RunningTaskInfo info =
ActivityManager.RunningTaskInfo.CREATOR
@@ -3087,7 +3164,8 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return taskThumbnail;
}
- public List getServices(int maxNum, int flags) throws RemoteException {
+ public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3095,10 +3173,10 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(flags);
mRemote.transact(GET_SERVICES_TRANSACTION, data, reply, 0);
reply.readException();
- ArrayList list = null;
+ ArrayList<ActivityManager.RunningServiceInfo> list = null;
int N = reply.readInt();
if (N >= 0) {
- list = new ArrayList();
+ list = new ArrayList<>();
while (N > 0) {
ActivityManager.RunningServiceInfo info =
ActivityManager.RunningServiceInfo.CREATOR
@@ -3168,17 +3246,6 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
- public void moveTaskToBack(int task) throws RemoteException
- {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeInt(task);
- mRemote.transact(MOVE_TASK_TO_BACK_TRANSACTION, data, reply, 0);
- reply.readException();
- data.recycle();
- reply.recycle();
- }
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -3288,6 +3355,18 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
@Override
+ public int getFocusedStackId() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_FOCUSED_STACK_ID_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int focusedStackId = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return focusedStackId;
+ }
+ @Override
public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -5115,6 +5194,19 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public void requestAssistContextExtras(int requestType, IResultReceiver receiver)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(requestType);
+ data.writeStrongBinder(receiver.asBinder());
+ mRemote.transact(REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
public void reportAssistContextExtras(IBinder token, Bundle extras)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -5211,14 +5303,14 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
- public IActivityContainer createActivityContainer(IBinder parentActivityToken,
+ public IActivityContainer createVirtualActivityContainer(IBinder parentActivityToken,
IActivityContainerCallback callback) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(parentActivityToken);
data.writeStrongBinder(callback == null ? null : callback.asBinder());
- mRemote.transact(CREATE_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0);
+ mRemote.transact(CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0);
reply.readException();
final int result = reply.readInt();
final IActivityContainer res;
@@ -5245,7 +5337,28 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
- public int getActivityDisplayId(IBinder activityToken) throws RemoteException {
+ public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(displayId);
+ mRemote.transact(CREATE_STACK_ON_DISPLAY, data, reply, 0);
+ reply.readException();
+ final int result = reply.readInt();
+ final IActivityContainer res;
+ if (result == 1) {
+ res = IActivityContainer.Stub.asInterface(reply.readStrongBinder());
+ } else {
+ res = null;
+ }
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
+ @Override
+ public int getActivityDisplayId(IBinder activityToken)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -5342,6 +5455,19 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
+ public int getLockTaskModeState() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_LOCK_TASK_MODE_STATE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int lockTaskModeState = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return lockTaskModeState;
+ }
+
+ @Override
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -5356,6 +5482,33 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
+ public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ data.writeInt(resizeable ? 1 : 0);
+ mRemote.transact(SET_TASK_RESIZEABLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public void resizeTask(int taskId, Rect r) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ r.writeToParcel(data, 0);
+ mRemote.transact(RESIZE_TASK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -5490,5 +5643,44 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ @Override
+ public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(processName);
+ data.writeLong(maxMemSize);
+ mRemote.transact(SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public void dumpHeapFinished(String path) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(path);
+ mRemote.transact(DUMP_HEAP_FINISHED_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(session.asBinder());
+ data.writeInt(keepAwake ? 1 : 0);
+ mRemote.transact(SET_VOICE_KEEP_AWAKE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 39ae65c..8909b28 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -140,6 +140,8 @@ public class ActivityOptions {
public static final int ANIM_THUMBNAIL_ASPECT_SCALE_DOWN = 9;
/** @hide */
public static final int ANIM_CUSTOM_IN_PLACE = 10;
+ /** @hide */
+ public static final int ANIM_CLIP_REVEAL = 11;
private String mPackageName;
private int mAnimationType = ANIM_NONE;
@@ -291,6 +293,33 @@ public class ActivityOptions {
}
/**
+ * Create an ActivityOptions specifying an animation where the new
+ * activity is revealed from a small originating area of the screen to
+ * its final full representation.
+ *
+ * @param source The View that the new activity is animating from. This
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+ * @param startX The x starting location of the new activity, relative to <var>source</var>.
+ * @param startY The y starting location of the activity, relative to <var>source</var>.
+ * @param width The initial width of the new activity.
+ * @param height The initial height of the new activity.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeClipRevealAnimation(View source,
+ int startX, int startY, int width, int height) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_CLIP_REVEAL;
+ int[] pts = new int[2];
+ source.getLocationOnScreen(pts);
+ opts.mStartX = pts[0] + startX;
+ opts.mStartY = pts[1] + startY;
+ opts.mWidth = width;
+ opts.mHeight = height;
+ return opts;
+ }
+
+ /**
* Create an ActivityOptions specifying an animation where a thumbnail
* is scaled from a given position to the new activity window that is
* being started.
@@ -582,6 +611,7 @@ public class ActivityOptions {
break;
case ANIM_SCALE_UP:
+ case ANIM_CLIP_REVEAL:
mStartX = opts.getInt(KEY_ANIM_START_X, 0);
mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
mWidth = opts.getInt(KEY_ANIM_WIDTH, 0);
@@ -809,6 +839,7 @@ public class ActivityOptions {
b.putInt(KEY_ANIM_IN_PLACE_RES_ID, mCustomInPlaceResId);
break;
case ANIM_SCALE_UP:
+ case ANIM_CLIP_REVEAL:
b.putInt(KEY_ANIM_START_X, mStartX);
b.putInt(KEY_ANIM_START_Y, mStartY);
b.putInt(KEY_ANIM_WIDTH, mWidth);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index dda9952..7b8ec74 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -46,7 +46,6 @@ 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;
@@ -87,8 +86,6 @@ 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;
@@ -163,8 +160,8 @@ public final class ActivityThread {
private static final boolean DEBUG_PROVIDER = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
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;
+ private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
+ private static final int LOG_AM_ON_RESUME_CALLED = 30022;
/** Type for IActivityManager.serviceDoneExecuting: anonymous operation */
public static final int SERVICE_DONE_EXECUTING_ANON = 0;
@@ -294,6 +291,9 @@ public final class ActivityThread {
boolean hideForNow;
Configuration newConfig;
Configuration createdConfig;
+ Configuration overrideConfig;
+ // Used for consolidating configs before sending on to Activity.
+ private Configuration tmpConfig = new Configuration();
ActivityClientRecord nextIdle;
ProfilerInfo profilerInfo;
@@ -557,6 +557,15 @@ public final class ActivityThread {
int requestType;
}
+ static final class ActivityConfigChangeData {
+ final IBinder activityToken;
+ final Configuration overrideConfig;
+ public ActivityConfigChangeData(IBinder token, Configuration config) {
+ activityToken = token;
+ overrideConfig = config;
+ }
+ }
+
private native void dumpGraphicsInfo(FileDescriptor fd);
private class ApplicationThread extends ApplicationThreadNative {
@@ -616,12 +625,13 @@ public final class ActivityThread {
// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
+ @Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
- PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
- ProfilerInfo profilerInfo) {
+ ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+ CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
+ int procState, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
@@ -645,16 +655,19 @@ public final class ActivityThread {
r.profilerInfo = profilerInfo;
+ r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
+ @Override
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config) {
+ int configChanges, boolean notResumed, Configuration config,
+ Configuration overrideConfig) {
requestRelaunchActivity(token, pendingResults, pendingNewIntents,
- configChanges, notResumed, config, true);
+ configChanges, notResumed, config, overrideConfig, true);
}
public final void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token) {
@@ -884,14 +897,19 @@ public final class ActivityThread {
sticky, sendingUser);
}
+ @Override
public void scheduleLowMemory() {
sendMessage(H.LOW_MEMORY, null);
}
- public void scheduleActivityConfigurationChanged(IBinder token) {
- sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token);
+ @Override
+ public void scheduleActivityConfigurationChanged(
+ IBinder token, Configuration overrideConfig) {
+ sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED,
+ new ActivityConfigChangeData(token, overrideConfig));
}
+ @Override
public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
}
@@ -1081,7 +1099,7 @@ public final class ActivityThread {
@Override
public void dumpGfxInfo(FileDescriptor fd, String[] args) {
dumpGraphicsInfo(fd);
- WindowManagerGlobal.getInstance().dumpGfxInfo(fd);
+ WindowManagerGlobal.getInstance().dumpGfxInfo(fd, args);
}
private void dumpDatabaseInfo(FileDescriptor fd, String[] args) {
@@ -1451,7 +1469,7 @@ public final class ActivityThread {
break;
case ACTIVITY_CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
- handleActivityConfigurationChanged((IBinder)msg.obj);
+ handleActivityConfigurationChanged((ActivityConfigChangeData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PROFILER_CONTROL:
@@ -1669,7 +1687,7 @@ public final class ActivityThread {
String[] libDirs, int displayId, Configuration overrideConfiguration,
LoadedApk pkgInfo) {
return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
- displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
+ displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
}
final Handler getHandler() {
@@ -2352,31 +2370,28 @@ public final class ActivityThread {
return activity;
}
- private Context createBaseContextForActivity(ActivityClientRecord r,
- final Activity activity) {
- ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
- appContext.setOuterContext(activity);
- Context baseContext = appContext;
-
- final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+ private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
+ int displayId = Display.DEFAULT_DISPLAY;
try {
- final int displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
- if (displayId > Display.DEFAULT_DISPLAY) {
- Display display = dm.getRealDisplay(displayId, r.token);
- baseContext = appContext.createDisplayContext(display);
- }
+ displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
} catch (RemoteException e) {
}
+ ContextImpl appContext = ContextImpl.createActivityContext(
+ this, r.packageInfo, displayId, r.overrideConfig);
+ appContext.setOuterContext(activity);
+ Context baseContext = appContext;
+
+ final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
- for (int displayId : dm.getDisplayIds()) {
- if (displayId != Display.DEFAULT_DISPLAY) {
- Display display = dm.getRealDisplay(displayId, r.token);
+ for (int id : dm.getDisplayIds()) {
+ if (id != Display.DEFAULT_DISPLAY) {
+ Display display = dm.getRealDisplay(id, r.overrideConfig);
baseContext = appContext.createDisplayContext(display);
break;
}
@@ -2504,6 +2519,17 @@ public final class ActivityThread {
if (r != null) {
r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
r.activity.onProvideAssistData(data);
+ if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL) {
+ data.putParcelable(AssistStructure.ASSIST_KEY, new AssistStructure(r.activity));
+ AssistContent content = new AssistContent();
+ Intent intent = new Intent(r.activity.getIntent());
+ intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+ intent.removeUnsafeExtras();
+ content.setIntent(intent);
+ r.activity.onProvideAssistContent(content);
+ data.putParcelable(AssistContent.ASSIST_KEY, content);
+ }
}
if (data.isEmpty()) {
data = null;
@@ -2995,7 +3021,7 @@ public final class ActivityThread {
}
r.activity.performResume();
- EventLog.writeEvent(LOG_ON_RESUME_CALLED,
+ EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED,
UserHandle.myUserId(), r.activity.getComponentName().getClassName());
r.paused = false;
@@ -3090,10 +3116,14 @@ public final class ActivityThread {
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
+ r.tmpConfig.setTo(r.newConfig);
+ if (r.overrideConfig != null) {
+ r.tmpConfig.updateFrom(r.overrideConfig);
+ }
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
- + r.activityInfo.name + " with newConfig " + r.newConfig);
- performConfigurationChanged(r.activity, r.newConfig);
- freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig));
+ + r.activityInfo.name + " with newConfig " + r.tmpConfig);
+ performConfigurationChanged(r.activity, r.tmpConfig);
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
r.newConfig = null;
}
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
@@ -3265,7 +3295,7 @@ public final class ActivityThread {
// Now we are idle.
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(),
+ EventLog.writeEvent(LOG_AM_ON_PAUSE_CALLED, UserHandle.myUserId(),
r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
@@ -3422,10 +3452,14 @@ public final class ActivityThread {
}
}
if (r.newConfig != null) {
+ r.tmpConfig.setTo(r.newConfig);
+ if (r.overrideConfig != null) {
+ r.tmpConfig.updateFrom(r.overrideConfig);
+ }
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
- + r.activityInfo.name + " with new config " + r.newConfig);
- performConfigurationChanged(r.activity, r.newConfig);
- freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig));
+ + r.activityInfo.name + " with new config " + r.tmpConfig);
+ performConfigurationChanged(r.activity, r.tmpConfig);
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
r.newConfig = null;
}
} else {
@@ -3559,7 +3593,7 @@ public final class ActivityThread {
// request all activities to relaunch for the changes to take place
for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
- requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, false);
+ requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, null, false);
}
}
}
@@ -3662,7 +3696,7 @@ public final class ActivityThread {
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(),
+ EventLog.writeEvent(LOG_AM_ON_PAUSE_CALLED, UserHandle.myUserId(),
r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
@@ -3803,7 +3837,7 @@ public final class ActivityThread {
public final void requestRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config,
- boolean fromServer) {
+ Configuration overrideConfig, boolean fromServer) {
ActivityClientRecord target = null;
synchronized (mResourcesManager) {
@@ -3838,6 +3872,7 @@ public final class ActivityThread {
ActivityClientRecord existing = mActivities.get(token);
if (existing != null) {
target.startsNotResumed = existing.paused;
+ target.overrideConfig = existing.overrideConfig;
}
target.onlyLocalRequest = true;
}
@@ -3852,6 +3887,9 @@ public final class ActivityThread {
if (config != null) {
target.createdConfig = config;
}
+ if (overrideConfig != null) {
+ target.overrideConfig = overrideConfig;
+ }
target.pendingConfigChanges |= configChanges;
}
}
@@ -3964,6 +4002,7 @@ public final class ActivityThread {
}
}
r.startsNotResumed = tmp.startsNotResumed;
+ r.overrideConfig = tmp.overrideConfig;
handleLaunchActivity(r, currentIntent);
}
@@ -4151,16 +4190,21 @@ public final class ActivityThread {
}
}
- final void handleActivityConfigurationChanged(IBinder token) {
- ActivityClientRecord r = mActivities.get(token);
+ final void handleActivityConfigurationChanged(ActivityConfigChangeData data) {
+ ActivityClientRecord r = mActivities.get(data.activityToken);
if (r == null || r.activity == null) {
return;
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name);
-
- performConfigurationChanged(r.activity, mCompatConfiguration);
+
+ r.tmpConfig.setTo(mCompatConfiguration);
+ if (data.overrideConfig != null) {
+ r.overrideConfig = data.overrideConfig;
+ r.tmpConfig.updateFrom(data.overrideConfig);
+ }
+ performConfigurationChanged(r.activity, r.tmpConfig);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration));
@@ -4212,6 +4256,10 @@ public final class ActivityThread {
} else {
Debug.dumpNativeHeap(dhd.fd.getFileDescriptor());
}
+ try {
+ ActivityManagerNative.getDefault().dumpHeapFinished(dhd.path);
+ } catch (RemoteException e) {
+ }
}
final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index e3b27b5..2939322 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -206,7 +206,6 @@ 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,
@@ -253,17 +252,10 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
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);
- }
- }
+ } else if (!isNested(view, sharedElements)) {
+ mSharedElementNames.add(name);
+ mSharedElements.add(view);
+ sharedElements.removeAt(i);
}
}
isFirstRun = false;
@@ -520,24 +512,9 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
}
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) {
+ final int index = mSharedElementParentMatrices == null ? -1
+ : mSharedElements.indexOf(view);
+ if (index < 0) {
matrix.reset();
ViewParent viewParent = view.getParent();
if (viewParent instanceof ViewGroup) {
@@ -545,6 +522,11 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
ViewGroup parent = (ViewGroup) viewParent;
parent.transformMatrixToLocal(matrix);
}
+ } else {
+ // The indices of mSharedElementParentMatrices matches the
+ // mSharedElement matrices.
+ Matrix parentMatrix = mSharedElementParentMatrices.get(index);
+ matrix.set(parentMatrix);
}
}
@@ -701,7 +683,6 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
mResultReceiver = null;
mPendingTransition = null;
mListener = null;
- mRootSharedElements.clear();
mSharedElementParentMatrices = null;
}
@@ -817,9 +798,12 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
ViewGroup decor = getDecor();
if (decor != null) {
boolean moveWithParent = moveSharedElementWithParent();
+ Matrix tempMatrix = new Matrix();
for (int i = 0; i < numSharedElements; i++) {
View view = mSharedElements.get(i);
- GhostView.addGhost(view, decor);
+ tempMatrix.reset();
+ mSharedElementParentMatrices.get(i).invert(tempMatrix);
+ GhostView.addGhost(view, decor, tempMatrix);
ViewGroup parent = (ViewGroup) view.getParent();
if (moveWithParent && !isInTransitionGroup(parent, decor)) {
GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index a2bfa4e..5c6fe46 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -18,7 +18,6 @@ package android.app;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.transition.Transition;
-import android.util.ArrayMap;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index fecaf6f..2cb27b0 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -82,7 +82,7 @@ public class ActivityView extends ViewGroup {
try {
mActivityContainer = new ActivityContainerWrapper(
- ActivityManagerNative.getDefault().createActivityContainer(
+ ActivityManagerNative.getDefault().createVirtualActivityContainer(
mActivity.getActivityToken(), new ActivityContainerCallback(this)));
} catch (RemoteException e) {
throw new RuntimeException("ActivityView: Unable to create ActivityContainer. "
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 2c596e5..5dd02ae 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -26,7 +26,6 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.WorkSource;
-import android.os.Parcelable.Creator;
/**
* This class provides access to the system alarm services. These allow you
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 3c6458f..3e545f9 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -18,6 +18,10 @@ package android.app;
import com.android.internal.app.AlertController;
+import android.annotation.ArrayRes;
+import android.annotation.AttrRes;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
@@ -34,6 +38,8 @@ import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
+import com.android.internal.R;
+
/**
* A subclass of Dialog that can display one, two or three buttons. If you only want to
* display a String in this dialog box, use the setMessage() method. If you
@@ -44,7 +50,7 @@ import android.widget.ListView;
* FrameLayout fl = (FrameLayout) findViewById(android.R.id.custom);
* fl.addView(myView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
* </pre>
- *
+ *
* <p>The AlertDialog class takes care of automatically setting
* {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
* WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} for you based on whether
@@ -66,31 +72,46 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Special theme constant for {@link #AlertDialog(Context, int)}: use
* the traditional (pre-Holo) alert dialog theme.
+ *
+ * @deprecated Use {@link android.R.style#Theme_Material_Dialog_Alert}.
*/
+ @Deprecated
public static final int THEME_TRADITIONAL = 1;
-
+
/**
* Special theme constant for {@link #AlertDialog(Context, int)}: use
* the holographic alert theme with a dark background.
+ *
+ * @deprecated Use {@link android.R.style#Theme_Material_Dialog_Alert}.
*/
+ @Deprecated
public static final int THEME_HOLO_DARK = 2;
-
+
/**
* Special theme constant for {@link #AlertDialog(Context, int)}: use
* the holographic alert theme with a light background.
+ *
+ * @deprecated Use {@link android.R.style#Theme_Material_Light_Dialog_Alert}.
*/
+ @Deprecated
public static final int THEME_HOLO_LIGHT = 3;
/**
* Special theme constant for {@link #AlertDialog(Context, int)}: use
* the device's default alert theme with a dark background.
+ *
+ * @deprecated Use {@link android.R.style#Theme_DeviceDefault_Dialog_Alert}.
*/
+ @Deprecated
public static final int THEME_DEVICE_DEFAULT_DARK = 4;
/**
* Special theme constant for {@link #AlertDialog(Context, int)}: use
* the device's default alert theme with a light background.
+ *
+ * @deprecated Use {@link android.R.style#Theme_DeviceDefault_Light_Dialog_Alert}.
*/
+ @Deprecated
public static final int THEME_DEVICE_DEFAULT_LIGHT = 5;
/**
@@ -104,55 +125,92 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @hide
*/
public static final int LAYOUT_HINT_SIDE = 1;
-
+
+ /**
+ * Creates an alert dialog that uses the default alert dialog theme.
+ * <p>
+ * The default alert dialog theme is defined by
+ * {@link android.R.attr#alertDialogTheme} within the parent
+ * {@code context}'s theme.
+ *
+ * @param context the parent context
+ */
protected AlertDialog(Context context) {
- this(context, resolveDialogTheme(context, 0), true);
+ this(context, 0);
}
/**
- * Construct an AlertDialog that uses an explicit theme. The actual style
- * that an AlertDialog uses is a private implementation, however you can
- * here supply either the name of an attribute in the theme from which
- * to get the dialog's style (such as {@link android.R.attr#alertDialogTheme}
- * or one of the constants {@link #THEME_TRADITIONAL},
- * {@link #THEME_HOLO_DARK}, or {@link #THEME_HOLO_LIGHT}.
+ * Creates an alert dialog that uses the default alert dialog theme and a
+ * custom cancel listener.
+ * <p>
+ * This is functionally identical to:
+ * <pre>
+ * AlertDialog dialog = new AlertDialog(context);
+ * alertDialog.setCancelable(cancelable);
+ * alertDialog.setOnCancelListener(cancelListener);
+ * </pre>
+ * <p>
+ * The default alert dialog theme is defined by
+ * {@link android.R.attr#alertDialogTheme} within the parent
+ * {@code context}'s theme.
+ *
+ * @param context the parent context
*/
- protected AlertDialog(Context context, int theme) {
- this(context, theme, true);
+ protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
+ this(context, 0);
+
+ setCancelable(cancelable);
+ setOnCancelListener(cancelListener);
}
- AlertDialog(Context context, int theme, boolean createThemeContextWrapper) {
- super(context, resolveDialogTheme(context, theme), createThemeContextWrapper);
+ /**
+ * Creates an alert dialog that uses an explicit theme resource.
+ * <p>
+ * The specified theme resource ({@code themeResId}) is applied on top of
+ * the parent {@code context}'s theme. It may be specified as a style
+ * resource containing a fully-populated theme, such as
+ * {@link android.R.style#Theme_Material_Dialog}, to replace all attributes
+ * in the parent {@code context}'s theme including primary and accent
+ * colors.
+ * <p>
+ * To preserve attributes such as primary and accent colors, the
+ * {@code themeResId} may instead be specified as an overlay theme such as
+ * {@link android.R.style#ThemeOverlay_Material_Dialog}. This will override
+ * only the window attributes necessary to style the alert window as a
+ * dialog.
+ * <p>
+ * Alternatively, the {@code themeResId} may be specified as {@code 0} to
+ * use the parent {@code context}'s resolved value for
+ * {@link android.R.attr#alertDialogTheme}.
+ *
+ * @param context the parent context
+ * @param themeResId the resource ID of the theme against which to inflate
+ * this dialog, or {@code 0} to use the parent
+ * {@code context}'s default alert dialog theme
+ */
+ protected AlertDialog(Context context, @AttrRes int themeResId) {
+ super(context, resolveDialogTheme(context, themeResId));
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = new AlertController(getContext(), this, getWindow());
}
- protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
- super(context, resolveDialogTheme(context, 0));
- mWindow.alwaysReadCloseOnTouchAttr();
- setCancelable(cancelable);
- setOnCancelListener(cancelListener);
- mAlert = new AlertController(context, this, getWindow());
- }
-
- static int resolveDialogTheme(Context context, int resid) {
- if (resid == THEME_TRADITIONAL) {
- return com.android.internal.R.style.Theme_Dialog_Alert;
- } else if (resid == THEME_HOLO_DARK) {
- return com.android.internal.R.style.Theme_Holo_Dialog_Alert;
- } else if (resid == THEME_HOLO_LIGHT) {
- return com.android.internal.R.style.Theme_Holo_Light_Dialog_Alert;
- } else if (resid == THEME_DEVICE_DEFAULT_DARK) {
- return com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert;
- } else if (resid == THEME_DEVICE_DEFAULT_LIGHT) {
- return com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog_Alert;
- } else if (resid >= 0x01000000) { // start of real resource IDs.
- return resid;
+ static int resolveDialogTheme(Context context, int themeResId) {
+ if (themeResId == THEME_TRADITIONAL) {
+ return R.style.Theme_Dialog_Alert;
+ } else if (themeResId == THEME_HOLO_DARK) {
+ return R.style.Theme_Holo_Dialog_Alert;
+ } else if (themeResId == THEME_HOLO_LIGHT) {
+ return R.style.Theme_Holo_Light_Dialog_Alert;
+ } else if (themeResId == THEME_DEVICE_DEFAULT_DARK) {
+ return R.style.Theme_DeviceDefault_Dialog_Alert;
+ } else if (themeResId == THEME_DEVICE_DEFAULT_LIGHT) {
+ return R.style.Theme_DeviceDefault_Light_Dialog_Alert;
+ } else if (themeResId >= 0x01000000) { // start of real resource IDs.
+ return themeResId;
} else {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
- outValue, true);
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);
return outValue.resourceId;
}
}
@@ -173,13 +231,13 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Gets the list view used in the dialog.
- *
+ *
* @return The {@link ListView} from the dialog.
*/
public ListView getListView() {
return mAlert.getListView();
}
-
+
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
@@ -192,7 +250,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView);
}
-
+
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
@@ -203,9 +261,9 @@ public class AlertDialog extends Dialog implements DialogInterface {
public void setView(View view) {
mAlert.setView(view);
}
-
+
/**
- * Set the view to display in that dialog, specifying the spacing to appear around that
+ * Set the view to display in that dialog, specifying the spacing to appear around that
* view.
*
* @param view The view to show in the content area of the dialog
@@ -229,7 +287,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set a message to be sent when a button is pressed.
- *
+ *
* @param whichButton Which button to set the message for, can be one of
* {@link DialogInterface#BUTTON_POSITIVE},
* {@link DialogInterface#BUTTON_NEGATIVE}, or
@@ -240,10 +298,10 @@ public class AlertDialog extends Dialog implements DialogInterface {
public void setButton(int whichButton, CharSequence text, Message msg) {
mAlert.setButton(whichButton, text, null, msg);
}
-
+
/**
* Set a listener to be invoked when the positive button of the dialog is pressed.
- *
+ *
* @param whichButton Which button to set the listener on, can be one of
* {@link DialogInterface#BUTTON_POSITIVE},
* {@link DialogInterface#BUTTON_NEGATIVE}, or
@@ -263,7 +321,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
public void setButton(CharSequence text, Message msg) {
setButton(BUTTON_POSITIVE, text, msg);
}
-
+
/**
* @deprecated Use {@link #setButton(int, CharSequence, Message)} with
* {@link DialogInterface#BUTTON_NEGATIVE}.
@@ -284,7 +342,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set a listener to be invoked when button 1 of the dialog is pressed.
- *
+ *
* @param text The text to display in button 1.
* @param listener The {@link DialogInterface.OnClickListener} to use.
* @deprecated Use
@@ -327,10 +385,10 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param resId the resourceId of the drawable to use as the icon or 0
* if you don't want an icon.
*/
- public void setIcon(int resId) {
+ public void setIcon(@DrawableRes int resId) {
mAlert.setIcon(resId);
}
-
+
public void setIcon(Drawable icon) {
mAlert.setIcon(icon);
}
@@ -340,7 +398,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @param attrId ID of a theme attribute that points to a drawable resource.
*/
- public void setIconAttribute(int attrId) {
+ public void setIconAttribute(@AttrRes int attrId) {
TypedValue out = new TypedValue();
mContext.getTheme().resolveAttribute(attrId, out, true);
mAlert.setIcon(out.resourceId);
@@ -349,7 +407,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
public void setInverseBackgroundForced(boolean forceInverseBackground) {
mAlert.setInverseBackgroundForced(forceInverseBackground);
}
-
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -367,35 +425,57 @@ public class AlertDialog extends Dialog implements DialogInterface {
if (mAlert.onKeyUp(keyCode, event)) return true;
return super.onKeyUp(keyCode, event);
}
-
+
public static class Builder {
private final AlertController.AlertParams P;
- private int mTheme;
-
+ private int mThemeResId;
+
/**
- * Constructor using a context for this builder and the {@link AlertDialog} it creates.
+ * Creates a builder for an alert dialog that uses the default alert
+ * dialog theme.
+ * <p>
+ * The default alert dialog theme is defined by
+ * {@link android.R.attr#alertDialogTheme} within the parent
+ * {@code context}'s theme.
+ *
+ * @param context the parent context
*/
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
/**
- * Constructor using a context and theme for this builder and
- * the {@link AlertDialog} it creates. The actual theme
- * that an AlertDialog uses is a private implementation, however you can
- * here supply either the name of an attribute in the theme from which
- * to get the dialog's style (such as {@link android.R.attr#alertDialogTheme}
- * or one of the constants
- * {@link AlertDialog#THEME_TRADITIONAL AlertDialog.THEME_TRADITIONAL},
- * {@link AlertDialog#THEME_HOLO_DARK AlertDialog.THEME_HOLO_DARK}, or
- * {@link AlertDialog#THEME_HOLO_LIGHT AlertDialog.THEME_HOLO_LIGHT}.
+ * Creates a builder for an alert dialog that uses an explicit theme
+ * resource.
+ * <p>
+ * The specified theme resource ({@code themeResId}) is applied on top
+ * of the parent {@code context}'s theme. It may be specified as a
+ * style resource containing a fully-populated theme, such as
+ * {@link android.R.style#Theme_Material_Dialog}, to replace all
+ * attributes in the parent {@code context}'s theme including primary
+ * and accent colors.
+ * <p>
+ * To preserve attributes such as primary and accent colors, the
+ * {@code themeResId} may instead be specified as an overlay theme such
+ * as {@link android.R.style#ThemeOverlay_Material_Dialog}. This will
+ * override only the window attributes necessary to style the alert
+ * window as a dialog.
+ * <p>
+ * Alternatively, the {@code themeResId} may be specified as {@code 0}
+ * to use the parent {@code context}'s resolved value for
+ * {@link android.R.attr#alertDialogTheme}.
+ *
+ * @param context the parent context
+ * @param themeResId the resource ID of the theme against which to inflate
+ * this dialog, or {@code 0} to use the parent
+ * {@code context}'s default alert dialog theme
*/
- public Builder(Context context, int theme) {
+ public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
- context, resolveDialogTheme(context, theme)));
- mTheme = theme;
+ context, resolveDialogTheme(context, themeResId)));
+ mThemeResId = themeResId;
}
-
+
/**
* Returns a {@link Context} with the appropriate theme for dialogs created by this Builder.
* Applications should use this Context for obtaining LayoutInflaters for inflating views
@@ -413,11 +493,11 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setTitle(int titleId) {
+ public Builder setTitle(@StringRes int titleId) {
P.mTitle = P.mContext.getText(titleId);
return this;
}
-
+
/**
* Set the title displayed in the {@link Dialog}.
*
@@ -427,33 +507,38 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mTitle = title;
return this;
}
-
+
/**
- * Set the title using the custom view {@code customTitleView}. The
- * methods {@link #setTitle(int)} and {@link #setIcon(int)} should be
- * sufficient for most titles, but this is provided if the title needs
- * more customization. Using this will replace the title and icon set
- * via the other methods.
- *
- * @param customTitleView The custom view to use as the title.
+ * Set the title using the custom view {@code customTitleView}.
+ * <p>
+ * The methods {@link #setTitle(int)} and {@link #setIcon(int)} should
+ * be sufficient for most titles, but this is provided if the title
+ * needs more customization. Using this will replace the title and icon
+ * set via the other methods.
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the custom view
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
*
- * @return This Builder object to allow for chaining of calls to set methods
+ * @param customTitleView the custom view to use as the title
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
*/
public Builder setCustomTitle(View customTitleView) {
P.mCustomTitleView = customTitleView;
return this;
}
-
+
/**
* Set the message to display using the given resource id.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setMessage(int messageId) {
+ public Builder setMessage(@StringRes int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
-
+
/**
* Set the message to display.
*
@@ -463,7 +548,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mMessage = message;
return this;
}
-
+
/**
* Set the resource id of the {@link Drawable} to be used in the title.
* <p>
@@ -471,15 +556,20 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setIcon(int iconId) {
+ public Builder setIcon(@DrawableRes int iconId) {
P.mIconId = iconId;
return this;
}
-
+
/**
* Set the {@link Drawable} to be used in the title.
- *
- * @return This Builder object to allow for chaining of calls to set methods
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the drawable
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
+ *
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
*/
public Builder setIcon(Drawable icon) {
P.mIcon = icon;
@@ -495,7 +585,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @param attrId ID of a theme attribute that points to a drawable resource.
*/
- public Builder setIconAttribute(int attrId) {
+ public Builder setIconAttribute(@AttrRes int attrId) {
TypedValue out = new TypedValue();
P.mContext.getTheme().resolveAttribute(attrId, out, true);
P.mIconId = out.resourceId;
@@ -509,12 +599,12 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setPositiveButton(int textId, final OnClickListener listener) {
+ public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
P.mPositiveButtonText = P.mContext.getText(textId);
P.mPositiveButtonListener = listener;
return this;
}
-
+
/**
* Set a listener to be invoked when the positive button of the dialog is pressed.
* @param text The text to display in the positive button
@@ -527,7 +617,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mPositiveButtonListener = listener;
return this;
}
-
+
/**
* Set a listener to be invoked when the negative button of the dialog is pressed.
* @param textId The resource id of the text to display in the negative button
@@ -535,12 +625,12 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setNegativeButton(int textId, final OnClickListener listener) {
+ public Builder setNegativeButton(@StringRes int textId, final OnClickListener listener) {
P.mNegativeButtonText = P.mContext.getText(textId);
P.mNegativeButtonListener = listener;
return this;
}
-
+
/**
* Set a listener to be invoked when the negative button of the dialog is pressed.
* @param text The text to display in the negative button
@@ -553,7 +643,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mNegativeButtonListener = listener;
return this;
}
-
+
/**
* Set a listener to be invoked when the neutral button of the dialog is pressed.
* @param textId The resource id of the text to display in the neutral button
@@ -561,12 +651,12 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setNeutralButton(int textId, final OnClickListener listener) {
+ public Builder setNeutralButton(@StringRes int textId, final OnClickListener listener) {
P.mNeutralButtonText = P.mContext.getText(textId);
P.mNeutralButtonListener = listener;
return this;
}
-
+
/**
* Set a listener to be invoked when the neutral button of the dialog is pressed.
* @param text The text to display in the neutral button
@@ -579,7 +669,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mNeutralButtonListener = listener;
return this;
}
-
+
/**
* Sets whether the dialog is cancelable or not. Default is true.
*
@@ -589,7 +679,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mCancelable = cancelable;
return this;
}
-
+
/**
* Sets the callback that will be called if the dialog is canceled.
*
@@ -607,7 +697,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mOnCancelListener = onCancelListener;
return this;
}
-
+
/**
* Sets the callback that will be called when the dialog is dismissed for any reason.
*
@@ -627,19 +717,19 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mOnKeyListener = onKeyListener;
return this;
}
-
+
/**
* Set a list of items to be displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener. This should be an array type i.e. R.array.foo
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setItems(int itemsId, final OnClickListener listener) {
+ public Builder setItems(@ArrayRes int itemsId, final OnClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
P.mOnClickListener = listener;
return this;
}
-
+
/**
* Set a list of items to be displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener.
@@ -651,12 +741,12 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mOnClickListener = listener;
return this;
}
-
+
/**
* Set a list of items, which are supplied by the given {@link ListAdapter}, to be
* displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener.
- *
+ *
* @param adapter The {@link ListAdapter} to supply the list of items
* @param listener The listener that will be called when an item is clicked.
*
@@ -667,12 +757,12 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mOnClickListener = listener;
return this;
}
-
+
/**
* Set a list of items, which are supplied by the given {@link Cursor}, to be
* displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener.
- *
+ *
* @param cursor The {@link Cursor} to supply the list of items
* @param listener The listener that will be called when an item is clicked.
* @param labelColumn The column name on the cursor containing the string to display
@@ -687,7 +777,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mOnClickListener = listener;
return this;
}
-
+
/**
* Set a list of items to be displayed in the dialog as the content,
* you will be notified of the selected item via the supplied listener.
@@ -695,7 +785,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
* a check mark displayed to the right of the text for each checked
* item. Clicking on an item in the list will not dismiss the dialog.
* Clicking on a button will dismiss the dialog.
- *
+ *
* @param itemsId the resource id of an array i.e. R.array.foo
* @param checkedItems specifies which items are checked. It should be null in which case no
* items are checked. If non null it must be exactly the same length as the array of
@@ -706,7 +796,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems,
+ public Builder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems,
final OnMultiChoiceClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
P.mOnCheckboxClickListener = listener;
@@ -714,14 +804,14 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mIsMultiChoice = true;
return this;
}
-
+
/**
* Set a list of items to be displayed in the dialog as the content,
* you will be notified of the selected item via the supplied listener.
* The list will have a check mark displayed to the right of the text
* for each checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
- *
+ *
* @param items the text of the items to be displayed in the list.
* @param checkedItems specifies which items are checked. It should be null in which case no
* items are checked. If non null it must be exactly the same length as the array of
@@ -732,7 +822,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
+ public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
final OnMultiChoiceClickListener listener) {
P.mItems = items;
P.mOnCheckboxClickListener = listener;
@@ -740,14 +830,14 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mIsMultiChoice = true;
return this;
}
-
+
/**
* Set a list of items to be displayed in the dialog as the content,
* you will be notified of the selected item via the supplied listener.
* The list will have a check mark displayed to the right of the text
* for each checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
- *
+ *
* @param cursor the cursor used to provide the items.
* @param isCheckedColumn specifies the column name on the cursor to use to determine
* whether a checkbox is checked or not. It must return an integer value where 1
@@ -760,7 +850,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn,
+ public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn,
final OnMultiChoiceClickListener listener) {
P.mCursor = cursor;
P.mOnCheckboxClickListener = listener;
@@ -769,14 +859,14 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mIsMultiChoice = true;
return this;
}
-
+
/**
* Set a list of items to be displayed in the dialog as the content, you will be notified of
* the selected item via the supplied listener. This should be an array type i.e.
* R.array.foo The list will have a check mark displayed to the right of the text for the
* checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a
* button will dismiss the dialog.
- *
+ *
* @param itemsId the resource id of an array i.e. R.array.foo
* @param checkedItem specifies which item is checked. If -1 no items are checked.
* @param listener notified when an item on the list is clicked. The dialog will not be
@@ -785,7 +875,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setSingleChoiceItems(int itemsId, int checkedItem,
+ public Builder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem,
final OnClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
P.mOnClickListener = listener;
@@ -793,13 +883,13 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mIsSingleChoice = true;
return this;
}
-
+
/**
* Set a list of items to be displayed in the dialog as the content, you will be notified of
* the selected item via the supplied listener. The list will have a check mark displayed to
* the right of the text for the checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
- *
+ *
* @param cursor the cursor to retrieve the items from.
* @param checkedItem specifies which item is checked. If -1 no items are checked.
* @param labelColumn The column name on the cursor containing the string to display in the
@@ -810,7 +900,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn,
+ public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn,
final OnClickListener listener) {
P.mCursor = cursor;
P.mOnClickListener = listener;
@@ -819,13 +909,13 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mIsSingleChoice = true;
return this;
}
-
+
/**
* Set a list of items to be displayed in the dialog as the content, you will be notified of
* the selected item via the supplied listener. The list will have a check mark displayed to
* the right of the text for the checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
- *
+ *
* @param items the items to be displayed.
* @param checkedItem specifies which item is checked. If -1 no items are checked.
* @param listener notified when an item on the list is clicked. The dialog will not be
@@ -840,14 +930,14 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mCheckedItem = checkedItem;
P.mIsSingleChoice = true;
return this;
- }
-
+ }
+
/**
* Set a list of items to be displayed in the dialog as the content, you will be notified of
* the selected item via the supplied listener. The list will have a check mark displayed to
* the right of the text for the checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
- *
+ *
* @param adapter The {@link ListAdapter} to supply the list of items
* @param checkedItem specifies which item is checked. If -1 no items are checked.
* @param listener notified when an item on the list is clicked. The dialog will not be
@@ -863,26 +953,25 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mIsSingleChoice = true;
return this;
}
-
+
/**
* Sets a listener to be invoked when an item in the list is selected.
- *
- * @param listener The listener to be invoked.
- * @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
*
- * @return This Builder object to allow for chaining of calls to set methods
+ * @param listener the listener to be invoked
+ * @return this Builder object to allow for chaining of calls to set methods
+ * @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
*/
public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) {
P.mOnItemSelectedListener = listener;
return this;
}
-
+
/**
* Set a custom view resource to be the contents of the Dialog. The
* resource will be inflated, adding all top-level views to the screen.
*
* @param layoutResId Resource ID to be inflated.
- * @return This Builder object to allow for chaining of calls to set
+ * @return this Builder object to allow for chaining of calls to set
* methods
*/
public Builder setView(int layoutResId) {
@@ -893,12 +982,18 @@ public class AlertDialog extends Dialog implements DialogInterface {
}
/**
- * Set a custom view to be the contents of the Dialog. If the supplied view is an instance
- * of a {@link ListView} the light background will be used.
+ * Sets a custom view to be the contents of the alert dialog.
+ * <p>
+ * When using a pre-Holo theme, if the supplied view is an instance of
+ * a {@link ListView} then the light background will be used.
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the custom view
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
*
- * @param view The view to use as the contents of the Dialog.
- *
- * @return This Builder object to allow for chaining of calls to set methods
+ * @param view the view to use as the contents of the alert dialog
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
*/
public Builder setView(View view) {
P.mView = view;
@@ -906,29 +1001,34 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mViewSpacingSpecified = false;
return this;
}
-
- /**
- * Set a custom view to be the contents of the Dialog, specifying the
- * spacing to appear around that view. If the supplied view is an
- * instance of a {@link ListView} the light background will be used.
- *
- * @param view The view to use as the contents of the Dialog.
- * @param viewSpacingLeft Spacing between the left edge of the view and
- * the dialog frame
- * @param viewSpacingTop Spacing between the top edge of the view and
- * the dialog frame
- * @param viewSpacingRight Spacing between the right edge of the view
- * and the dialog frame
- * @param viewSpacingBottom Spacing between the bottom edge of the view
- * and the dialog frame
- * @return This Builder object to allow for chaining of calls to set
+
+ /**
+ * Sets a custom view to be the contents of the alert dialog and
+ * specifies additional padding around that view.
+ * <p>
+ * When using a pre-Holo theme, if the supplied view is an instance of
+ * a {@link ListView} then the light background will be used.
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the custom view
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
+ *
+ * @param view the view to use as the contents of the alert dialog
+ * @param viewSpacingLeft spacing between the left edge of the view and
+ * the dialog frame
+ * @param viewSpacingTop spacing between the top edge of the view and
+ * the dialog frame
+ * @param viewSpacingRight spacing between the right edge of the view
+ * and the dialog frame
+ * @param viewSpacingBottom spacing between the bottom edge of the view
+ * and the dialog frame
+ * @return this Builder object to allow for chaining of calls to set
* methods
- *
- *
- * This is currently hidden because it seems like people should just
- * be able to put padding around the view.
- * @hide
+ *
+ * @hide Remove once the framework usages have been replaced.
+ * @deprecated Set the padding on the view itself.
*/
+ @Deprecated
public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
int viewSpacingRight, int viewSpacingBottom) {
P.mView = view;
@@ -940,15 +1040,18 @@ public class AlertDialog extends Dialog implements DialogInterface {
P.mViewSpacingBottom = viewSpacingBottom;
return this;
}
-
+
/**
- * Sets the Dialog to use the inverse background, regardless of what the
- * contents is.
- *
- * @param useInverseBackground Whether to use the inverse background
- *
- * @return This Builder object to allow for chaining of calls to set methods
+ * Sets the alert dialog to use the inverse background, regardless of
+ * what the contents is.
+ *
+ * @param useInverseBackground whether to use the inverse background
+ * @return this Builder object to allow for chaining of calls to set methods
+ * @deprecated This flag is only used for pre-Material themes. Instead,
+ * specify the window background using on the alert dialog
+ * theme.
*/
+ @Deprecated
public Builder setInverseBackgroundForced(boolean useInverseBackground) {
P.mForceInverseBackground = useInverseBackground;
return this;
@@ -964,13 +1067,15 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
- * Creates a {@link AlertDialog} with the arguments supplied to this builder. It does not
- * {@link Dialog#show()} the dialog. This allows the user to do any extra processing
- * before displaying the dialog. Use {@link #show()} if you don't have any other processing
- * to do and want this to be created and displayed.
+ * Creates an {@link AlertDialog} with the arguments supplied to this
+ * builder.
+ * <p>
+ * Calling this method does not display the dialog. If no additional
+ * processing is needed, {@link #show()} may be called instead to both
+ * create and display the dialog.
*/
public AlertDialog create() {
- final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
+ final AlertDialog dialog = new AlertDialog(P.mContext, mThemeResId);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
@@ -985,14 +1090,20 @@ public class AlertDialog extends Dialog implements DialogInterface {
}
/**
- * Creates a {@link AlertDialog} with the arguments supplied to this builder and
- * {@link Dialog#show()}'s the dialog.
+ * Creates an {@link AlertDialog} with the arguments supplied to this
+ * builder and immediately displays the dialog.
+ * <p>
+ * Calling this method is functionally identical to:
+ * <pre>
+ * AlertDialog dialog = builder.create();
+ * dialog.show();
+ * </pre>
*/
public AlertDialog show() {
- AlertDialog dialog = create();
+ final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
-
+
}
diff --git a/core/java/android/app/AppImportanceMonitor.java b/core/java/android/app/AppImportanceMonitor.java
index c760e1e..e0d0d8d 100644
--- a/core/java/android/app/AppImportanceMonitor.java
+++ b/core/java/android/app/AppImportanceMonitor.java
@@ -22,8 +22,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.SparseArray;
-import android.util.SparseIntArray;
-
import java.util.List;
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 95870cf..4bd2332 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -206,8 +206,10 @@ public class AppOpsManager {
public static final int OP_PROJECT_MEDIA = 46;
/** @hide Activate a VPN connection without user intervention. */
public static final int OP_ACTIVATE_VPN = 47;
+ /** @hide Access the WallpaperManagerAPI to write wallpapers. */
+ public static final int OP_WRITE_WALLPAPER = 48;
/** @hide */
- public static final int _NUM_OP = 48;
+ public static final int _NUM_OP = 49;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION =
@@ -285,6 +287,7 @@ public class AppOpsManager {
OP_TOAST_WINDOW,
OP_PROJECT_MEDIA,
OP_ACTIVATE_VPN,
+ OP_WRITE_WALLPAPER,
};
/**
@@ -340,6 +343,7 @@ public class AppOpsManager {
null,
null,
OPSTR_ACTIVATE_VPN,
+ null,
};
/**
@@ -395,6 +399,7 @@ public class AppOpsManager {
"TOAST_WINDOW",
"PROJECT_MEDIA",
"ACTIVATE_VPN",
+ "WRITE_WALLPAPER",
};
/**
@@ -450,6 +455,7 @@ public class AppOpsManager {
null, // no permission for displaying toasts
null, // no permission for projecting media
null, // no permission for activating vpn
+ null, // no permission for supporting wallpaper
};
/**
@@ -506,6 +512,7 @@ public class AppOpsManager {
UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW
null, //PROJECT_MEDIA
UserManager.DISALLOW_CONFIG_VPN, // ACTIVATE_VPN
+ UserManager.DISALLOW_WALLPAPER, // WRITE_WALLPAPER
};
/**
@@ -561,6 +568,7 @@ public class AppOpsManager {
true, //TOAST_WINDOW
false, //PROJECT_MEDIA
false, //ACTIVATE_VPN
+ false, //WALLPAPER
};
/**
@@ -615,6 +623,7 @@ public class AppOpsManager {
AppOpsManager.MODE_ALLOWED,
AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA
AppOpsManager.MODE_IGNORED, // OP_ACTIVATE_VPN
+ AppOpsManager.MODE_ALLOWED,
};
/**
@@ -673,6 +682,7 @@ public class AppOpsManager {
false,
false,
false,
+ false,
};
private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index d808c8b..9f81670 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,6 +16,9 @@
package android.app;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.annotation.XmlRes;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
@@ -730,7 +733,7 @@ final class ApplicationPackageManager extends PackageManager {
}
}
- @Override public Drawable getDrawable(String packageName, int resid,
+ @Override public Drawable getDrawable(String packageName, @DrawableRes int resid,
ApplicationInfo appInfo) {
ResourceName name = new ResourceName(packageName, resid);
Drawable dr = getCachedIcon(name);
@@ -1137,7 +1140,7 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
- public CharSequence getText(String packageName, int resid,
+ public CharSequence getText(String packageName, @StringRes int resid,
ApplicationInfo appInfo) {
ResourceName name = new ResourceName(packageName, resid);
CharSequence text = getCachedString(name);
@@ -1170,7 +1173,7 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
- public XmlResourceParser getXml(String packageName, int resid,
+ public XmlResourceParser getXml(String packageName, @XmlRes int resid,
ApplicationInfo appInfo) {
if (appInfo == null) {
try {
@@ -1659,7 +1662,7 @@ final class ApplicationPackageManager extends PackageManager {
int flags) {
try {
mPM.addCrossProfileIntentFilter(filter, mContext.getOpPackageName(),
- mContext.getUserId(), sourceUserId, targetUserId, flags);
+ sourceUserId, targetUserId, flags);
} catch (RemoteException e) {
// Should never happen!
}
@@ -1671,8 +1674,7 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public void clearCrossProfileIntentFilters(int sourceUserId) {
try {
- mPM.clearCrossProfileIntentFilters(sourceUserId, mContext.getOpPackageName(),
- mContext.getUserId());
+ mPM.clearCrossProfileIntentFilters(sourceUserId, mContext.getOpPackageName());
} catch (RemoteException e) {
// Should never happen!
}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index eb3ddb2..b6989ab 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -140,6 +140,10 @@ public abstract class ApplicationThreadNative extends Binder
int ident = data.readInt();
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
+ Configuration overrideConfig = null;
+ if (data.readInt() != 0) {
+ overrideConfig = Configuration.CREATOR.createFromParcel(data);
+ }
CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
String referrer = data.readString();
IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
@@ -153,8 +157,8 @@ public abstract class ApplicationThreadNative extends Binder
boolean isForward = data.readInt() != 0;
ProfilerInfo profilerInfo = data.readInt() != 0
? ProfilerInfo.CREATOR.createFromParcel(data) : null;
- scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, referrer,
- voiceInteractor, procState, state, persistentState, ri, pi,
+ scheduleLaunchActivity(intent, b, ident, info, curConfig, overrideConfig, compatInfo,
+ referrer, voiceInteractor, procState, state, persistentState, ri, pi,
notResumed, isForward, profilerInfo);
return true;
}
@@ -167,14 +171,15 @@ public abstract class ApplicationThreadNative extends Binder
List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
int configChanges = data.readInt();
boolean notResumed = data.readInt() != 0;
- Configuration config = null;
+ Configuration config = Configuration.CREATOR.createFromParcel(data);
+ Configuration overrideConfig = null;
if (data.readInt() != 0) {
- config = Configuration.CREATOR.createFromParcel(data);
+ overrideConfig = Configuration.CREATOR.createFromParcel(data);
}
- scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config);
+ scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config, overrideConfig);
return true;
}
-
+
case SCHEDULE_NEW_INTENT_TRANSACTION:
{
data.enforceInterface(IApplicationThread.descriptor);
@@ -404,7 +409,11 @@ public abstract class ApplicationThreadNative extends Binder
{
data.enforceInterface(IApplicationThread.descriptor);
IBinder b = data.readStrongBinder();
- scheduleActivityConfigurationChanged(b);
+ Configuration overrideConfig = null;
+ if (data.readInt() != 0) {
+ overrideConfig = Configuration.CREATOR.createFromParcel(data);
+ }
+ scheduleActivityConfigurationChanged(b, overrideConfig);
return true;
}
@@ -775,11 +784,11 @@ class ApplicationThreadProxy implements IApplicationThread {
}
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
- PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
- ProfilerInfo profilerInfo) throws RemoteException {
+ ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+ CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
+ int procState, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
intent.writeToParcel(data, 0);
@@ -787,6 +796,12 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeInt(ident);
info.writeToParcel(data, 0);
curConfig.writeToParcel(data, 0);
+ if (overrideConfig != null) {
+ data.writeInt(1);
+ overrideConfig.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
compatInfo.writeToParcel(data, 0);
data.writeString(referrer);
data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
@@ -810,8 +825,8 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config)
- throws RemoteException {
+ int configChanges, boolean notResumed, Configuration config,
+ Configuration overrideConfig) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
@@ -819,9 +834,10 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeTypedList(pendingNewIntents);
data.writeInt(configChanges);
data.writeInt(notResumed ? 1 : 0);
- if (config != null) {
+ config.writeToParcel(data, 0);
+ if (overrideConfig != null) {
data.writeInt(1);
- config.writeToParcel(data, 0);
+ overrideConfig.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
@@ -1104,6 +1120,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+ @Override
public final void scheduleLowMemory() throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
@@ -1112,16 +1129,24 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+ @Override
public final void scheduleActivityConfigurationChanged(
- IBinder token) throws RemoteException {
+ IBinder token, Configuration overrideConfig) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
+ if (overrideConfig != null) {
+ data.writeInt(1);
+ overrideConfig.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
+ @Override
public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType)
throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/AssistContent.aidl b/core/java/android/app/AssistContent.aidl
new file mode 100644
index 0000000..a6321bf
--- /dev/null
+++ b/core/java/android/app/AssistContent.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable AssistContent;
diff --git a/core/java/android/app/AssistContent.java b/core/java/android/app/AssistContent.java
new file mode 100644
index 0000000..ace4af7
--- /dev/null
+++ b/core/java/android/app/AssistContent.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Holds information about the content an application is viewing, to hand to an
+ * assistant at the user's request. This is filled in by
+ * {@link Activity#onProvideAssistContent Activity.onProvideAssistContent}.
+ */
+public class AssistContent implements Parcelable {
+ private Intent mIntent;
+ private ClipData mClipData;
+
+ /**
+ * Key name this data structure is stored in the Bundle generated by
+ * {@link Activity#onProvideAssistData}.
+ */
+ public static final String ASSIST_KEY = "android:assist_content";
+
+ /**
+ * Retrieve the framework-generated AssistContent that is stored within
+ * the Bundle filled in by {@link Activity#onProvideAssistContent}.
+ */
+ public static AssistContent getAssistContent(Bundle assistBundle) {
+ return assistBundle.getParcelable(ASSIST_KEY);
+ }
+
+ public AssistContent() {
+ }
+
+ /**
+ * Sets the Intent associated with the content, describing the current top-level context of
+ * the activity. If this contains a reference to a piece of data related to the activity,
+ * be sure to set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} so the accessibilty
+ * service can access it.
+ */
+ public void setIntent(Intent intent) {
+ mIntent = intent;
+ }
+
+ /**
+ * Return the current {@link #setIntent}, which you can modify in-place.
+ */
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Optional additional content items that are involved with
+ * the current UI. Access to this content will be granted to the assistant as if you
+ * are sending it through an Intent with {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}.
+ */
+ public void setClipData(ClipData clip) {
+ mClipData = clip;
+ }
+
+ /**
+ * Return the current {@link #setClipData}, which you can modify in-place.
+ */
+ public ClipData getClipData() {
+ return mClipData;
+ }
+
+ AssistContent(Parcel in) {
+ if (in.readInt() != 0) {
+ mIntent = Intent.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
+ mClipData = ClipData.CREATOR.createFromParcel(in);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mIntent != null) {
+ dest.writeInt(1);
+ mIntent.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mClipData != null) {
+ dest.writeInt(1);
+ mClipData.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ public static final Parcelable.Creator<AssistContent> CREATOR
+ = new Parcelable.Creator<AssistContent>() {
+ public AssistContent createFromParcel(Parcel in) {
+ return new AssistContent(in);
+ }
+
+ public AssistContent[] newArray(int size) {
+ return new AssistContent[size];
+ }
+ };
+}
diff --git a/core/java/android/app/AssistStructure.aidl b/core/java/android/app/AssistStructure.aidl
new file mode 100644
index 0000000..07fb2453
--- /dev/null
+++ b/core/java/android/app/AssistStructure.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable AssistStructure;
diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java
new file mode 100644
index 0000000..25153fc
--- /dev/null
+++ b/core/java/android/app/AssistStructure.java
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.ComponentName;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PooledStringReader;
+import android.os.PooledStringWriter;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewAssistStructure;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+import android.widget.Checkable;
+
+import java.util.ArrayList;
+
+/**
+ * Assist data automatically created by the platform's implementation
+ * of {@link Activity#onProvideAssistData}. Retrieve it from the assist
+ * data with {@link #getAssistStructure(android.os.Bundle)}.
+ */
+final public class AssistStructure implements Parcelable {
+ static final String TAG = "AssistStructure";
+
+ /**
+ * Key name this data structure is stored in the Bundle generated by
+ * {@link Activity#onProvideAssistData}.
+ */
+ public static final String ASSIST_KEY = "android:assist_structure";
+
+ final ComponentName mActivityComponent;
+
+ final ArrayList<ViewNodeImpl> mRootViews = new ArrayList<>();
+
+ ViewAssistStructureImpl mTmpViewAssistStructureImpl = new ViewAssistStructureImpl();
+ Bundle mTmpExtras = new Bundle();
+
+ final static class ViewAssistStructureImpl extends ViewAssistStructure {
+ CharSequence mText;
+ int mTextSelectionStart = -1;
+ int mTextSelectionEnd = -1;
+ int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED;
+ int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED;
+ float mTextSize = 0;
+ int mTextStyle = 0;
+ CharSequence mHint;
+
+ @Override
+ public void setText(CharSequence text) {
+ mText = text;
+ mTextSelectionStart = mTextSelectionEnd = -1;
+ }
+
+ @Override
+ public void setText(CharSequence text, int selectionStart, int selectionEnd) {
+ mText = text;
+ mTextSelectionStart = selectionStart;
+ mTextSelectionEnd = selectionEnd;
+ }
+
+ @Override
+ public void setTextPaint(TextPaint paint) {
+ mTextColor = paint.getColor();
+ mTextBackgroundColor = paint.bgColor;
+ mTextSize = paint.getTextSize();
+ mTextStyle = 0;
+ Typeface tf = paint.getTypeface();
+ if (tf != null) {
+ if (tf.isBold()) {
+ mTextStyle |= ViewNode.TEXT_STYLE_BOLD;
+ }
+ if (tf.isItalic()) {
+ mTextStyle |= ViewNode.TEXT_STYLE_ITALIC;
+ }
+ }
+ int pflags = paint.getFlags();
+ if ((pflags& Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
+ mTextStyle |= ViewNode.TEXT_STYLE_BOLD;
+ }
+ if ((pflags& Paint.UNDERLINE_TEXT_FLAG) != 0) {
+ mTextStyle |= ViewNode.TEXT_STYLE_UNDERLINE;
+ }
+ if ((pflags& Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
+ mTextStyle |= ViewNode.TEXT_STYLE_STRIKE_THRU;
+ }
+ }
+
+ @Override
+ public void setHint(CharSequence hint) {
+ mHint = hint;
+ }
+
+ @Override
+ public CharSequence getText() {
+ return mText;
+ }
+
+ @Override
+ public int getTextSelectionStart() {
+ return mTextSelectionStart;
+ }
+
+ @Override
+ public int getTextSelectionEnd() {
+ return mTextSelectionEnd;
+ }
+
+ @Override
+ public CharSequence getHint() {
+ return mHint;
+ }
+ }
+
+ final static class ViewNodeTextImpl {
+ final CharSequence mText;
+ final int mTextSelectionStart;
+ final int mTextSelectionEnd;
+ int mTextColor;
+ int mTextBackgroundColor;
+ float mTextSize;
+ int mTextStyle;
+ final String mHint;
+
+ ViewNodeTextImpl(ViewAssistStructureImpl data) {
+ mText = data.mText;
+ mTextSelectionStart = data.mTextSelectionStart;
+ mTextSelectionEnd = data.mTextSelectionEnd;
+ mTextColor = data.mTextColor;
+ mTextBackgroundColor = data.mTextBackgroundColor;
+ mTextSize = data.mTextSize;
+ mTextStyle = data.mTextStyle;
+ mHint = data.mHint != null ? data.mHint.toString() : null;
+ }
+
+ ViewNodeTextImpl(Parcel in) {
+ mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mTextSelectionStart = in.readInt();
+ mTextSelectionEnd = in.readInt();
+ mTextColor = in.readInt();
+ mTextBackgroundColor = in.readInt();
+ mTextSize = in.readFloat();
+ mTextStyle = in.readInt();
+ mHint = in.readString();
+ }
+
+ void writeToParcel(Parcel out) {
+ TextUtils.writeToParcel(mText, out, 0);
+ out.writeInt(mTextSelectionStart);
+ out.writeInt(mTextSelectionEnd);
+ out.writeInt(mTextColor);
+ out.writeInt(mTextBackgroundColor);
+ out.writeFloat(mTextSize);
+ out.writeInt(mTextStyle);
+ out.writeString(mHint);
+ }
+ }
+
+ final static class ViewNodeImpl {
+ final int mX;
+ final int mY;
+ final int mScrollX;
+ final int mScrollY;
+ final int mWidth;
+ final int mHeight;
+
+ static final int FLAGS_DISABLED = 0x00000001;
+ static final int FLAGS_VISIBILITY_MASK = View.VISIBLE|View.INVISIBLE|View.GONE;
+ static final int FLAGS_FOCUSABLE = 0x00000010;
+ static final int FLAGS_FOCUSED = 0x00000020;
+ static final int FLAGS_ACCESSIBILITY_FOCUSED = 0x04000000;
+ static final int FLAGS_SELECTED = 0x00000040;
+ static final int FLAGS_ACTIVATED = 0x40000000;
+ static final int FLAGS_CHECKABLE = 0x00000100;
+ static final int FLAGS_CHECKED = 0x00000200;
+ static final int FLAGS_CLICKABLE = 0x00004000;
+ static final int FLAGS_LONG_CLICKABLE = 0x00200000;
+
+ final int mFlags;
+
+ final String mClassName;
+ final String mContentDescription;
+
+ final ViewNodeTextImpl mText;
+ final Bundle mExtras;
+
+ final ViewNodeImpl[] mChildren;
+
+ ViewNodeImpl(AssistStructure assistStructure, View view, int left, int top,
+ CharSequence contentDescription) {
+ mX = left;
+ mY = top;
+ mScrollX = view.getScrollX();
+ mScrollY = view.getScrollY();
+ mWidth = view.getWidth();
+ mHeight = view.getHeight();
+ int flags = view.getVisibility();
+ if (!view.isEnabled()) {
+ flags |= FLAGS_DISABLED;
+ }
+ if (!view.isClickable()) {
+ flags |= FLAGS_CLICKABLE;
+ }
+ if (!view.isFocusable()) {
+ flags |= FLAGS_FOCUSABLE;
+ }
+ if (!view.isFocused()) {
+ flags |= FLAGS_FOCUSED;
+ }
+ if (!view.isAccessibilityFocused()) {
+ flags |= FLAGS_ACCESSIBILITY_FOCUSED;
+ }
+ if (!view.isSelected()) {
+ flags |= FLAGS_SELECTED;
+ }
+ if (!view.isActivated()) {
+ flags |= FLAGS_ACTIVATED;
+ }
+ if (!view.isLongClickable()) {
+ flags |= FLAGS_LONG_CLICKABLE;
+ }
+ if (view instanceof Checkable) {
+ flags |= FLAGS_CHECKABLE;
+ if (((Checkable)view).isChecked()) {
+ flags |= FLAGS_CHECKED;
+ }
+ }
+ mFlags = flags;
+ mClassName = view.getAccessibilityClassName().toString();
+ mContentDescription = contentDescription != null ? contentDescription.toString() : null;
+ final ViewAssistStructureImpl viewData = assistStructure.mTmpViewAssistStructureImpl;
+ final Bundle extras = assistStructure.mTmpExtras;
+ view.onProvideAssistStructure(viewData, extras);
+ if (viewData.mText != null || viewData.mHint != null) {
+ mText = new ViewNodeTextImpl(viewData);
+ assistStructure.mTmpViewAssistStructureImpl = new ViewAssistStructureImpl();
+ } else {
+ mText = null;
+ }
+ if (!extras.isEmpty()) {
+ mExtras = extras;
+ assistStructure.mTmpExtras = new Bundle();
+ } else {
+ mExtras = null;
+ }
+ if (view instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup)view;
+ final int NCHILDREN = vg.getChildCount();
+ if (NCHILDREN > 0) {
+ mChildren = new ViewNodeImpl[NCHILDREN];
+ for (int i=0; i<NCHILDREN; i++) {
+ mChildren[i] = new ViewNodeImpl(assistStructure, vg.getChildAt(i));
+ }
+ } else {
+ mChildren = null;
+ }
+ } else {
+ mChildren = null;
+ }
+ }
+
+ ViewNodeImpl(AssistStructure assistStructure, View view) {
+ this(assistStructure, view, view.getLeft(), view.getTop(), view.getContentDescription());
+ }
+
+ ViewNodeImpl(Parcel in, PooledStringReader preader) {
+ mX = in.readInt();
+ mY = in.readInt();
+ mScrollX = in.readInt();
+ mScrollY = in.readInt();
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ mFlags = in.readInt();
+ mClassName = preader.readString();
+ mContentDescription = in.readString();
+ if (in.readInt() != 0) {
+ mText = new ViewNodeTextImpl(in);
+ } else {
+ mText = null;
+ }
+ mExtras = in.readBundle();
+ final int NCHILDREN = in.readInt();
+ if (NCHILDREN > 0) {
+ mChildren = new ViewNodeImpl[NCHILDREN];
+ for (int i=0; i<NCHILDREN; i++) {
+ mChildren[i] = new ViewNodeImpl(in, preader);
+ }
+ } else {
+ mChildren = null;
+ }
+ }
+
+ void writeToParcel(Parcel out, PooledStringWriter pwriter) {
+ out.writeInt(mX);
+ out.writeInt(mY);
+ out.writeInt(mScrollX);
+ out.writeInt(mScrollY);
+ out.writeInt(mWidth);
+ out.writeInt(mHeight);
+ out.writeInt(mFlags);
+ pwriter.writeString(mClassName);
+ out.writeString(mContentDescription);
+ if (mText != null) {
+ out.writeInt(1);
+ mText.writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeBundle(mExtras);
+ if (mChildren != null) {
+ final int NCHILDREN = mChildren.length;
+ out.writeInt(NCHILDREN);
+ for (int i=0; i<NCHILDREN; i++) {
+ mChildren[i].writeToParcel(out, pwriter);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ }
+ }
+
+ /**
+ * Provides access to information about a single view in the assist data.
+ */
+ static public class ViewNode {
+ /**
+ * Magic value for text color that has not been defined, which is very unlikely
+ * to be confused with a real text color.
+ */
+ public static final int TEXT_COLOR_UNDEFINED = 1;
+
+ public static final int TEXT_STYLE_BOLD = 1<<0;
+ public static final int TEXT_STYLE_ITALIC = 1<<1;
+ public static final int TEXT_STYLE_UNDERLINE = 1<<2;
+ public static final int TEXT_STYLE_STRIKE_THRU = 1<<3;
+
+ ViewNodeImpl mImpl;
+
+ public ViewNode() {
+ }
+
+ public int getLeft() {
+ return mImpl.mX;
+ }
+
+ public int getTop() {
+ return mImpl.mY;
+ }
+
+ public int getScrollX() {
+ return mImpl.mScrollX;
+ }
+
+ public int getScrollY() {
+ return mImpl.mScrollY;
+ }
+
+ public int getWidth() {
+ return mImpl.mWidth;
+ }
+
+ public int getHeight() {
+ return mImpl.mHeight;
+ }
+
+ public int getVisibility() {
+ return mImpl.mFlags&ViewNodeImpl.FLAGS_VISIBILITY_MASK;
+ }
+
+ public boolean isEnabled() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_DISABLED) == 0;
+ }
+
+ public boolean isClickable() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_CLICKABLE) != 0;
+ }
+
+ public boolean isFocusable() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_FOCUSABLE) != 0;
+ }
+
+ public boolean isFocused() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_FOCUSED) != 0;
+ }
+
+ public boolean isAccessibilityFocused() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_ACCESSIBILITY_FOCUSED) != 0;
+ }
+
+ public boolean isCheckable() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_CHECKABLE) != 0;
+ }
+
+ public boolean isChecked() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_CHECKED) != 0;
+ }
+
+ public boolean isSelected() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_SELECTED) != 0;
+ }
+
+ public boolean isActivated() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_ACTIVATED) != 0;
+ }
+
+ public boolean isLongClickable() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_LONG_CLICKABLE) != 0;
+ }
+
+ public String getClassName() {
+ return mImpl.mClassName;
+ }
+
+ public String getContentDescription() {
+ return mImpl.mContentDescription;
+ }
+
+ public CharSequence getText() {
+ return mImpl.mText != null ? mImpl.mText.mText : null;
+ }
+
+ public int getTextSelectionStart() {
+ return mImpl.mText != null ? mImpl.mText.mTextSelectionStart : -1;
+ }
+
+ public int getTextSelectionEnd() {
+ return mImpl.mText != null ? mImpl.mText.mTextSelectionEnd : -1;
+ }
+
+ public int getTextColor() {
+ return mImpl.mText != null ? mImpl.mText.mTextColor : TEXT_COLOR_UNDEFINED;
+ }
+
+ public int getTextBackgroundColor() {
+ return mImpl.mText != null ? mImpl.mText.mTextBackgroundColor : TEXT_COLOR_UNDEFINED;
+ }
+
+ public float getTextSize() {
+ return mImpl.mText != null ? mImpl.mText.mTextSize : 0;
+ }
+
+ public int getTextStyle() {
+ return mImpl.mText != null ? mImpl.mText.mTextStyle : 0;
+ }
+
+ public String getHint() {
+ return mImpl.mText != null ? mImpl.mText.mHint : null;
+ }
+
+ public Bundle getExtras() {
+ return mImpl.mExtras;
+ }
+
+ public int getChildCount() {
+ return mImpl.mChildren != null ? mImpl.mChildren.length : 0;
+ }
+
+ public void getChildAt(int index, ViewNode outNode) {
+ outNode.mImpl = mImpl.mChildren[index];
+ }
+ }
+
+ AssistStructure(Activity activity) {
+ mActivityComponent = activity.getComponentName();
+ ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
+ activity.getActivityToken());
+ for (int i=0; i<views.size(); i++) {
+ ViewRootImpl root = views.get(i);
+ View view = root.getView();
+ Rect rect = new Rect();
+ view.getBoundsOnScreen(rect);
+ CharSequence title = root.getTitle();
+ mRootViews.add(new ViewNodeImpl(this, view, rect.left, rect.top,
+ title != null ? title : view.getContentDescription()));
+ }
+ }
+
+ AssistStructure(Parcel in) {
+ PooledStringReader preader = new PooledStringReader(in);
+ mActivityComponent = ComponentName.readFromParcel(in);
+ final int N = in.readInt();
+ for (int i=0; i<N; i++) {
+ mRootViews.add(new ViewNodeImpl(in, preader));
+ }
+ //dump();
+ }
+
+ /** @hide */
+ public void dump() {
+ Log.i(TAG, "Activity: " + mActivityComponent.flattenToShortString());
+ ViewNode node = new ViewNode();
+ final int N = getWindowCount();
+ for (int i=0; i<N; i++) {
+ Log.i(TAG, "Window #" + i + ":");
+ getWindowAt(i, node);
+ dump(" ", node);
+ }
+ }
+
+ void dump(String prefix, ViewNode node) {
+ Log.i(TAG, prefix + "View [" + node.getLeft() + "," + node.getTop()
+ + " " + node.getWidth() + "x" + node.getHeight() + "]" + " " + node.getClassName());
+ int scrollX = node.getScrollX();
+ int scrollY = node.getScrollY();
+ if (scrollX != 0 || scrollY != 0) {
+ Log.i(TAG, prefix + " Scroll: " + scrollX + "," + scrollY);
+ }
+ String contentDescription = node.getContentDescription();
+ if (contentDescription != null) {
+ Log.i(TAG, prefix + " Content description: " + contentDescription);
+ }
+ CharSequence text = node.getText();
+ if (text != null) {
+ Log.i(TAG, prefix + " Text (sel " + node.getTextSelectionStart() + "-"
+ + node.getTextSelectionEnd() + "): " + text);
+ Log.i(TAG, prefix + " Text size: " + node.getTextSize() + " , style: #"
+ + node.getTextStyle());
+ Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor())
+ + ", bg: #" + Integer.toHexString(node.getTextBackgroundColor()));
+ }
+ String hint = node.getHint();
+ if (hint != null) {
+ Log.i(TAG, prefix + " Hint: " + hint);
+ }
+ Bundle extras = node.getExtras();
+ if (extras != null) {
+ Log.i(TAG, prefix + " Extras: " + extras);
+ }
+ final int NCHILDREN = node.getChildCount();
+ if (NCHILDREN > 0) {
+ Log.i(TAG, prefix + " Children:");
+ String cprefix = prefix + " ";
+ ViewNode cnode = new ViewNode();
+ for (int i=0; i<NCHILDREN; i++) {
+ node.getChildAt(i, cnode);
+ dump(cprefix, cnode);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the framework-generated AssistStructure that is stored within
+ * the Bundle filled in by {@link Activity#onProvideAssistData}.
+ */
+ public static AssistStructure getAssistStructure(Bundle assistBundle) {
+ return assistBundle.getParcelable(ASSIST_KEY);
+ }
+
+ public ComponentName getActivityComponent() {
+ return mActivityComponent;
+ }
+
+ /**
+ * Return the number of window contents that have been collected in this assist data.
+ */
+ public int getWindowCount() {
+ return mRootViews.size();
+ }
+
+ /**
+ * Return the root view for one of the windows in the assist data.
+ * @param index Which window to retrieve, may be 0 to {@link #getWindowCount()}-1.
+ * @param outNode Node in which to place the window's root view.
+ */
+ public void getWindowAt(int index, ViewNode outNode) {
+ outNode.mImpl = mRootViews.get(index);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ int start = out.dataPosition();
+ PooledStringWriter pwriter = new PooledStringWriter(out);
+ ComponentName.writeToParcel(mActivityComponent, out);
+ final int N = mRootViews.size();
+ out.writeInt(N);
+ for (int i=0; i<N; i++) {
+ mRootViews.get(i).writeToParcel(out, pwriter);
+ }
+ pwriter.finish();
+ Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes");
+ }
+
+ public static final Parcelable.Creator<AssistStructure> CREATOR
+ = new Parcelable.Creator<AssistStructure>() {
+ public AssistStructure createFromParcel(Parcel in) {
+ return new AssistStructure(in);
+ }
+
+ public AssistStructure[] newArray(int size) {
+ return new AssistStructure[size];
+ }
+ };
+}
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 2784d44..83451aa 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -25,7 +25,6 @@ import android.text.TextUtils;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
-import android.transition.TransitionUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LogWriter;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2ef046d..eb27830 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -16,18 +16,9 @@
package android.app;
-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.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
-import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -35,19 +26,15 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.IContentProvider;
+import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.IIntentReceiver;
import android.content.IntentSender;
-import android.content.IRestrictionsManager;
import android.content.ReceiverCallNotAllowedException;
-import android.content.RestrictionsManager;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ILauncherApps;
import android.content.pm.IPackageManager;
-import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
@@ -59,96 +46,28 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.hardware.ConsumerIrManager;
-import android.hardware.ISerialManager;
-import android.hardware.SerialManager;
-import android.hardware.SystemSensorManager;
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlService;
-import android.hardware.camera2.CameraManager;
import android.hardware.display.DisplayManager;
-import android.hardware.input.InputManager;
-import android.hardware.usb.IUsbManager;
-import android.hardware.usb.UsbManager;
-import android.location.CountryDetector;
-import android.location.ICountryDetector;
-import android.location.ILocationManager;
-import android.location.LocationManager;
-import android.media.AudioManager;
-import android.media.MediaRouter;
-import android.media.projection.MediaProjectionManager;
-import android.media.session.MediaSessionManager;
-import android.media.tv.ITvInputManager;
-import android.media.tv.TvInputManager;
-import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
-import android.net.EthernetManager;
-import android.net.IEthernetManager;
-import android.net.INetworkPolicyManager;
-import android.net.NetworkPolicyManager;
-import android.net.NetworkScoreManager;
import android.net.Uri;
-import android.net.nsd.INsdManager;
-import android.net.nsd.NsdManager;
-import android.net.wifi.IWifiManager;
-import android.net.wifi.WifiManager;
-import android.net.wifi.p2p.IWifiP2pManager;
-import android.net.wifi.p2p.WifiP2pManager;
-import android.net.wifi.IWifiScanner;
-import android.net.wifi.WifiScanner;
-import android.net.wifi.IRttManager;
-import android.net.wifi.RttManager;
-import android.nfc.NfcManager;
-import android.os.BatteryManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
-import android.os.DropBoxManager;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
-import android.os.IPowerManager;
-import android.os.IUserManager;
import android.os.Looper;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.os.SystemVibrator;
-import android.os.UserManager;
import android.os.storage.IMountService;
-import android.os.storage.StorageManager;
-import android.print.IPrintManager;
-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;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
-import android.view.DisplayAdjustments;
-import android.view.ContextThemeWrapper;
import android.view.Display;
-import android.view.WindowManagerImpl;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.CaptioningManager;
-import android.view.inputmethod.InputMethodManager;
-import android.view.textservice.TextServicesManager;
-import android.accounts.AccountManager;
-import android.accounts.IAccountManager;
-import android.app.admin.DevicePolicyManager;
-import android.app.job.IJobScheduler;
-import android.app.trust.TrustManager;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.os.IDropBoxManagerService;
+import android.view.DisplayAdjustments;
import java.io.File;
import java.io.FileInputStream;
@@ -156,8 +75,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
@@ -231,7 +148,6 @@ class ContextImpl extends Context {
private final Resources mResources;
private final Display mDisplay; // may be null if default display
private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
- private final Configuration mOverrideConfiguration;
private final boolean mRestricted;
@@ -267,508 +183,8 @@ class ContextImpl extends Context {
private static final String[] EMPTY_FILE_LIST = {};
- /**
- * Override this class when the system service constructor needs a
- * ContextImpl. Else, use StaticServiceFetcher below.
- */
- /*package*/ static class ServiceFetcher {
- int mContextCacheIndex = -1;
-
- /**
- * Main entrypoint; only override if you don't need caching.
- */
- public Object getService(ContextImpl ctx) {
- ArrayList<Object> cache = ctx.mServiceCache;
- Object service;
- synchronized (cache) {
- if (cache.size() == 0) {
- // Initialize the cache vector on first access.
- // At this point sNextPerContextServiceCacheIndex
- // is the number of potential services that are
- // cached per-Context.
- for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
- cache.add(null);
- }
- } else {
- service = cache.get(mContextCacheIndex);
- if (service != null) {
- return service;
- }
- }
- service = createService(ctx);
- cache.set(mContextCacheIndex, service);
- return service;
- }
- }
-
- /**
- * Override this to create a new per-Context instance of the
- * service. getService() will handle locking and caching.
- */
- public Object createService(ContextImpl ctx) {
- throw new RuntimeException("Not implemented");
- }
- }
-
- /**
- * Override this class for services to be cached process-wide.
- */
- abstract static class StaticServiceFetcher extends ServiceFetcher {
- private Object mCachedInstance;
-
- @Override
- public final Object getService(ContextImpl unused) {
- synchronized (StaticServiceFetcher.this) {
- Object service = mCachedInstance;
- if (service != null) {
- return service;
- }
- return mCachedInstance = createStaticService();
- }
- }
-
- public abstract Object createStaticService();
- }
-
- private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
- new HashMap<String, ServiceFetcher>();
-
- private static int sNextPerContextServiceCacheIndex = 0;
- private static void registerService(String serviceName, ServiceFetcher fetcher) {
- if (!(fetcher instanceof StaticServiceFetcher)) {
- fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
- }
- SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
- }
-
- // This one's defined separately and given a variable name so it
- // can be re-used by getWallpaperManager(), avoiding a HashMap
- // lookup.
- private static ServiceFetcher WALLPAPER_FETCHER = new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new WallpaperManager(ctx.getOuterContext(),
- ctx.mMainThread.getHandler());
- }};
-
- static {
- registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() {
- public Object getService(ContextImpl ctx) {
- return AccessibilityManager.getInstance(ctx);
- }});
-
- registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
- public Object getService(ContextImpl ctx) {
- return new CaptioningManager(ctx);
- }});
-
- registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
- IAccountManager service = IAccountManager.Stub.asInterface(b);
- return new AccountManager(ctx, service);
- }});
-
- registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
- }});
-
- registerService(ALARM_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(ALARM_SERVICE);
- IAlarmManager service = IAlarmManager.Stub.asInterface(b);
- return new AlarmManager(service, ctx);
- }});
-
- registerService(AUDIO_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new AudioManager(ctx);
- }});
-
- registerService(MEDIA_ROUTER_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new MediaRouter(ctx);
- }});
-
- registerService(BLUETOOTH_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new BluetoothManager(ctx);
- }});
-
- registerService(HDMI_CONTROL_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
- IBinder b = ServiceManager.getService(HDMI_CONTROL_SERVICE);
- return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b));
- }});
-
- registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new ClipboardManager(ctx.getOuterContext(),
- ctx.mMainThread.getHandler());
- }});
-
- registerService(CONNECTIVITY_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE);
- return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b));
- }});
-
- registerService(COUNTRY_DETECTOR, new StaticServiceFetcher() {
- public Object createStaticService() {
- IBinder b = ServiceManager.getService(COUNTRY_DETECTOR);
- return new CountryDetector(ICountryDetector.Stub.asInterface(b));
- }});
-
- registerService(DEVICE_POLICY_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return DevicePolicyManager.create(ctx, ctx.mMainThread.getHandler());
- }});
-
- registerService(DOWNLOAD_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new DownloadManager(ctx.getContentResolver(), ctx.getPackageName());
- }});
-
- registerService(BATTERY_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new BatteryManager();
- }});
-
- registerService(NFC_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new NfcManager(ctx);
- }});
-
- registerService(DROPBOX_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
- return createDropBoxManager();
- }});
-
- registerService(INPUT_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
- return InputManager.getInstance();
- }});
-
- registerService(DISPLAY_SERVICE, new ServiceFetcher() {
- @Override
- public Object createService(ContextImpl ctx) {
- return new DisplayManager(ctx.getOuterContext());
- }});
-
- registerService(INPUT_METHOD_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
- return InputMethodManager.getInstance();
- }});
-
- registerService(TEXT_SERVICES_MANAGER_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return TextServicesManager.getInstance();
- }});
-
- registerService(KEYGUARD_SERVICE, new ServiceFetcher() {
- public Object getService(ContextImpl ctx) {
- // TODO: why isn't this caching it? It wasn't
- // before, so I'm preserving the old behavior and
- // using getService(), instead of createService()
- // which would do the caching.
- return new KeyguardManager();
- }});
-
- registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
- }});
-
- registerService(LOCATION_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(LOCATION_SERVICE);
- return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
- }});
-
- registerService(NETWORK_POLICY_SERVICE, new ServiceFetcher() {
- @Override
- public Object createService(ContextImpl ctx) {
- return new NetworkPolicyManager(INetworkPolicyManager.Stub.asInterface(
- ServiceManager.getService(NETWORK_POLICY_SERVICE)));
- }
- });
-
- registerService(NOTIFICATION_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- final Context outerContext = ctx.getOuterContext();
- return new NotificationManager(
- new ContextThemeWrapper(outerContext,
- Resources.selectSystemTheme(0,
- outerContext.getApplicationInfo().targetSdkVersion,
- com.android.internal.R.style.Theme_Dialog,
- com.android.internal.R.style.Theme_Holo_Dialog,
- com.android.internal.R.style.Theme_DeviceDefault_Dialog,
- com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)),
- ctx.mMainThread.getHandler());
- }});
-
- registerService(NSD_SERVICE, new ServiceFetcher() {
- @Override
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(NSD_SERVICE);
- INsdManager service = INsdManager.Stub.asInterface(b);
- return new NsdManager(ctx.getOuterContext(), service);
- }});
-
- // Note: this was previously cached in a static variable, but
- // constructed using mMainThread.getHandler(), so converting
- // it to be a regular Context-cached service...
- registerService(POWER_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(POWER_SERVICE);
- IPowerManager service = IPowerManager.Stub.asInterface(b);
- if (service == null) {
- Log.wtf(TAG, "Failed to get power manager service.");
- }
- return new PowerManager(ctx.getOuterContext(),
- service, ctx.mMainThread.getHandler());
- }});
-
- registerService(SEARCH_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new SearchManager(ctx.getOuterContext(),
- ctx.mMainThread.getHandler());
- }});
-
- registerService(SENSOR_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new SystemSensorManager(ctx.getOuterContext(),
- ctx.mMainThread.getHandler().getLooper());
- }});
-
- registerService(STATUS_BAR_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new StatusBarManager(ctx.getOuterContext());
- }});
-
- registerService(STORAGE_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- try {
- return new StorageManager(
- ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper());
- } catch (RemoteException rex) {
- Log.e(TAG, "Failed to create StorageManager", rex);
- return null;
- }
- }});
-
- registerService(TELEPHONY_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- 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());
- }});
-
- registerService(UI_MODE_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new UiModeManager();
- }});
-
- registerService(USB_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(USB_SERVICE);
- return new UsbManager(ctx, IUsbManager.Stub.asInterface(b));
- }});
-
- registerService(SERIAL_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(SERIAL_SERVICE);
- return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));
- }});
-
- registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new SystemVibrator(ctx);
- }});
-
- registerService(WALLPAPER_SERVICE, WALLPAPER_FETCHER);
-
- registerService(WIFI_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(WIFI_SERVICE);
- IWifiManager service = IWifiManager.Stub.asInterface(b);
- return new WifiManager(ctx.getOuterContext(), service);
- }});
-
- registerService(WIFI_P2P_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(WIFI_P2P_SERVICE);
- IWifiP2pManager service = IWifiP2pManager.Stub.asInterface(b);
- return new WifiP2pManager(service);
- }});
-
- registerService(WIFI_SCANNING_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(WIFI_SCANNING_SERVICE);
- IWifiScanner service = IWifiScanner.Stub.asInterface(b);
- return new WifiScanner(ctx.getOuterContext(), service);
- }});
-
- registerService(WIFI_RTT_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(WIFI_RTT_SERVICE);
- IRttManager service = IRttManager.Stub.asInterface(b);
- return new RttManager(ctx.getOuterContext(), service);
- }});
-
- registerService(ETHERNET_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(ETHERNET_SERVICE);
- IEthernetManager service = IEthernetManager.Stub.asInterface(b);
- return new EthernetManager(ctx.getOuterContext(), service);
- }});
-
- registerService(WINDOW_SERVICE, new ServiceFetcher() {
- Display mDefaultDisplay;
- public Object getService(ContextImpl ctx) {
- Display display = ctx.mDisplay;
- if (display == null) {
- if (mDefaultDisplay == null) {
- DisplayManager dm = (DisplayManager)ctx.getOuterContext().
- getSystemService(Context.DISPLAY_SERVICE);
- mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
- }
- display = mDefaultDisplay;
- }
- return new WindowManagerImpl(display);
- }});
-
- registerService(USER_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(USER_SERVICE);
- IUserManager service = IUserManager.Stub.asInterface(b);
- return new UserManager(ctx, service);
- }});
-
- registerService(APP_OPS_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(APP_OPS_SERVICE);
- IAppOpsService service = IAppOpsService.Stub.asInterface(b);
- return new AppOpsManager(ctx, service);
- }});
-
- registerService(CAMERA_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new CameraManager(ctx);
- }
- });
-
- registerService(LAUNCHER_APPS_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(LAUNCHER_APPS_SERVICE);
- ILauncherApps service = ILauncherApps.Stub.asInterface(b);
- return new LauncherApps(ctx, service);
- }
- });
-
- registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE);
- IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b);
- return new RestrictionsManager(ctx, service);
- }
- });
- registerService(PRINT_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
- IPrintManager service = IPrintManager.Stub.asInterface(iBinder);
- return new PrintManager(ctx.getOuterContext(), service, UserHandle.myUserId(),
- UserHandle.getAppId(Process.myUid()));
- }});
-
- registerService(CONSUMER_IR_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new ConsumerIrManager(ctx);
- }});
-
- registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new MediaSessionManager(ctx);
- }
- });
-
- registerService(TRUST_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(TRUST_SERVICE);
- return new TrustManager(b);
- }
- });
-
- registerService(FINGERPRINT_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder binder = ServiceManager.getService(FINGERPRINT_SERVICE);
- IFingerprintService service = IFingerprintService.Stub.asInterface(binder);
- return new FingerprintManager(ctx.getOuterContext(), service);
- }
- });
-
- registerService(TV_INPUT_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder iBinder = ServiceManager.getService(TV_INPUT_SERVICE);
- ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder);
- return new TvInputManager(service, UserHandle.myUserId());
- }
- });
-
- registerService(NETWORK_SCORE_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new NetworkScoreManager(ctx);
- }
- });
-
- registerService(USAGE_STATS_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder iBinder = ServiceManager.getService(USAGE_STATS_SERVICE);
- IUsageStatsManager service = IUsageStatsManager.Stub.asInterface(iBinder);
- return new UsageStatsManager(ctx.getOuterContext(), service);
- }
- });
-
- registerService(JOB_SCHEDULER_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(JOB_SCHEDULER_SERVICE);
- return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
- }});
-
- registerService(PERSISTENT_DATA_BLOCK_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(PERSISTENT_DATA_BLOCK_SERVICE);
- IPersistentDataBlockService persistentDataBlockService =
- IPersistentDataBlockService.Stub.asInterface(b);
- if (persistentDataBlockService != null) {
- return new PersistentDataBlockManager(persistentDataBlockService);
- } else {
- // not supported
- return null;
- }
- }
- });
-
- registerService(MEDIA_PROJECTION_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new MediaProjectionManager(ctx);
- }
- });
-
- registerService(APPWIDGET_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- IBinder b = ServiceManager.getService(APPWIDGET_SERVICE);
- return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
- }});
- }
+ // The system service cache for the system services that are cached per-ContextImpl.
+ final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
static ContextImpl getImpl(Context context) {
Context nextContext;
@@ -779,11 +195,6 @@ class ContextImpl extends Context {
return (ContextImpl)context;
}
- // The system service cache for the system services that are
- // cached per-ContextImpl. Package-scoped to avoid accessor
- // methods.
- final ArrayList<Object> mServiceCache = new ArrayList<Object>();
-
@Override
public AssetManager getAssets() {
return getResources().getAssets();
@@ -898,6 +309,7 @@ class ContextImpl extends Context {
throw new RuntimeException("Not supported in system context");
}
+ @Override
public File getSharedPrefsFile(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
@@ -1185,40 +597,51 @@ class ContextImpl extends Context {
}
@Override
+ @Deprecated
public Drawable getWallpaper() {
return getWallpaperManager().getDrawable();
}
@Override
+ @Deprecated
public Drawable peekWallpaper() {
return getWallpaperManager().peekDrawable();
}
@Override
+ @Deprecated
public int getWallpaperDesiredMinimumWidth() {
return getWallpaperManager().getDesiredMinimumWidth();
}
@Override
+ @Deprecated
public int getWallpaperDesiredMinimumHeight() {
return getWallpaperManager().getDesiredMinimumHeight();
}
@Override
- public void setWallpaper(Bitmap bitmap) throws IOException {
+ @Deprecated
+ public void setWallpaper(Bitmap bitmap) throws IOException {
getWallpaperManager().setBitmap(bitmap);
}
@Override
+ @Deprecated
public void setWallpaper(InputStream data) throws IOException {
getWallpaperManager().setStream(data);
}
@Override
+ @Deprecated
public void clearWallpaper() throws IOException {
getWallpaperManager().clear();
}
+ private WallpaperManager getWallpaperManager() {
+ return getSystemService(WallpaperManager.class);
+ }
+
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
@@ -1490,6 +913,7 @@ class ContextImpl extends Context {
}
@Override
+ @Deprecated
public void sendStickyBroadcast(Intent intent) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
@@ -1504,6 +928,7 @@ class ContextImpl extends Context {
}
@Override
+ @Deprecated
public void sendStickyOrderedBroadcast(Intent intent,
BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
@@ -1538,6 +963,7 @@ class ContextImpl extends Context {
}
@Override
+ @Deprecated
public void removeStickyBroadcast(Intent intent) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
if (resolvedType != null) {
@@ -1553,6 +979,7 @@ class ContextImpl extends Context {
}
@Override
+ @Deprecated
public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
@@ -1565,6 +992,7 @@ class ContextImpl extends Context {
}
@Override
+ @Deprecated
public void sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
@@ -1598,6 +1026,7 @@ class ContextImpl extends Context {
}
@Override
+ @Deprecated
public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
if (resolvedType != null) {
@@ -1834,25 +1263,12 @@ class ContextImpl extends Context {
@Override
public Object getSystemService(String name) {
- ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
- return fetcher == null ? null : fetcher.getService(this);
+ return SystemServiceRegistry.getSystemService(this, name);
}
- private WallpaperManager getWallpaperManager() {
- return (WallpaperManager) WALLPAPER_FETCHER.getService(this);
- }
-
- /* package */ static DropBoxManager createDropBoxManager() {
- IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
- IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
- if (service == null) {
- // Don't return a DropBoxManager that will NPE upon use.
- // This also avoids caching a broken DropBoxManager in
- // getDropBoxManager during early boot, before the
- // DROPBOX_SERVICE is registered.
- return null;
- }
- return new DropBoxManager(service);
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ return SystemServiceRegistry.getSystemServiceName(serviceClass);
}
@Override
@@ -1921,6 +1337,7 @@ class ContextImpl extends Context {
}
}
+ @Override
public void enforcePermission(
String permission, int pid, int uid, String message) {
enforce(permission,
@@ -1930,6 +1347,7 @@ class ContextImpl extends Context {
message);
}
+ @Override
public void enforceCallingPermission(String permission, String message) {
enforce(permission,
checkCallingPermission(permission),
@@ -1938,6 +1356,7 @@ class ContextImpl extends Context {
message);
}
+ @Override
public void enforceCallingOrSelfPermission(
String permission, String message) {
enforce(permission,
@@ -2075,6 +1494,7 @@ class ContextImpl extends Context {
}
}
+ @Override
public void enforceUriPermission(
Uri uri, int pid, int uid, int modeFlags, String message) {
enforceForUri(
@@ -2082,6 +1502,7 @@ class ContextImpl extends Context {
false, uid, uri, message);
}
+ @Override
public void enforceCallingUriPermission(
Uri uri, int modeFlags, String message) {
enforceForUri(
@@ -2090,6 +1511,7 @@ class ContextImpl extends Context {
Binder.getCallingUid(), uri, message);
}
+ @Override
public void enforceCallingOrSelfUriPermission(
Uri uri, int modeFlags, String message) {
enforceForUri(
@@ -2098,6 +1520,7 @@ class ContextImpl extends Context {
Binder.getCallingUid(), uri, message);
}
+ @Override
public void enforceUriPermission(
Uri uri, String readPermission, String writePermission,
int pid, int uid, int modeFlags, String message) {
@@ -2132,7 +1555,7 @@ class ContextImpl extends Context {
final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
new UserHandle(UserHandle.getUserId(application.uid)), restricted,
- mDisplay, mOverrideConfiguration);
+ mDisplay, null);
if (c.mResources != null) {
return c;
}
@@ -2155,14 +1578,14 @@ class ContextImpl extends Context {
final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
if (packageName.equals("system") || packageName.equals("android")) {
return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- user, restricted, mDisplay, mOverrideConfiguration);
+ user, restricted, mDisplay, null);
}
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
- user, restricted, mDisplay, mOverrideConfiguration);
+ user, restricted, mDisplay, null);
if (c.mResources != null) {
return c;
}
@@ -2190,7 +1613,15 @@ class ContextImpl extends Context {
}
return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- mUser, mRestricted, display, mOverrideConfiguration);
+ mUser, mRestricted, display, null);
+ }
+
+ Display getDisplay() {
+ if (mDisplay != null) {
+ return mDisplay;
+ }
+ DisplayManager dm = getSystemService(DisplayManager.class);
+ return dm.getDisplay(Display.DEFAULT_DISPLAY);
}
private int getDisplayId() {
@@ -2227,6 +1658,7 @@ class ContextImpl extends Context {
}
/** {@hide} */
+ @Override
public int getUserId() {
return mUser.getIdentifier();
}
@@ -2236,7 +1668,7 @@ class ContextImpl extends Context {
ContextImpl context = new ContextImpl(null, mainThread,
packageInfo, null, null, false, null, null);
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
- context.mResourcesManager.getDisplayMetricsLocked(Display.DEFAULT_DISPLAY));
+ context.mResourcesManager.getDisplayMetricsLocked());
return context;
}
@@ -2247,11 +1679,12 @@ class ContextImpl extends Context {
}
static ContextImpl createActivityContext(ActivityThread mainThread,
- LoadedApk packageInfo, IBinder activityToken) {
+ LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
- if (activityToken == null) throw new IllegalArgumentException("activityInfo");
- return new ContextImpl(null, mainThread,
- packageInfo, activityToken, null, false, null, null);
+ final Display display = ResourcesManager.getInstance().getAdjustedDisplay(
+ displayId, overrideConfiguration);
+ return new ContextImpl(null, mainThread, packageInfo, null, null, false, display,
+ overrideConfiguration);
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
@@ -2271,30 +1704,30 @@ class ContextImpl extends Context {
mPackageInfo = packageInfo;
mResourcesManager = ResourcesManager.getInstance();
mDisplay = display;
- mOverrideConfiguration = overrideConfiguration;
final int displayId = getDisplayId();
CompatibilityInfo compatInfo = null;
if (container != null) {
compatInfo = container.getDisplayAdjustments(displayId).getCompatibilityInfo();
}
- if (compatInfo == null && displayId == Display.DEFAULT_DISPLAY) {
- compatInfo = packageInfo.getCompatibilityInfo();
+ if (compatInfo == null) {
+ compatInfo = (displayId == Display.DEFAULT_DISPLAY)
+ ? packageInfo.getCompatibilityInfo()
+ : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
}
mDisplayAdjustments.setCompatibilityInfo(compatInfo);
- mDisplayAdjustments.setActivityToken(activityToken);
+ mDisplayAdjustments.setConfiguration(overrideConfiguration);
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
- if (activityToken != null
- || displayId != Display.DEFAULT_DISPLAY
+ if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
- overrideConfiguration, compatInfo, activityToken);
+ overrideConfiguration, compatInfo);
}
}
mResources = resources;
@@ -2351,6 +1784,7 @@ class ContextImpl extends Context {
return mActivityToken;
}
+ @SuppressWarnings("deprecation")
static void setFilePermissionsFromMode(String name, int mode,
int extraPermissions) {
int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index f79d32b..3fbbdff 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -131,6 +131,9 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
switch (which) {
case BUTTON_POSITIVE:
if (mDateSetListener != null) {
+ // Clearing focus forces the dialog to commit any pending
+ // changes, e.g. typed text in a NumberPicker.
+ mDatePicker.clearFocus();
mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(),
mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 067073a..9defcbe 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,14 +16,19 @@
package android.app;
-import android.content.pm.ApplicationInfo;
+import android.annotation.CallSuper;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.annotation.StringRes;
import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.PolicyManager;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -42,6 +47,7 @@ import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.PhoneWindow;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
@@ -115,6 +121,8 @@ public class Dialog implements DialogInterface, Window.Callback,
private ActionMode mActionMode;
+ private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
+
private final Runnable mDismissAction = new Runnable() {
public void run() {
dismissDialog();
@@ -134,14 +142,14 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* Create a Dialog window that uses a custom dialog style.
- *
+ *
* @param context The Context in which the Dialog should run. In particular, it
* uses the window manager and theme from this context to
* present its UI.
- * @param theme A style resource describing the theme to use for the
- * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
- * and Theme Resources</a> for more information about defining and using
- * styles. This theme is applied on top of the current theme in
+ * @param theme A style resource describing the theme to use for the
+ * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
+ * and Theme Resources</a> for more information about defining and using
+ * styles. This theme is applied on top of the current theme in
* <var>context</var>. If 0, the default dialog theme will be used.
*/
public Dialog(Context context, int theme) {
@@ -162,7 +170,7 @@ public class Dialog implements DialogInterface, Window.Callback,
}
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- Window w = PolicyManager.makeNewWindow(mContext);
+ Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
@@ -476,7 +484,8 @@ public class Dialog implements DialogInterface, Window.Callback,
* @param id the identifier of the view to find
* @return The view with the given id or null.
*/
- public View findViewById(int id) {
+ @Nullable
+ public View findViewById(@IdRes int id) {
return mWindow.findViewById(id);
}
@@ -486,7 +495,7 @@ public class Dialog implements DialogInterface, Window.Callback,
*
* @param layoutResID Resource ID to be inflated.
*/
- public void setContentView(int layoutResID) {
+ public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
@@ -540,7 +549,7 @@ public class Dialog implements DialogInterface, Window.Callback,
*
* @param titleId the title's text resource identifier
*/
- public void setTitle(int titleId) {
+ public void setTitle(@StringRes int titleId) {
setTitle(mContext.getText(titleId));
}
@@ -966,13 +975,13 @@ public class Dialog implements DialogInterface, Window.Callback,
public boolean onContextItemSelected(MenuItem item) {
return false;
}
-
+
/**
* @see Activity#onContextMenuClosed(Menu)
*/
public void onContextMenuClosed(Menu menu) {
}
-
+
/**
* This hook is called when the user signals the desire to start a search.
*/
@@ -991,8 +1000,12 @@ public class Dialog implements DialogInterface, Window.Callback,
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
- if (mActionBar != null) {
+ if (mActionBar != null && mActionModeTypeStarting == ActionMode.TYPE_PRIMARY) {
return mActionBar.startActionMode(callback);
}
return null;
@@ -1000,10 +1013,24 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* {@inheritDoc}
+ */
+ @Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+ try {
+ mActionModeTypeStarting = type;
+ return onWindowStartingActionMode(callback);
+ } finally {
+ mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
*
* Note that if you override this method you should always call through
* to the superclass implementation by calling super.onActionModeStarted(mode).
*/
+ @CallSuper
public void onActionModeStarted(ActionMode mode) {
mActionMode = mode;
}
@@ -1014,6 +1041,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* Note that if you override this method you should always call through
* to the superclass implementation by calling super.onActionModeFinished(mode).
*/
+ @CallSuper
public void onActionModeFinished(ActionMode mode) {
if (mode == mActionMode) {
mActionMode = null;
@@ -1070,7 +1098,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* Convenience for calling
* {@link android.view.Window#setFeatureDrawableResource}.
*/
- public final void setFeatureDrawableResource(int featureId, int resId) {
+ public final void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
getWindow().setFeatureDrawableResource(featureId, resId);
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index ab28d95..bdcc312 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -18,6 +18,7 @@ package android.app;
import android.animation.Animator;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
@@ -795,7 +796,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
*
* @param resId Resource id for the CharSequence text
*/
- public final CharSequence getText(int resId) {
+ public final CharSequence getText(@StringRes int resId) {
return getResources().getText(resId);
}
@@ -805,7 +806,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
*
* @param resId Resource id for the string
*/
- public final String getString(int resId) {
+ public final String getString(@StringRes int resId) {
return getResources().getString(resId);
}
@@ -818,7 +819,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* @param formatArgs The format arguments that will be used for substitution.
*/
- public final String getString(int resId, Object... formatArgs) {
+ public final String getString(@StringRes int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}
@@ -1243,7 +1244,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
mCalled = true;
}
@@ -1309,7 +1310,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
- public void onActivityCreated(Bundle savedInstanceState) {
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
mCalled = true;
}
@@ -2008,6 +2009,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
mChildFragmentManager = new FragmentManagerImpl();
mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() {
@Override
+ @Nullable
public View findViewById(int id) {
if (mView == null) {
throw new IllegalStateException("Fragment does not have a view");
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index ccceef4..975b20d 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -19,7 +19,9 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
import android.content.Context;
+import android.annotation.IdRes;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -394,7 +396,8 @@ final class FragmentManagerState implements Parcelable {
* Callbacks from FragmentManagerImpl to its container.
*/
interface FragmentContainer {
- public View findViewById(int id);
+ @Nullable
+ public View findViewById(@IdRes int id);
public boolean hasView();
}
diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl
index cc3b10c..170aff3 100644
--- a/core/java/android/app/IActivityContainer.aidl
+++ b/core/java/android/app/IActivityContainer.aidl
@@ -30,6 +30,7 @@ interface IActivityContainer {
int startActivity(in Intent intent);
int startActivityIntentSender(in IIntentSender intentSender);
int getDisplayId();
+ int getStackId();
boolean injectEvent(in InputEvent event);
void release();
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index a138dbb..d794aa3 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -51,6 +51,7 @@ import android.os.RemoteException;
import android.os.StrictMode;
import android.service.voice.IVoiceInteractionSession;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.os.IResultReceiver;
import java.util.List;
@@ -131,7 +132,6 @@ public interface IActivityManager extends IInterface {
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
throws RemoteException;
public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException;
- public void moveTaskToBack(int task) throws RemoteException;
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
public void moveTaskBackwards(int task) throws RemoteException;
public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException;
@@ -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 int getFocusedStackId() throws RemoteException;
public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException;
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
public ContentProviderHolder getContentProvider(IApplicationThread caller,
@@ -419,6 +420,9 @@ public interface IActivityManager extends IInterface {
public Bundle getAssistContextExtras(int requestType) throws RemoteException;
+ public void requestAssistContextExtras(int requestType, IResultReceiver receiver)
+ throws RemoteException;
+
public void reportAssistContextExtras(IBinder token, Bundle extras) throws RemoteException;
public boolean launchAssistIntent(Intent intent, int requestType, String hint, int userHandle)
@@ -434,9 +438,11 @@ public interface IActivityManager extends IInterface {
public void performIdleMaintenance() throws RemoteException;
- public IActivityContainer createActivityContainer(IBinder parentActivityToken,
+ public IActivityContainer createVirtualActivityContainer(IBinder parentActivityToken,
IActivityContainerCallback callback) throws RemoteException;
+ public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException;
+
public void deleteActivityContainer(IActivityContainer container) throws RemoteException;
public int getActivityDisplayId(IBinder activityToken) throws RemoteException;
@@ -455,8 +461,12 @@ public interface IActivityManager extends IInterface {
public boolean isInLockTaskMode() throws RemoteException;
+ public int getLockTaskModeState() throws RemoteException;
+
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException;
+ public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
+ public void resizeTask(int taskId, Rect bounds) throws RemoteException;
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
@@ -472,6 +482,12 @@ public interface IActivityManager extends IInterface {
public void systemBackupRestored() throws RemoteException;
public void notifyCleartextNetwork(int uid, byte[] firstPacket) throws RemoteException;
+ public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException;
+ public void dumpHeapFinished(String path) throws RemoteException;
+
+ public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake)
+ throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -502,7 +518,7 @@ public interface IActivityManager extends IInterface {
dest.writeStrongBinder(null);
}
dest.writeStrongBinder(connection);
- dest.writeInt(noReleaseNeeded ? 1:0);
+ dest.writeInt(noReleaseNeeded ? 1 : 0);
}
public static final Parcelable.Creator<ContentProviderHolder> CREATOR
@@ -521,7 +537,7 @@ public interface IActivityManager extends IInterface {
private ContentProviderHolder(Parcel source) {
info = ProviderInfo.CREATOR.createFromParcel(source);
provider = ContentProviderNative.asInterface(
- source.readStrongBinder());
+ source.readStrongBinder());
connection = source.readStrongBinder();
noReleaseNeeded = source.readInt() != 0;
}
@@ -598,7 +614,7 @@ public interface IActivityManager extends IInterface {
int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21;
int GET_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22;
int MOVE_TASK_TO_FRONT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23;
- int MOVE_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24;
+
int MOVE_TASK_BACKWARDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25;
int GET_TASK_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
@@ -738,7 +754,7 @@ public interface IActivityManager extends IInterface {
int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164;
int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165;
int HANG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166;
- int CREATE_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167;
+ int CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167;
int MOVE_TASK_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+168;
int RESIZE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+169;
int GET_ALL_STACK_INFOS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+170;
@@ -797,4 +813,13 @@ public interface IActivityManager extends IInterface {
// Start of M transactions
int NOTIFY_CLEARTEXT_NETWORK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+280;
+ int CREATE_STACK_ON_DISPLAY = IBinder.FIRST_CALL_TRANSACTION+281;
+ int GET_FOCUSED_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+282;
+ int SET_TASK_RESIZEABLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+283;
+ int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+284;
+ int RESIZE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+285;
+ int GET_LOCK_TASK_MODE_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+286;
+ int SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+287;
+ int DUMP_HEAP_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+288;
+ int SET_VOICE_KEEP_AWAKE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+289;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 8bf8cd7..3fb82f6 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -33,7 +33,6 @@ import android.os.PersistableBundle;
import android.os.RemoteException;
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;
@@ -59,14 +58,14 @@ public interface IApplicationThread extends IInterface {
throws RemoteException;
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
- PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
- ProfilerInfo profilerInfo) throws RemoteException;
+ ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+ CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
+ int procState, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, int configChanges,
- boolean notResumed, Configuration config) throws RemoteException;
+ List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
+ Configuration config, Configuration overrideConfig) throws RemoteException;
void scheduleNewIntent(List<ReferrerIntent> intent, IBinder token) throws RemoteException;
void scheduleDestroyActivity(IBinder token, boolean finished,
int configChanges) throws RemoteException;
@@ -115,7 +114,8 @@ public interface IApplicationThread extends IInterface {
int resultCode, String data, Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState) throws RemoteException;
void scheduleLowMemory() throws RemoteException;
- void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
+ void scheduleActivityConfigurationChanged(IBinder token, Configuration overrideConfig)
+ throws RemoteException;
void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType)
throws RemoteException;
void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd)
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 88b9080..5d864df 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -75,6 +75,7 @@ interface INotificationManager
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
+ oneway void setZenMode(int mode);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
oneway void setZenModeCondition(in Condition condition);
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 03e7ff4..6d27910 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -31,5 +31,5 @@ interface ISearchManager {
ComponentName getGlobalSearchActivity();
ComponentName getWebSearchActivity();
ComponentName getAssistIntent(int userHandle);
- boolean launchAssistAction(int requestType, String hint, int userHandle);
+ boolean launchAssistAction(String hint, int userHandle);
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 3b5900b..ccba250 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -29,13 +29,18 @@ interface IWallpaperManager {
/**
* Set the wallpaper.
*/
- ParcelFileDescriptor setWallpaper(String name);
+ ParcelFileDescriptor setWallpaper(String name, in String callingPackage);
/**
* Set the live wallpaper.
*/
+ void setWallpaperComponentChecked(in ComponentName name, in String callingPackage);
+
+ /**
+ * Set the live wallpaper.
+ */
void setWallpaperComponent(in ComponentName name);
-
+
/**
* Get the wallpaper.
*/
@@ -50,7 +55,7 @@ interface IWallpaperManager {
/**
* Clear the wallpaper.
*/
- void clearWallpaper();
+ void clearWallpaper(in String callingPackage);
/**
* Return whether there is a wallpaper set with the given name.
@@ -61,7 +66,7 @@ interface IWallpaperManager {
* Sets the dimension hint for the wallpaper. These hints indicate the desired
* minimum width and height for the wallpaper.
*/
- void setDimensionHints(in int width, in int height);
+ void setDimensionHints(in int width, in int height, in String callingPackage);
/**
* Returns the desired minimum width for the wallpaper.
@@ -76,7 +81,7 @@ interface IWallpaperManager {
/**
* Sets extra padding that we would like the wallpaper to have outside of the display.
*/
- void setDisplayPadding(in Rect padding);
+ void setDisplayPadding(in Rect padding, in String callingPackage);
/**
* Returns the name of the wallpaper. Private API.
@@ -87,4 +92,9 @@ interface IWallpaperManager {
* Informs the service that wallpaper settings have been restored. Private API.
*/
void settingsRestored();
+
+ /**
+ * Check whether wallpapers are supported for the calling user.
+ */
+ boolean isWallpaperSupported(in String callingPackage);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index ad2b61f..5572d30 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1322,7 +1322,10 @@ public class Instrumentation {
/*
* Starts allocation counting. This triggers a gc and resets the counts.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public void startAllocCounting() {
// Before we start trigger a GC and reset the debug counts. Run the
// finalizers and another GC before starting and stopping the alloc
@@ -1340,7 +1343,10 @@ public class Instrumentation {
/*
* Stops allocation counting.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public void stopAllocCounting() {
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 860b9cc..b824e97 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -16,6 +16,8 @@
package android.app;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -165,6 +167,7 @@ public class Notification implements Parcelable
* The resource id of a drawable to use as the icon in the status bar.
* This is required; notifications with an invalid icon resource will not be shown.
*/
+ @DrawableRes
public int icon;
/**
@@ -336,6 +339,7 @@ public class Notification implements Parcelable
* @see #FLAG_SHOW_LIGHTS
* @see #flags
*/
+ @ColorInt
public int ledARGB;
/**
@@ -413,7 +417,6 @@ public class Notification implements Parcelable
* Bit to be bitwise-ored into the {@link #flags} field that should be
* set if the notification should be canceled when it is clicked by the
* user.
-
*/
public static final int FLAG_AUTO_CANCEL = 0x00000010;
@@ -518,12 +521,14 @@ public class Notification implements Parcelable
* {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are
* ignored.
*/
+ @ColorInt
public int color = COLOR_DEFAULT;
/**
* Special value of {@link #color} telling the system not to decorate this notification with
* any special color but instead use default colors when presenting this notification.
*/
+ @ColorInt
public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT
/**
@@ -2128,7 +2133,7 @@ public class Notification implements Parcelable
* A resource ID in the application's package of the drawable to use.
* @see Notification#icon
*/
- public Builder setSmallIcon(int icon) {
+ public Builder setSmallIcon(@DrawableRes int icon) {
mSmallIcon = icon;
return this;
}
@@ -2144,7 +2149,7 @@ public class Notification implements Parcelable
* @see Notification#icon
* @see Notification#iconLevel
*/
- public Builder setSmallIcon(int icon, int level) {
+ public Builder setSmallIcon(@DrawableRes int icon, int level) {
mSmallIcon = icon;
mSmallIconLevel = level;
return this;
@@ -2386,7 +2391,7 @@ public class Notification implements Parcelable
* @see Notification#ledOnMS
* @see Notification#ledOffMS
*/
- public Builder setLights(int argb, int onMs, int offMs) {
+ public Builder setLights(@ColorInt int argb, int onMs, int offMs) {
mLedArgb = argb;
mLedOnMs = onMs;
mLedOffMs = offMs;
@@ -2710,7 +2715,7 @@ public class Notification implements Parcelable
*
* @return The same Builder.
*/
- public Builder setColor(int argb) {
+ public Builder setColor(@ColorInt int argb) {
mColor = argb;
return this;
}
@@ -2865,7 +2870,7 @@ public class Notification implements Parcelable
contentView.setProgressBar(
R.id.progress, mProgressMax, mProgress, mProgressIndeterminate);
contentView.setProgressBackgroundTintList(
- R.id.progress, ColorStateList.valueOf(mContext.getResources().getColor(
+ R.id.progress, ColorStateList.valueOf(mContext.getColor(
R.color.notification_progress_background_color)));
if (mColor != COLOR_DEFAULT) {
ColorStateList colorStateList = ColorStateList.valueOf(mColor);
@@ -3039,7 +3044,7 @@ public class Notification implements Parcelable
private void processLegacyAction(Action action, RemoteViews button) {
if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.icon)) {
button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0,
- mContext.getResources().getColor(R.color.notification_action_color_filter),
+ mContext.getColor(R.color.notification_action_color_filter),
PorterDuff.Mode.MULTIPLY);
}
}
@@ -3137,7 +3142,7 @@ public class Notification implements Parcelable
private int resolveColor() {
if (mColor == COLOR_DEFAULT) {
- return mContext.getResources().getColor(R.color.notification_icon_bg_color);
+ return mContext.getColor(R.color.notification_icon_bg_color);
}
return mColor;
}
@@ -4316,9 +4321,9 @@ public class Notification implements Parcelable
* Applies the special text colors for media notifications to all text views.
*/
private void styleText(RemoteViews contentView) {
- int primaryColor = mBuilder.mContext.getResources().getColor(
+ int primaryColor = mBuilder.mContext.getColor(
R.color.notification_media_primary_color);
- int secondaryColor = mBuilder.mContext.getResources().getColor(
+ int secondaryColor = mBuilder.mContext.getColor(
R.color.notification_media_secondary_color);
contentView.setTextColor(R.id.title, primaryColor);
if (mBuilder.showsTimeOrChronometer()) {
@@ -5065,6 +5070,868 @@ public class Notification implements Parcelable
}
/**
+ * <p>Helper class to add Android Auto extensions to notifications. To create a notification
+ * with car extensions:
+ *
+ * <ol>
+ * <li>Create an {@link Notification.Builder}, setting any desired
+ * properties.
+ * <li>Create a {@link CarExtender}.
+ * <li>Set car-specific properties using the {@code add} and {@code set} methods of
+ * {@link CarExtender}.
+ * <li>Call {@link Notification.Builder#extend(Notification.Extender)}
+ * to apply the extensions to a notification.
+ * </ol>
+ *
+ * <pre class="prettyprint">
+ * Notification notification = new Notification.Builder(context)
+ * ...
+ * .extend(new CarExtender()
+ * .set*(...))
+ * .build();
+ * </pre>
+ *
+ * <p>Car extensions can be accessed on an existing notification by using the
+ * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
+ * to access values.
+ */
+ public static final class CarExtender implements Extender {
+ private static final String TAG = "CarExtender";
+
+ private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
+ private static final String EXTRA_LARGE_ICON = "large_icon";
+ private static final String EXTRA_CONVERSATION = "car_conversation";
+ private static final String EXTRA_COLOR = "app_color";
+
+ private Bitmap mLargeIcon;
+ private UnreadConversation mUnreadConversation;
+ private int mColor = Notification.COLOR_DEFAULT;
+
+ /**
+ * Create a {@link CarExtender} with default options.
+ */
+ public CarExtender() {
+ }
+
+ /**
+ * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
+ *
+ * @param notif The notification from which to copy options.
+ */
+ public CarExtender(Notification notif) {
+ Bundle carBundle = notif.extras == null ?
+ null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
+ if (carBundle != null) {
+ mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
+ mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
+
+ Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
+ mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
+ }
+ }
+
+ /**
+ * Apply car extensions to a notification that is being built. This is typically called by
+ * the {@link Notification.Builder#extend(Notification.Extender)}
+ * method of {@link Notification.Builder}.
+ */
+ @Override
+ public Notification.Builder extend(Notification.Builder builder) {
+ Bundle carExtensions = new Bundle();
+
+ if (mLargeIcon != null) {
+ carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
+ }
+ if (mColor != Notification.COLOR_DEFAULT) {
+ carExtensions.putInt(EXTRA_COLOR, mColor);
+ }
+
+ if (mUnreadConversation != null) {
+ Bundle b = mUnreadConversation.getBundleForUnreadConversation();
+ carExtensions.putBundle(EXTRA_CONVERSATION, b);
+ }
+
+ builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
+ return builder;
+ }
+
+ /**
+ * Sets the accent color to use when Android Auto presents the notification.
+ *
+ * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
+ * to accent the displayed notification. However, not all colors are acceptable in an
+ * automotive setting. This method can be used to override the color provided in the
+ * notification in such a situation.
+ */
+ public CarExtender setColor(@ColorInt int color) {
+ mColor = color;
+ return this;
+ }
+
+ /**
+ * Gets the accent color.
+ *
+ * @see setColor
+ */
+ @ColorInt
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Sets the large icon of the car notification.
+ *
+ * If no large icon is set in the extender, Android Auto will display the icon
+ * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
+ *
+ * @param largeIcon The large icon to use in the car notification.
+ * @return This object for method chaining.
+ */
+ public CarExtender setLargeIcon(Bitmap largeIcon) {
+ mLargeIcon = largeIcon;
+ return this;
+ }
+
+ /**
+ * Gets the large icon used in this car notification, or null if no icon has been set.
+ *
+ * @return The large icon for the car notification.
+ * @see CarExtender#setLargeIcon
+ */
+ public Bitmap getLargeIcon() {
+ return mLargeIcon;
+ }
+
+ /**
+ * Sets the unread conversation in a message notification.
+ *
+ * @param unreadConversation The unread part of the conversation this notification conveys.
+ * @return This object for method chaining.
+ */
+ public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
+ mUnreadConversation = unreadConversation;
+ return this;
+ }
+
+ /**
+ * Returns the unread conversation conveyed by this notification.
+ * @see #setUnreadConversation(UnreadConversation)
+ */
+ public UnreadConversation getUnreadConversation() {
+ return mUnreadConversation;
+ }
+
+ /**
+ * A class which holds the unread messages from a conversation.
+ */
+ public static class UnreadConversation {
+ private static final String KEY_AUTHOR = "author";
+ private static final String KEY_TEXT = "text";
+ private static final String KEY_MESSAGES = "messages";
+ private static final String KEY_REMOTE_INPUT = "remote_input";
+ private static final String KEY_ON_REPLY = "on_reply";
+ private static final String KEY_ON_READ = "on_read";
+ private static final String KEY_PARTICIPANTS = "participants";
+ private static final String KEY_TIMESTAMP = "timestamp";
+
+ private final String[] mMessages;
+ private final RemoteInput mRemoteInput;
+ private final PendingIntent mReplyPendingIntent;
+ private final PendingIntent mReadPendingIntent;
+ private final String[] mParticipants;
+ private final long mLatestTimestamp;
+
+ UnreadConversation(String[] messages, RemoteInput remoteInput,
+ PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
+ String[] participants, long latestTimestamp) {
+ mMessages = messages;
+ mRemoteInput = remoteInput;
+ mReadPendingIntent = readPendingIntent;
+ mReplyPendingIntent = replyPendingIntent;
+ mParticipants = participants;
+ mLatestTimestamp = latestTimestamp;
+ }
+
+ /**
+ * Gets the list of messages conveyed by this notification.
+ */
+ public String[] getMessages() {
+ return mMessages;
+ }
+
+ /**
+ * Gets the remote input that will be used to convey the response to a message list, or
+ * null if no such remote input exists.
+ */
+ public RemoteInput getRemoteInput() {
+ return mRemoteInput;
+ }
+
+ /**
+ * Gets the pending intent that will be triggered when the user replies to this
+ * notification.
+ */
+ public PendingIntent getReplyPendingIntent() {
+ return mReplyPendingIntent;
+ }
+
+ /**
+ * Gets the pending intent that Android Auto will send after it reads aloud all messages
+ * in this object's message list.
+ */
+ public PendingIntent getReadPendingIntent() {
+ return mReadPendingIntent;
+ }
+
+ /**
+ * Gets the participants in the conversation.
+ */
+ public String[] getParticipants() {
+ return mParticipants;
+ }
+
+ /**
+ * Gets the firs participant in the conversation.
+ */
+ public String getParticipant() {
+ return mParticipants.length > 0 ? mParticipants[0] : null;
+ }
+
+ /**
+ * Gets the timestamp of the conversation.
+ */
+ public long getLatestTimestamp() {
+ return mLatestTimestamp;
+ }
+
+ Bundle getBundleForUnreadConversation() {
+ Bundle b = new Bundle();
+ String author = null;
+ if (mParticipants != null && mParticipants.length > 1) {
+ author = mParticipants[0];
+ }
+ Parcelable[] messages = new Parcelable[mMessages.length];
+ for (int i = 0; i < messages.length; i++) {
+ Bundle m = new Bundle();
+ m.putString(KEY_TEXT, mMessages[i]);
+ m.putString(KEY_AUTHOR, author);
+ messages[i] = m;
+ }
+ b.putParcelableArray(KEY_MESSAGES, messages);
+ if (mRemoteInput != null) {
+ b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
+ }
+ b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
+ b.putParcelable(KEY_ON_READ, mReadPendingIntent);
+ b.putStringArray(KEY_PARTICIPANTS, mParticipants);
+ b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
+ return b;
+ }
+
+ static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
+ if (b == null) {
+ return null;
+ }
+ Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
+ String[] messages = null;
+ if (parcelableMessages != null) {
+ String[] tmp = new String[parcelableMessages.length];
+ boolean success = true;
+ for (int i = 0; i < tmp.length; i++) {
+ if (!(parcelableMessages[i] instanceof Bundle)) {
+ success = false;
+ break;
+ }
+ tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
+ if (tmp[i] == null) {
+ success = false;
+ break;
+ }
+ }
+ if (success) {
+ messages = tmp;
+ } else {
+ return null;
+ }
+ }
+
+ PendingIntent onRead = b.getParcelable(KEY_ON_READ);
+ PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
+
+ RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
+
+ String[] participants = b.getStringArray(KEY_PARTICIPANTS);
+ if (participants == null || participants.length != 1) {
+ return null;
+ }
+
+ return new UnreadConversation(messages,
+ remoteInput,
+ onReply,
+ onRead,
+ participants, b.getLong(KEY_TIMESTAMP));
+ }
+ };
+
+ /**
+ * Builder class for {@link CarExtender.UnreadConversation} objects.
+ */
+ public static class Builder {
+ private final List<String> mMessages = new ArrayList<String>();
+ private final String mParticipant;
+ private RemoteInput mRemoteInput;
+ private PendingIntent mReadPendingIntent;
+ private PendingIntent mReplyPendingIntent;
+ private long mLatestTimestamp;
+
+ /**
+ * Constructs a new builder for {@link CarExtender.UnreadConversation}.
+ *
+ * @param name The name of the other participant in the conversation.
+ */
+ public Builder(String name) {
+ mParticipant = name;
+ }
+
+ /**
+ * Appends a new unread message to the list of messages for this conversation.
+ *
+ * The messages should be added from oldest to newest.
+ *
+ * @param message The text of the new unread message.
+ * @return This object for method chaining.
+ */
+ public Builder addMessage(String message) {
+ mMessages.add(message);
+ return this;
+ }
+
+ /**
+ * Sets the pending intent and remote input which will convey the reply to this
+ * notification.
+ *
+ * @param pendingIntent The pending intent which will be triggered on a reply.
+ * @param remoteInput The remote input parcelable which will carry the reply.
+ * @return This object for method chaining.
+ *
+ * @see CarExtender.UnreadConversation#getRemoteInput
+ * @see CarExtender.UnreadConversation#getReplyPendingIntent
+ */
+ public Builder setReplyAction(
+ PendingIntent pendingIntent, RemoteInput remoteInput) {
+ mRemoteInput = remoteInput;
+ mReplyPendingIntent = pendingIntent;
+
+ return this;
+ }
+
+ /**
+ * Sets the pending intent that will be sent once the messages in this notification
+ * are read.
+ *
+ * @param pendingIntent The pending intent to use.
+ * @return This object for method chaining.
+ */
+ public Builder setReadPendingIntent(PendingIntent pendingIntent) {
+ mReadPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the timestamp of the most recent message in an unread conversation.
+ *
+ * If a messaging notification has been posted by your application and has not
+ * yet been cancelled, posting a later notification with the same id and tag
+ * but without a newer timestamp may result in Android Auto not displaying a
+ * heads up notification for the later notification.
+ *
+ * @param timestamp The timestamp of the most recent message in the conversation.
+ * @return This object for method chaining.
+ */
+ public Builder setLatestTimestamp(long timestamp) {
+ mLatestTimestamp = timestamp;
+ return this;
+ }
+
+ /**
+ * Builds a new unread conversation object.
+ *
+ * @return The new unread conversation object.
+ */
+ public UnreadConversation build() {
+ String[] messages = mMessages.toArray(new String[mMessages.size()]);
+ String[] participants = { mParticipant };
+ return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
+ mReadPendingIntent, participants, mLatestTimestamp);
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Helper class to add content info extensions to notifications. To create a notification with
+ * content info extensions:
+ * <ol>
+ * <li>Create an {@link Notification.Builder}, setting any desired properties.
+ * <li>Create a {@link ContentInfoExtender}.
+ * <li>Set content info specific properties using the {@code add} and {@code set} methods of
+ * {@link ContentInfoExtender}.
+ * <li>Call {@link Notification.Builder#extend(Notification.Extender)} to apply the extensions
+ * to a notification.
+ * </ol>
+ *
+ * <pre class="prettyprint">Notification notification = new Notification.Builder(context) * ... * .extend(new ContentInfoExtender() * .set*(...)) * .build(); * </pre>
+ * <p>
+ * Content info extensions can be accessed on an existing notification by using the
+ * {@code ContentInfoExtender(Notification)} constructor, and then using the {@code get} methods
+ * to access values.
+ */
+ public static final class ContentInfoExtender implements Extender {
+ private static final String TAG = "ContentInfoExtender";
+
+ // Key for the Content info extensions bundle in the main Notification extras bundle
+ private static final String EXTRA_CONTENT_INFO_EXTENDER = "android.CONTENT_INFO_EXTENSIONS";
+
+ // Keys within EXTRA_CONTENT_INFO_EXTENDER for individual content info options.
+
+ private static final String KEY_CONTENT_TYPE = "android.contentType";
+
+ private static final String KEY_CONTENT_GENRES = "android.contentGenre";
+
+ private static final String KEY_CONTENT_PRICING_TYPE = "android.contentPricing.type";
+
+ private static final String KEY_CONTENT_PRICING_VALUE = "android.contentPricing.value";
+
+ private static final String KEY_CONTENT_STATUS = "android.contentStatus";
+
+ private static final String KEY_CONTENT_MATURITY_RATING = "android.contentMaturity";
+
+ private static final String KEY_CONTENT_RUN_LENGTH = "android.contentLength";
+
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a video clip.
+ */
+ public static final String CONTENT_TYPE_VIDEO = "android.contentType.video";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a movie.
+ */
+ public static final String CONTENT_TYPE_MOVIE = "android.contentType.movie";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a trailer.
+ */
+ public static final String CONTENT_TYPE_TRAILER = "android.contentType.trailer";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is serial. It can refer to an entire show, a single season or
+ * series, or a single episode.
+ */
+ public static final String CONTENT_TYPE_SERIAL = "android.contentType.serial";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a song or album.
+ */
+ public static final String CONTENT_TYPE_MUSIC = "android.contentType.music";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a radio station.
+ */
+ public static final String CONTENT_TYPE_RADIO = "android.contentType.radio";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a podcast.
+ */
+ public static final String CONTENT_TYPE_PODCAST = "android.contentType.podcast";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a news item.
+ */
+ public static final String CONTENT_TYPE_NEWS = "android.contentType.news";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is sports.
+ */
+ public static final String CONTENT_TYPE_SPORTS = "android.contentType.sports";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is an application.
+ */
+ public static final String CONTENT_TYPE_APP = "android.contentType.app";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a game.
+ */
+ public static final String CONTENT_TYPE_GAME = "android.contentType.game";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a book.
+ */
+ public static final String CONTENT_TYPE_BOOK = "android.contentType.book";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a comic book.
+ */
+ public static final String CONTENT_TYPE_COMIC = "android.contentType.comic";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a magazine.
+ */
+ public static final String CONTENT_TYPE_MAGAZINE = "android.contentType.magazine";
+
+ /**
+ * Value to be used with {@link #setContentTypes} to indicate that the content referred by
+ * the notification item is a website.
+ */
+ public static final String CONTENT_TYPE_WEBSITE = "android.contentType.website";
+
+
+ /**
+ * Value to be used with {@link #setPricingInformation} to indicate that the content
+ * referred by the notification item is free to consume.
+ */
+ public static final String CONTENT_PRICING_FREE = "android.contentPrice.free";
+
+ /**
+ * Value to be used with {@link #setPricingInformation} to indicate that the content
+ * referred by the notification item is available as a rental, and the price value provided
+ * is the rental price for the item.
+ */
+ public static final String CONTENT_PRICING_RENTAL = "android.contentPrice.rental";
+
+ /**
+ * Value to be used with {@link #setPricingInformation} to indicate that the content
+ * referred by the notification item is available for purchase, and the price value provided
+ * is the purchase price for the item.
+ */
+ public static final String CONTENT_PRICING_PURCHASE = "android.contentPrice.purchase";
+
+ /**
+ * Value to be used with {@link #setPricingInformation} to indicate that the content
+ * referred by the notification item is available as part of a subscription based service,
+ * and the price value provided is the subscription price for the service.
+ */
+ public static final String CONTENT_PRICING_SUBSCRIPTION =
+ "android.contentPrice.subscription";
+
+ /**
+ * Value to be used with {@link #setStatus} to indicate that the content referred by the
+ * notification is available and ready to be consumed immediately.
+ */
+ public static final int CONTENT_STATUS_READY = 0;
+
+ /**
+ * Value to be used with {@link #setStatus} to indicate that the content referred by the
+ * notification is pending, waiting on either a download or purchase operation to complete
+ * before it can be consumed.
+ */
+ public static final int CONTENT_STATUS_PENDING = 1;
+
+ /**
+ * Value to be used with {@link #setStatus} to indicate that the content referred by the
+ * notification is available, but needs to be first purchased, rented, subscribed or
+ * downloaded before it can be consumed.
+ */
+ public static final int CONTENT_STATUS_AVAILABLE = 2;
+
+ /**
+ * Value to be used with {@link #setStatus} to indicate that the content referred by the
+ * notification is not available. This could be content not available in a certain region or
+ * incompatible with the device in use.
+ */
+ public static final int CONTENT_STATUS_UNAVAILABLE = 3;
+
+ /**
+ * Value to be used with {@link #setMaturityRating} to indicate that the content referred by
+ * the notification is suitable for all audiences.
+ */
+ public static final String CONTENT_MATURITY_ALL = "android.contentMaturity.all";
+
+ /**
+ * Value to be used with {@link #setMaturityRating} to indicate that the content
+ * referred by the notification is suitable for audiences of low maturity and above.
+ */
+ public static final String CONTENT_MATURITY_LOW = "android.contentMaturity.low";
+
+ /**
+ * Value to be used with {@link #setMaturityRating} to indicate that the content
+ * referred by the notification is suitable for audiences of medium maturity and above.
+ */
+ public static final String CONTENT_MATURITY_MEDIUM = "android.contentMaturity.medium";
+
+ /**
+ * Value to be used with {@link #setMaturityRating} to indicate that the content
+ * referred by the notification is suitable for audiences of high maturity and above.
+ */
+ public static final String CONTENT_MATURITY_HIGH = "android.contentMaturity.high";
+
+ private String[] mTypes;
+ private String[] mGenres;
+ private String mPricingType;
+ private String mPricingValue;
+ private int mContentStatus = -1;
+ private String mMaturityRating;
+ private long mRunLength = -1;
+
+ /**
+ * Create a {@link ContentInfoExtender} with default options.
+ */
+ public ContentInfoExtender() {
+ }
+
+ /**
+ * Create a {@link ContentInfoExtender} from the ContentInfoExtender options of an existing
+ * Notification.
+ *
+ * @param notif The notification from which to copy options.
+ */
+ public ContentInfoExtender(Notification notif) {
+ Bundle contentBundle = notif.extras == null ?
+ null : notif.extras.getBundle(EXTRA_CONTENT_INFO_EXTENDER);
+ if (contentBundle != null) {
+ mTypes = contentBundle.getStringArray(KEY_CONTENT_TYPE);
+ mGenres = contentBundle.getStringArray(KEY_CONTENT_GENRES);
+ mPricingType = contentBundle.getString(KEY_CONTENT_PRICING_TYPE);
+ mPricingValue = contentBundle.getString(KEY_CONTENT_PRICING_VALUE);
+ mContentStatus = contentBundle.getInt(KEY_CONTENT_STATUS, -1);
+ mMaturityRating = contentBundle.getString(KEY_CONTENT_MATURITY_RATING);
+ mRunLength = contentBundle.getLong(KEY_CONTENT_RUN_LENGTH, -1);
+ }
+ }
+
+ /**
+ * Apply content extensions to a notification that is being built. This is typically called
+ * by the {@link Notification.Builder#extend(Notification.Extender)} method of
+ * {@link Notification.Builder}.
+ */
+ @Override
+ public Notification.Builder extend(Notification.Builder builder) {
+ Bundle contentBundle = new Bundle();
+
+ if (mTypes != null) {
+ contentBundle.putStringArray(KEY_CONTENT_TYPE, mTypes);
+ }
+ if (mGenres != null) {
+ contentBundle.putStringArray(KEY_CONTENT_GENRES, mGenres);
+ }
+ if (mPricingType != null) {
+ contentBundle.putString(KEY_CONTENT_PRICING_TYPE, mPricingType);
+ }
+ if (mPricingValue != null) {
+ contentBundle.putString(KEY_CONTENT_PRICING_VALUE, mPricingValue);
+ }
+ if (mContentStatus != -1) {
+ contentBundle.putInt(KEY_CONTENT_STATUS, mContentStatus);
+ }
+ if (mMaturityRating != null) {
+ contentBundle.putString(KEY_CONTENT_MATURITY_RATING, mMaturityRating);
+ }
+ if (mRunLength > 0) {
+ contentBundle.putLong(KEY_CONTENT_RUN_LENGTH, mRunLength);
+ }
+
+ builder.getExtras().putBundle(EXTRA_CONTENT_INFO_EXTENDER, contentBundle);
+ return builder;
+ }
+
+ /**
+ * Sets the content types associated with the notification content. The first tag entry will
+ * be considered the primary type for the content and will be used for ranking purposes.
+ * Other secondary type tags may be provided, if applicable, and may be used for filtering
+ * purposes.
+ *
+ * @param types Array of predefined type tags (see the <code>CONTENT_TYPE_*</code>
+ * constants) that describe the content referred to by a notification.
+ */
+ public ContentInfoExtender setContentTypes(String[] types) {
+ mTypes = types;
+ return this;
+ }
+
+ /**
+ * Returns an array containing the content types that describe the content associated with
+ * the notification. The first tag entry is considered the primary type for the content, and
+ * is used for content ranking purposes.
+ *
+ * @return An array of predefined type tags (see the <code>CONTENT_TYPE_*</code> constants)
+ * that describe the content associated with the notification.
+ * @see ContentInfoExtender#setContentTypes
+ */
+ public String[] getContentTypes() {
+ return mTypes;
+ }
+
+ /**
+ * Returns the primary content type tag for the content associated with the notification.
+ *
+ * @return A predefined type tag (see the <code>CONTENT_TYPE_*</code> constants) indicating
+ * the primary type for the content associated with the notification.
+ * @see ContentInfoExtender#setContentTypes
+ */
+ public String getPrimaryContentType() {
+ if (mTypes == null || mTypes.length == 0) {
+ return null;
+ }
+ return mTypes[0];
+ }
+
+ /**
+ * Sets the content genres associated with the notification content. These genres may be
+ * used for content ranking. Genres are open ended String tags.
+ * <p>
+ * Some examples: "comedy", "action", "dance", "electronica", "racing", etc.
+ *
+ * @param genres Array of genre string tags that describe the content referred to by a
+ * notification.
+ */
+ public ContentInfoExtender setGenres(String[] genres) {
+ mGenres = genres;
+ return this;
+ }
+
+ /**
+ * Returns an array containing the content genres that describe the content associated with
+ * the notification.
+ *
+ * @return An array of genre tags that describe the content associated with the
+ * notification.
+ * @see ContentInfoExtender#setGenres
+ */
+ public String[] getGenres() {
+ return mGenres;
+ }
+
+ /**
+ * Sets the pricing and availability information for the content associated with the
+ * notification. The provided information will indicate the access model for the content
+ * (free, rental, purchase or subscription) and the price value (if not free).
+ *
+ * @param priceType Pricing type for this content. Must be one of the predefined pricing
+ * type tags (see the <code>CONTENT_PRICING_*</code> constants).
+ * @param priceValue A string containing a representation of the content price in the
+ * current locale and currency.
+ * @return This object for method chaining.
+ */
+ public ContentInfoExtender setPricingInformation(String priceType, String priceValue) {
+ mPricingType = priceType;
+ mPricingValue = priceValue;
+ return this;
+ }
+
+ /**
+ * Gets the pricing type for the content associated with the notification.
+ *
+ * @return A predefined tag indicating the pricing type for the content (see the <code>
+ * CONTENT_PRICING_*</code> constants).
+ * @see ContentInfoExtender#setPricingInformation
+ */
+ public String getPricingType() {
+ return mPricingType;
+ }
+
+ /**
+ * Gets the price value (when applicable) for the content associated with a notification.
+ * The value will be provided as a String containing the price in the appropriate currency
+ * for the current locale.
+ *
+ * @return A string containing a representation of the content price in the current locale
+ * and currency.
+ * @see ContentInfoExtender#setPricingInformation
+ */
+ public String getPricingValue() {
+ if (mPricingType == null || CONTENT_PRICING_FREE.equals(mPricingType)) {
+ return null;
+ }
+ return mPricingValue;
+ }
+
+ /**
+ * Sets the availability status for the content associated with the notification. This
+ * status indicates whether the referred content is ready to be consumed on the device, or
+ * if the user must first purchase, rent, subscribe to, or download the content.
+ *
+ * @param contentStatus The status value for this content. Must be one of the predefined
+ * content status values (see the <code>CONTENT_STATUS_*</code> constants).
+ */
+ public ContentInfoExtender setStatus(int contentStatus) {
+ mContentStatus = contentStatus;
+ return this;
+ }
+
+ /**
+ * Returns status value for the content associated with the notification. This status
+ * indicates whether the referred content is ready to be consumed on the device, or if the
+ * user must first purchase, rent, subscribe to, or download the content.
+ *
+ * @return The status value for this content, or -1 is a valid status has not been specified
+ * (see the <code>CONTENT_STATUS_*</code> for the defined valid status values).
+ * @see ContentInfoExtender#setStatus
+ */
+ public int getStatus() {
+ return mContentStatus;
+ }
+
+ /**
+ * Sets the maturity level rating for the content associated with the notification.
+ *
+ * @param maturityRating A tag indicating the maturity level rating for the content. This
+ * tag must be one of the predefined maturity rating tags (see the <code>
+ * CONTENT_MATURITY_*</code> constants).
+ */
+ public ContentInfoExtender setMaturityRating(String maturityRating) {
+ mMaturityRating = maturityRating;
+ return this;
+ }
+
+ /**
+ * Returns the maturity level rating for the content associated with the notification.
+ *
+ * @return returns a predefined tag indicating the maturity level rating for the content
+ * (see the <code> CONTENT_MATURITY_*</code> constants).
+ * @see ContentInfoExtender#setMaturityRating
+ */
+ public String getMaturityRating() {
+ return mMaturityRating;
+ }
+
+ /**
+ * Sets the running time (when applicable) for the content associated with the notification.
+ *
+ * @param length The runing time, in seconds, of the content associated with the
+ * notification.
+ */
+ public ContentInfoExtender setRunningTime(long length) {
+ mRunLength = length;
+ return this;
+ }
+
+ /**
+ * Returns the running time for the content associated with the notification.
+ *
+ * @return The running time, in seconds, of the content associated with the notification.
+ * @see ContentInfoExtender#setRunningTime
+ */
+ public long getRunningTime() {
+ return mRunLength;
+ }
+ }
+
+ /**
* Get an array of Notification objects from a parcelable array bundle field.
* Update the bundle to have a typed array so fetches in the future don't need
* to do an array copy.
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index cf54107..479327d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -27,6 +27,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.UserHandle;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.ZenModeConfig;
import android.util.Log;
/**
@@ -276,5 +279,53 @@ public class NotificationManager
}
}
+ /**
+ * @hide
+ */
+ public void setZenMode(int mode) {
+ INotificationManager service = getService();
+ try {
+ service.setZenMode(mode);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void requestZenModeConditions(IConditionListener listener, int relevance) {
+ INotificationManager service = getService();
+ try {
+ service.requestZenModeConditions(listener, relevance);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setZenModeCondition(Condition exitCondition) {
+ INotificationManager service = getService();
+ try {
+ service.setZenModeCondition(exitCondition);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public Condition getZenModeCondition() {
+ INotificationManager service = getService();
+ try {
+ final ZenModeConfig config = service.getZenModeConfig();
+ if (config != null) {
+ return config.exitCondition;
+ }
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
private Context mContext;
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1691d8e..79797c9 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -25,33 +25,29 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.ResourcesKey;
import android.hardware.display.DisplayManagerGlobal;
-import android.os.IBinder;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.view.Display;
-import android.view.DisplayAdjustments;
-
import java.lang.ref.WeakReference;
import java.util.Locale;
/** @hide */
public class ResourcesManager {
static final String TAG = "ResourcesManager";
- static final boolean DEBUG_CACHE = false;
- static final boolean DEBUG_STATS = true;
+ private static final boolean DEBUG = false;
private static ResourcesManager sResourcesManager;
- final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources
- = new ArrayMap<ResourcesKey, WeakReference<Resources> >();
-
- final ArrayMap<DisplayAdjustments, DisplayMetrics> mDefaultDisplayMetrics
- = new ArrayMap<DisplayAdjustments, DisplayMetrics>();
+ private final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources =
+ new ArrayMap<>();
+ private final ArrayMap<Pair<Integer, Configuration>, WeakReference<Display>> mDisplays =
+ new ArrayMap<>();
CompatibilityInfo mResCompatibilityInfo;
Configuration mResConfiguration;
- final Configuration mTmpConfig = new Configuration();
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
@@ -66,46 +62,18 @@ public class ResourcesManager {
return mResConfiguration;
}
- public void flushDisplayMetricsLocked() {
- mDefaultDisplayMetrics.clear();
- }
-
- public DisplayMetrics getDisplayMetricsLocked(int displayId) {
- return getDisplayMetricsLocked(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
+ DisplayMetrics getDisplayMetricsLocked() {
+ return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
}
- public DisplayMetrics getDisplayMetricsLocked(int displayId, DisplayAdjustments daj) {
- boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
- DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(daj) : null;
- if (dm != null) {
- return dm;
- }
- dm = new DisplayMetrics();
-
- DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
- if (displayManager == null) {
- // may be null early in system startup
- dm.setToDefaults();
- return dm;
- }
-
- if (isDefaultDisplay) {
- mDefaultDisplayMetrics.put(daj, dm);
- }
-
- Display d = displayManager.getCompatibleDisplay(displayId, daj);
- if (d != null) {
- d.getMetrics(dm);
+ DisplayMetrics getDisplayMetricsLocked(int displayId) {
+ DisplayMetrics dm = new DisplayMetrics();
+ final Display display = getAdjustedDisplay(displayId, Configuration.EMPTY);
+ if (display != null) {
+ display.getMetrics(dm);
} else {
- // Display no longer exists
- // FIXME: This would not be a problem if we kept the Display object around
- // instead of using the raw display id everywhere. The Display object caches
- // its information even after the display has been removed.
dm.setToDefaults();
}
- //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
- // + metrics.heightPixels + " den=" + metrics.density
- // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
return dm;
}
@@ -141,39 +109,73 @@ public class ResourcesManager {
}
/**
+ * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
+ * available.
+ *
+ * @param displayId display Id.
+ * @param overrideConfiguration override configurations.
+ */
+ public Display getAdjustedDisplay(final int displayId, Configuration overrideConfiguration) {
+ final Configuration configCopy = (overrideConfiguration != null)
+ ? new Configuration(overrideConfiguration) : new Configuration();
+ final Pair<Integer, Configuration> key = Pair.create(displayId, configCopy);
+ synchronized (this) {
+ WeakReference<Display> wd = mDisplays.get(key);
+ if (wd != null) {
+ final Display display = wd.get();
+ if (display != null) {
+ return display;
+ }
+ }
+ final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+ if (dm == null) {
+ // may be null early in system startup
+ return null;
+ }
+ final Display display = dm.getRealDisplay(displayId, key.second);
+ if (display != null) {
+ mDisplays.put(key, new WeakReference<>(display));
+ }
+ return display;
+ }
+ }
+
+ /**
* Creates the top level Resources for applications with the given compatibility info.
*
* @param resDir the resource directory.
+ * @param splitResDirs split resource directories.
* @param overlayDirs the resource overlay directories.
* @param libDirs the shared library resource dirs this app references.
- * @param compatInfo the compability info. Must not be null.
- * @param token the application token for determining stack bounds.
+ * @param displayId display Id.
+ * @param overrideConfiguration override configurations.
+ * @param compatInfo the compatibility info. Must not be null.
*/
- public Resources getTopLevelResources(String resDir, String[] splitResDirs,
+ Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId,
- Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
+ Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
final float scale = compatInfo.applicationScale;
- ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
+ Configuration overrideConfigCopy = (overrideConfiguration != null)
+ ? new Configuration(overrideConfiguration) : null;
+ ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
- if (false) {
- Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
- }
+ if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
+
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
- //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
+ //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
- if (false) {
- Slog.w(TAG, "Returning cached resources " + r + " " + resDir
- + ": appScale=" + r.getCompatibilityInfo().applicationScale);
- }
+ if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ + ": appScale=" + r.getCompatibilityInfo().applicationScale
+ + " key=" + key + " overrideConfig=" + overrideConfiguration);
return r;
}
}
//if (r != null) {
- // Slog.w(TAG, "Throwing away out-of-date resources!!!! "
+ // Log.w(TAG, "Throwing away out-of-date resources!!!! "
// + r + " " + resDir);
//}
@@ -203,17 +205,21 @@ public class ResourcesManager {
if (libDirs != null) {
for (String libDir : libDirs) {
- if (assets.addAssetPath(libDir) == 0) {
- Slog.w(TAG, "Asset path '" + libDir +
- "' does not exist or contains no resources.");
+ if (libDir.endsWith(".apk")) {
+ // Avoid opening files we know do not have resources,
+ // like code-only .jar files.
+ if (assets.addAssetPath(libDir) == 0) {
+ Log.w(TAG, "Asset path '" + libDir +
+ "' does not exist or contains no resources.");
+ }
}
}
}
- //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
+ //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics dm = getDisplayMetricsLocked(displayId);
Configuration config;
- boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
final boolean hasOverrideConfig = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfig) {
config = new Configuration(getConfiguration());
@@ -222,16 +228,14 @@ public class ResourcesManager {
}
if (hasOverrideConfig) {
config.updateFrom(key.mOverrideConfiguration);
+ if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
}
} else {
config = getConfiguration();
}
- r = new Resources(assets, dm, config, compatInfo, token);
- if (false) {
- Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
- + r.getConfiguration() + " appScale="
- + r.getCompatibilityInfo().applicationScale);
- }
+ r = new Resources(assets, dm, config, compatInfo);
+ if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale);
synchronized (this) {
WeakReference<Resources> wr = mActiveResources.get(key);
@@ -244,24 +248,26 @@ public class ResourcesManager {
}
// XXX need to remove entries when weak references go away
- mActiveResources.put(key, new WeakReference<Resources>(r));
+ mActiveResources.put(key, new WeakReference<>(r));
+ if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());
return r;
}
}
- public final boolean applyConfigurationToResourcesLocked(Configuration config,
+ final boolean applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
if (mResConfiguration == null) {
mResConfiguration = new Configuration();
}
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ mResConfiguration.seq + ", newSeq=" + config.seq);
return false;
}
int changes = mResConfiguration.updateFrom(config);
- flushDisplayMetricsLocked();
- DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
+ // Things might have changed in display manager, so clear the cached displays.
+ mDisplays.clear();
+ DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
if (compat != null && (mResCompatibilityInfo == null ||
!mResCompatibilityInfo.equals(compat))) {
@@ -283,11 +289,11 @@ public class ResourcesManager {
Configuration tmpConfig = null;
- for (int i=mActiveResources.size()-1; i>=0; i--) {
+ for (int i = mActiveResources.size() - 1; i >= 0; i--) {
ResourcesKey key = mActiveResources.keyAt(i);
Resources r = mActiveResources.valueAt(i).get();
if (r != null) {
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + config);
int displayId = key.mDisplayId;
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index af1810b..c719a0e 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -639,6 +639,12 @@ public class SearchDialog extends Dialog {
public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
return null;
}
+
+ @Override
+ public ActionMode startActionModeForChild(
+ View child, ActionMode.Callback callback, int type) {
+ return null;
+ }
}
private boolean isEmpty(AutoCompleteTextView actv) {
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index d7c4467..fa27631 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -969,7 +969,7 @@ public class SearchManager
intent.setComponent(comp);
if (inclContext) {
IActivityManager am = ActivityManagerNative.getDefault();
- Bundle extras = am.getAssistContextExtras(0);
+ Bundle extras = am.getAssistContextExtras(ActivityManager.ASSIST_CONTEXT_BASIC);
if (extras != null) {
intent.replaceExtras(extras);
}
@@ -985,12 +985,12 @@ public class SearchManager
* Launch an assist action for the current top activity.
* @hide
*/
- public boolean launchAssistAction(int requestType, String hint, int userHandle) {
+ public boolean launchAssistAction(String hint, int userHandle) {
try {
if (mService == null) {
return false;
}
- return mService.launchAssistAction(requestType, hint, userHandle);
+ return mService.launchAssistAction(hint, userHandle);
} catch (RemoteException re) {
Log.e(TAG, "launchAssistAction() failed: " + re);
return false;
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index 922ebdd..c7d2140 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -19,6 +19,7 @@ package android.app;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.StringRes;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -682,6 +683,7 @@ public final class SearchableInfo implements Parcelable {
* @return A resource id, or {@code 0} if no language model was specified.
* @see android.R.styleable#Searchable_voiceLanguageModel
*/
+ @StringRes
public int getVoiceLanguageModeId() {
return mVoiceLanguageModeId;
}
@@ -692,6 +694,7 @@ public final class SearchableInfo implements Parcelable {
* @return A resource id, or {@code 0} if no voice prompt text was specified.
* @see android.R.styleable#Searchable_voicePromptText
*/
+ @StringRes
public int getVoicePromptTextId() {
return mVoicePromptTextId;
}
@@ -702,6 +705,7 @@ public final class SearchableInfo implements Parcelable {
* @return A resource id, or {@code 0} if no language was specified.
* @see android.R.styleable#Searchable_voiceLanguage
*/
+ @StringRes
public int getVoiceLanguageId() {
return mVoiceLanguageId;
}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index c8e0031..21a3543 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.Nullable;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Intent;
@@ -498,6 +499,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* @return Return an IBinder through which clients can call on to the
* service.
*/
+ @Nullable
public abstract IBinder onBind(Intent intent);
/**
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 4427ce1..e617553 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.Nullable;
import android.content.SharedPreferences;
import android.os.FileUtils;
import android.os.Looper;
@@ -217,7 +218,8 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
- public String getString(String key, String defValue) {
+ @Nullable
+ public String getString(String key, @Nullable String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
@@ -225,7 +227,8 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
- public Set<String> getStringSet(String key, Set<String> defValues) {
+ @Nullable
+ public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
synchronized (this) {
awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
@@ -303,13 +306,13 @@ final class SharedPreferencesImpl implements SharedPreferences {
private final Map<String, Object> mModified = Maps.newHashMap();
private boolean mClear = false;
- public Editor putString(String key, String value) {
+ public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
- public Editor putStringSet(String key, Set<String> values) {
+ public Editor putStringSet(String key, @Nullable Set<String> values) {
synchronized (this) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
new file mode 100644
index 0000000..fd7bae7
--- /dev/null
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -0,0 +1,784 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.os.IDropBoxManagerService;
+
+import android.accounts.AccountManager;
+import android.accounts.IAccountManager;
+import android.app.admin.DevicePolicyManager;
+import android.app.job.IJobScheduler;
+import android.app.job.JobScheduler;
+import android.app.trust.TrustManager;
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageStatsManager;
+import android.appwidget.AppWidgetManager;
+import android.bluetooth.BluetoothManager;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.IRestrictionsManager;
+import android.content.RestrictionsManager;
+import android.content.pm.ILauncherApps;
+import android.content.pm.LauncherApps;
+import android.content.res.Resources;
+import android.hardware.ConsumerIrManager;
+import android.hardware.ISerialManager;
+import android.hardware.SensorManager;
+import android.hardware.SerialManager;
+import android.hardware.SystemSensorManager;
+import android.hardware.camera2.CameraManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlService;
+import android.hardware.input.InputManager;
+import android.hardware.usb.IUsbManager;
+import android.hardware.usb.UsbManager;
+import android.hardware.radio.RadioManager;
+import android.location.CountryDetector;
+import android.location.ICountryDetector;
+import android.location.ILocationManager;
+import android.location.LocationManager;
+import android.media.AudioManager;
+import android.media.MediaRouter;
+import android.media.midi.IMidiManager;
+import android.media.midi.MidiManager;
+import android.media.projection.MediaProjectionManager;
+import android.media.session.MediaSessionManager;
+import android.media.tv.ITvInputManager;
+import android.media.tv.TvInputManager;
+import android.net.ConnectivityManager;
+import android.net.EthernetManager;
+import android.net.IConnectivityManager;
+import android.net.IEthernetManager;
+import android.net.INetworkPolicyManager;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkScoreManager;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
+import android.net.wifi.IRttManager;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.IWifiScanner;
+import android.net.wifi.RttManager;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.p2p.IWifiP2pManager;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.passpoint.IWifiPasspointManager;
+import android.net.wifi.passpoint.WifiPasspointManager;
+import android.nfc.NfcManager;
+import android.os.BatteryManager;
+import android.os.DropBoxManager;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.IUserManager;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemVibrator;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.Vibrator;
+import android.os.storage.StorageManager;
+import android.print.IPrintManager;
+import android.print.PrintManager;
+import android.service.fingerprint.FingerprintManager;
+import android.service.fingerprint.IFingerprintService;
+import android.service.persistentdata.IPersistentDataBlockService;
+import android.service.persistentdata.PersistentDataBlockManager;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.PhoneLayoutInflater;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.CaptioningManager;
+import android.view.inputmethod.InputMethodManager;
+import android.view.textservice.TextServicesManager;
+
+import java.util.HashMap;
+
+/**
+ * Manages all of the system services that can be returned by {@link Context#getSystemService}.
+ * Used by {@link ContextImpl}.
+ */
+final class SystemServiceRegistry {
+ private final static String TAG = "SystemServiceRegistry";
+
+ // Service registry information.
+ // This information is never changed once static initialization has completed.
+ private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
+ new HashMap<Class<?>, String>();
+ private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
+ new HashMap<String, ServiceFetcher<?>>();
+ private static int sServiceCacheSize;
+
+ // Not instantiable.
+ private SystemServiceRegistry() { }
+
+ static {
+ registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
+ new CachedServiceFetcher<AccessibilityManager>() {
+ @Override
+ public AccessibilityManager createService(ContextImpl ctx) {
+ return AccessibilityManager.getInstance(ctx);
+ }});
+
+ registerService(Context.CAPTIONING_SERVICE, CaptioningManager.class,
+ new CachedServiceFetcher<CaptioningManager>() {
+ @Override
+ public CaptioningManager createService(ContextImpl ctx) {
+ return new CaptioningManager(ctx);
+ }});
+
+ registerService(Context.ACCOUNT_SERVICE, AccountManager.class,
+ new CachedServiceFetcher<AccountManager>() {
+ @Override
+ public AccountManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.ACCOUNT_SERVICE);
+ IAccountManager service = IAccountManager.Stub.asInterface(b);
+ return new AccountManager(ctx, service);
+ }});
+
+ registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
+ new CachedServiceFetcher<ActivityManager>() {
+ @Override
+ public ActivityManager createService(ContextImpl ctx) {
+ return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
+ }});
+
+ registerService(Context.ALARM_SERVICE, AlarmManager.class,
+ new CachedServiceFetcher<AlarmManager>() {
+ @Override
+ public AlarmManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);
+ IAlarmManager service = IAlarmManager.Stub.asInterface(b);
+ return new AlarmManager(service, ctx);
+ }});
+
+ registerService(Context.AUDIO_SERVICE, AudioManager.class,
+ new CachedServiceFetcher<AudioManager>() {
+ @Override
+ public AudioManager createService(ContextImpl ctx) {
+ return new AudioManager(ctx);
+ }});
+
+ registerService(Context.MEDIA_ROUTER_SERVICE, MediaRouter.class,
+ new CachedServiceFetcher<MediaRouter>() {
+ @Override
+ public MediaRouter createService(ContextImpl ctx) {
+ return new MediaRouter(ctx);
+ }});
+
+ registerService(Context.BLUETOOTH_SERVICE, BluetoothManager.class,
+ new CachedServiceFetcher<BluetoothManager>() {
+ @Override
+ public BluetoothManager createService(ContextImpl ctx) {
+ return new BluetoothManager(ctx);
+ }});
+
+ registerService(Context.HDMI_CONTROL_SERVICE, HdmiControlManager.class,
+ new StaticServiceFetcher<HdmiControlManager>() {
+ @Override
+ public HdmiControlManager createService() {
+ IBinder b = ServiceManager.getService(Context.HDMI_CONTROL_SERVICE);
+ return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b));
+ }});
+
+ registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
+ new CachedServiceFetcher<ClipboardManager>() {
+ @Override
+ public ClipboardManager createService(ContextImpl ctx) {
+ return new ClipboardManager(ctx.getOuterContext(),
+ ctx.mMainThread.getHandler());
+ }});
+
+ // The clipboard service moved to a new package. If someone asks for the old
+ // interface by class then we want to redirect over to the new interface instead
+ // (which extends it).
+ SYSTEM_SERVICE_NAMES.put(android.text.ClipboardManager.class, Context.CLIPBOARD_SERVICE);
+
+ registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class,
+ new StaticServiceFetcher<ConnectivityManager>() {
+ @Override
+ public ConnectivityManager createService() {
+ IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+ return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b));
+ }});
+
+ registerService(Context.COUNTRY_DETECTOR, CountryDetector.class,
+ new StaticServiceFetcher<CountryDetector>() {
+ @Override
+ public CountryDetector createService() {
+ IBinder b = ServiceManager.getService(Context.COUNTRY_DETECTOR);
+ return new CountryDetector(ICountryDetector.Stub.asInterface(b));
+ }});
+
+ registerService(Context.DEVICE_POLICY_SERVICE, DevicePolicyManager.class,
+ new CachedServiceFetcher<DevicePolicyManager>() {
+ @Override
+ public DevicePolicyManager createService(ContextImpl ctx) {
+ return DevicePolicyManager.create(ctx, ctx.mMainThread.getHandler());
+ }});
+
+ registerService(Context.DOWNLOAD_SERVICE, DownloadManager.class,
+ new CachedServiceFetcher<DownloadManager>() {
+ @Override
+ public DownloadManager createService(ContextImpl ctx) {
+ return new DownloadManager(ctx.getContentResolver(), ctx.getPackageName());
+ }});
+
+ registerService(Context.BATTERY_SERVICE, BatteryManager.class,
+ new StaticServiceFetcher<BatteryManager>() {
+ @Override
+ public BatteryManager createService() {
+ return new BatteryManager();
+ }});
+
+ registerService(Context.NFC_SERVICE, NfcManager.class,
+ new CachedServiceFetcher<NfcManager>() {
+ @Override
+ public NfcManager createService(ContextImpl ctx) {
+ return new NfcManager(ctx);
+ }});
+
+ registerService(Context.DROPBOX_SERVICE, DropBoxManager.class,
+ new StaticServiceFetcher<DropBoxManager>() {
+ @Override
+ public DropBoxManager createService() {
+ IBinder b = ServiceManager.getService(Context.DROPBOX_SERVICE);
+ IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
+ if (service == null) {
+ // Don't return a DropBoxManager that will NPE upon use.
+ // This also avoids caching a broken DropBoxManager in
+ // getDropBoxManager during early boot, before the
+ // DROPBOX_SERVICE is registered.
+ return null;
+ }
+ return new DropBoxManager(service);
+ }});
+
+ registerService(Context.INPUT_SERVICE, InputManager.class,
+ new StaticServiceFetcher<InputManager>() {
+ @Override
+ public InputManager createService() {
+ return InputManager.getInstance();
+ }});
+
+ registerService(Context.DISPLAY_SERVICE, DisplayManager.class,
+ new CachedServiceFetcher<DisplayManager>() {
+ @Override
+ public DisplayManager createService(ContextImpl ctx) {
+ return new DisplayManager(ctx.getOuterContext());
+ }});
+
+ registerService(Context.INPUT_METHOD_SERVICE, InputMethodManager.class,
+ new StaticServiceFetcher<InputMethodManager>() {
+ @Override
+ public InputMethodManager createService() {
+ return InputMethodManager.getInstance();
+ }});
+
+ registerService(Context.TEXT_SERVICES_MANAGER_SERVICE, TextServicesManager.class,
+ new StaticServiceFetcher<TextServicesManager>() {
+ @Override
+ public TextServicesManager createService() {
+ return TextServicesManager.getInstance();
+ }});
+
+ registerService(Context.KEYGUARD_SERVICE, KeyguardManager.class,
+ new StaticServiceFetcher<KeyguardManager>() {
+ @Override
+ public KeyguardManager createService() {
+ return new KeyguardManager();
+ }});
+
+ registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
+ new CachedServiceFetcher<LayoutInflater>() {
+ @Override
+ public LayoutInflater createService(ContextImpl ctx) {
+ return new PhoneLayoutInflater(ctx.getOuterContext());
+ }});
+
+ registerService(Context.LOCATION_SERVICE, LocationManager.class,
+ new CachedServiceFetcher<LocationManager>() {
+ @Override
+ public LocationManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE);
+ return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
+ }});
+
+ registerService(Context.NETWORK_POLICY_SERVICE, NetworkPolicyManager.class,
+ new StaticServiceFetcher<NetworkPolicyManager>() {
+ @Override
+ public NetworkPolicyManager createService() {
+ return new NetworkPolicyManager(INetworkPolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)));
+ }});
+
+ registerService(Context.NOTIFICATION_SERVICE, NotificationManager.class,
+ new CachedServiceFetcher<NotificationManager>() {
+ @Override
+ public NotificationManager createService(ContextImpl ctx) {
+ final Context outerContext = ctx.getOuterContext();
+ return new NotificationManager(
+ new ContextThemeWrapper(outerContext,
+ Resources.selectSystemTheme(0,
+ outerContext.getApplicationInfo().targetSdkVersion,
+ com.android.internal.R.style.Theme_Dialog,
+ com.android.internal.R.style.Theme_Holo_Dialog,
+ com.android.internal.R.style.Theme_DeviceDefault_Dialog,
+ com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)),
+ ctx.mMainThread.getHandler());
+ }});
+
+ registerService(Context.NSD_SERVICE, NsdManager.class,
+ new CachedServiceFetcher<NsdManager>() {
+ @Override
+ public NsdManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.NSD_SERVICE);
+ INsdManager service = INsdManager.Stub.asInterface(b);
+ return new NsdManager(ctx.getOuterContext(), service);
+ }});
+
+ registerService(Context.POWER_SERVICE, PowerManager.class,
+ new CachedServiceFetcher<PowerManager>() {
+ @Override
+ public PowerManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.POWER_SERVICE);
+ IPowerManager service = IPowerManager.Stub.asInterface(b);
+ if (service == null) {
+ Log.wtf(TAG, "Failed to get power manager service.");
+ }
+ return new PowerManager(ctx.getOuterContext(),
+ service, ctx.mMainThread.getHandler());
+ }});
+
+ registerService(Context.SEARCH_SERVICE, SearchManager.class,
+ new CachedServiceFetcher<SearchManager>() {
+ @Override
+ public SearchManager createService(ContextImpl ctx) {
+ return new SearchManager(ctx.getOuterContext(),
+ ctx.mMainThread.getHandler());
+ }});
+
+ registerService(Context.SENSOR_SERVICE, SensorManager.class,
+ new CachedServiceFetcher<SensorManager>() {
+ @Override
+ public SensorManager createService(ContextImpl ctx) {
+ return new SystemSensorManager(ctx.getOuterContext(),
+ ctx.mMainThread.getHandler().getLooper());
+ }});
+
+ registerService(Context.STATUS_BAR_SERVICE, StatusBarManager.class,
+ new CachedServiceFetcher<StatusBarManager>() {
+ @Override
+ public StatusBarManager createService(ContextImpl ctx) {
+ return new StatusBarManager(ctx.getOuterContext());
+ }});
+
+ registerService(Context.STORAGE_SERVICE, StorageManager.class,
+ new CachedServiceFetcher<StorageManager>() {
+ @Override
+ public StorageManager createService(ContextImpl ctx) {
+ try {
+ return new StorageManager(
+ ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper());
+ } catch (RemoteException rex) {
+ Log.e(TAG, "Failed to create StorageManager", rex);
+ return null;
+ }
+ }});
+
+ registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class,
+ new CachedServiceFetcher<TelephonyManager>() {
+ @Override
+ public TelephonyManager createService(ContextImpl ctx) {
+ return new TelephonyManager(ctx.getOuterContext());
+ }});
+
+ registerService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class,
+ new CachedServiceFetcher<SubscriptionManager>() {
+ @Override
+ public SubscriptionManager createService(ContextImpl ctx) {
+ return new SubscriptionManager(ctx.getOuterContext());
+ }});
+
+ registerService(Context.TELECOM_SERVICE, TelecomManager.class,
+ new CachedServiceFetcher<TelecomManager>() {
+ @Override
+ public TelecomManager createService(ContextImpl ctx) {
+ return new TelecomManager(ctx.getOuterContext());
+ }});
+
+ registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
+ new CachedServiceFetcher<UiModeManager>() {
+ @Override
+ public UiModeManager createService(ContextImpl ctx) {
+ return new UiModeManager();
+ }});
+
+ registerService(Context.USB_SERVICE, UsbManager.class,
+ new CachedServiceFetcher<UsbManager>() {
+ @Override
+ public UsbManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.USB_SERVICE);
+ return new UsbManager(ctx, IUsbManager.Stub.asInterface(b));
+ }});
+
+ registerService(Context.SERIAL_SERVICE, SerialManager.class,
+ new CachedServiceFetcher<SerialManager>() {
+ @Override
+ public SerialManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.SERIAL_SERVICE);
+ return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));
+ }});
+
+ registerService(Context.VIBRATOR_SERVICE, Vibrator.class,
+ new CachedServiceFetcher<Vibrator>() {
+ @Override
+ public Vibrator createService(ContextImpl ctx) {
+ return new SystemVibrator(ctx);
+ }});
+
+ registerService(Context.WALLPAPER_SERVICE, WallpaperManager.class,
+ new CachedServiceFetcher<WallpaperManager>() {
+ @Override
+ public WallpaperManager createService(ContextImpl ctx) {
+ return new WallpaperManager(ctx.getOuterContext(),
+ ctx.mMainThread.getHandler());
+ }});
+
+ registerService(Context.WIFI_SERVICE, WifiManager.class,
+ new CachedServiceFetcher<WifiManager>() {
+ @Override
+ public WifiManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.WIFI_SERVICE);
+ IWifiManager service = IWifiManager.Stub.asInterface(b);
+ return new WifiManager(ctx.getOuterContext(), service);
+ }});
+
+ registerService(Context.WIFI_PASSPOINT_SERVICE, WifiPasspointManager.class,
+ new CachedServiceFetcher<WifiPasspointManager>() {
+ @Override
+ public WifiPasspointManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.WIFI_PASSPOINT_SERVICE);
+ IWifiPasspointManager service = IWifiPasspointManager.Stub.asInterface(b);
+ return new WifiPasspointManager(ctx.getOuterContext(), service);
+ }});
+
+ registerService(Context.WIFI_P2P_SERVICE, WifiP2pManager.class,
+ new StaticServiceFetcher<WifiP2pManager>() {
+ @Override
+ public WifiP2pManager createService() {
+ IBinder b = ServiceManager.getService(Context.WIFI_P2P_SERVICE);
+ IWifiP2pManager service = IWifiP2pManager.Stub.asInterface(b);
+ return new WifiP2pManager(service);
+ }});
+
+ registerService(Context.WIFI_SCANNING_SERVICE, WifiScanner.class,
+ new CachedServiceFetcher<WifiScanner>() {
+ @Override
+ public WifiScanner createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.WIFI_SCANNING_SERVICE);
+ IWifiScanner service = IWifiScanner.Stub.asInterface(b);
+ return new WifiScanner(ctx.getOuterContext(), service);
+ }});
+
+ registerService(Context.WIFI_RTT_SERVICE, RttManager.class,
+ new CachedServiceFetcher<RttManager>() {
+ @Override
+ public RttManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.WIFI_RTT_SERVICE);
+ IRttManager service = IRttManager.Stub.asInterface(b);
+ return new RttManager(ctx.getOuterContext(), service);
+ }});
+
+ registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
+ new CachedServiceFetcher<EthernetManager>() {
+ @Override
+ public EthernetManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.ETHERNET_SERVICE);
+ IEthernetManager service = IEthernetManager.Stub.asInterface(b);
+ return new EthernetManager(ctx.getOuterContext(), service);
+ }});
+
+ registerService(Context.WINDOW_SERVICE, WindowManager.class,
+ new CachedServiceFetcher<WindowManager>() {
+ @Override
+ public WindowManager createService(ContextImpl ctx) {
+ return new WindowManagerImpl(ctx.getDisplay());
+ }});
+
+ registerService(Context.USER_SERVICE, UserManager.class,
+ new CachedServiceFetcher<UserManager>() {
+ @Override
+ public UserManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.USER_SERVICE);
+ IUserManager service = IUserManager.Stub.asInterface(b);
+ return new UserManager(ctx, service);
+ }});
+
+ registerService(Context.APP_OPS_SERVICE, AppOpsManager.class,
+ new CachedServiceFetcher<AppOpsManager>() {
+ @Override
+ public AppOpsManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+ IAppOpsService service = IAppOpsService.Stub.asInterface(b);
+ return new AppOpsManager(ctx, service);
+ }});
+
+ registerService(Context.CAMERA_SERVICE, CameraManager.class,
+ new CachedServiceFetcher<CameraManager>() {
+ @Override
+ public CameraManager createService(ContextImpl ctx) {
+ return new CameraManager(ctx);
+ }});
+
+ registerService(Context.LAUNCHER_APPS_SERVICE, LauncherApps.class,
+ new CachedServiceFetcher<LauncherApps>() {
+ @Override
+ public LauncherApps createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.LAUNCHER_APPS_SERVICE);
+ ILauncherApps service = ILauncherApps.Stub.asInterface(b);
+ return new LauncherApps(ctx, service);
+ }});
+
+ registerService(Context.RESTRICTIONS_SERVICE, RestrictionsManager.class,
+ new CachedServiceFetcher<RestrictionsManager>() {
+ @Override
+ public RestrictionsManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.RESTRICTIONS_SERVICE);
+ IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b);
+ return new RestrictionsManager(ctx, service);
+ }});
+
+ registerService(Context.PRINT_SERVICE, PrintManager.class,
+ new CachedServiceFetcher<PrintManager>() {
+ @Override
+ public PrintManager createService(ContextImpl ctx) {
+ IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
+ IPrintManager service = IPrintManager.Stub.asInterface(iBinder);
+ return new PrintManager(ctx.getOuterContext(), service, UserHandle.myUserId(),
+ UserHandle.getAppId(Process.myUid()));
+ }});
+
+ registerService(Context.CONSUMER_IR_SERVICE, ConsumerIrManager.class,
+ new CachedServiceFetcher<ConsumerIrManager>() {
+ @Override
+ public ConsumerIrManager createService(ContextImpl ctx) {
+ return new ConsumerIrManager(ctx);
+ }});
+
+ registerService(Context.MEDIA_SESSION_SERVICE, MediaSessionManager.class,
+ new CachedServiceFetcher<MediaSessionManager>() {
+ @Override
+ public MediaSessionManager createService(ContextImpl ctx) {
+ return new MediaSessionManager(ctx);
+ }});
+
+ registerService(Context.TRUST_SERVICE, TrustManager.class,
+ new StaticServiceFetcher<TrustManager>() {
+ @Override
+ public TrustManager createService() {
+ IBinder b = ServiceManager.getService(Context.TRUST_SERVICE);
+ return new TrustManager(b);
+ }});
+
+ registerService(Context.FINGERPRINT_SERVICE, FingerprintManager.class,
+ new CachedServiceFetcher<FingerprintManager>() {
+ @Override
+ public FingerprintManager createService(ContextImpl ctx) {
+ IBinder binder = ServiceManager.getService(Context.FINGERPRINT_SERVICE);
+ IFingerprintService service = IFingerprintService.Stub.asInterface(binder);
+ return new FingerprintManager(ctx.getOuterContext(), service);
+ }});
+
+ registerService(Context.TV_INPUT_SERVICE, TvInputManager.class,
+ new StaticServiceFetcher<TvInputManager>() {
+ @Override
+ public TvInputManager createService() {
+ IBinder iBinder = ServiceManager.getService(Context.TV_INPUT_SERVICE);
+ ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder);
+ return new TvInputManager(service, UserHandle.myUserId());
+ }});
+
+ registerService(Context.NETWORK_SCORE_SERVICE, NetworkScoreManager.class,
+ new CachedServiceFetcher<NetworkScoreManager>() {
+ @Override
+ public NetworkScoreManager createService(ContextImpl ctx) {
+ return new NetworkScoreManager(ctx);
+ }});
+
+ registerService(Context.USAGE_STATS_SERVICE, UsageStatsManager.class,
+ new CachedServiceFetcher<UsageStatsManager>() {
+ @Override
+ public UsageStatsManager createService(ContextImpl ctx) {
+ IBinder iBinder = ServiceManager.getService(Context.USAGE_STATS_SERVICE);
+ IUsageStatsManager service = IUsageStatsManager.Stub.asInterface(iBinder);
+ return new UsageStatsManager(ctx.getOuterContext(), service);
+ }});
+
+ registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,
+ new StaticServiceFetcher<JobScheduler>() {
+ @Override
+ public JobScheduler createService() {
+ IBinder b = ServiceManager.getService(Context.JOB_SCHEDULER_SERVICE);
+ return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
+ }});
+
+ registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class,
+ new StaticServiceFetcher<PersistentDataBlockManager>() {
+ @Override
+ public PersistentDataBlockManager createService() {
+ IBinder b = ServiceManager.getService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ IPersistentDataBlockService persistentDataBlockService =
+ IPersistentDataBlockService.Stub.asInterface(b);
+ if (persistentDataBlockService != null) {
+ return new PersistentDataBlockManager(persistentDataBlockService);
+ } else {
+ // not supported
+ return null;
+ }
+ }});
+
+ registerService(Context.MEDIA_PROJECTION_SERVICE, MediaProjectionManager.class,
+ new CachedServiceFetcher<MediaProjectionManager>() {
+ @Override
+ public MediaProjectionManager createService(ContextImpl ctx) {
+ return new MediaProjectionManager(ctx);
+ }});
+
+ registerService(Context.APPWIDGET_SERVICE, AppWidgetManager.class,
+ new CachedServiceFetcher<AppWidgetManager>() {
+ @Override
+ public AppWidgetManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
+ return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
+ }});
+
+ registerService(Context.MIDI_SERVICE, MidiManager.class,
+ new CachedServiceFetcher<MidiManager>() {
+ @Override
+ public MidiManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(Context.MIDI_SERVICE);
+ return new MidiManager(ctx, IMidiManager.Stub.asInterface(b));
+ }});
+
+ registerService(Context.RADIO_SERVICE, RadioManager.class,
+ new CachedServiceFetcher<RadioManager>() {
+ @Override
+ public RadioManager createService(ContextImpl ctx) {
+ return new RadioManager(ctx);
+ }});
+ }
+
+ /**
+ * Creates an array which is used to cache per-Context service instances.
+ */
+ public static Object[] createServiceCache() {
+ return new Object[sServiceCacheSize];
+ }
+
+ /**
+ * Gets a system service from a given context.
+ */
+ public static Object getSystemService(ContextImpl ctx, String name) {
+ ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
+ return fetcher != null ? fetcher.getService(ctx) : null;
+ }
+
+ /**
+ * Gets the name of the system-level service that is represented by the specified class.
+ */
+ public static String getSystemServiceName(Class<?> serviceClass) {
+ return SYSTEM_SERVICE_NAMES.get(serviceClass);
+ }
+
+ /**
+ * Statically registers a system service with the context.
+ * This method must be called during static initialization only.
+ */
+ private static <T> void registerService(String serviceName, Class<T> serviceClass,
+ ServiceFetcher<T> serviceFetcher) {
+ SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
+ SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
+ }
+
+ /**
+ * Base interface for classes that fetch services.
+ * These objects must only be created during static initialization.
+ */
+ static abstract interface ServiceFetcher<T> {
+ T getService(ContextImpl ctx);
+ }
+
+ /**
+ * Override this class when the system service constructor needs a
+ * ContextImpl and should be cached and retained by that context.
+ */
+ static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
+ private final int mCacheIndex;
+
+ public CachedServiceFetcher() {
+ mCacheIndex = sServiceCacheSize++;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public final T getService(ContextImpl ctx) {
+ final Object[] cache = ctx.mServiceCache;
+ synchronized (cache) {
+ // Fetch or create the service.
+ Object service = cache[mCacheIndex];
+ if (service == null) {
+ service = createService(ctx);
+ cache[mCacheIndex] = service;
+ }
+ return (T)service;
+ }
+ }
+
+ public abstract T createService(ContextImpl ctx);
+ }
+
+ /**
+ * Override this class when the system service does not need a ContextImpl
+ * and should be cached and retained process-wide.
+ */
+ static abstract class StaticServiceFetcher<T> implements ServiceFetcher<T> {
+ private T mCachedInstance;
+
+ @Override
+ public final T getService(ContextImpl unused) {
+ synchronized (StaticServiceFetcher.this) {
+ if (mCachedInstance == null) {
+ mCachedInstance = createService();
+ }
+ return mCachedInstance;
+ }
+ }
+
+ public abstract T createService();
+ }
+}
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 3a2c21b..a3b3022 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -31,20 +31,21 @@ import android.widget.TimePicker.ValidationCallback;
import com.android.internal.R;
/**
- * A dialog that prompts the user for the time of day using a {@link TimePicker}.
+ * A dialog that prompts the user for the time of day using a
+ * {@link TimePicker}.
*
- * <p>See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
- * guide.</p>
+ * <p>
+ * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
+ * guide.
*/
public class TimePickerDialog extends AlertDialog implements OnClickListener,
OnTimeChangedListener {
-
private static final String HOUR = "hour";
private static final String MINUTE = "minute";
private static final String IS_24_HOUR = "is24hour";
private final TimePicker mTimePicker;
- private final OnTimeSetListener mTimeSetCallback;
+ private final OnTimeSetListener mTimeSetListener;
private final int mInitialHourOfDay;
private final int mInitialMinute;
@@ -52,59 +53,70 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
/**
* The callback interface used to indicate the user is done filling in
- * the time (they clicked on the 'Done' button).
+ * the time (e.g. they clicked on the 'OK' button).
*/
public interface OnTimeSetListener {
-
/**
- * @param view The view associated with this listener.
- * @param hourOfDay The hour that was set.
- * @param minute The minute that was set.
+ * Called when the user is done setting a new time and the dialog has
+ * closed.
+ *
+ * @param view the view associated with this listener
+ * @param hourOfDay the hour that was set
+ * @param minute the minute that was set
*/
- void onTimeSet(TimePicker view, int hourOfDay, int minute);
+ public void onTimeSet(TimePicker view, int hourOfDay, int minute);
}
/**
- * @param context Parent.
- * @param callBack How parent is notified.
- * @param hourOfDay The initial hour.
- * @param minute The initial minute.
- * @param is24HourView Whether this is a 24 hour view, or AM/PM.
+ * Creates a new time picker dialog.
+ *
+ * @param context the parent context
+ * @param listener the listener to call when the time is set
+ * @param hourOfDay the initial hour
+ * @param minute the initial minute
+ * @param is24HourView whether this is a 24 hour view or AM/PM
*/
- public TimePickerDialog(Context context,
- OnTimeSetListener callBack,
- int hourOfDay, int minute, boolean is24HourView) {
- this(context, 0, callBack, hourOfDay, minute, is24HourView);
+ public TimePickerDialog(Context context, OnTimeSetListener listener, int hourOfDay, int minute,
+ boolean is24HourView) {
+ this(context, 0, listener, hourOfDay, minute, is24HourView);
}
- static int resolveDialogTheme(Context context, int resid) {
- if (resid == 0) {
+ static int resolveDialogTheme(Context context, int resId) {
+ if (resId == 0) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true);
return outValue.resourceId;
} else {
- return resid;
+ return resId;
}
}
/**
- * @param context Parent.
- * @param theme the theme to apply to this dialog
- * @param callBack How parent is notified.
- * @param hourOfDay The initial hour.
- * @param minute The initial minute.
+ * Creates a new time picker dialog with the specified theme.
+ *
+ * @param context the parent context
+ * @param themeResId the resource ID of the theme to apply to this dialog
+ * @param listener the listener to call when the time is set
+ * @param hourOfDay the initial hour
+ * @param minute the initial minute
* @param is24HourView Whether this is a 24 hour view, or AM/PM.
*/
- public TimePickerDialog(Context context, int theme, OnTimeSetListener callBack, int hourOfDay,
- int minute, boolean is24HourView) {
- super(context, resolveDialogTheme(context, theme));
+ public TimePickerDialog(Context context, int themeResId, OnTimeSetListener listener,
+ int hourOfDay, int minute, boolean is24HourView) {
+ super(context, resolveDialogTheme(context, themeResId));
- mTimeSetCallback = callBack;
+ mTimeSetListener = listener;
mInitialHourOfDay = hourOfDay;
mInitialMinute = minute;
mIs24HourView = is24HourView;
final Context themeContext = getContext();
+
+
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true);
+ final int layoutResId = outValue.resourceId;
+
final LayoutInflater inflater = LayoutInflater.from(themeContext);
final View view = inflater.inflate(R.layout.time_picker_dialog, null);
setView(view);
@@ -129,8 +141,8 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case BUTTON_POSITIVE:
- if (mTimeSetCallback != null) {
- mTimeSetCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
+ if (mTimeSetListener != null) {
+ mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
mTimePicker.getCurrentMinute());
}
break;
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 0a255f7..0f6ce12 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -219,10 +219,9 @@ public class UiModeManager {
}
/**
- * Returns the currently configured night mode.
- *
- * @return {@link #MODE_NIGHT_NO}, {@link #MODE_NIGHT_YES}, or
- * {@link #MODE_NIGHT_AUTO}. When an error occurred -1 is returned.
+ * @return the currently configured night mode. May be one of
+ * {@link #MODE_NIGHT_NO}, {@link #MODE_NIGHT_YES},
+ * {@link #MODE_NIGHT_AUTO}, or -1 on error.
*/
public int getNightMode() {
if (mService != null) {
diff --git a/core/java/android/app/VoiceInteractor.aidl b/core/java/android/app/VoiceInteractor.aidl
new file mode 100644
index 0000000..40a4a0e
--- /dev/null
+++ b/core/java/android/app/VoiceInteractor.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable VoiceInteractor.PickOptionRequest.Option;
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 723cb9b..da7bb05 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -16,12 +16,13 @@
package android.app;
-import android.annotation.SystemApi;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -34,7 +35,6 @@ import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
/**
- * @hide
* Interface for an {@link Activity} to interact with the user through voice. Use
* {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor}
* to retrieve the interface, if the activity is currently involved in a voice interaction.
@@ -56,7 +56,6 @@ import java.util.ArrayList;
* request, rather than holding on to the activity instance yourself, either explicitly
* or implicitly through a non-static inner class.
*/
-@SystemApi
public class VoiceInteractor {
static final String TAG = "VoiceInteractor";
static final boolean DEBUG = true;
@@ -72,6 +71,7 @@ public class VoiceInteractor {
public void executeMessage(Message msg) {
SomeArgs args = (SomeArgs)msg.obj;
Request request;
+ boolean complete;
switch (msg.what) {
case MSG_CONFIRMATION_RESULT:
request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
@@ -84,13 +84,28 @@ public class VoiceInteractor {
request.clear();
}
break;
+ case MSG_PICK_OPTION_RESULT:
+ complete = msg.arg1 != 0;
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, complete);
+ if (DEBUG) Log.d(TAG, "onPickOptionResult: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " finished=" + complete + " selection=" + args.arg2
+ + " result=" + args.arg3);
+ if (request != null) {
+ ((PickOptionRequest)request).onPickOptionResult(complete,
+ (PickOptionRequest.Option[]) args.arg2, (Bundle) args.arg3);
+ if (complete) {
+ request.clear();
+ }
+ }
+ break;
case MSG_COMPLETE_VOICE_RESULT:
request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
if (DEBUG) Log.d(TAG, "onCompleteVoice: req="
+ ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ " result=" + args.arg1);
if (request != null) {
- ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2);
+ ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg1);
request.clear();
}
break;
@@ -98,20 +113,22 @@ public class VoiceInteractor {
request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
if (DEBUG) Log.d(TAG, "onAbortVoice: req="
+ ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
- + " result=" + args.arg1);
+ + " result=" + args.arg2);
if (request != null) {
((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2);
request.clear();
}
break;
case MSG_COMMAND_RESULT:
- request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
+ complete = msg.arg1 != 0;
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, complete);
if (DEBUG) Log.d(TAG, "onCommandResult: req="
+ ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
- + " result=" + args.arg2);
+ + " completed=" + msg.arg1 + " result=" + args.arg2);
if (request != null) {
- ((CommandRequest)request).onCommandResult((Bundle) args.arg2);
- if (msg.arg1 != 0) {
+ ((CommandRequest)request).onCommandResult(msg.arg1 != 0,
+ (Bundle) args.arg2);
+ if (complete) {
request.clear();
}
}
@@ -131,10 +148,17 @@ public class VoiceInteractor {
final IVoiceInteractorCallback.Stub mCallback = new IVoiceInteractorCallback.Stub() {
@Override
- public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
+ public void deliverConfirmationResult(IVoiceInteractorRequest request, boolean finished,
Bundle result) {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
- MSG_CONFIRMATION_RESULT, confirmed ? 1 : 0, request, result));
+ MSG_CONFIRMATION_RESULT, finished ? 1 : 0, request, result));
+ }
+
+ @Override
+ public void deliverPickOptionResult(IVoiceInteractorRequest request,
+ boolean finished, PickOptionRequest.Option[] options, Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOOO(
+ MSG_PICK_OPTION_RESULT, finished ? 1 : 0, request, options, result));
}
@Override
@@ -158,25 +182,30 @@ public class VoiceInteractor {
@Override
public void deliverCancel(IVoiceInteractorRequest request) throws RemoteException {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(
- MSG_CANCEL_RESULT, request));
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
+ MSG_CANCEL_RESULT, request, null));
}
};
final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
static final int MSG_CONFIRMATION_RESULT = 1;
- static final int MSG_COMPLETE_VOICE_RESULT = 2;
- static final int MSG_ABORT_VOICE_RESULT = 3;
- static final int MSG_COMMAND_RESULT = 4;
- static final int MSG_CANCEL_RESULT = 5;
+ static final int MSG_PICK_OPTION_RESULT = 2;
+ static final int MSG_COMPLETE_VOICE_RESULT = 3;
+ static final int MSG_ABORT_VOICE_RESULT = 4;
+ static final int MSG_COMMAND_RESULT = 5;
+ static final int MSG_CANCEL_RESULT = 6;
+ /**
+ * Base class for voice interaction requests that can be submitted to the interactor.
+ * Do not instantiate this directly -- instead, use the appropriate subclass.
+ */
public static abstract class Request {
IVoiceInteractorRequest mRequestInterface;
Context mContext;
Activity mActivity;
- public Request() {
+ Request() {
}
public void cancel() {
@@ -214,22 +243,25 @@ public class VoiceInteractor {
String packageName, IVoiceInteractorCallback callback) throws RemoteException;
}
+ /**
+ * Confirms an operation with the user via the trusted system
+ * VoiceInteractionService. This allows an Activity to complete an unsafe operation that
+ * would require the user to touch the screen when voice interaction mode is not enabled.
+ * The result of the confirmation will be returned through an asynchronous call to
+ * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or
+ * {@link #onCancel()}.
+ *
+ * <p>In some cases this may be a simple yes / no confirmation or the confirmation could
+ * include context information about how the action will be completed
+ * (e.g. booking a cab might include details about how long until the cab arrives)
+ * so the user can give a confirmation.
+ */
public static class ConfirmationRequest extends Request {
final CharSequence mPrompt;
final Bundle mExtras;
/**
- * Confirms an operation with the user via the trusted system
- * VoiceInteractionService. This allows an Activity to complete an unsafe operation that
- * would require the user to touch the screen when voice interaction mode is not enabled.
- * The result of the confirmation will be returned through an asynchronous call to
- * either {@link #onConfirmationResult(boolean, android.os.Bundle)} or
- * {@link #onCancel()}.
- *
- * <p>In some cases this may be a simple yes / no confirmation or the confirmation could
- * include context information about how the action will be completed
- * (e.g. booking a cab might include details about how long until the cab arrives)
- * so the user can give a confirmation.
+ * Create a new confirmation request.
* @param prompt Optional confirmation text to read to the user as the action being
* confirmed.
* @param extras Additional optional information.
@@ -248,19 +280,155 @@ public class VoiceInteractor {
}
}
+ /**
+ * Select a single option from multiple potential options with the user via the trusted system
+ * VoiceInteractionService. Typically, the application would present this visually as
+ * a list view to allow selecting the option by touch.
+ * The result of the confirmation will be returned through an asynchronous call to
+ * either {@link #onPickOptionResult} or {@link #onCancel()}.
+ */
+ public static class PickOptionRequest extends Request {
+ final CharSequence mPrompt;
+ final Option[] mOptions;
+ final Bundle mExtras;
+
+ /**
+ * Represents a single option that the user may select using their voice.
+ */
+ public static final class Option implements Parcelable {
+ final CharSequence mLabel;
+ ArrayList<CharSequence> mSynonyms;
+ Bundle mExtras;
+
+ /**
+ * Creates an option that a user can select with their voice by matching the label
+ * or one of several synonyms.
+ * @param label The label that will both be matched against what the user speaks
+ * and displayed visually.
+ */
+ public Option(CharSequence label) {
+ mLabel = label;
+ }
+
+ /**
+ * Add a synonym term to the option to indicate an alternative way the content
+ * may be matched.
+ * @param synonym The synonym that will be matched against what the user speaks,
+ * but not displayed.
+ */
+ public Option addSynonym(CharSequence synonym) {
+ if (mSynonyms == null) {
+ mSynonyms = new ArrayList<>();
+ }
+ mSynonyms.add(synonym);
+ return this;
+ }
+
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ public int countSynonyms() {
+ return mSynonyms != null ? mSynonyms.size() : 0;
+ }
+
+ public CharSequence getSynonymAt(int index) {
+ return mSynonyms != null ? mSynonyms.get(index) : null;
+ }
+
+ /**
+ * Set optional extra information associated with this option. Note that this
+ * method takes ownership of the supplied extras Bundle.
+ */
+ public void setExtras(Bundle extras) {
+ mExtras = extras;
+ }
+
+ /**
+ * Return any optional extras information associated with this option, or null
+ * if there is none. Note that this method returns a reference to the actual
+ * extras Bundle in the option, so modifications to it will directly modify the
+ * extras in the option.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ Option(Parcel in) {
+ mLabel = in.readCharSequence();
+ mSynonyms = in.readCharSequenceList();
+ mExtras = in.readBundle();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeCharSequence(mLabel);
+ dest.writeCharSequenceList(mSynonyms);
+ dest.writeBundle(mExtras);
+ }
+
+ public static final Parcelable.Creator<Option> CREATOR
+ = new Parcelable.Creator<Option>() {
+ public Option createFromParcel(Parcel in) {
+ return new Option(in);
+ }
+
+ public Option[] newArray(int size) {
+ return new Option[size];
+ }
+ };
+ };
+
+ /**
+ * Create a new pick option request.
+ * @param prompt Optional question to be spoken to the user via text to speech.
+ * @param options The set of {@link Option}s the user is selecting from.
+ * @param extras Additional optional information.
+ */
+ public PickOptionRequest(CharSequence prompt, Option[] options, Bundle extras) {
+ mPrompt = prompt;
+ mOptions = options;
+ mExtras = extras;
+ }
+
+ /**
+ * Called when a single option is confirmed or narrowed to one of several options.
+ * @param finished True if the voice interaction has finished making a selection, in
+ * which case {@code selections} contains the final result. If false, this request is
+ * still active and you will continue to get calls on it.
+ * @param selections Either a single {@link Option} or one of several {@link Option}s the
+ * user has narrowed the choices down to.
+ * @param result Additional optional information.
+ */
+ public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startPickOption(packageName, callback, mPrompt, mOptions, mExtras);
+ }
+ }
+
+ /**
+ * Reports that the current interaction was successfully completed with voice, so the
+ * application can report the final status to the user. When the response comes back, the
+ * voice system has handled the request and is ready to switch; at that point the
+ * application can start a new non-voice activity or finish. Be sure when starting the new
+ * activity to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+ * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
+ * interaction task.
+ */
public static class CompleteVoiceRequest extends Request {
final CharSequence mMessage;
final Bundle mExtras;
/**
- * Reports that the current interaction was successfully completed with voice, so the
- * application can report the final status to the user. When the response comes back, the
- * voice system has handled the request and is ready to switch; at that point the
- * application can start a new non-voice activity or finish. Be sure when starting the new
- * activity to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
- * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
- * interaction task.
- *
+ * Create a new completed voice interaction request.
* @param message Optional message to tell user about the completion status of the task.
* @param extras Additional optional information.
*/
@@ -278,21 +446,23 @@ public class VoiceInteractor {
}
}
+ /**
+ * Reports that the current interaction can not be complete with voice, so the
+ * application will need to switch to a traditional input UI. Applications should
+ * only use this when they need to completely bail out of the voice interaction
+ * and switch to a traditional UI. When the response comes back, the voice
+ * system has handled the request and is ready to switch; at that point the application
+ * can start a new non-voice activity. Be sure when starting the new activity
+ * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+ * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
+ * interaction task.
+ */
public static class AbortVoiceRequest extends Request {
final CharSequence mMessage;
final Bundle mExtras;
/**
- * Reports that the current interaction can not be complete with voice, so the
- * application will need to switch to a traditional input UI. Applications should
- * only use this when they need to completely bail out of the voice interaction
- * and switch to a traditional UI. When the response comes back, the voice
- * system has handled the request and is ready to switch; at that point the application
- * can start a new non-voice activity. Be sure when starting the new activity
- * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
- * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
- * interaction task.
- *
+ * Create a new voice abort request.
* @param message Optional message to tell user about not being able to complete
* the interaction with voice.
* @param extras Additional optional information.
@@ -311,25 +481,27 @@ public class VoiceInteractor {
}
}
+ /**
+ * Execute an extended command using the trusted system VoiceInteractionService.
+ * This allows an Activity to request additional information from the user needed to
+ * complete an action (e.g. booking a table might have several possible times that the
+ * user could select from or an app might need the user to agree to a terms of service).
+ * The result of the confirmation will be returned through an asynchronous call to
+ * either {@link #onCommandResult(boolean, android.os.Bundle)} or
+ * {@link #onCancel()}.
+ *
+ * <p>The command is a string that describes the generic operation to be performed.
+ * The command will determine how the properties in extras are interpreted and the set of
+ * available commands is expected to grow over time. An example might be
+ * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of
+ * airline check-in. (This is not an actual working example.)
+ */
public static class CommandRequest extends Request {
final String mCommand;
final Bundle mArgs;
/**
- * Execute a command using the trusted system VoiceInteractionService.
- * This allows an Activity to request additional information from the user needed to
- * complete an action (e.g. booking a table might have several possible times that the
- * user could select from or an app might need the user to agree to a terms of service).
- * The result of the confirmation will be returned through an asynchronous call to
- * either {@link #onCommandResult(android.os.Bundle)} or
- * {@link #onCancel()}.
- *
- * <p>The command is a string that describes the generic operation to be performed.
- * The command will determine how the properties in extras are interpreted and the set of
- * available commands is expected to grow over time. An example might be
- * "com.google.voice.commands.REQUEST_NUMBER_BAGS" to request the number of bags as part of
- * airline check-in. (This is not an actual working example.)
- *
+ * Create a new generic command request.
* @param command The desired command to perform.
* @param args Additional arguments to control execution of the command.
*/
@@ -338,7 +510,12 @@ public class VoiceInteractor {
mArgs = args;
}
- public void onCommandResult(Bundle result) {
+ /**
+ * Results for CommandRequest can be returned in partial chunks.
+ * The isCompleted is set to true iff all results have been returned, indicating the
+ * CommandRequest has completed.
+ */
+ public void onCommandResult(boolean isCompleted, Bundle result) {
}
IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 90d84ee..22e79b6 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.RawRes;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -63,7 +64,10 @@ import java.util.List;
* Provides access to the system wallpaper. With WallpaperManager, you can
* get the current wallpaper, get the desired dimensions for the wallpaper, set
* the wallpaper, and more. Get an instance of WallpaperManager with
- * {@link #getInstance(android.content.Context) getInstance()}.
+ * {@link #getInstance(android.content.Context) getInstance()}.
+ *
+ * <p> An app can check whether wallpapers are supported for the current user, by calling
+ * {@link #isWallpaperSupported()}.
*/
public class WallpaperManager {
private static String TAG = "WallpaperManager";
@@ -187,7 +191,7 @@ public class WallpaperManager {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
throw new UnsupportedOperationException("Not supported with this drawable");
}
@@ -248,6 +252,15 @@ public class WallpaperManager {
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
synchronized (this) {
+ if (mService != null) {
+ try {
+ if (!mService.isWallpaperSupported(context.getOpPackageName())) {
+ return null;
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
if (mWallpaper != null) {
return mWallpaper;
}
@@ -617,7 +630,9 @@ public class WallpaperManager {
* wallpaper will require reloading it again from disk.
*/
public void forgetLoadedWallpaper() {
- sGlobals.forgetLoadedWallpaper();
+ if (isWallpaperSupported()) {
+ sGlobals.forgetLoadedWallpaper();
+ }
}
/**
@@ -707,7 +722,7 @@ public class WallpaperManager {
* @throws IOException If an error occurs reverting to the built-in
* wallpaper.
*/
- public void setResource(int resid) throws IOException {
+ public void setResource(@RawRes int resid) throws IOException {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
return;
@@ -716,7 +731,7 @@ public class WallpaperManager {
Resources resources = mContext.getResources();
/* Set the wallpaper to the default values */
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(
- "res:" + resources.getResourceName(resid));
+ "res:" + resources.getResourceName(resid), mContext.getOpPackageName());
if (fd != null) {
FileOutputStream fos = null;
try {
@@ -752,7 +767,8 @@ public class WallpaperManager {
return;
}
try {
- ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
+ ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+ mContext.getOpPackageName());
if (fd == null) {
return;
}
@@ -791,7 +807,8 @@ public class WallpaperManager {
return;
}
try {
- ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
+ ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
+ mContext.getOpPackageName());
if (fd == null) {
return;
}
@@ -823,7 +840,7 @@ public class WallpaperManager {
* with the given resource ID. That is, their wallpaper has been
* set through {@link #setResource(int)} with the same resource id.
*/
- public boolean hasResourceWallpaper(int resid) {
+ public boolean hasResourceWallpaper(@RawRes int resid) {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
return false;
@@ -944,7 +961,8 @@ public class WallpaperManager {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
} else {
- sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
+ sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight,
+ mContext.getOpPackageName());
}
} catch (RemoteException e) {
// Ignore
@@ -965,7 +983,7 @@ public class WallpaperManager {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
} else {
- sGlobals.mService.setDisplayPadding(padding);
+ sGlobals.mService.setDisplayPadding(padding, mContext.getOpPackageName());
}
} catch (RemoteException e) {
// Ignore
@@ -1005,7 +1023,7 @@ public class WallpaperManager {
return;
}
try {
- sGlobals.mService.clearWallpaper();
+ sGlobals.mService.clearWallpaper(mContext.getOpPackageName());
} catch (RemoteException e) {
// Ignore
}
@@ -1026,7 +1044,7 @@ public class WallpaperManager {
return false;
}
try {
- sGlobals.mService.setWallpaperComponent(name);
+ sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
// Ignore
@@ -1095,7 +1113,24 @@ public class WallpaperManager {
// Ignore.
}
}
-
+
+ /**
+ * Returns whether wallpapers are supported for the calling user. If this function returns
+ * false, any attempts to changing the wallpaper will have no effect.
+ */
+ public boolean isWallpaperSupported() {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ } else {
+ try {
+ return sGlobals.mService.isWallpaperSupported(mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+ return false;
+ }
+
/**
* Clear the offsets previously associated with this window through
* {@link #setWallpaperOffsets(IBinder, float, float)}. This reverts
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 3074b49..d1e40ae 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -165,15 +165,24 @@ public final class DeviceAdminInfo implements Parcelable {
/** @hide */
public static class PolicyInfo {
public final int ident;
- final public String tag;
- final public int label;
- final public int description;
-
- public PolicyInfo(int identIn, String tagIn, int labelIn, int descriptionIn) {
- ident = identIn;
- tag = tagIn;
- label = labelIn;
- description = descriptionIn;
+ public final String tag;
+ public final int label;
+ public final int description;
+ public final int labelForSecondaryUsers;
+ public final int descriptionForSecondaryUsers;
+
+ public PolicyInfo(int ident, String tag, int label, int description) {
+ this(ident, tag, label, description, label, description);
+ }
+
+ public PolicyInfo(int ident, String tag, int label, int description,
+ int labelForSecondaryUsers, int descriptionForSecondaryUsers) {
+ this.ident = ident;
+ this.tag = tag;
+ this.label = label;
+ this.description = description;
+ this.labelForSecondaryUsers = labelForSecondaryUsers;
+ this.descriptionForSecondaryUsers = descriptionForSecondaryUsers;
}
}
@@ -184,7 +193,10 @@ public final class DeviceAdminInfo implements Parcelable {
static {
sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_WIPE_DATA, "wipe-data",
com.android.internal.R.string.policylab_wipeData,
- com.android.internal.R.string.policydesc_wipeData));
+ com.android.internal.R.string.policydesc_wipeData,
+ com.android.internal.R.string.policylab_wipeData_secondaryUser,
+ com.android.internal.R.string.policydesc_wipeData_secondaryUser
+ ));
sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_RESET_PASSWORD, "reset-password",
com.android.internal.R.string.policylab_resetPassword,
com.android.internal.R.string.policydesc_resetPassword));
@@ -193,7 +205,10 @@ public final class DeviceAdminInfo implements Parcelable {
com.android.internal.R.string.policydesc_limitPassword));
sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_WATCH_LOGIN, "watch-login",
com.android.internal.R.string.policylab_watchLogin,
- com.android.internal.R.string.policydesc_watchLogin));
+ com.android.internal.R.string.policydesc_watchLogin,
+ com.android.internal.R.string.policylab_watchLogin,
+ com.android.internal.R.string.policydesc_watchLogin_secondaryUser
+ ));
sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_FORCE_LOCK, "force-lock",
com.android.internal.R.string.policylab_forceLock,
com.android.internal.R.string.policydesc_forceLock));
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index c53e749..fe284ce 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -25,6 +25,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.security.KeyChain;
/**
* Base class for implementing a device administration component. This
@@ -167,8 +168,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
/**
* Action sent to a device administrator to notify that the device is entering
- * lock task mode from an authorized package. The extra {@link #EXTRA_LOCK_TASK_PACKAGE}
- * will describe the authorized package using lock task mode.
+ * lock task mode. The extra {@link #EXTRA_LOCK_TASK_PACKAGE}
+ * will describe the package using lock task mode.
*
* <p>The calling device admin must be the device owner or profile
* owner to receive this broadcast.
@@ -181,7 +182,7 @@ 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.
+ * lock task mode.
*
* <p>The calling device admin must be the device owner or profile
* owner to receive this broadcast.
@@ -214,7 +215,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* <p>A device admin application which listens to this intent can find out if the device was
* provisioned for the device owner or profile owner case by calling respectively
* {@link android.app.admin.DevicePolicyManager#isDeviceOwnerApp} and
- * {@link android.app.admin.DevicePolicyManager#isProfileOwnerApp}.
+ * {@link android.app.admin.DevicePolicyManager#isProfileOwnerApp}. You will generally handle
+ * this in {@link DeviceAdminReceiver#onProfileProvisioningComplete}.
*
* <p>Input: Nothing.</p>
* <p>Output: Nothing</p>
@@ -224,6 +226,44 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
"android.app.action.PROFILE_PROVISIONING_COMPLETE";
/**
+ * Broadcast Action: This broadcast is sent to indicate that the system is ready for the device
+ * initializer to perform user setup tasks. This is only applicable to devices managed by a
+ * device owner app.
+ *
+ * <p>The broadcast will be limited to the {@link DeviceAdminReceiver} component specified in
+ * the (@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME) field
+ * of the original intent or NFC bump that started the provisioning process. You will generally
+ * handle this in {@link DeviceAdminReceiver#onReadyForUserInitialization}.
+ *
+ * <p>Input: Nothing.</p>
+ * <p>Output: Nothing</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_READY_FOR_USER_INITIALIZATION =
+ "android.app.action.READY_FOR_USER_INITIALIZATION";
+
+ /** @hide */
+ public static final String ACTION_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.action.CHOOSE_PRIVATE_KEY_ALIAS";
+
+ /** @hide */
+ public static final String EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID = "android.app.extra.CHOOSE_PRIVATE_KEY_SENDER_UID";
+
+ /** @hide */
+ public static final String EXTRA_CHOOSE_PRIVATE_KEY_HOST = "android.app.extra.CHOOSE_PRIVATE_KEY_HOST";
+
+ /** @hide */
+ public static final String EXTRA_CHOOSE_PRIVATE_KEY_PORT = "android.app.extra.CHOOSE_PRIVATE_KEY_PORT";
+
+ /** @hide */
+ public static final String EXTRA_CHOOSE_PRIVATE_KEY_URL = "android.app.extra.CHOOSE_PRIVATE_KEY_URL";
+
+ /** @hide */
+ public static final String EXTRA_CHOOSE_PRIVATE_KEY_ALIAS = "android.app.extra.CHOOSE_PRIVATE_KEY_ALIAS";
+
+ /** @hide */
+ public static final String EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE = "android.app.extra.CHOOSE_PRIVATE_KEY_RESPONSE";
+
+ /**
* Name under which a DevicePolicy component publishes information
* about itself. This meta-data must reference an XML resource containing
* a device-admin tag.
@@ -360,20 +400,20 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
/**
* Called when provisioning of a managed profile or managed device has completed successfully.
*
- * <p> As a prerequisit for the execution of this callback the (@link DeviceAdminReceiver} has
+ * <p> As a prerequisite for the execution of this callback the {@link DeviceAdminReceiver} has
* to declare an intent filter for {@link #ACTION_PROFILE_PROVISIONING_COMPLETE}.
* Its component must also be specified in the {@link DevicePolicyManager#EXTRA_DEVICE_ADMIN}
* of the {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} intent that started the
* managed provisioning.
*
- * <p>When provisioning is complete, the managed profile is hidden until the profile owner
- * calls {DevicePolicyManager#setProfileEnabled(ComponentName admin)}. Typically a profile
- * owner will enable the profile when it has finished any additional setup such as adding an
- * account by using the {@link AccountManager} and calling apis to bring the profile into the
- * desired state.
+ * <p>When provisioning of a managed profile is complete, the managed profile is hidden until
+ * the profile owner calls {DevicePolicyManager#setProfileEnabled(ComponentName admin)}.
+ * Typically a profile owner will enable the profile when it has finished any additional setup
+ * such as adding an account by using the {@link AccountManager} and calling apis to bring the
+ * profile into the desired state.
*
* <p> Note that provisioning completes without waiting for any server interactions, so the
- * profile owner needs to wait for data to be available if required (e.g android device ids or
+ * profile owner needs to wait for data to be available if required (e.g. android device ids or
* other data that is set as a result of server interactions).
*
* @param context The running context as per {@link #onReceive}.
@@ -383,8 +423,31 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
}
/**
- * Called when a device is entering lock task mode by a package authorized
- * by {@link DevicePolicyManager#isLockTaskPermitted(String)}
+ * Called during provisioning of a managed device to allow the device initializer to perform
+ * user setup steps. Only device initializers should override this method.
+ *
+ * <p> Called when the DeviceAdminReceiver receives a
+ * {@link #ACTION_READY_FOR_USER_INITIALIZATION} broadcast. As a prerequisite for the execution
+ * of this callback the {@link DeviceAdminReceiver} has
+ * to declare an intent filter for {@link #ACTION_READY_FOR_USER_INITIALIZATION}. Only the
+ * component specified in the
+ * {@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME} field of the
+ * original intent or NFC bump that started the provisioning process will receive this callback.
+ *
+ * <p>It is not assumed that the device initializer is finished when it returns from
+ * this call, as it may do additional setup asynchronously. The device initializer must call
+ * {DevicePolicyManager#setUserEnabled(ComponentName admin)} when it has finished any additional
+ * setup (such as adding an account by using the {@link AccountManager}) in order for the user
+ * to be functional.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onReadyForUserInitialization(Context context, Intent intent) {
+ }
+
+ /**
+ * Called when a device is entering lock task mode.
*
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
@@ -394,8 +457,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
}
/**
- * Called when a device is exiting lock task mode by a package authorized
- * by {@link DevicePolicyManager#isLockTaskPermitted(String)}
+ * Called when a device is exiting lock task mode.
*
* @param context The running context as per {@link #onReceive}.
* @param intent The received intent as per {@link #onReceive}.
@@ -404,6 +466,26 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
}
/**
+ * Allows this receiver to select the alias for a private key and certificate pair for
+ * authentication. If this method returns null, the default {@link android.app.Activity} will be
+ * shown that lets the user pick a private key and certificate pair.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ * @param uid The uid asking for the private key and certificate pair.
+ * @param host The authentication host, may be null.
+ * @param port The authentication port, or -1.
+ * @param url The URL to authenticate, may be null.
+ * @param alias The alias preselected by the client, or null.
+ * @return The private key alias to return and grant access to.
+ * @see KeyChain#choosePrivateKeyAlias
+ */
+ public String onChoosePrivateKeyAlias(Context context, Intent intent, int uid, String host,
+ int port, String url, String alias) {
+ return null;
+ }
+
+ /**
* Intercept standard device administrator broadcasts. Implementations
* should not override this method; it is better to implement the
* convenience callbacks for each action.
@@ -432,11 +514,22 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
onPasswordExpiring(context, intent);
} else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) {
onProfileProvisioningComplete(context, intent);
+ } else if (ACTION_CHOOSE_PRIVATE_KEY_ALIAS.equals(action)) {
+ int uid = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_SENDER_UID, -1);
+ String host = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_HOST);
+ int port = intent.getIntExtra(EXTRA_CHOOSE_PRIVATE_KEY_PORT, -1);
+ String url = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_URL);
+ String alias = intent.getStringExtra(EXTRA_CHOOSE_PRIVATE_KEY_ALIAS);
+ String chosenAlias = onChoosePrivateKeyAlias(context, intent, uid, host, port, url,
+ alias);
+ setResultData(chosenAlias);
} else if (ACTION_LOCK_TASK_ENTERING.equals(action)) {
String pkg = intent.getStringExtra(EXTRA_LOCK_TASK_PACKAGE);
onLockTaskModeEntering(context, intent, pkg);
} else if (ACTION_LOCK_TASK_EXITING.equals(action)) {
onLockTaskModeExiting(context, intent);
+ } else if (ACTION_READY_FOR_USER_INITIALIZATION.equals(action)) {
+ onReadyForUserInitialization(context, intent);
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a01a96a..a659acb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -28,6 +28,7 @@ import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
import android.net.ProxyInfo;
import android.os.Bundle;
import android.os.Handler;
@@ -41,7 +42,6 @@ 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;
@@ -52,11 +52,15 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
+import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -64,7 +68,7 @@ import java.util.List;
/**
* 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 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.
@@ -105,11 +109,17 @@ public class DevicePolicyManager {
* Provisioning adds a managed profile and sets the MDM as the profile owner who has full
* control over the profile.
*
- * <p>This intent must contain the extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}.
+ * In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this intent must contain the
+ * extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}.
+ * As of {@link android.os.Build.VERSION_CODES#MNC}, it should contain the extra
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, although specifying only
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported.
*
- * <p> When managed provisioning has completed, an intent of the type
- * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
- * managed profile.
+ * <p> When managed provisioning has completed, broadcasts are sent to the application specified
+ * in the provisioning intent. The
+ * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} broadcast is sent in the
+ * managed profile and the {@link #ACTION_MANAGED_PROFILE_PROVISIONED} broadcast is sent in
+ * the primary profile.
*
* <p> If provisioning fails, the managedProfile is removed so the device returns to its
* previous state.
@@ -144,11 +154,36 @@ public class DevicePolicyManager {
*
* <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> When this extra is set, the application must have exactly one device admin receiver.
+ * This receiver will be set as the profile or device owner and active admin.</p>
+
+ * @see DeviceAdminReceiver
+ * @deprecated Use {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}. This extra is still
+ * supported.
*/
+ @Deprecated
public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME";
/**
+ * A ComponentName extra indicating the device admin receiver of the mobile device management
+ * application that will be set as the profile owner or device owner and active admin.
+ *
+ * <p>If an application starts provisioning directly via an intent with action
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE} the package name of this component has to match the
+ * package name of the application that started provisioning.
+ *
+ * <p>This component is set as device owner and active admin when device owner provisioning is
+ * started by an NFC message containing an NFC record with MIME type
+ * {@link #MIME_TYPE_PROVISIONING_NFC}.
+ *
+ * @see DeviceAdminReceiver
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME
+ = "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_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.
@@ -328,6 +363,76 @@ public class DevicePolicyManager {
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM";
/**
+ * Broadcast Action: This broadcast is sent to indicate that provisioning of a managed profile
+ * has completed successfully.
+ *
+ * <p>The broadcast is limited to the primary profile, to the app specified in the provisioning
+ * intent (@see #ACTION_PROVISION_MANAGED_PROFILE).
+ *
+ * <p>This intent will contain the extra {@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE} which
+ * corresponds to the account requested to be migrated at provisioning time, if any.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGED_PROFILE_PROVISIONED
+ = "android.app.action.MANAGED_PROFILE_PROVISIONED";
+
+ /**
+ * A boolean extra indicating whether device encryption is required as part of Device Owner
+ * provisioning.
+ *
+ * <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_SKIP_ENCRYPTION =
+ "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
+
+ /**
+ * On devices managed by a device owner app, a String representation of a Component name extra
+ * indicating the component of the application that is temporarily granted device owner
+ * privileges during device initialization and profile owner privileges during secondary user
+ * initialization.
+ *
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
+ * @see ComponentName#unflattenFromString()
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME
+ = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME";
+
+ /**
+ * A String extra holding an http url that specifies the download location of the device
+ * initializer package. When not provided it is assumed that the device initializer 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.
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION
+ = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION";
+
+ /**
+ * 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_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}.
+ *
+ * <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_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER
+ = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_COOKIE_HEADER";
+
+ /**
+ * A String extra holding the SHA-1 checksum of the file at download location specified in
+ * {@link #EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_DOWNLOAD_LOCATION}. If this doesn't
+ * match 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.
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM
+ = "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_PACKAGE_CHECKSUM";
+
+ /**
* This MIME type is used for starting the Device Owner provisioning.
*
* <p>During device owner provisioning a device admin app is set as the owner of the device.
@@ -343,7 +448,6 @@ public class DevicePolicyManager {
* <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>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}</li>
@@ -357,7 +461,17 @@ public class DevicePolicyManager {
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT} (convert to String), optional</li>
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li></ul>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li></ul>
+ *
+ * <p>
+ * In version {@link android.os.Build.VERSION_CODES#LOLLIPOP}, it should also contain
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME}.
+ * As of {@link android.os.Build.VERSION_CODES#MNC}, it should contain
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, (although
+ * specifying only {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported).
+ * This componentName must have been converted to a String via
+ * {@link android.content.ComponentName#flattenToString()}
*
* <p> When device owner provisioning has completed, an intent of the type
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcasted to the
@@ -690,7 +804,7 @@ public class DevicePolicyManager {
public void setPasswordQuality(ComponentName admin, int quality) {
if (mService != null) {
try {
- mService.setPasswordQuality(admin, quality, UserHandle.myUserId());
+ mService.setPasswordQuality(admin, quality);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -743,7 +857,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLength(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLength(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumLength(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -797,7 +911,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumUpperCase(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumUpperCase(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumUpperCase(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -858,7 +972,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLowerCase(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLowerCase(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumLowerCase(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -918,7 +1032,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLetters(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLetters(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumLetters(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -976,7 +1090,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumNumeric(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumNumeric(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumNumeric(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1035,7 +1149,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumSymbols(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumSymbols(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumSymbols(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1093,7 +1207,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumNonLetter(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumNonLetter(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumNonLetter(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1153,7 +1267,7 @@ public class DevicePolicyManager {
public void setPasswordHistoryLength(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordHistoryLength(admin, length, UserHandle.myUserId());
+ mService.setPasswordHistoryLength(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1185,7 +1299,7 @@ public class DevicePolicyManager {
public void setPasswordExpirationTimeout(ComponentName admin, long timeout) {
if (mService != null) {
try {
- mService.setPasswordExpirationTimeout(admin, timeout, UserHandle.myUserId());
+ mService.setPasswordExpirationTimeout(admin, timeout);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1330,7 +1444,7 @@ public class DevicePolicyManager {
public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) {
if (mService != null) {
try {
- mService.setMaximumFailedPasswordsForWipe(admin, num, UserHandle.myUserId());
+ mService.setMaximumFailedPasswordsForWipe(admin, num);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1414,7 +1528,7 @@ public class DevicePolicyManager {
public boolean resetPassword(String password, int flags) {
if (mService != null) {
try {
- return mService.resetPassword(password, flags, UserHandle.myUserId());
+ return mService.resetPassword(password, flags);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1438,7 +1552,7 @@ public class DevicePolicyManager {
public void setMaximumTimeToLock(ComponentName admin, long timeMs) {
if (mService != null) {
try {
- mService.setMaximumTimeToLock(admin, timeMs, UserHandle.myUserId());
+ mService.setMaximumTimeToLock(admin, timeMs);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1503,8 +1617,8 @@ public class DevicePolicyManager {
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.
+ * Ask the user data be wiped. Wiping the primary user will cause the
+ * device to reboot, 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
@@ -1588,7 +1702,7 @@ public class DevicePolicyManager {
!= android.net.Proxy.PROXY_VALID)
throw new IllegalArgumentException();
}
- return mService.setGlobalProxy(admin, hostSpec, exclSpec, UserHandle.myUserId());
+ return mService.setGlobalProxy(admin, hostSpec, exclSpec);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1652,7 +1766,7 @@ public class DevicePolicyManager {
public static final int ENCRYPTION_STATUS_INACTIVE = 1;
/**
- * Result code for {@link #setStorageEncryption} and {@link #getStorageEncryptionStatus}:
+ * Result code for {@link #getStorageEncryptionStatus}:
* indicating that encryption is not currently active, but is currently
* being activated. This is only reported by devices that support
* encryption of data and only when the storage is currently
@@ -1668,6 +1782,13 @@ public class DevicePolicyManager {
public static final int ENCRYPTION_STATUS_ACTIVE = 3;
/**
+ * Result code for {@link #getStorageEncryptionStatus}:
+ * indicating that encryption is active, but an encryption key has not
+ * been set by the user.
+ */
+ public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4;
+
+ /**
* Activity action: begin the process of encrypting data on the device. This activity should
* be launched after using {@link #setStorageEncryption} to request encryption be activated.
* After resuming from this activity, use {@link #getStorageEncryption}
@@ -1754,7 +1875,7 @@ public class DevicePolicyManager {
public int setStorageEncryption(ComponentName admin, boolean encrypt) {
if (mService != null) {
try {
- return mService.setStorageEncryption(admin, encrypt, UserHandle.myUserId());
+ return mService.setStorageEncryption(admin, encrypt);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1791,12 +1912,15 @@ public class DevicePolicyManager {
* storage system does not support encryption. If the
* result is {@link #ENCRYPTION_STATUS_INACTIVE}, use {@link
* #ACTION_START_ENCRYPTION} to begin the process of encrypting or decrypting the
- * storage. If the result is {@link #ENCRYPTION_STATUS_ACTIVATING} or
+ * storage. If the result is {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY}, the
+ * storage system has enabled encryption but no password is set so further action
+ * may be required. If the result is {@link #ENCRYPTION_STATUS_ACTIVATING} or
* {@link #ENCRYPTION_STATUS_ACTIVE}, no further action is required.
*
* @return current status of encryption. The value will be one of
* {@link #ENCRYPTION_STATUS_UNSUPPORTED}, {@link #ENCRYPTION_STATUS_INACTIVE},
- * {@link #ENCRYPTION_STATUS_ACTIVATING}, or{@link #ENCRYPTION_STATUS_ACTIVE}.
+ * {@link #ENCRYPTION_STATUS_ACTIVATING}, {@link #ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY},
+ * or {@link #ENCRYPTION_STATUS_ACTIVE}.
*/
public int getStorageEncryptionStatus() {
return getStorageEncryptionStatus(UserHandle.myUserId());
@@ -1934,13 +2058,15 @@ public class DevicePolicyManager {
String alias) {
try {
final byte[] pemCert = Credentials.convertToPem(cert);
- return mService.installKeyPair(who, privKey.getEncoded(), pemCert, alias);
- } catch (CertificateException e) {
- Log.w(TAG, "Error encoding certificate", e);
- } catch (IOException e) {
- Log.w(TAG, "Error writing certificate", e);
+ final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
+ .getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
+ return mService.installKeyPair(who, pkcs8Key, pemCert, alias);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ Log.w(TAG, "Failed to obtain private key material", e);
+ } catch (CertificateException | IOException e) {
+ Log.w(TAG, "Could not pem-encode certificate", e);
}
return false;
}
@@ -1971,7 +2097,7 @@ public class DevicePolicyManager {
public void setCameraDisabled(ComponentName admin, boolean disabled) {
if (mService != null) {
try {
- mService.setCameraDisabled(admin, disabled, UserHandle.myUserId());
+ mService.setCameraDisabled(admin, disabled);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2015,7 +2141,7 @@ public class DevicePolicyManager {
public void setScreenCaptureDisabled(ComponentName admin, boolean disabled) {
if (mService != null) {
try {
- mService.setScreenCaptureDisabled(admin, UserHandle.myUserId(), disabled);
+ mService.setScreenCaptureDisabled(admin, disabled);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2059,7 +2185,7 @@ public class DevicePolicyManager {
public void setAutoTimeRequired(ComponentName admin, boolean required) {
if (mService != null) {
try {
- mService.setAutoTimeRequired(admin, UserHandle.myUserId(), required);
+ mService.setAutoTimeRequired(admin, required);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2100,7 +2226,7 @@ public class DevicePolicyManager {
public void setKeyguardDisabledFeatures(ComponentName admin, int which) {
if (mService != null) {
try {
- mService.setKeyguardDisabledFeatures(admin, which, UserHandle.myUserId());
+ mService.setKeyguardDisabledFeatures(admin, which);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2350,6 +2476,113 @@ public class DevicePolicyManager {
}
/**
+ * Sets the given component as the device initializer. The package must already be installed and
+ * set as an active device administrator, and there must not be an existing device initializer,
+ * for this call to succeed. This method can only be called by an app holding the
+ * MANAGE_DEVICE_ADMINS permission before the device is provisioned or by a device owner app. A
+ * device initializer app is granted device owner privileges during device initialization and
+ * profile owner privileges during secondary user initialization.
+ * @param who Which {@link DeviceAdminReceiver} this request is associated with, or null if not
+ * called by the device owner.
+ * @param initializer Which {@link DeviceAdminReceiver} to make device initializer.
+ * @param initializerName The user-visible name of the device initializer.
+ * @return whether the package was successfully registered as the device initializer.
+ * @throws IllegalArgumentException if the package name is null or invalid
+ * @throws IllegalStateException if the caller is not device owner or the device has
+ * already been provisioned or a device initializer already exists.
+ */
+ public boolean setDeviceInitializer(ComponentName who, ComponentName initializer,
+ String initializerName) throws IllegalArgumentException, IllegalStateException {
+ if (mService != null) {
+ try {
+ return mService.setDeviceInitializer(who, initializer, initializerName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to set device initializer");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Used to determine if a particular package has been registered as the device initializer.
+ *
+ * @param packageName the package name of the app, to compare with the registered device
+ * initializer app, if any.
+ * @return whether or not the caller is registered as the device initializer app.
+ */
+ public boolean isDeviceInitializerApp(String packageName) {
+ if (mService != null) {
+ try {
+ return mService.isDeviceInitializer(packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to check device initializer");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes the device initializer, so that it will not be invoked on user initialization for any
+ * subsequently created users. This method can be called by either the device owner or device
+ * initializer itself. The caller must be an active administrator.
+ *
+ * @param who Which {@link DeviceAdminReceiver} this request is associated with.
+ */
+ public void clearDeviceInitializerApp(ComponentName who) {
+ if (mService != null) {
+ try {
+ mService.clearDeviceInitializer(who);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to clear device initializer");
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Gets the device initializer of the system.
+ *
+ * @return the package name of the device initializer.
+ */
+ @SystemApi
+ public String getDeviceInitializerApp() {
+ if (mService != null) {
+ try {
+ return mService.getDeviceInitializer();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to get device initializer");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the enabled state of the user. A user should be enabled only once it is ready to
+ * be used.
+ *
+ * <p>Device initializer must call this method to mark the user as functional.
+ * Only the device initializer agent can call this.
+ *
+ * <p>When the user is enabled, if the device initializer is not also the device owner, the
+ * device initializer will no longer have elevated permissions to call methods in this class.
+ * Additionally, it will be removed as an active administrator and its
+ * {@link DeviceAdminReceiver} will be disabled.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return whether the user is now enabled.
+ */
+ public boolean setUserEnabled(ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.setUserEnabled(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
* @hide
* @deprecated Use #ACTION_SET_PROFILE_OWNER
* Sets the given component as an active admin and registers the package as the profile
@@ -2417,27 +2650,6 @@ public class DevicePolicyManager {
}
/**
- * @deprecated Use setProfileOwner(ComponentName ...)
- * @hide
- * Sets the given package as the profile owner of the given user profile. The package must
- * already be installed and there shouldn't be an existing profile owner registered for this
- * user. Also, this method must be called before the user has been used for the first time.
- * @param packageName the package name of the application to be registered as profile owner.
- * @param ownerName the human readable name of the organisation associated with this DPM.
- * @param userHandle the userId to set the profile owner for.
- * @return whether the package was successfully registered as the profile owner.
- * @throws IllegalArgumentException if packageName is null, the package isn't installed, or
- * the user has already been set up.
- */
- public boolean setProfileOwner(String packageName, String ownerName, int userHandle)
- throws IllegalArgumentException {
- if (packageName == null) {
- throw new NullPointerException("packageName cannot be null");
- }
- return setProfileOwner(new ComponentName(packageName, ""), ownerName, userHandle);
- }
-
- /**
* @hide
* Sets the given component as the profile owner of the given user profile. The package must
* already be installed and there shouldn't be an existing profile owner registered for this
@@ -2698,14 +2910,12 @@ public class DevicePolicyManager {
* <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.
- * @hide
*/
public void setTrustAgentConfiguration(ComponentName admin, ComponentName target,
PersistableBundle configuration) {
if (mService != null) {
try {
- mService.setTrustAgentConfiguration(admin, target, configuration,
- UserHandle.myUserId());
+ mService.setTrustAgentConfiguration(admin, target, configuration);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2725,7 +2935,6 @@ public class DevicePolicyManager {
* for this {@param agent} or calls it with a null configuration, null is returned.
* @param agent Which component to get enabled features for.
* @return configuration for the given trust agent.
- * @hide
*/
public List<PersistableBundle> getTrustAgentConfiguration(ComponentName admin,
ComponentName agent) {
@@ -3109,8 +3318,7 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile or device owner to set a user restriction specified
- * by the key.
+ * Called by a profile or device owner to set a user restriction specified by the key.
* <p>
* The calling device admin must be a profile or device owner; if it is not,
* a security exception will be thrown.
@@ -3131,8 +3339,7 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile or device owner to clear a user restriction specified
- * by the key.
+ * Called by a profile or device owner to clear a user restriction specified by the key.
* <p>
* The calling device admin must be a profile or device owner; if it is not,
* a security exception will be thrown.
@@ -3153,7 +3360,7 @@ public class DevicePolicyManager {
}
/**
- * Called by device or profile owner to hide or unhide packages. When a package is hidden it
+ * Called by profile or device owners to hide or unhide packages. When a package is hidden it
* is unavailable for use, but the data and actual package file remain.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -3175,7 +3382,7 @@ public class DevicePolicyManager {
}
/**
- * Called by device or profile owner to determine if a package is hidden.
+ * Called by profile or device owners to determine if a package is hidden.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param packageName The name of the package to retrieve the hidden status of.
@@ -3193,7 +3400,7 @@ public class DevicePolicyManager {
}
/**
- * Called by profile or device owner to re-enable a system app that was disabled by default
+ * Called by profile or device owners to re-enable a system app that was disabled by default
* when the user was initialized.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -3210,7 +3417,7 @@ public class DevicePolicyManager {
}
/**
- * Called by profile or device owner to re-enable system apps by intent that were disabled
+ * Called by profile or device owners to re-enable system apps by intent that were disabled
* by default when the user was initialized.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -3296,7 +3503,8 @@ public class DevicePolicyManager {
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
*
* @see Activity#startLockTask()
- * @see DeviceAdminReceiver#onLockTaskModeChanged(Context, Intent, boolean, String)
+ * @see DeviceAdminReceiver#onLockTaskModeEntering(Context, Intent, String)
+ * @see DeviceAdminReceiver#onLockTaskModeExiting(Context, Intent)
* @see UserManager#DISALLOW_CREATE_WINDOWS
*/
public void setLockTaskPackages(ComponentName admin, String[] packages)
@@ -3351,14 +3559,22 @@ public class DevicePolicyManager {
* <li>{@link Settings.Global#ADB_ENABLED}</li>
* <li>{@link Settings.Global#AUTO_TIME}</li>
* <li>{@link Settings.Global#AUTO_TIME_ZONE}</li>
- * <li>{@link Settings.Global#BLUETOOTH_ON}</li>
+ * <li>{@link Settings.Global#BLUETOOTH_ON}
+ * Changing this setting has not effect as of {@link android.os.Build.VERSION_CODES#MNC}. Use
+ * {@link android.bluetooth.BluetoothAdapter#enable()} and
+ * {@link android.bluetooth.BluetoothAdapter#disable()} instead.</li>
* <li>{@link Settings.Global#DATA_ROAMING}</li>
* <li>{@link Settings.Global#DEVELOPMENT_SETTINGS_ENABLED}</li>
* <li>{@link Settings.Global#MODE_RINGER}</li>
* <li>{@link Settings.Global#NETWORK_PREFERENCE}</li>
* <li>{@link Settings.Global#USB_MASS_STORAGE_ENABLED}</li>
- * <li>{@link Settings.Global#WIFI_ON}</li>
+ * <li>{@link Settings.Global#WIFI_ON}
+ * Changing this setting has not effect as of {@link android.os.Build.VERSION_CODES#MNC}. Use
+ * {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)} instead.</li>
* <li>{@link Settings.Global#WIFI_SLEEP_POLICY}</li>
+ * <li>{@link Settings.Global#STAY_ON_WHILE_PLUGGED_IN}
+ * This setting is only available from {@link android.os.Build.VERSION_CODES#MNC} onwards
+ * and can only be set if {@link #setMaximumTimeToLock} is not used to set a timeout.</li>
* </ul>
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -3478,9 +3694,14 @@ public class DevicePolicyManager {
/**
* 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.
+ * <p>
+ * <strong>Note:</strong> Starting from {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}, the
+ * behavior of this API is changed such that passing <code>null</code> as the <code>admin</code>
+ * parameter will return if any admin has blocked the uninstallation. Before L MR1, passing
+ * <code>null</code> will cause a NullPointerException to be raised.
*
* @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.
+ * to check if any admin has blocked the uninstallation.
* @param packageName package to check.
* @return true if uninstallation is blocked.
*/
@@ -3575,4 +3796,18 @@ public class DevicePolicyManager {
}
return Collections.emptyList();
}
+
+ /**
+ * Called by profile or device owners to set the current user's photo.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param icon the bitmap to set as the photo.
+ */
+ public void setUserIcon(ComponentName admin, Bitmap icon) {
+ try {
+ mService.setUserIcon(admin, icon);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not set the user icon ", re);
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0ca60c0..f69cf36 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -20,6 +20,7 @@ package android.app.admin;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Bitmap;
import android.net.ProxyInfo;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -32,34 +33,34 @@ import java.util.List;
* {@hide}
*/
interface IDevicePolicyManager {
- void setPasswordQuality(in ComponentName who, int quality, int userHandle);
+ void setPasswordQuality(in ComponentName who, int quality);
int getPasswordQuality(in ComponentName who, int userHandle);
- void setPasswordMinimumLength(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumLength(in ComponentName who, int length);
int getPasswordMinimumLength(in ComponentName who, int userHandle);
- void setPasswordMinimumUpperCase(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumUpperCase(in ComponentName who, int length);
int getPasswordMinimumUpperCase(in ComponentName who, int userHandle);
- void setPasswordMinimumLowerCase(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumLowerCase(in ComponentName who, int length);
int getPasswordMinimumLowerCase(in ComponentName who, int userHandle);
- void setPasswordMinimumLetters(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumLetters(in ComponentName who, int length);
int getPasswordMinimumLetters(in ComponentName who, int userHandle);
- void setPasswordMinimumNumeric(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumNumeric(in ComponentName who, int length);
int getPasswordMinimumNumeric(in ComponentName who, int userHandle);
- void setPasswordMinimumSymbols(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumSymbols(in ComponentName who, int length);
int getPasswordMinimumSymbols(in ComponentName who, int userHandle);
- void setPasswordMinimumNonLetter(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumNonLetter(in ComponentName who, int length);
int getPasswordMinimumNonLetter(in ComponentName who, int userHandle);
- void setPasswordHistoryLength(in ComponentName who, int length, int userHandle);
+ void setPasswordHistoryLength(in ComponentName who, int length);
int getPasswordHistoryLength(in ComponentName who, int userHandle);
- void setPasswordExpirationTimeout(in ComponentName who, long expiration, int userHandle);
+ void setPasswordExpirationTimeout(in ComponentName who, long expiration);
long getPasswordExpirationTimeout(in ComponentName who, int userHandle);
long getPasswordExpiration(in ComponentName who, int userHandle);
@@ -68,33 +69,33 @@ interface IDevicePolicyManager {
int getCurrentFailedPasswordAttempts(int userHandle);
int getProfileWithMinimumFailedPasswordsForWipe(int userHandle);
- void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, int userHandle);
+ void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num);
int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle);
- boolean resetPassword(String password, int flags, int userHandle);
+ boolean resetPassword(String password, int flags);
- void setMaximumTimeToLock(in ComponentName who, long timeMs, int userHandle);
+ void setMaximumTimeToLock(in ComponentName who, long timeMs);
long getMaximumTimeToLock(in ComponentName who, int userHandle);
void lockNow();
void wipeData(int flags, int userHandle);
- ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle);
+ ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
ComponentName getGlobalProxyAdmin(int userHandle);
void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
- int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle);
+ int setStorageEncryption(in ComponentName who, boolean encrypt);
boolean getStorageEncryption(in ComponentName who, int userHandle);
int getStorageEncryptionStatus(int userHandle);
- void setCameraDisabled(in ComponentName who, boolean disabled, int userHandle);
+ void setCameraDisabled(in ComponentName who, boolean disabled);
boolean getCameraDisabled(in ComponentName who, int userHandle);
- void setScreenCaptureDisabled(in ComponentName who, int userHandle, boolean disabled);
+ void setScreenCaptureDisabled(in ComponentName who, boolean disabled);
boolean getScreenCaptureDisabled(in ComponentName who, int userHandle);
- void setKeyguardDisabledFeatures(in ComponentName who, int which, int userHandle);
+ void setKeyguardDisabledFeatures(in ComponentName who, int which);
int getKeyguardDisabledFeatures(in ComponentName who, int userHandle);
void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing, int userHandle);
@@ -129,6 +130,7 @@ interface IDevicePolicyManager {
void enforceCanManageCaCerts(in ComponentName admin);
boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer, String alias);
+ void choosePrivateKeyAlias(int uid, in String host, int port, in String url, in String alias, IBinder aliasCallback);
void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
@@ -186,7 +188,7 @@ interface IDevicePolicyManager {
boolean getCrossProfileCallerIdDisabledForUser(int userId);
void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent,
- in PersistableBundle args, int userId);
+ in PersistableBundle args);
List<PersistableBundle> getTrustAgentConfiguration(in ComponentName admin,
in ComponentName agent, int userId);
@@ -194,8 +196,16 @@ interface IDevicePolicyManager {
boolean removeCrossProfileWidgetProvider(in ComponentName admin, String packageName);
List<String> getCrossProfileWidgetProviders(in ComponentName admin);
- void setAutoTimeRequired(in ComponentName who, int userHandle, boolean required);
+ void setAutoTimeRequired(in ComponentName who, boolean required);
boolean getAutoTimeRequired();
boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle);
+
+ boolean setUserEnabled(in ComponentName who);
+ boolean isDeviceInitializer(String packageName);
+ void clearDeviceInitializer(in ComponentName who);
+ boolean setDeviceInitializer(in ComponentName who, in ComponentName initializer, String initializerName);
+ String getDeviceInitializer();
+
+ void setUserIcon(in ComponentName admin, in Bitmap icon);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 1b1e600..7f89100 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -258,6 +258,7 @@ public abstract class BackupAgent extends ContextWrapper {
*
* <ul>
* <li>The contents of the {@link #getCacheDir()} directory</li>
+ * <li>The contents of the {@link #getCodeCacheDir()} directory</li>
* <li>The contents of the {@link #getNoBackupFilesDir()} directory</li>
* <li>The contents of the app's shared library directory</li>
* </ul>
@@ -285,6 +286,7 @@ public abstract class BackupAgent extends ContextWrapper {
String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
String sharedPrefsDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
String cacheDir = getCacheDir().getCanonicalPath();
+ String codeCacheDir = getCodeCacheDir().getCanonicalPath();
String libDir = (appInfo.nativeLibraryDir != null)
? new File(appInfo.nativeLibraryDir).getCanonicalPath()
: null;
@@ -298,6 +300,7 @@ public abstract class BackupAgent extends ContextWrapper {
filterSet.add(libDir);
}
filterSet.add(cacheDir);
+ filterSet.add(codeCacheDir);
filterSet.add(databaseDir);
filterSet.add(sharedPrefsDir);
filterSet.add(filesDir);
@@ -354,6 +357,7 @@ public abstract class BackupAgent extends ContextWrapper {
String dbDir;
String spDir;
String cacheDir;
+ String codeCacheDir;
String libDir;
String efDir = null;
String filePath;
@@ -367,6 +371,7 @@ public abstract class BackupAgent extends ContextWrapper {
dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath();
spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
cacheDir = getCacheDir().getCanonicalPath();
+ codeCacheDir = getCodeCacheDir().getCanonicalPath();
libDir = (appInfo.nativeLibraryDir == null)
? null
: new File(appInfo.nativeLibraryDir).getCanonicalPath();
@@ -380,7 +385,8 @@ public abstract class BackupAgent extends ContextWrapper {
}
// Now figure out which well-defined tree the file is placed in, working from
- // most to least specific. We also specifically exclude the lib and cache dirs.
+ // most to least specific. We also specifically exclude the lib, cache,
+ // and code_cache dirs.
filePath = file.getCanonicalPath();
} catch (IOException e) {
Log.w(TAG, "Unable to obtain canonical paths");
@@ -388,9 +394,10 @@ public abstract class BackupAgent extends ContextWrapper {
}
if (filePath.startsWith(cacheDir)
+ || filePath.startsWith(codeCacheDir)
|| filePath.startsWith(libDir)
|| filePath.startsWith(nbFilesDir)) {
- Log.w(TAG, "lib, cache, and no_backup files are not backed up");
+ Log.w(TAG, "lib, cache, code_cache, and no_backup files are not backed up");
return;
}
diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java
index 048a4bb..1fe63e7 100644
--- a/core/java/android/app/backup/BackupDataOutput.java
+++ b/core/java/android/app/backup/BackupDataOutput.java
@@ -18,8 +18,6 @@ package android.app.backup;
import android.annotation.SystemApi;
import android.os.ParcelFileDescriptor;
-import android.os.Process;
-
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index 62734f2..7ee39f5 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -17,7 +17,6 @@
package android.app.job;
import android.app.job.IJobCallback;
-import android.app.job.IJobCallback.Stub;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java
index 89efeb2..a1c11f3 100644
--- a/core/java/android/app/job/JobScheduler.java
+++ b/core/java/android/app/job/JobScheduler.java
@@ -18,8 +18,6 @@ package android.app.job;
import java.util.List;
-import android.content.Context;
-
/**
* This is an API for scheduling various types of jobs against the framework that will be executed
* in your application's own process.
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 3cf3c95..58279d7 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -68,6 +68,11 @@ public final class UsageEvents implements Parcelable {
public static final int CONFIGURATION_CHANGE = 5;
/**
+ * An event type denoting that a package was interacted with in some way.
+ */
+ public static final int INTERACTION = 6;
+
+ /**
* {@hide}
*/
public String mPackage;
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 083a48a..0122069 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -37,6 +37,16 @@ public abstract class UsageStatsManagerInternal {
public abstract void reportEvent(ComponentName component, int userId, int eventType);
/**
+ * Reports an event to the UsageStatsManager.
+ *
+ * @param packageName The package for which this event occurred.
+ * @param userId The user id to which the component belongs to.
+ * @param eventType The event that occurred. Valid values can be found at
+ * {@link UsageEvents}
+ */
+ public abstract void reportEvent(String packageName, int userId, int eventType);
+
+ /**
* Reports a configuration change to the UsageStatsManager.
*
* @param config The new device configuration.
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 8e86824..1af4953 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -16,6 +16,7 @@
package android.appwidget;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
@@ -58,18 +59,28 @@ public class AppWidgetHost {
private DisplayMetrics mDisplayMetrics;
private String mContextOpPackageName;
- Handler mHandler;
- int mHostId;
- Callbacks mCallbacks = new Callbacks();
- final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
+ private final Handler mHandler;
+ private final int mHostId;
+ private final Callbacks mCallbacks;
+ private final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<>();
private OnClickHandler mOnClickHandler;
- class Callbacks extends IAppWidgetHost.Stub {
+ static class Callbacks extends IAppWidgetHost.Stub {
+ private final WeakReference<Handler> mWeakHandler;
+
+ public Callbacks(Handler handler) {
+ mWeakHandler = new WeakReference<>(handler);
+ }
+
public void updateAppWidget(int appWidgetId, RemoteViews views) {
if (isLocalBinder() && views != null) {
views = views.clone();
}
- Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
msg.sendToTarget();
}
@@ -77,20 +88,36 @@ public class AppWidgetHost {
if (isLocalBinder() && info != null) {
info = info.clone();
}
- Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED,
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED,
appWidgetId, 0, info);
msg.sendToTarget();
}
public void providersChanged() {
- mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
}
public void viewDataChanged(int appWidgetId, int viewId) {
- Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
appWidgetId, viewId);
msg.sendToTarget();
}
+
+ private static boolean isLocalBinder() {
+ return Process.myPid() == Binder.getCallingPid();
+ }
}
class UpdateHandler extends Handler {
@@ -132,6 +159,7 @@ public class AppWidgetHost {
mHostId = hostId;
mOnClickHandler = handler;
mHandler = new UpdateHandler(looper);
+ mCallbacks = new Callbacks(mHandler);
mDisplayMetrics = context.getResources().getDisplayMetrics();
bindService();
}
@@ -251,10 +279,6 @@ public class AppWidgetHost {
}
}
- private boolean isLocalBinder() {
- return Process.myPid() == Binder.getCallingPid();
- }
-
/**
* Stop listening to changes for this AppWidget.
*/
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 1ff476e..3219fe7 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -31,9 +31,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Process;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -588,9 +586,10 @@ public class AppWidgetHostView extends FrameLayout {
return tv;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(AppWidgetHostView.class.getName());
}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 0450150..767f59e 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.media.AudioManager;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -415,9 +416,16 @@ public final class BluetoothA2dp implements BluetoothProfile {
}
/**
- * Tells remote device to adjust volume. Only if absolute volume is supported.
+ * Tells remote device to adjust volume. Only if absolute volume is
+ * supported. Uses the following values:
+ * <ul>
+ * <li>{@link AudioManager#ADJUST_LOWER}</li>
+ * <li>{@link AudioManager#ADJUST_RAISE}</li>
+ * <li>{@link AudioManager#ADJUST_MUTE}</li>
+ * <li>{@link AudioManager#ADJUST_UNMUTE}</li>
+ * </ul>
*
- * @param direction 1 to increase volume, or -1 to decrease volume
+ * @param direction One of the supported adjust values.
* @hide
*/
public void adjustAvrcpAbsoluteVolume(int direction) {
diff --git a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
index ce87329..161c339 100644
--- a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
+++ b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java
@@ -26,32 +26,32 @@ import android.os.Parcelable;
* @hide
*/
public final class BluetoothActivityEnergyInfo implements Parcelable {
+ private final long mTimestamp;
private final int mBluetoothStackState;
private final int mControllerTxTimeMs;
private final int mControllerRxTimeMs;
private final int mControllerIdleTimeMs;
private final int mControllerEnergyUsed;
- private final long timestamp;
public static final int BT_STACK_STATE_INVALID = 0;
public static final int BT_STACK_STATE_STATE_ACTIVE = 1;
public static final int BT_STACK_STATE_STATE_SCANNING = 2;
public static final int BT_STACK_STATE_STATE_IDLE = 3;
- public BluetoothActivityEnergyInfo(int stackState, int txTime, int rxTime,
- int idleTime, int energyUsed) {
+ public BluetoothActivityEnergyInfo(long timestamp, int stackState,
+ int txTime, int rxTime, int idleTime, int energyUsed) {
+ mTimestamp = timestamp;
mBluetoothStackState = stackState;
mControllerTxTimeMs = txTime;
mControllerRxTimeMs = rxTime;
mControllerIdleTimeMs = idleTime;
mControllerEnergyUsed = energyUsed;
- timestamp = System.currentTimeMillis();
}
@Override
public String toString() {
return "BluetoothActivityEnergyInfo{"
- + " timestamp=" + timestamp
+ + " mTimestamp=" + mTimestamp
+ " mBluetoothStackState=" + mBluetoothStackState
+ " mControllerTxTimeMs=" + mControllerTxTimeMs
+ " mControllerRxTimeMs=" + mControllerRxTimeMs
@@ -63,13 +63,14 @@ public final class BluetoothActivityEnergyInfo implements Parcelable {
public static final Parcelable.Creator<BluetoothActivityEnergyInfo> CREATOR =
new Parcelable.Creator<BluetoothActivityEnergyInfo>() {
public BluetoothActivityEnergyInfo createFromParcel(Parcel in) {
+ long timestamp = in.readLong();
int stackState = in.readInt();
int txTime = in.readInt();
int rxTime = in.readInt();
int idleTime = in.readInt();
int energyUsed = in.readInt();
- return new BluetoothActivityEnergyInfo(stackState, txTime, rxTime,
- idleTime, energyUsed);
+ return new BluetoothActivityEnergyInfo(timestamp, stackState,
+ txTime, rxTime, idleTime, energyUsed);
}
public BluetoothActivityEnergyInfo[] newArray(int size) {
return new BluetoothActivityEnergyInfo[size];
@@ -77,6 +78,7 @@ public final class BluetoothActivityEnergyInfo implements Parcelable {
};
public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mTimestamp);
out.writeInt(mBluetoothStackState);
out.writeInt(mControllerTxTimeMs);
out.writeInt(mControllerRxTimeMs);
@@ -123,11 +125,12 @@ public final class BluetoothActivityEnergyInfo implements Parcelable {
public int getControllerEnergyUsed() {
return mControllerEnergyUsed;
}
+
/**
- * @return timestamp(wall clock) of record creation
+ * @return timestamp(real time elapsed in milliseconds since boot) of record creation.
*/
public long getTimeStamp() {
- return timestamp;
+ return mTimestamp;
}
/**
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index b8f4bf8..be26eac 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -26,9 +26,7 @@ import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -36,7 +34,6 @@ import android.util.Log;
import android.util.Pair;
import java.io.IOException;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
index a15bd97..7b5a045 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -61,6 +61,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
*/
public static final int CALL_STATE_TERMINATED = 7;
+ private final BluetoothDevice mDevice;
private final int mId;
private int mState;
private String mNumber;
@@ -70,8 +71,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
/**
* Creates BluetoothHeadsetClientCall instance.
*/
- public BluetoothHeadsetClientCall(int id, int state, String number, boolean multiParty,
- boolean outgoing) {
+ public BluetoothHeadsetClientCall(BluetoothDevice device, int id, int state, String number,
+ boolean multiParty, boolean outgoing) {
+ mDevice = device;
mId = id;
mState = state;
mNumber = number != null ? number : "";
@@ -114,6 +116,15 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
}
/**
+ * Gets call's device.
+ *
+ * @return call device.
+ */
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
* Gets call's Id.
*
* @return call id.
@@ -161,7 +172,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
}
public String toString() {
- StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mId: ");
+ StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mDevice: ");
+ builder.append(mDevice);
+ builder.append(", mId: ");
builder.append(mId);
builder.append(", mState: ");
switch (mState) {
@@ -192,8 +205,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
new Parcelable.Creator<BluetoothHeadsetClientCall>() {
@Override
public BluetoothHeadsetClientCall createFromParcel(Parcel in) {
- return new BluetoothHeadsetClientCall(in.readInt(), in.readInt(),
- in.readString(), in.readInt() == 1, in.readInt() == 1);
+ return new BluetoothHeadsetClientCall((BluetoothDevice)in.readParcelable(null),
+ in.readInt(), in.readInt(), in.readString(),
+ in.readInt() == 1, in.readInt() == 1);
}
@Override
@@ -204,6 +218,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mDevice, 0);
out.writeInt(mId);
out.writeInt(mState);
out.writeString(mNumber);
diff --git a/core/java/android/bluetooth/le/TruncatedFilter.java b/core/java/android/bluetooth/le/TruncatedFilter.java
index 6a6b3e3..685b174 100644
--- a/core/java/android/bluetooth/le/TruncatedFilter.java
+++ b/core/java/android/bluetooth/le/TruncatedFilter.java
@@ -17,9 +17,6 @@
package android.bluetooth.le;
import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-
import java.util.List;
/**
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 0cff4c0..393cf8e 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -19,6 +19,7 @@ package android.content;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
@@ -364,7 +365,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public Bundle call(String callingPkg, String method, String arg, Bundle extras) {
+ public Bundle call(
+ String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras) {
final String original = setCallingPackage(callingPkg);
try {
return ContentProvider.this.call(method, arg, extras);
@@ -1742,7 +1744,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* @return provider-defined return value. May be {@code null}, which is also
* the default for providers which don't implement any call methods.
*/
- public Bundle call(String method, String arg, Bundle extras) {
+ public Bundle call(String method, @Nullable String arg, @Nullable Bundle extras) {
return null;
}
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 136e54d..49ac062 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -208,6 +208,22 @@ public class ContentProviderOperation implements Parcelable {
return mType;
}
+ public boolean isInsert() {
+ return mType == TYPE_INSERT;
+ }
+
+ public boolean isDelete() {
+ return mType == TYPE_DELETE;
+ }
+
+ public boolean isUpdate() {
+ return mType == TYPE_UPDATE;
+ }
+
+ public boolean isAssertQuery() {
+ return mType == TYPE_ASSERT;
+ }
+
public boolean isWriteOperation() {
return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE;
}
diff --git a/core/java/android/content/ContentProviderResult.java b/core/java/android/content/ContentProviderResult.java
index ec3d002..4196f27 100644
--- a/core/java/android/content/ContentProviderResult.java
+++ b/core/java/android/content/ContentProviderResult.java
@@ -18,7 +18,6 @@ package android.content;
import android.content.ContentProvider;
import android.net.Uri;
-import android.os.UserHandle;
import android.os.Parcelable;
import android.os.Parcel;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index a09fee9..17a8eb7 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -17,6 +17,7 @@
package android.content;
import android.accounts.Account;
+import android.annotation.Nullable;
import android.app.ActivityManagerNative;
import android.app.ActivityThread;
import android.app.AppGlobals;
@@ -1357,7 +1358,8 @@ public abstract class ContentResolver {
* @throws NullPointerException if uri or method is null
* @throws IllegalArgumentException if uri is not known
*/
- public final Bundle call(Uri uri, String method, String arg, Bundle extras) {
+ public final Bundle call(
+ Uri uri, String method, @Nullable String arg, @Nullable Bundle extras) {
if (uri == null) {
throw new NullPointerException("uri == null");
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 26735a6..80b5e0b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -16,14 +16,19 @@
package android.content;
+import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
+import android.annotation.StyleableRes;
import android.annotation.SystemApi;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -32,7 +37,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.media.MediaScannerConnection.OnScanCompletedListener;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
@@ -146,12 +150,13 @@ public abstract class Context {
@IntDef(flag = true,
value = {
BIND_AUTO_CREATE,
- BIND_AUTO_CREATE,
BIND_DEBUG_UNBIND,
BIND_NOT_FOREGROUND,
BIND_ABOVE_CLIENT,
BIND_ALLOW_OOM_MANAGEMENT,
- BIND_WAIVE_PRIORITY
+ BIND_WAIVE_PRIORITY,
+ BIND_IMPORTANT,
+ BIND_ADJUST_WITH_ACTIVITY
})
@Retention(RetentionPolicy.SOURCE)
public @interface BindServiceFlags {}
@@ -363,7 +368,7 @@ public abstract class Context {
*
* @param resId Resource id for the CharSequence text
*/
- public final CharSequence getText(int resId) {
+ public final CharSequence getText(@StringRes int resId) {
return getResources().getText(resId);
}
@@ -373,7 +378,7 @@ public abstract class Context {
*
* @param resId Resource id for the string
*/
- public final String getString(int resId) {
+ public final String getString(@StringRes int resId) {
return getResources().getString(resId);
}
@@ -386,23 +391,60 @@ public abstract class Context {
* @param formatArgs The format arguments that will be used for substitution.
*/
- public final String getString(int resId, Object... formatArgs) {
+ public final String getString(@StringRes int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}
/**
- * Return a drawable object associated with a particular resource ID and
+ * Returns a color associated with a particular resource ID and styled for
+ * the current theme.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @return A single color value in the form 0xAARRGGBB.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
+ */
+ @Nullable
+ public final int getColor(int id) {
+ return getResources().getColor(id, getTheme());
+ }
+
+ /**
+ * Returns a drawable object associated with a particular resource ID and
* styled for the current theme.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
- * @return Drawable An object that can be used to draw this resource.
+ * @return An object that can be used to draw this resource, or
+ * {@code null} if the resource could not be resolved.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
*/
+ @Nullable
public final Drawable getDrawable(int id) {
return getResources().getDrawable(id, getTheme());
}
+ /**
+ * Returns a color state list associated with a particular resource ID and
+ * styled for the current theme.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @return A color state list, or {@code null} if the resource could not be
+ * resolved.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
+ */
+ @Nullable
+ public final ColorStateList getColorStateList(int id) {
+ return getResources().getColorStateList(id, getTheme());
+ }
+
/**
* Set the base theme for this context. Note that this should be called
* before any views are instantiated in the Context (for example before
@@ -411,7 +453,7 @@ public abstract class Context {
*
* @param resid The style resource describing the theme.
*/
- public abstract void setTheme(int resid);
+ public abstract void setTheme(@StyleRes int resid);
/** @hide Needed for some internal implementation... not public because
* you can't assume this actually means anything. */
@@ -427,10 +469,10 @@ public abstract class Context {
/**
* Retrieve styled attribute information in this Context's theme. See
- * {@link Resources.Theme#obtainStyledAttributes(int[])}
+ * {@link android.content.res.Resources.Theme#obtainStyledAttributes(int[])}
* for more information.
*
- * @see Resources.Theme#obtainStyledAttributes(int[])
+ * @see android.content.res.Resources.Theme#obtainStyledAttributes(int[])
*/
public final TypedArray obtainStyledAttributes(
int[] attrs) {
@@ -439,22 +481,22 @@ public abstract class Context {
/**
* Retrieve styled attribute information in this Context's theme. See
- * {@link Resources.Theme#obtainStyledAttributes(int, int[])}
+ * {@link android.content.res.Resources.Theme#obtainStyledAttributes(int, int[])}
* for more information.
*
- * @see Resources.Theme#obtainStyledAttributes(int, int[])
+ * @see android.content.res.Resources.Theme#obtainStyledAttributes(int, int[])
*/
public final TypedArray obtainStyledAttributes(
- int resid, int[] attrs) throws Resources.NotFoundException {
+ @StyleableRes int resid, int[] attrs) throws Resources.NotFoundException {
return getTheme().obtainStyledAttributes(resid, attrs);
}
/**
* Retrieve styled attribute information in this Context's theme. See
- * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* for more information.
*
- * @see Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
*/
public final TypedArray obtainStyledAttributes(
AttributeSet set, int[] attrs) {
@@ -463,10 +505,10 @@ public abstract class Context {
/**
* Retrieve styled attribute information in this Context's theme. See
- * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
+ * {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* for more information.
*
- * @see Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
+ * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
*/
public final TypedArray obtainStyledAttributes(
AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
@@ -711,7 +753,8 @@ public abstract class Context {
* are not automatically scanned by the media scanner, you can explicitly
* add them to the media database with
* {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[],
- * OnScanCompletedListener) MediaScannerConnection.scanFile}.
+ * android.media.MediaScannerConnection.OnScanCompletedListener)
+ * MediaScannerConnection.scanFile}.
* Note that this is not the same as
* {@link android.os.Environment#getExternalStoragePublicDirectory
* Environment.getExternalStoragePublicDirectory()}, which provides
@@ -1876,7 +1919,7 @@ public abstract class Context {
* @return The first sticky intent found that matches <var>filter</var>,
* or null if there are none.
*
- * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
* @see #sendBroadcast
* @see #unregisterReceiver
*/
@@ -2039,7 +2082,9 @@ public abstract class Context {
* @hide
*/
@SystemApi
- public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) {
+ @SuppressWarnings("unused")
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn,
+ int flags, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -2110,17 +2155,21 @@ public abstract class Context {
WIFI_PASSPOINT_SERVICE,
WIFI_P2P_SERVICE,
WIFI_SCANNING_SERVICE,
+ //@hide: WIFI_RTT_SERVICE,
//@hide: ETHERNET_SERVICE,
WIFI_RTT_SERVICE,
NSD_SERVICE,
AUDIO_SERVICE,
+ //@hide: FINGERPRINT_SERVICE,
MEDIA_ROUTER_SERVICE,
TELEPHONY_SERVICE,
+ TELEPHONY_SUBSCRIPTION_SERVICE,
TELECOM_SERVICE,
CLIPBOARD_SERVICE,
INPUT_METHOD_SERVICE,
TEXT_SERVICES_MANAGER_SERVICE,
APPWIDGET_SERVICE,
+ //@hide: VOICE_INTERACTION_MANAGER_SERVICE,
//@hide: BACKUP_SERVICE,
DROPBOX_SERVICE,
DEVICE_POLICY_SERVICE,
@@ -2132,17 +2181,26 @@ public abstract class Context {
USB_SERVICE,
LAUNCHER_APPS_SERVICE,
//@hide: SERIAL_SERVICE,
+ //@hide: HDMI_CONTROL_SERVICE,
INPUT_SERVICE,
DISPLAY_SERVICE,
- //@hide: SCHEDULING_POLICY_SERVICE,
USER_SERVICE,
- //@hide: APP_OPS_SERVICE
+ RESTRICTIONS_SERVICE,
+ APP_OPS_SERVICE,
CAMERA_SERVICE,
PRINT_SERVICE,
+ CONSUMER_IR_SERVICE,
+ //@hide: TRUST_SERVICE,
+ TV_INPUT_SERVICE,
+ //@hide: NETWORK_SCORE_SERVICE,
+ USAGE_STATS_SERVICE,
MEDIA_SESSION_SERVICE,
BATTERY_SERVICE,
JOB_SCHEDULER_SERVICE,
+ //@hide: PERSISTENT_DATA_BLOCK_SERVICE,
MEDIA_PROJECTION_SERVICE,
+ MIDI_SERVICE,
+ RADIO_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -2262,6 +2320,51 @@ public abstract class Context {
public abstract Object getSystemService(@ServiceName @NonNull String name);
/**
+ * Return the handle to a system-level service by class.
+ * <p>
+ * Currently available classes are:
+ * {@link android.view.WindowManager}, {@link android.view.LayoutInflater},
+ * {@link android.app.ActivityManager}, {@link android.os.PowerManager},
+ * {@link android.app.AlarmManager}, {@link android.app.NotificationManager},
+ * {@link android.app.KeyguardManager}, {@link android.location.LocationManager},
+ * {@link android.app.SearchManager}, {@link android.os.Vibrator},
+ * {@link android.net.ConnectivityManager},
+ * {@link android.net.wifi.WifiManager},
+ * {@link android.media.AudioManager}, {@link android.media.MediaRouter},
+ * {@link android.telephony.TelephonyManager}, {@link android.telephony.SubscriptionManager},
+ * {@link android.view.inputmethod.InputMethodManager},
+ * {@link android.app.UiModeManager}, {@link android.app.DownloadManager},
+ * {@link android.os.BatteryManager}, {@link android.app.job.JobScheduler}.
+ * </p><p>
+ * Note: System services obtained via this API may be closely associated with
+ * the Context in which they are obtained from. In general, do not share the
+ * service objects between various different contexts (Activities, Applications,
+ * Services, Providers, etc.)
+ * </p>
+ *
+ * @param serviceClass The class of the desired service.
+ * @return The service or null if the class is not a supported system service.
+ */
+ @SuppressWarnings("unchecked")
+ public final <T> T getSystemService(Class<T> serviceClass) {
+ // Because subclasses may override getSystemService(String) we cannot
+ // perform a lookup by class alone. We must first map the class to its
+ // service name then invoke the string-based method.
+ String serviceName = getSystemServiceName(serviceClass);
+ return serviceName != null ? (T)getSystemService(serviceName) : null;
+ }
+
+ /**
+ * Gets the name of the system-level service that is represented by the specified class.
+ *
+ * @param serviceClass The class of the desired service.
+ * @return The service name or null if the class is not a supported system service.
+ *
+ * @hide
+ */
+ public abstract String getSystemServiceName(Class<?> serviceClass);
+
+ /**
* Use with {@link #getSystemService} to retrieve a
* {@link android.os.PowerManager} for controlling power management,
* including "wake locks," which let you keep the device on while
@@ -2556,7 +2659,7 @@ public abstract class Context {
* of fingerprints.
*
* @see #getSystemService
- * @see android.app.FingerprintManager
+ * @see android.service.fingerprint.FingerprintManager
* @hide
*/
public static final String FINGERPRINT_SERVICE = "fingerprint";
@@ -2612,11 +2715,11 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.text.ClipboardManager} for accessing and modifying
+ * {@link android.content.ClipboardManager} for accessing and modifying
* the contents of the global clipboard.
*
* @see #getSystemService
- * @see android.text.ClipboardManager
+ * @see android.content.ClipboardManager
*/
public static final String CLIPBOARD_SERVICE = "clipboard";
@@ -2911,11 +3014,31 @@ public abstract class Context {
* android.media.projection.MediaProjectionManager} instance for managing
* media projection sessions.
* @see #getSystemService
- * @see android.media.projection.ProjectionManager
+ * @see android.media.projection.MediaProjectionManager
*/
public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.midi.MidiManager} for accessing the MIDI service.
+ *
+ * @see #getSystemService
+ * @hide
+ */
+ public static final String MIDI_SERVICE = "midi";
+
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.radio.RadioManager} for accessing the broadcast radio service.
+ *
+ * @see #getSystemService
+ * @hide
+ */
+ public static final String RADIO_SERVICE = "radio";
+
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -2931,6 +3054,7 @@ public abstract class Context {
* @see PackageManager#checkPermission(String, String)
* @see #checkCallingPermission
*/
+ @CheckResult(suggest="#enforcePermission(String,int,int,String)")
@PackageManager.PermissionResult
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
@@ -2960,6 +3084,7 @@ public abstract class Context {
* @see #checkPermission
* @see #checkCallingOrSelfPermission
*/
+ @CheckResult(suggest="#enforceCallingPermission(String,String)")
@PackageManager.PermissionResult
public abstract int checkCallingPermission(@NonNull String permission);
@@ -2979,6 +3104,7 @@ public abstract class Context {
* @see #checkPermission
* @see #checkCallingPermission
*/
+ @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
@PackageManager.PermissionResult
public abstract int checkCallingOrSelfPermission(@NonNull String permission);
@@ -3123,6 +3249,7 @@ public abstract class Context {
*
* @see #checkCallingUriPermission
*/
+ @CheckResult(suggest="#enforceUriPermission(Uri,int,int,String)")
public abstract int checkUriPermission(Uri uri, int pid, int uid,
@Intent.AccessUriMode int modeFlags);
@@ -3151,6 +3278,7 @@ public abstract class Context {
*
* @see #checkUriPermission(Uri, int, int, int)
*/
+ @CheckResult(suggest="#enforceCallingUriPermission(Uri,int,String)")
public abstract int checkCallingUriPermission(Uri uri, @Intent.AccessUriMode int modeFlags);
/**
@@ -3170,6 +3298,7 @@ public abstract class Context {
*
* @see #checkCallingUriPermission
*/
+ @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)")
public abstract int checkCallingOrSelfUriPermission(Uri uri,
@Intent.AccessUriMode int modeFlags);
@@ -3195,6 +3324,7 @@ public abstract class Context {
* is allowed to access that uri or holds one of the given permissions, or
* {@link PackageManager#PERMISSION_DENIED} if it is not.
*/
+ @CheckResult(suggest="#enforceUriPermission(Uri,String,String,int,int,int,String)")
public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
@Nullable String writePermission, int pid, int uid,
@Intent.AccessUriMode int modeFlags);
@@ -3338,7 +3468,7 @@ public abstract class Context {
* are not shared, however they share common state (Resources, ClassLoader,
* etc) so the Context instance itself is fairly lightweight.
*
- * <p>Throws {@link PackageManager.NameNotFoundException} if there is no
+ * <p>Throws {@link android.content.pm.PackageManager.NameNotFoundException} if there is no
* application with the given package name.
*
* <p>Throws {@link java.lang.SecurityException} if the Context requested
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index cfae1cf..6e8b7c1 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -284,36 +284,43 @@ public class ContextWrapper extends Context {
}
@Override
+ @Deprecated
public Drawable getWallpaper() {
return mBase.getWallpaper();
}
@Override
+ @Deprecated
public Drawable peekWallpaper() {
return mBase.peekWallpaper();
}
@Override
+ @Deprecated
public int getWallpaperDesiredMinimumWidth() {
return mBase.getWallpaperDesiredMinimumWidth();
}
@Override
+ @Deprecated
public int getWallpaperDesiredMinimumHeight() {
return mBase.getWallpaperDesiredMinimumHeight();
}
@Override
+ @Deprecated
public void setWallpaper(Bitmap bitmap) throws IOException {
mBase.setWallpaper(bitmap);
}
@Override
+ @Deprecated
public void setWallpaper(InputStream data) throws IOException {
mBase.setWallpaper(data);
}
@Override
+ @Deprecated
public void clearWallpaper() throws IOException {
mBase.clearWallpaper();
}
@@ -445,11 +452,13 @@ public class ContextWrapper extends Context {
}
@Override
+ @Deprecated
public void sendStickyBroadcast(Intent intent) {
mBase.sendStickyBroadcast(intent);
}
@Override
+ @Deprecated
public void sendStickyOrderedBroadcast(
Intent intent, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
@@ -460,16 +469,19 @@ public class ContextWrapper extends Context {
}
@Override
+ @Deprecated
public void removeStickyBroadcast(Intent intent) {
mBase.removeStickyBroadcast(intent);
}
@Override
+ @Deprecated
public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
mBase.sendStickyBroadcastAsUser(intent, user);
}
@Override
+ @Deprecated
public void sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
@@ -479,6 +491,7 @@ public class ContextWrapper extends Context {
}
@Override
+ @Deprecated
public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
mBase.removeStickyBroadcastAsUser(intent, user);
}
@@ -563,6 +576,11 @@ public class ContextWrapper extends Context {
}
@Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ return mBase.getSystemServiceName(serviceClass);
+ }
+
+ @Override
public int checkPermission(String permission, int pid, int uid) {
return mBase.checkPermission(permission, pid, uid);
}
@@ -679,6 +697,7 @@ public class ContextWrapper extends Context {
}
/** @hide */
+ @Override
public Context createApplicationContext(ApplicationInfo application,
int flags) throws PackageManager.NameNotFoundException {
return mBase.createApplicationContext(application, flags);
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index f858406..4afe38b 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -16,6 +16,7 @@
package android.content;
+import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
@@ -56,7 +57,8 @@ public interface IContentProvider extends IInterface {
public ContentProviderResult[] applyBatch(String callingPkg,
ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
- public Bundle call(String callingPkg, String method, String arg, Bundle extras)
+ public Bundle call(
+ String callingPkg, String method, @Nullable String arg, @Nullable Bundle extras)
throws RemoteException;
public ICancellationSignal createCancellationSignal() throws RemoteException;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2fe727c..f685475 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -23,6 +23,7 @@ import android.util.ArraySet;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.AnyRes;
import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
@@ -762,11 +763,11 @@ public class Intent implements Parcelable, Cloneable {
* identifier.
*
* @param context The context of the application.
- * @param resourceId The resource idenfitier for the icon.
+ * @param resourceId The resource identifier for the icon.
* @return A new ShortcutIconResource with the specified's context package name
- * and icon resource idenfitier.
+ * and icon resource identifier.``
*/
- public static ShortcutIconResource fromContext(Context context, int resourceId) {
+ public static ShortcutIconResource fromContext(Context context, @AnyRes int resourceId) {
ShortcutIconResource icon = new ShortcutIconResource();
icon.packageName = context.getPackageName();
icon.resourceName = context.getResources().getResourceName(resourceId);
@@ -1238,6 +1239,13 @@ public class Intent implements Parcelable, Cloneable {
= "android.intent.extra.ASSIST_PACKAGE";
/**
+ * An optional field on {@link #ACTION_ASSIST} containing the uid of the current foreground
+ * application package at the time the assist was invoked.
+ */
+ public static final String EXTRA_ASSIST_UID
+ = "android.intent.extra.ASSIST_UID";
+
+ /**
* An optional field on {@link #ACTION_ASSIST} and containing additional contextual
* information supplied by the current foreground app at the time of the assist request.
* This is a {@link Bundle} of additional data.
@@ -1760,6 +1768,7 @@ public class Intent implements Parcelable, Cloneable {
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
+ @SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
/**
@@ -2781,6 +2790,31 @@ public class Intent implements Parcelable, Cloneable {
/** {@hide} */
public static final String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR";
+ /**
+ * Broadcast action: report that a settings element is being restored from backup. The intent
+ * contains three extras: EXTRA_SETTING_NAME is a string naming the restored setting,
+ * EXTRA_SETTING_NEW_VALUE is the value being restored, and EXTRA_SETTING_PREVIOUS_VALUE
+ * is the value of that settings entry prior to the restore operation. All of these values are
+ * represented as strings.
+ *
+ * <p>This broadcast is sent only for settings provider entries known to require special handling
+ * around restore time. These entries are found in the BROADCAST_ON_RESTORE table within
+ * the provider's backup agent implementation.
+ *
+ * @see #EXTRA_SETTING_NAME
+ * @see #EXTRA_SETTING_PREVIOUS_VALUE
+ * @see #EXTRA_SETTING_NEW_VALUE
+ * {@hide}
+ */
+ public static final String ACTION_SETTING_RESTORED = "android.os.action.SETTING_RESTORED";
+
+ /** {@hide} */
+ public static final String EXTRA_SETTING_NAME = "setting_name";
+ /** {@hide} */
+ public static final String EXTRA_SETTING_PREVIOUS_VALUE = "previous_value";
+ /** {@hide} */
+ public static final String EXTRA_SETTING_NEW_VALUE = "new_value";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -2808,14 +2842,12 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
/**
- * @hide
* Categories for activities that can participate in voice interaction.
* An activity that supports this category must be prepared to run with
* no UI shown at all (though in some case it may have a UI shown), and
* rely on {@link android.app.VoiceInteractor} to interact with the user.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
- @SystemApi
public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
/**
* Set if the activity should be considered as an alternative action to
@@ -3254,6 +3286,7 @@ public class Intent implements Parcelable, Cloneable {
/**
* @hide String array of package names.
*/
+ @SystemApi
public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
/**
@@ -5385,6 +5418,16 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Filter extras to only basic types.
+ * @hide
+ */
+ public void removeUnsafeExtras() {
+ if (mExtras != null) {
+ mExtras.filterValues();
+ }
+ }
+
+ /**
* Retrieve any special flags associated with this intent. You will
* normally just set them with {@link #setFlags} and let the system
* take the appropriate action with them.
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 5341ea8..6d79626 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -16,6 +16,7 @@
package android.content;
+import android.annotation.ArrayRes;
import android.os.Parcel;
import android.os.Parcelable;
@@ -277,7 +278,7 @@ public class RestrictionEntry implements Parcelable {
* @param stringArrayResId the resource id for a string array containing the possible values.
* @see #setChoiceValues(String[])
*/
- public void setChoiceValues(Context context, int stringArrayResId) {
+ public void setChoiceValues(Context context, @ArrayRes int stringArrayResId) {
mChoiceValues = context.getResources().getStringArray(stringArrayResId);
}
@@ -307,7 +308,7 @@ public class RestrictionEntry implements Parcelable {
* @param context the application context, used for retrieving the resources.
* @param stringArrayResId the resource id of a string array containing the possible entries.
*/
- public void setChoiceEntries(Context context, int stringArrayResId) {
+ public void setChoiceEntries(Context context, @ArrayRes int stringArrayResId) {
mChoiceEntries = context.getResources().getStringArray(stringArrayResId);
}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index 462f473..1d16516 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -16,6 +16,8 @@
package android.content;
+import android.annotation.Nullable;
+
import java.util.Map;
import java.util.Set;
@@ -76,7 +78,7 @@ public interface SharedPreferences {
* @return Returns a reference to the same Editor object, so you can
* chain put calls together.
*/
- Editor putString(String key, String value);
+ Editor putString(String key, @Nullable String value);
/**
* Set a set of String values in the preferences editor, to be written
@@ -89,7 +91,7 @@ public interface SharedPreferences {
* @return Returns a reference to the same Editor object, so you can
* chain put calls together.
*/
- Editor putStringSet(String key, Set<String> values);
+ Editor putStringSet(String key, @Nullable Set<String> values);
/**
* Set an int value in the preferences editor, to be written back once
@@ -252,7 +254,8 @@ public interface SharedPreferences {
*
* @throws ClassCastException
*/
- String getString(String key, String defValue);
+ @Nullable
+ String getString(String key, @Nullable String defValue);
/**
* Retrieve a set of String values from the preferences.
@@ -270,7 +273,8 @@ public interface SharedPreferences {
*
* @throws ClassCastException
*/
- Set<String> getStringSet(String key, Set<String> defValues);
+ @Nullable
+ Set<String> getStringSet(String key, @Nullable Set<String> defValues);
/**
* Retrieve an int value from the preferences.
diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java
index e9ec5a4..46c2c6e 100644
--- a/core/java/android/content/UndoManager.java
+++ b/core/java/android/content/UndoManager.java
@@ -20,9 +20,9 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelableParcel;
import android.text.TextUtils;
+import android.util.ArrayMap;
import java.util.ArrayList;
-import java.util.HashMap;
/**
* Top-level class for managing and interacting with the global undo state for
@@ -54,7 +54,9 @@ import java.util.HashMap;
* @hide
*/
public class UndoManager {
- private final HashMap<String, UndoOwner> mOwners = new HashMap<String, UndoOwner>();
+ // The common case is a single undo owner (e.g. for a TextView), so default to that capacity.
+ private final ArrayMap<String, UndoOwner> mOwners =
+ new ArrayMap<String, UndoOwner>(1 /* capacity */);
private final ArrayList<UndoState> mUndos = new ArrayList<UndoState>();
private final ArrayList<UndoState> mRedos = new ArrayList<UndoState>();
private int mUpdateCount;
@@ -103,8 +105,7 @@ public class UndoManager {
return owner;
}
- owner = new UndoOwner(tag);
- owner.mManager = this;
+ owner = new UndoOwner(tag, this);
owner.mData = data;
mOwners.put(tag, owner);
return owner;
@@ -114,20 +115,17 @@ public class UndoManager {
// XXX need to figure out how to prune.
if (false) {
mOwners.remove(owner.mTag);
- owner.mManager = null;
}
}
/**
- * Flatten the current undo state into a Parcelable object, which can later be restored
- * with {@link #restoreInstanceState(android.os.Parcelable)}.
+ * Flatten the current undo state into a Parcel object, which can later be restored
+ * with {@link #restoreInstanceState(android.os.Parcel, java.lang.ClassLoader)}.
*/
- public Parcelable saveInstanceState() {
+ public void saveInstanceState(Parcel p) {
if (mUpdateCount > 0) {
throw new IllegalStateException("Can't save state while updating");
}
- ParcelableParcel pp = new ParcelableParcel(getClass().getClassLoader());
- Parcel p = pp.getParcel();
mStateSeq++;
if (mStateSeq <= 0) {
mStateSeq = 0;
@@ -151,7 +149,6 @@ public class UndoManager {
mRedos.get(i).writeToParcel(p);
}
p.writeInt(0);
- return pp;
}
void saveOwner(UndoOwner owner, Parcel out) {
@@ -162,31 +159,30 @@ public class UndoManager {
owner.mSavedIdx = mNextSavedIdx;
out.writeInt(owner.mSavedIdx);
out.writeString(owner.mTag);
+ out.writeInt(owner.mOpCount);
mNextSavedIdx++;
}
}
/**
- * Restore an undo state previously created with {@link #saveInstanceState()}. This will
- * restore the UndoManager's state to almost exactly what it was at the point it had
+ * Restore an undo state previously created with {@link #saveInstanceState(Parcel)}. This
+ * will restore the UndoManager's state to almost exactly what it was at the point it had
* been previously saved; the only information not restored is the data object
* associated with each {@link UndoOwner}, which requires separate calls to
* {@link #getOwner(String, Object)} to re-associate the owner with its data.
*/
- public void restoreInstanceState(Parcelable state) {
+ public void restoreInstanceState(Parcel p, ClassLoader loader) {
if (mUpdateCount > 0) {
throw new IllegalStateException("Can't save state while updating");
}
forgetUndos(null, -1);
forgetRedos(null, -1);
- ParcelableParcel pp = (ParcelableParcel)state;
- Parcel p = pp.getParcel();
mHistorySize = p.readInt();
mStateOwners = new UndoOwner[p.readInt()];
int stype;
while ((stype=p.readInt()) != 0) {
- UndoState ustate = new UndoState(this, p, pp.getClassLoader());
+ UndoState ustate = new UndoState(this, p, loader);
if (stype == 1) {
mUndos.add(0, ustate);
} else {
@@ -200,7 +196,9 @@ public class UndoManager {
UndoOwner owner = mStateOwners[idx];
if (owner == null) {
String tag = in.readString();
- owner = new UndoOwner(tag);
+ int opCount = in.readInt();
+ owner = new UndoOwner(tag, this);
+ owner.mOpCount = opCount;
mStateOwners[idx] = owner;
mOwners.put(tag, owner);
}
@@ -308,12 +306,15 @@ public class UndoManager {
}
int removed = 0;
- for (int i=0; i<mUndos.size() && removed < count; i++) {
+ int i = 0;
+ while (i < mUndos.size() && removed < count) {
UndoState state = mUndos.get(i);
if (count > 0 && matchOwners(state, owners)) {
state.destroy();
mUndos.remove(i);
removed++;
+ } else {
+ i++;
}
}
@@ -326,12 +327,15 @@ public class UndoManager {
}
int removed = 0;
- for (int i=0; i<mRedos.size() && removed < count; i++) {
+ int i = 0;
+ while (i < mRedos.size() && removed < count) {
UndoState state = mRedos.get(i);
if (count > 0 && matchOwners(state, owners)) {
state.destroy();
mRedos.remove(i);
removed++;
+ } else {
+ i++;
}
}
diff --git a/core/java/android/content/UndoOwner.java b/core/java/android/content/UndoOwner.java
index d0cdc95..fd257ab 100644
--- a/core/java/android/content/UndoOwner.java
+++ b/core/java/android/content/UndoOwner.java
@@ -23,8 +23,8 @@ package android.content;
*/
public class UndoOwner {
final String mTag;
+ final UndoManager mManager;
- UndoManager mManager;
Object mData;
int mOpCount;
@@ -32,8 +32,15 @@ public class UndoOwner {
int mStateSeq;
int mSavedIdx;
- UndoOwner(String tag) {
+ UndoOwner(String tag, UndoManager manager) {
+ if (tag == null) {
+ throw new NullPointerException("tag can't be null");
+ }
+ if (manager == null) {
+ throw new NullPointerException("manager can't be null");
+ }
mTag = tag;
+ mManager = manager;
}
/**
@@ -54,4 +61,15 @@ public class UndoOwner {
public Object getData() {
return mData;
}
+
+ @Override
+ public String toString() {
+ return "UndoOwner:[mTag=" + mTag +
+ " mManager=" + mManager +
+ " mData=" + mData +
+ " mData=" + mData +
+ " mOpCount=" + mOpCount +
+ " mStateSeq=" + mStateSeq +
+ " mSavedIdx=" + mSavedIdx + "]";
+ }
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 641f843..4723c0d 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -641,6 +641,13 @@ public class ActivityInfo extends ComponentInfo
*/
public String parentActivityName;
+ /**
+ * Value indicating if the activity is resizeable to any dimension.
+ * See {@link android.R.attr#resizeableActivity}.
+ * @hide
+ */
+ public boolean resizeable;
+
public ActivityInfo() {
}
@@ -702,6 +709,7 @@ public class ActivityInfo extends ComponentInfo
if (uiOptions != 0) {
pw.println(prefix + " uiOptions=0x" + Integer.toHexString(uiOptions));
}
+ pw.println(prefix + "resizeable=" + resizeable);
super.dumpBack(pw, prefix);
}
@@ -730,6 +738,7 @@ public class ActivityInfo extends ComponentInfo
dest.writeString(parentActivityName);
dest.writeInt(persistableMode);
dest.writeInt(maxRecents);
+ dest.writeInt(resizeable ? 1 : 0);
}
public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -757,5 +766,6 @@ public class ActivityInfo extends ComponentInfo
parentActivityName = source.readString();
persistableMode = source.readInt();
maxRecents = source.readInt();
+ resizeable = (source.readInt() == 1);
}
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 05c19db..29befc8 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -610,6 +610,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public int installLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ /**
+ * True when the application's rendering should be hardware accelerated.
+ */
+ public boolean hardwareAccelerated;
+
public void dump(Printer pw, String prefix) {
super.dumpFront(pw, prefix);
if (className != null) {
@@ -649,6 +654,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion
+ " versionCode=" + versionCode);
+ pw.println(prefix + "hardwareAccelerated=" + hardwareAccelerated);
if (manageSpaceActivityName != null) {
pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName);
}
@@ -734,6 +740,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
descriptionRes = orig.descriptionRes;
uiOptions = orig.uiOptions;
backupAgentName = orig.backupAgentName;
+ hardwareAccelerated = orig.hardwareAccelerated;
}
@@ -785,6 +792,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeString(backupAgentName);
dest.writeInt(descriptionRes);
dest.writeInt(uiOptions);
+ dest.writeInt(hardwareAccelerated ? 1 : 0);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -835,6 +843,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
backupAgentName = source.readString();
descriptionRes = source.readInt();
uiOptions = source.readInt();
+ hardwareAccelerated = source.readInt() != 0;
}
/**
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b518498..3e5d362 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -261,9 +261,9 @@ interface IPackageManager {
void clearPackagePersistentPreferredActivities(String packageName, int userId);
void addCrossProfileIntentFilter(in IntentFilter intentFilter, String ownerPackage,
- int ownerUserId, int sourceUserId, int targetUserId, int flags);
+ int sourceUserId, int targetUserId, int flags);
- void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage, int ownerUserId);
+ void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index ee23fcd..87b97aa 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -39,6 +39,7 @@ public class LauncherActivityInfo {
private ActivityInfo mActivityInfo;
private ComponentName mComponentName;
+ private ResolveInfo mResolveInfo;
private UserHandle mUser;
private long mFirstInstallTime;
@@ -52,6 +53,7 @@ public class LauncherActivityInfo {
LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user,
long firstInstallTime) {
this(context);
+ mResolveInfo = info;
mActivityInfo = info.activityInfo;
mComponentName = LauncherApps.getComponentName(info);
mUser = user;
@@ -92,7 +94,7 @@ public class LauncherActivityInfo {
* @return The label for the activity.
*/
public CharSequence getLabel() {
- return mActivityInfo.loadLabel(mPm);
+ return mResolveInfo.loadLabel(mPm);
}
/**
@@ -104,8 +106,22 @@ public class LauncherActivityInfo {
* @return The drawable associated with the activity
*/
public Drawable getIcon(int density) {
- // TODO: Use density
- return mActivityInfo.loadIcon(mPm);
+ int iconRes = mResolveInfo.getIconResource();
+ Resources resources = null;
+ Drawable icon = null;
+ // Get the preferred density icon from the app's resources
+ if (density != 0 && iconRes != 0) {
+ try {
+ resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
+ icon = resources.getDrawableForDensity(iconRes, density);
+ } catch (NameNotFoundException | Resources.NotFoundException exc) {
+ }
+ }
+ // Get the default density icon
+ if (icon == null) {
+ icon = mResolveInfo.loadIcon(mPm);
+ }
+ return icon;
}
/**
@@ -151,23 +167,7 @@ public class LauncherActivityInfo {
* @return A badged icon for the activity.
*/
public Drawable getBadgedIcon(int density) {
- int iconRes = mActivityInfo.getIconResource();
- Resources resources = null;
- Drawable originalIcon = null;
- try {
- resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
- try {
- if (density != 0) {
- originalIcon = resources.getDrawableForDensity(iconRes, density);
- }
- } catch (Resources.NotFoundException e) {
- }
- } catch (NameNotFoundException nnfe) {
- }
-
- if (originalIcon == null) {
- originalIcon = mActivityInfo.loadIcon(mPm);
- }
+ Drawable originalIcon = getIcon(density);
if (originalIcon instanceof BitmapDrawable) {
return mPm.getUserBadgedIcon(originalIcon, mUser);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c164340..c81517a 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -16,7 +16,6 @@
package android.content.pm;
-import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e9f7c50..3da57cb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,11 +16,15 @@
package android.content.pm;
+import android.annotation.CheckResult;
+import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringRes;
import android.annotation.SystemApi;
+import android.annotation.XmlRes;
import android.app.PackageDeleteObserver;
import android.app.PackageInstallObserver;
import android.app.admin.DevicePolicyManager;
@@ -2118,6 +2122,7 @@ public abstract class PackageManager {
* @see #PERMISSION_GRANTED
* @see #PERMISSION_DENIED
*/
+ @CheckResult
public abstract int checkPermission(String permName, String pkgName);
/**
@@ -2245,6 +2250,7 @@ public abstract class PackageManager {
* @see #SIGNATURE_NO_MATCH
* @see #SIGNATURE_UNKNOWN_PACKAGE
*/
+ @CheckResult
public abstract int checkSignatures(String pkg1, String pkg2);
/**
@@ -2267,6 +2273,7 @@ public abstract class PackageManager {
* @see #SIGNATURE_NO_MATCH
* @see #SIGNATURE_UNKNOWN_PACKAGE
*/
+ @CheckResult
public abstract int checkSignatures(int uid1, int uid2);
/**
@@ -2710,7 +2717,7 @@ public abstract class PackageManager {
* @return Returns a Drawable holding the requested image. Returns null if
* an image could not be found for any reason.
*/
- public abstract Drawable getDrawable(String packageName, int resid,
+ public abstract Drawable getDrawable(String packageName, @DrawableRes int resid,
ApplicationInfo appInfo);
/**
@@ -3012,7 +3019,7 @@ public abstract class PackageManager {
* @return Returns a CharSequence holding the requested text. Returns null
* if the text could not be found for any reason.
*/
- public abstract CharSequence getText(String packageName, int resid,
+ public abstract CharSequence getText(String packageName, @StringRes int resid,
ApplicationInfo appInfo);
/**
@@ -3031,7 +3038,7 @@ public abstract class PackageManager {
* data. Returns null if the xml resource could not be found for any
* reason.
*/
- public abstract XmlResourceParser getXml(String packageName, int resid,
+ public abstract XmlResourceParser getXml(String packageName, @XmlRes int resid,
ApplicationInfo appInfo);
/**
@@ -3685,11 +3692,11 @@ public abstract class PackageManager {
* {@link #addPreferredActivity}, that are
* currently registered with the system.
*
- * @param outFilters A list in which to place the filters of all of the
- * preferred activities, or null for none.
- * @param outActivities A list in which to place the component names of
- * all of the preferred activities, or null for none.
- * @param packageName An option package in which you would like to limit
+ * @param outFilters A required list in which to place the filters of all of the
+ * preferred activities.
+ * @param outActivities A required list in which to place the component names of
+ * all of the preferred activities.
+ * @param packageName An optional package in which you would like to limit
* the list. If null, all activities will be returned; if non-null, only
* those activities in the given package are returned.
*
@@ -3697,8 +3704,8 @@ public abstract class PackageManager {
* (the number of distinct IntentFilter records, not the number of unique
* activity components) that were found.
*/
- public abstract int getPreferredActivities(List<IntentFilter> outFilters,
- List<ComponentName> outActivities, String packageName);
+ public abstract int getPreferredActivities(@NonNull List<IntentFilter> outFilters,
+ @NonNull List<ComponentName> outActivities, String packageName);
/**
* Ask for the set of available 'home' activities and the current explicit
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4952ba1..c443ff3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -49,6 +49,7 @@ import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
+import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
@@ -2518,6 +2519,7 @@ public class PackageParser {
owner.baseHardwareAccelerated = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
+ ai.hardwareAccelerated = owner.baseHardwareAccelerated;
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_hasCode,
@@ -2766,9 +2768,17 @@ public class PackageParser {
}
}
+ addSharedLibrariesForBackwardCompatibility(owner);
+
return true;
}
+ private static void addSharedLibrariesForBackwardCompatibility(Package owner) {
+ if (owner.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, "org.apache.http.legacy");
+ }
+ }
+
/**
* Parse the {@code application} XML tree at the current parse location in a
* <em>split APK</em> manifest.
@@ -2953,20 +2963,19 @@ public class PackageParser {
XmlPullParser parser, AttributeSet attrs, int flags, String[] outError,
boolean receiver, boolean hardwareAccelerated)
throws XmlPullParserException, IOException {
- TypedArray sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AndroidManifestActivity);
+ TypedArray sa = res.obtainAttributes(attrs, R.styleable.AndroidManifestActivity);
if (mParseActivityArgs == null) {
mParseActivityArgs = new ParseComponentArgs(owner, outError,
- com.android.internal.R.styleable.AndroidManifestActivity_name,
- com.android.internal.R.styleable.AndroidManifestActivity_label,
- com.android.internal.R.styleable.AndroidManifestActivity_icon,
- com.android.internal.R.styleable.AndroidManifestActivity_logo,
- com.android.internal.R.styleable.AndroidManifestActivity_banner,
+ R.styleable.AndroidManifestActivity_name,
+ R.styleable.AndroidManifestActivity_label,
+ R.styleable.AndroidManifestActivity_icon,
+ R.styleable.AndroidManifestActivity_logo,
+ R.styleable.AndroidManifestActivity_banner,
mSeparateProcesses,
- com.android.internal.R.styleable.AndroidManifestActivity_process,
- com.android.internal.R.styleable.AndroidManifestActivity_description,
- com.android.internal.R.styleable.AndroidManifestActivity_enabled);
+ R.styleable.AndroidManifestActivity_process,
+ R.styleable.AndroidManifestActivity_description,
+ R.styleable.AndroidManifestActivity_enabled);
}
mParseActivityArgs.tag = receiver ? "<receiver>" : "<activity>";
@@ -2979,22 +2988,18 @@ public class PackageParser {
return null;
}
- boolean setExported = sa.hasValue(
- com.android.internal.R.styleable.AndroidManifestActivity_exported);
+ boolean setExported = sa.hasValue(R.styleable.AndroidManifestActivity_exported);
if (setExported) {
- a.info.exported = sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_exported, false);
+ a.info.exported = sa.getBoolean(R.styleable.AndroidManifestActivity_exported, false);
}
- a.info.theme = sa.getResourceId(
- com.android.internal.R.styleable.AndroidManifestActivity_theme, 0);
+ a.info.theme = sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0);
- a.info.uiOptions = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_uiOptions,
+ a.info.uiOptions = sa.getInt(R.styleable.AndroidManifestActivity_uiOptions,
a.info.applicationInfo.uiOptions);
String parentName = sa.getNonConfigurationString(
- com.android.internal.R.styleable.AndroidManifestActivity_parentActivityName,
+ R.styleable.AndroidManifestActivity_parentActivityName,
Configuration.NATIVE_CONFIG_VERSION);
if (parentName != null) {
String parentClassName = buildClassName(a.info.packageName, parentName, outError);
@@ -3008,8 +3013,7 @@ public class PackageParser {
}
String str;
- str = sa.getNonConfigurationString(
- com.android.internal.R.styleable.AndroidManifestActivity_permission, 0);
+ str = sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_permission, 0);
if (str == null) {
a.info.permission = owner.applicationInfo.permission;
} else {
@@ -3017,140 +3021,116 @@ public class PackageParser {
}
str = sa.getNonConfigurationString(
- com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity,
+ R.styleable.AndroidManifestActivity_taskAffinity,
Configuration.NATIVE_CONFIG_VERSION);
a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
owner.applicationInfo.taskAffinity, str, outError);
a.info.flags = 0;
if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_multiprocess,
- false)) {
+ R.styleable.AndroidManifestActivity_multiprocess, false)) {
a.info.flags |= ActivityInfo.FLAG_MULTIPROCESS;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_finishOnTaskLaunch,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnTaskLaunch, false)) {
a.info.flags |= ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_clearTaskOnLaunch,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_clearTaskOnLaunch, false)) {
a.info.flags |= ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_noHistory,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_noHistory, false)) {
a.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_alwaysRetainTaskState,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysRetainTaskState, false)) {
a.info.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_stateNotNeeded,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_stateNotNeeded, false)) {
a.info.flags |= ActivityInfo.FLAG_STATE_NOT_NEEDED;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_excludeFromRecents,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_excludeFromRecents, false)) {
a.info.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_allowTaskReparenting,
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowTaskReparenting,
(owner.applicationInfo.flags&ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING) != 0)) {
a.info.flags |= ActivityInfo.FLAG_ALLOW_TASK_REPARENTING;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, false)) {
a.info.flags |= ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_showOnLockScreen,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_showOnLockScreen, false)) {
a.info.flags |= ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_immersive,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_immersive, false)) {
a.info.flags |= ActivityInfo.FLAG_IMMERSIVE;
}
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_primaryUserOnly, false)) {
+ a.info.flags |= ActivityInfo.FLAG_PRIMARY_USER_ONLY;
+ }
+
if (!receiver) {
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_hardwareAccelerated,
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_hardwareAccelerated,
hardwareAccelerated)) {
a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED;
}
a.info.launchMode = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_launchMode,
- ActivityInfo.LAUNCH_MULTIPLE);
+ R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE);
a.info.documentLaunchMode = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_documentLaunchMode,
+ R.styleable.AndroidManifestActivity_documentLaunchMode,
ActivityInfo.DOCUMENT_LAUNCH_NONE);
a.info.maxRecents = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_maxRecents,
+ R.styleable.AndroidManifestActivity_maxRecents,
ActivityManager.getDefaultAppRecentsLimitStatic());
- a.info.screenOrientation = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_screenOrientation,
- ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- a.info.configChanges = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_configChanges,
- 0);
+ a.info.configChanges = sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0);
a.info.softInputMode = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode,
- 0);
+ R.styleable.AndroidManifestActivity_windowSoftInputMode, 0);
a.info.persistableMode = sa.getInteger(
- com.android.internal.R.styleable.AndroidManifestActivity_persistableMode,
+ R.styleable.AndroidManifestActivity_persistableMode,
ActivityInfo.PERSIST_ROOT_ONLY);
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowEmbedded, false)) {
a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_autoRemoveFromRecents,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_autoRemoveFromRecents, false)) {
a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_relinquishTaskIdentity, false)) {
a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_resumeWhilePausing,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_resumeWhilePausing, false)) {
a.info.flags |= ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
}
+
+ a.info.resizeable = sa.getBoolean(
+ R.styleable.AndroidManifestActivity_resizeableActivity,
+ owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.MNC);
+ if (a.info.resizeable) {
+ // Fixed screen orientation isn't supported with resizeable activities.
+ a.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ } else {
+ a.info.screenOrientation = sa.getInt(
+ R.styleable.AndroidManifestActivity_screenOrientation,
+ ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
} else {
a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
a.info.configChanges = 0;
- }
- if (receiver) {
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_singleUser,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_singleUser, false)) {
a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Activity exported request ignored due to singleUser: "
@@ -3160,11 +3140,6 @@ public class PackageParser {
setExported = true;
}
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly,
- false)) {
- a.info.flags |= ActivityInfo.FLAG_PRIMARY_USER_ONLY;
- }
}
sa.recycle();
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 391ef22..c2d2f65 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -27,6 +27,7 @@ import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Handler;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.Log;
@@ -35,6 +36,7 @@ import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.google.android.collect.Lists;
@@ -46,9 +48,9 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
@@ -56,6 +58,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
+import libcore.io.IoUtils;
+
/**
* Cache of registered services. This cache is lazily built by interrogating
* {@link PackageManager} on a per-user basis. It's updated as packages are
@@ -71,6 +75,7 @@ import java.util.Map;
public abstract class RegisteredServicesCache<V> {
private static final String TAG = "PackageManager";
private static final boolean DEBUG = false;
+ protected static final String REGISTERED_SERVICES_DIR = "registered_services";
public final Context mContext;
private final String mInterfaceName;
@@ -81,32 +86,54 @@ public abstract class RegisteredServicesCache<V> {
private final Object mServicesLock = new Object();
@GuardedBy("mServicesLock")
- private boolean mPersistentServicesFileDidNotExist;
- @GuardedBy("mServicesLock")
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
private static class UserServices<V> {
@GuardedBy("mServicesLock")
- public final Map<V, Integer> persistentServices = Maps.newHashMap();
+ final Map<V, Integer> persistentServices = Maps.newHashMap();
+ @GuardedBy("mServicesLock")
+ Map<V, ServiceInfo<V>> services = null;
@GuardedBy("mServicesLock")
- public Map<V, ServiceInfo<V>> services = null;
+ boolean mPersistentServicesFileDidNotExist = true;
}
+ @GuardedBy("mServicesLock")
private UserServices<V> findOrCreateUserLocked(int userId) {
+ return findOrCreateUserLocked(userId, true);
+ }
+
+ @GuardedBy("mServicesLock")
+ private UserServices<V> findOrCreateUserLocked(int userId, boolean loadFromFileIfNew) {
UserServices<V> services = mUserServices.get(userId);
if (services == null) {
services = new UserServices<V>();
mUserServices.put(userId, services);
+ if (loadFromFileIfNew && mSerializerAndParser != null) {
+ // Check if user exists and try loading data from file
+ // clear existing data if there was an error during migration
+ UserInfo user = getUser(userId);
+ if (user != null) {
+ AtomicFile file = createFileForUser(user.id);
+ if (file.getBaseFile().exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, String.format("Loading u%s data from %s", user.id, file));
+ }
+ InputStream is = null;
+ try {
+ is = file.openRead();
+ readPersistentServicesLocked(is);
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading persistent services for user " + user.id, e);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+ }
+ }
}
return services;
}
- /**
- * This file contains the list of known services. We would like to maintain this forever
- * so we store it as an XML file.
- */
- private final AtomicFile mPersistentServicesFile;
-
// the listener and handler are synchronized on "this" and must be updated together
private RegisteredServicesCacheListener<V> mListener;
private Handler mHandler;
@@ -119,13 +146,7 @@ public abstract class RegisteredServicesCache<V> {
mAttributesName = attributeName;
mSerializerAndParser = serializerAndParser;
- File dataDir = Environment.getDataDirectory();
- File systemDir = new File(dataDir, "system");
- File syncDir = new File(systemDir, "registered_services");
- mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml"));
-
- // Load persisted services from disk
- readPersistentServicesLocked();
+ migrateIfNecessaryLocked();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -139,6 +160,11 @@ public abstract class RegisteredServicesCache<V> {
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mExternalReceiver, sdFilter);
+
+ // Register for user-related events
+ IntentFilter userFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(mUserRemovedReceiver, userFilter);
}
private final void handlePackageEvent(Intent intent, int userId) {
@@ -190,6 +216,17 @@ public abstract class RegisteredServicesCache<V> {
}
};
+ private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (DEBUG) {
+ Slog.d(TAG, "u" + userId + " removed - cleaning up");
+ }
+ onUserRemoved(userId);
+ }
+ };
+
public void invalidateCache(int userId) {
synchronized (mServicesLock) {
final UserServices<V> user = findOrCreateUserLocked(userId);
@@ -303,7 +340,8 @@ public abstract class RegisteredServicesCache<V> {
}
}
- private boolean inSystemImage(int callerUid) {
+ @VisibleForTesting
+ protected boolean inSystemImage(int callerUid) {
String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
for (String name : packages) {
try {
@@ -319,6 +357,13 @@ public abstract class RegisteredServicesCache<V> {
return false;
}
+ @VisibleForTesting
+ protected List<ResolveInfo> queryIntentServices(int userId) {
+ final PackageManager pm = mContext.getPackageManager();
+ return pm.queryIntentServicesAsUser(
+ new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
+ }
+
/**
* Populate {@link UserServices#services} by scanning installed packages for
* given {@link UserHandle}.
@@ -331,10 +376,8 @@ public abstract class RegisteredServicesCache<V> {
Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " + changedUids);
}
- final PackageManager pm = mContext.getPackageManager();
final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
- final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(
- new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
+ final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
@@ -343,9 +386,7 @@ public abstract class RegisteredServicesCache<V> {
continue;
}
serviceInfos.add(info);
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
- } catch (IOException e) {
+ } catch (XmlPullParserException|IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
@@ -377,7 +418,7 @@ public abstract class RegisteredServicesCache<V> {
changed = true;
user.services.put(info.type, info);
user.persistentServices.put(info.type, info.uid);
- if (!(mPersistentServicesFileDidNotExist && firstScan)) {
+ if (!(user.mPersistentServicesFileDidNotExist && firstScan)) {
notifyListener(info.type, userId, false /* removed */);
}
} else if (previousUid == info.uid) {
@@ -447,7 +488,7 @@ public abstract class RegisteredServicesCache<V> {
}
}
if (changed) {
- writePersistentServicesLocked();
+ writePersistentServicesLocked(user, userId);
}
}
}
@@ -481,7 +522,8 @@ public abstract class RegisteredServicesCache<V> {
return false;
}
- private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
+ @VisibleForTesting
+ protected ServiceInfo<V> parseServiceInfo(ResolveInfo service)
throws XmlPullParserException, IOException {
android.content.pm.ServiceInfo si = service.serviceInfo;
ComponentName componentName = new ComponentName(si.packageName, si.name);
@@ -528,93 +570,156 @@ public abstract class RegisteredServicesCache<V> {
/**
* Read all sync status back in to the initial engine state.
*/
- private void readPersistentServicesLocked() {
- mUserServices.clear();
+ private void readPersistentServicesLocked(InputStream is)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(is, null);
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG
+ && eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ String tagName = parser.getName();
+ if ("services".equals(tagName)) {
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
+ tagName = parser.getName();
+ if ("service".equals(tagName)) {
+ V service = mSerializerAndParser.createFromXml(parser);
+ if (service == null) {
+ break;
+ }
+ String uidString = parser.getAttributeValue(null, "uid");
+ final int uid = Integer.parseInt(uidString);
+ final int userId = UserHandle.getUserId(uid);
+ final UserServices<V> user = findOrCreateUserLocked(userId,
+ false /*loadFromFileIfNew*/) ;
+ user.persistentServices.put(service, uid);
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+ }
+
+ private void migrateIfNecessaryLocked() {
if (mSerializerAndParser == null) {
return;
}
- FileInputStream fis = null;
- try {
- mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists();
- if (mPersistentServicesFileDidNotExist) {
- return;
- }
- fis = mPersistentServicesFile.openRead();
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, null);
- int eventType = parser.getEventType();
- while (eventType != XmlPullParser.START_TAG
- && eventType != XmlPullParser.END_DOCUMENT) {
- eventType = parser.next();
- }
- String tagName = parser.getName();
- if ("services".equals(tagName)) {
- eventType = parser.next();
- do {
- if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
- tagName = parser.getName();
- if ("service".equals(tagName)) {
- V service = mSerializerAndParser.createFromXml(parser);
- if (service == null) {
- break;
+ File systemDir = new File(getDataDirectory(), "system");
+ File syncDir = new File(systemDir, REGISTERED_SERVICES_DIR);
+ AtomicFile oldFile = new AtomicFile(new File(syncDir, mInterfaceName + ".xml"));
+ boolean oldFileExists = oldFile.getBaseFile().exists();
+
+ if (oldFileExists) {
+ File marker = new File(syncDir, mInterfaceName + ".xml.migrated");
+ // if not migrated, perform the migration and add a marker
+ if (!marker.exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Marker file " + marker + " does not exist - running migration");
+ }
+ InputStream is = null;
+ try {
+ is = oldFile.openRead();
+ mUserServices.clear();
+ readPersistentServicesLocked(is);
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading persistent services, starting from scratch", e);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ try {
+ for (UserInfo user : getUsers()) {
+ UserServices<V> userServices = mUserServices.get(user.id);
+ if (userServices != null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Migrating u" + user.id + " services "
+ + userServices.persistentServices);
}
- String uidString = parser.getAttributeValue(null, "uid");
- final int uid = Integer.parseInt(uidString);
- final int userId = UserHandle.getUserId(uid);
- final UserServices<V> user = findOrCreateUserLocked(userId);
- user.persistentServices.put(service, uid);
+ writePersistentServicesLocked(userServices, user.id);
}
}
- eventType = parser.next();
- } while (eventType != XmlPullParser.END_DOCUMENT);
- }
- } catch (Exception e) {
- Log.w(TAG, "Error reading persistent services, starting from scratch", e);
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (java.io.IOException e1) {
+ marker.createNewFile();
+ } catch (Exception e) {
+ Log.w(TAG, "Migration failed", e);
}
+ // Migration is complete and we don't need to keep data for all users anymore,
+ // It will be loaded from a new location when requested
+ mUserServices.clear();
}
}
}
/**
- * Write all sync status to the sync status file.
+ * Writes services of a specified user to the file.
*/
- private void writePersistentServicesLocked() {
+ private void writePersistentServicesLocked(UserServices<V> user, int userId) {
if (mSerializerAndParser == null) {
return;
}
+ AtomicFile atomicFile = createFileForUser(userId);
FileOutputStream fos = null;
try {
- fos = mPersistentServicesFile.startWrite();
+ fos = atomicFile.startWrite();
XmlSerializer out = new FastXmlSerializer();
out.setOutput(fos, "utf-8");
out.startDocument(null, true);
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
out.startTag(null, "services");
- for (int i = 0; i < mUserServices.size(); i++) {
- final UserServices<V> user = mUserServices.valueAt(i);
- for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
- out.startTag(null, "service");
- out.attribute(null, "uid", Integer.toString(service.getValue()));
- mSerializerAndParser.writeAsXml(service.getKey(), out);
- out.endTag(null, "service");
- }
+ for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
+ out.startTag(null, "service");
+ out.attribute(null, "uid", Integer.toString(service.getValue()));
+ mSerializerAndParser.writeAsXml(service.getKey(), out);
+ out.endTag(null, "service");
}
out.endTag(null, "services");
out.endDocument();
- mPersistentServicesFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
+ atomicFile.finishWrite(fos);
+ } catch (IOException e1) {
Log.w(TAG, "Error writing accounts", e1);
if (fos != null) {
- mPersistentServicesFile.failWrite(fos);
+ atomicFile.failWrite(fos);
}
}
}
+ @VisibleForTesting
+ protected void onUserRemoved(int userId) {
+ mUserServices.remove(userId);
+ }
+
+ @VisibleForTesting
+ protected List<UserInfo> getUsers() {
+ return UserManager.get(mContext).getUsers(true);
+ }
+
+ @VisibleForTesting
+ protected UserInfo getUser(int userId) {
+ return UserManager.get(mContext).getUserInfo(userId);
+ }
+
+ private AtomicFile createFileForUser(int userId) {
+ File userDir = getUserSystemDirectory(userId);
+ File userFile = new File(userDir, REGISTERED_SERVICES_DIR + "/" + mInterfaceName + ".xml");
+ return new AtomicFile(userFile);
+ }
+
+ @VisibleForTesting
+ protected File getUserSystemDirectory(int userId) {
+ return Environment.getUserSystemDirectory(userId);
+ }
+
+ @VisibleForTesting
+ protected File getDataDirectory() {
+ return Environment.getDataDirectory();
+ }
+
+ @VisibleForTesting
+ protected Map<V, Integer> getPersistentServices(int userId) {
+ return findOrCreateUserLocked(userId).persistentServices;
+ }
+
public abstract V parseServiceAttributes(Resources res,
String packageName, AttributeSet attrs);
}
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 68a39d3..841b09d 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -16,8 +16,13 @@
package android.content.res;
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources.Theme;
import android.graphics.Color;
+import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -25,6 +30,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.MathUtils;
import android.util.SparseArray;
import android.util.StateSet;
@@ -64,71 +70,128 @@ import java.util.Arrays;
* List Resource</a>.</p>
*/
public class ColorStateList implements Parcelable {
- private int[][] mStateSpecs; // must be parallel to mColors
- private int[] mColors; // must be parallel to mStateSpecs
- private int mDefaultColor = 0xffff0000;
-
+ private static final String TAG = "ColorStateList";
+ private static final int DEFAULT_COLOR = Color.RED;
private static final int[][] EMPTY = new int[][] { new int[0] };
private static final SparseArray<WeakReference<ColorStateList>> sCache =
new SparseArray<WeakReference<ColorStateList>>();
- private ColorStateList() { }
+ private int[][] mThemeAttrs;
+ private int mChangingConfigurations;
+
+ private int[][] mStateSpecs;
+ private int[] mColors;
+ private int mDefaultColor;
+ private boolean mIsOpaque;
+
+ private ColorStateList() {
+ // Not publicly instantiable.
+ }
/**
* Creates a ColorStateList that returns the specified mapping from
* states to colors.
*/
- public ColorStateList(int[][] states, int[] colors) {
+ public ColorStateList(int[][] states, @ColorInt int[] colors) {
mStateSpecs = states;
mColors = colors;
- if (states.length > 0) {
- mDefaultColor = colors[0];
-
- for (int i = 0; i < states.length; i++) {
- if (states[i].length == 0) {
- mDefaultColor = colors[i];
- }
- }
- }
+ onColorsChanged();
}
/**
- * Creates or retrieves a ColorStateList that always returns a single color.
+ * @return A ColorStateList containing a single color.
*/
- public static ColorStateList valueOf(int color) {
- // TODO: should we collect these eventually?
+ @NonNull
+ public static ColorStateList valueOf(@ColorInt int color) {
synchronized (sCache) {
- final WeakReference<ColorStateList> ref = sCache.get(color);
+ final int index = sCache.indexOfKey(color);
+ if (index >= 0) {
+ final ColorStateList cached = sCache.valueAt(index).get();
+ if (cached != null) {
+ return cached;
+ }
+
+ // Prune missing entry.
+ sCache.removeAt(index);
+ }
- ColorStateList csl = ref != null ? ref.get() : null;
- if (csl != null) {
- return csl;
+ // Prune the cache before adding new items.
+ final int N = sCache.size();
+ for (int i = N - 1; i >= 0; i--) {
+ if (sCache.valueAt(i).get() == null) {
+ sCache.removeAt(i);
+ }
}
- csl = new ColorStateList(EMPTY, new int[] { color });
+ final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color });
sCache.put(color, new WeakReference<ColorStateList>(csl));
return csl;
}
}
/**
- * Create a ColorStateList from an XML document, given a set of {@link Resources}.
+ * Creates a ColorStateList with the same properties as another
+ * ColorStateList.
+ * <p>
+ * The properties of the new ColorStateList can be modified without
+ * affecting the source ColorStateList.
+ *
+ * @param orig the source color state list
*/
+ private ColorStateList(ColorStateList orig) {
+ if (orig != null) {
+ mStateSpecs = orig.mStateSpecs;
+ mDefaultColor = orig.mDefaultColor;
+ mIsOpaque = orig.mIsOpaque;
+
+ // Deep copy, this may change due to theming.
+ mColors = orig.mColors.clone();
+ }
+ }
+
+ /**
+ * Creates a ColorStateList from an XML document.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @return A new color state list.
+ *
+ * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme)
+ */
+ @NonNull
+ @Deprecated
public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
throws XmlPullParserException, IOException {
+ return createFromXml(r, parser, null);
+ }
+
+ /**
+ * Creates a ColorStateList from an XML document using given a set of
+ * {@link Resources} and a {@link Theme}.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @param theme Optional theme to apply to the color state list, may be
+ * {@code null}.
+ * @return A new color state list.
+ */
+ @NonNull
+ public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @Nullable Theme theme) throws XmlPullParserException, IOException {
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
- while ((type=parser.next()) != XmlPullParser.START_TAG
+ while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
- return createFromXmlInner(r, parser, attrs);
+ return createFromXmlInner(r, parser, attrs, theme);
}
/**
@@ -136,28 +199,31 @@ public class ColorStateList implements Parcelable {
* tag in an XML document, tries to create a ColorStateList from that tag.
*
* @throws XmlPullParserException if the current tag is not &lt;selector>
- * @return A color state list for the current tag.
+ * @return A new color state list for the current tag.
*/
- private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
- AttributeSet attrs) throws XmlPullParserException, IOException {
- final ColorStateList colorStateList;
+ @NonNull
+ private static ColorStateList createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
final String name = parser.getName();
- if (name.equals("selector")) {
- colorStateList = new ColorStateList();
- } else {
+ if (!name.equals("selector")) {
throw new XmlPullParserException(
- parser.getPositionDescription() + ": invalid drawable tag " + name);
+ parser.getPositionDescription() + ": invalid color state list tag " + name);
}
- colorStateList.inflate(r, parser, attrs);
+ final ColorStateList colorStateList = new ColorStateList();
+ colorStateList.inflate(r, parser, attrs, theme);
return colorStateList;
}
/**
- * Creates a new ColorStateList that has the same states and
- * colors as this one but where each color has the specified alpha value
- * (0-255).
+ * Creates a new ColorStateList that has the same states and colors as this
+ * one but where each color has the specified alpha value (0-255).
+ *
+ * @param alpha The new alpha channel value (0-255).
+ * @return A new color state list.
*/
+ @NonNull
public ColorStateList withAlpha(int alpha) {
final int[] colors = new int[mColors.length];
final int len = colors.length;
@@ -171,88 +237,154 @@ public class ColorStateList implements Parcelable {
/**
* Fill in this object based on the contents of an XML "selector" element.
*/
- private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
+ private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
- int type;
-
final int innerDepth = parser.getDepth()+1;
int depth;
+ int type;
+
+ int changingConfigurations = 0;
+ int defaultColor = DEFAULT_COLOR;
+
+ boolean hasUnresolvedAttrs = false;
int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
+ int[][] themeAttrsList = new int[stateSpecList.length][];
int[] colorList = new int[stateSpecList.length];
int listSize = 0;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && ((depth=parser.getDepth()) >= innerDepth
- || type != XmlPullParser.END_TAG)) {
- if (type != XmlPullParser.START_TAG) {
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG || depth > innerDepth
+ || !parser.getName().equals("item")) {
continue;
}
- if (depth > innerDepth || !parser.getName().equals("item")) {
- continue;
- }
+ final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
+ R.styleable.ColorStateListItem);
+ final int[] themeAttrs = a.extractThemeAttrs();
+ final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, 0);
+ final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f);
- int alphaRes = 0;
- float alpha = 1.0f;
- int colorRes = 0;
- int color = 0xffff0000;
- boolean haveColor = false;
+ changingConfigurations |= a.getChangingConfigurations();
- int i;
+ a.recycle();
+
+ // Parse all unrecognized attributes as state specifiers.
int j = 0;
final int numAttrs = attrs.getAttributeCount();
int[] stateSpec = new int[numAttrs];
- for (i = 0; i < numAttrs; i++) {
+ for (int i = 0; i < numAttrs; i++) {
final int stateResId = attrs.getAttributeNameResource(i);
- if (stateResId == 0) break;
- if (stateResId == com.android.internal.R.attr.alpha) {
- alphaRes = attrs.getAttributeResourceValue(i, 0);
- if (alphaRes == 0) {
- alpha = attrs.getAttributeFloatValue(i, 1.0f);
- }
- } else if (stateResId == com.android.internal.R.attr.color) {
- colorRes = attrs.getAttributeResourceValue(i, 0);
- if (colorRes == 0) {
- color = attrs.getAttributeIntValue(i, color);
- haveColor = true;
- }
- } else {
- stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
- ? stateResId : -stateResId;
+ switch (stateResId) {
+ case R.attr.color:
+ case R.attr.alpha:
+ // Recognized attribute, ignore.
+ break;
+ default:
+ stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+ ? stateResId : -stateResId;
}
}
stateSpec = StateSet.trimStateSet(stateSpec, j);
- if (colorRes != 0) {
- color = r.getColor(colorRes);
- } else if (!haveColor) {
- throw new XmlPullParserException(
- parser.getPositionDescription()
- + ": <item> tag requires a 'android:color' attribute.");
- }
-
- if (alphaRes != 0) {
- alpha = r.getFloat(alphaRes);
- }
-
// Apply alpha modulation.
- final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255);
- color = (color & 0xFFFFFF) | (alphaMod << 24);
-
+ final int color = modulateColorAlpha(baseColor, alphaMod);
if (listSize == 0 || stateSpec.length == 0) {
- mDefaultColor = color;
+ defaultColor = color;
+ }
+
+ if (themeAttrs != null) {
+ hasUnresolvedAttrs = true;
}
colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
listSize++;
}
+ mChangingConfigurations = changingConfigurations;
+ mDefaultColor = defaultColor;
+
+ if (hasUnresolvedAttrs) {
+ mThemeAttrs = new int[listSize][];
+ System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize);
+ } else {
+ mThemeAttrs = null;
+ }
+
mColors = new int[listSize];
mStateSpecs = new int[listSize][];
System.arraycopy(colorList, 0, mColors, 0, listSize);
System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
+
+ onColorsChanged();
+ }
+
+ /**
+ * Returns whether a theme can be applied to this color state list, which
+ * usually indicates that the color state list has unresolved theme
+ * attributes.
+ *
+ * @return whether a theme can be applied to this color state list
+ */
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null;
+ }
+
+ /**
+ * Applies a theme to this color state list.
+ *
+ * @param t the theme to apply
+ */
+ public void applyTheme(Theme t) {
+ if (mThemeAttrs == null) {
+ return;
+ }
+
+ boolean hasUnresolvedAttrs = false;
+
+ final int[][] themeAttrsList = mThemeAttrs;
+ final int N = themeAttrsList.length;
+ for (int i = 0; i < N; i++) {
+ if (themeAttrsList[i] != null) {
+ final TypedArray a = t.resolveAttributes(themeAttrsList[i],
+ R.styleable.ColorStateListItem);
+ final int baseColor = a.getColor(
+ R.styleable.ColorStateListItem_color, mColors[i]);
+ final float alphaMod = a.getFloat(
+ R.styleable.ColorStateListItem_alpha, 1.0f);
+
+ mColors[i] = modulateColorAlpha(baseColor, alphaMod);
+ mChangingConfigurations |= a.getChangingConfigurations();
+
+ themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]);
+ if (themeAttrsList[i] != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ a.recycle();
+ }
+ }
+
+ if (!hasUnresolvedAttrs) {
+ mThemeAttrs = null;
+ }
+
+ onColorsChanged();
+ }
+
+ private int modulateColorAlpha(int baseColor, float alphaMod) {
+ if (alphaMod == 1.0f) {
+ return baseColor;
+ }
+
+ final int baseAlpha = Color.alpha(baseColor);
+ final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255);
+ final int color = (baseColor & 0xFFFFFF) | (alpha << 24);
+ return color;
}
/**
@@ -275,28 +407,24 @@ public class ColorStateList implements Parcelable {
* @return True if this color state list is opaque.
*/
public boolean isOpaque() {
- final int n = mColors.length;
- for (int i = 0; i < n; i++) {
- if (Color.alpha(mColors[i]) != 0xFF) {
- return false;
- }
- }
- return true;
+ return mIsOpaque;
}
/**
- * Return the color associated with the given set of {@link android.view.View} states.
+ * Return the color associated with the given set of
+ * {@link android.view.View} states.
*
* @param stateSet an array of {@link android.view.View} states
- * @param defaultColor the color to return if there's not state spec in this
- * {@link ColorStateList} that matches the stateSet.
+ * @param defaultColor the color to return if there's no matching state
+ * spec in this {@link ColorStateList} that matches the
+ * stateSet.
*
* @return the color associated with that set of states in this {@link ColorStateList}.
*/
- public int getColorForState(int[] stateSet, int defaultColor) {
+ public int getColorForState(@Nullable int[] stateSet, int defaultColor) {
final int setLength = mStateSpecs.length;
for (int i = 0; i < setLength; i++) {
- int[] stateSpec = mStateSpecs[i];
+ final int[] stateSpec = mStateSpecs[i];
if (StateSet.stateSetMatches(stateSpec, stateSet)) {
return mColors[i];
}
@@ -309,12 +437,15 @@ public class ColorStateList implements Parcelable {
*
* @return the default color in this {@link ColorStateList}.
*/
+ @ColorInt
public int getDefaultColor() {
return mDefaultColor;
}
/**
- * Return the states in this {@link ColorStateList}.
+ * Return the states in this {@link ColorStateList}. The returned array
+ * should not be modified.
+ *
* @return the states in this {@link ColorStateList}
* @hide
*/
@@ -323,7 +454,9 @@ public class ColorStateList implements Parcelable {
}
/**
- * Return the colors in this {@link ColorStateList}.
+ * Return the colors in this {@link ColorStateList}. The returned array
+ * should not be modified.
+ *
* @return the colors in this {@link ColorStateList}
* @hide
*/
@@ -331,52 +464,82 @@ public class ColorStateList implements Parcelable {
return mColors;
}
+ @Override
+ public String toString() {
+ return "ColorStateList{" +
+ "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) +
+ "mChangingConfigurations=" + mChangingConfigurations +
+ "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
+ "mColors=" + Arrays.toString(mColors) +
+ "mDefaultColor=" + mDefaultColor + '}';
+ }
+
/**
- * If the color state list does not already have an entry matching the
- * specified state, prepends a state set and color pair to a color state
- * list.
- * <p>
- * This is a workaround used in TimePicker and DatePicker until we can
- * add support for theme attributes in ColorStateList.
- *
- * @param colorStateList the source color state list
- * @param state the state to prepend
- * @param color the color to use for the given state
- * @return a new color state list, or the source color state list if there
- * was already a matching state set
- *
- * @hide Remove when we can support theme attributes.
+ * Updates the default color and opacity.
*/
- public static ColorStateList addFirstIfMissing(
- ColorStateList colorStateList, int state, int color) {
- final int[][] inputStates = colorStateList.getStates();
- for (int i = 0; i < inputStates.length; i++) {
- final int[] inputState = inputStates[i];
- for (int j = 0; j < inputState.length; j++) {
- if (inputState[j] == state) {
- return colorStateList;
+ private void onColorsChanged() {
+ int defaultColor = DEFAULT_COLOR;
+ boolean isOpaque = true;
+
+ final int[][] states = mStateSpecs;
+ final int[] colors = mColors;
+ final int N = states.length;
+ if (N > 0) {
+ defaultColor = colors[0];
+
+ for (int i = N - 1; i > 0; i--) {
+ if (states[i].length == 0) {
+ defaultColor = colors[i];
+ break;
}
}
- }
- final int[][] outputStates = new int[inputStates.length + 1][];
- System.arraycopy(inputStates, 0, outputStates, 1, inputStates.length);
- outputStates[0] = new int[] { state };
+ for (int i = 0; i < N; i++) {
+ if (Color.alpha(colors[i]) != 0xFF) {
+ isOpaque = false;
+ break;
+ }
+ }
+ }
- final int[] inputColors = colorStateList.getColors();
- final int[] outputColors = new int[inputColors.length + 1];
- System.arraycopy(inputColors, 0, outputColors, 1, inputColors.length);
- outputColors[0] = color;
+ mDefaultColor = defaultColor;
+ mIsOpaque = isOpaque;
+ }
- return new ColorStateList(outputStates, outputColors);
+ /**
+ * @return A factory that can create new instances of this ColorStateList.
+ */
+ ColorStateListFactory getFactory() {
+ return new ColorStateListFactory(this);
}
- @Override
- public String toString() {
- return "ColorStateList{" +
- "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
- "mColors=" + Arrays.toString(mColors) +
- "mDefaultColor=" + mDefaultColor + '}';
+ static class ColorStateListFactory extends ConstantState<ColorStateList> {
+ final ColorStateList mSrc;
+
+ public ColorStateListFactory(ColorStateList src) {
+ mSrc = src;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mSrc.mChangingConfigurations;
+ }
+
+ @Override
+ public ColorStateList newInstance() {
+ return mSrc;
+ }
+
+ @Override
+ public ColorStateList newInstance(Resources res, Theme theme) {
+ if (theme == null || !mSrc.canApplyTheme()) {
+ return mSrc;
+ }
+
+ final ColorStateList clone = new ColorStateList(mSrc);
+ clone.applyTheme(theme);
+ return clone;
+ }
}
@Override
@@ -386,6 +549,9 @@ public class ColorStateList implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
+ if (canApplyTheme()) {
+ Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!");
+ }
final int N = mStateSpecs.length;
dest.writeInt(N);
for (int i = 0; i < N; i++) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 73913b6..95ad57e 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,33 +16,48 @@
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 android.annotation.ColorInt;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.animation.Animator;
+import android.animation.StateListAnimator;
+import android.annotation.AnimRes;
+import android.annotation.AnyRes;
+import android.annotation.ArrayRes;
+import android.annotation.BoolRes;
+import android.annotation.ColorRes;
+import android.annotation.DimenRes;
+import android.annotation.DrawableRes;
+import android.annotation.FractionRes;
+import android.annotation.IntegerRes;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.PluralsRes;
+import android.annotation.RawRes;
+import android.annotation.StringRes;
+import android.annotation.XmlRes;
import android.content.pm.ActivityInfo;
+import android.content.res.ColorStateList.ColorStateListFactory;
import android.graphics.Movie;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.os.Build;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Pools.SynchronizedPool;
import android.util.Slog;
import android.util.TypedValue;
-import android.util.LongSparseArray;
+import android.view.ViewDebug;
import java.io.IOException;
import java.io.InputStream;
@@ -97,8 +112,8 @@ public class Resources {
private static final LongSparseArray<ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<ConstantState> sPreloadedColorDrawables
= new LongSparseArray<ConstantState>();
- private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
- = new LongSparseArray<ColorStateList>();
+ private static final LongSparseArray<ColorStateListFactory> sPreloadedColorStateLists
+ = new LongSparseArray<ColorStateListFactory>();
// Pool of TypedArrays targeted to this Resources object.
final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<TypedArray>(5);
@@ -116,8 +131,8 @@ public class Resources {
new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache =
new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
- private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache =
- new LongSparseArray<WeakReference<ColorStateList>>();
+ private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache =
+ new ConfigurationBoundResourceCache<ColorStateList>(this);
private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
new ConfigurationBoundResourceCache<Animator>(this);
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
@@ -140,9 +155,6 @@ public class Resources {
private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- @SuppressWarnings("unused")
- private WeakReference<IBinder> mToken;
-
static {
sPreloadedDrawables = new LongSparseArray[2];
sPreloadedDrawables[0] = new LongSparseArray<ConstantState>();
@@ -223,46 +235,44 @@ public class Resources {
/**
* Create a new Resources object on top of an existing set of assets in an
* AssetManager.
- *
- * @param assets Previously created AssetManager.
- * @param metrics Current display metrics to consider when
+ *
+ * @param assets Previously created AssetManager.
+ * @param metrics Current display metrics to consider when
* selecting/computing resource values.
- * @param config Desired device configuration to consider when
+ * @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
- this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
}
/**
* Creates a new Resources object with CompatibilityInfo.
- *
- * @param assets Previously created AssetManager.
- * @param metrics Current display metrics to consider when
+ *
+ * @param assets Previously created AssetManager.
+ * @param metrics Current display metrics to consider when
* selecting/computing resource values.
- * @param config Desired device configuration to consider when
+ * @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
* @param compatInfo this resource's compatibility info. Must not be null.
- * @param token The Activity token for determining stack affiliation. Usually null.
* @hide
*/
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
- CompatibilityInfo compatInfo, IBinder token) {
+ CompatibilityInfo compatInfo) {
mAssets = assets;
mMetrics.setToDefaults();
if (compatInfo != null) {
mCompatibilityInfo = compatInfo;
}
- mToken = new WeakReference<IBinder>(token);
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
}
/**
* Return a global shared Resources object that provides access to only
- * system resources (no application resources), and is not configured for
- * the current screen (can not use dimension units, does not change based
- * on orientation, etc).
+ * system resources (no application resources), and is not configured for
+ * the current screen (can not use dimension units, does not change based
+ * on orientation, etc).
*/
public static Resources getSystem() {
synchronized (sSync) {
@@ -291,7 +301,7 @@ public class Resources {
* @return CharSequence The string data associated with the resource, plus
* possibly styled text information.
*/
- public CharSequence getText(int id) throws NotFoundException {
+ public CharSequence getText(@StringRes int id) throws NotFoundException {
CharSequence res = mAssets.getResourceText(id);
if (res != null) {
return res;
@@ -320,7 +330,8 @@ public class Resources {
* @return CharSequence The string data associated with the resource, plus
* possibly styled text information.
*/
- public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+ public CharSequence getQuantityText(@PluralsRes int id, int quantity)
+ throws NotFoundException {
NativePluralRules rule = getPluralRule();
CharSequence res = mAssets.getResourceBagText(id,
attrForQuantityCode(rule.quantityForInt(quantity)));
@@ -381,7 +392,7 @@ public class Resources {
* @return String The string data associated with the resource,
* stripped of styled text information.
*/
- public String getString(int id) throws NotFoundException {
+ public String getString(@StringRes int id) throws NotFoundException {
CharSequence res = getText(id);
if (res != null) {
return res.toString();
@@ -409,7 +420,8 @@ public class Resources {
* @return String The string data associated with the resource,
* stripped of styled text information.
*/
- public String getString(int id, Object... formatArgs) throws NotFoundException {
+ public String getString(@StringRes int id, Object... formatArgs)
+ throws NotFoundException {
String raw = getString(id);
return String.format(mConfiguration.locale, raw, formatArgs);
}
@@ -439,7 +451,7 @@ public class Resources {
* @return String The string data associated with the resource,
* stripped of styled text information.
*/
- public String getQuantityString(int id, int quantity, Object... formatArgs)
+ public String getQuantityString(@PluralsRes int id, int quantity, Object... formatArgs)
throws NotFoundException {
String raw = getQuantityText(id, quantity).toString();
return String.format(mConfiguration.locale, raw, formatArgs);
@@ -465,7 +477,8 @@ public class Resources {
* @return String The string data associated with the resource,
* stripped of styled text information.
*/
- public String getQuantityString(int id, int quantity) throws NotFoundException {
+ public String getQuantityString(@PluralsRes int id, int quantity)
+ throws NotFoundException {
return getQuantityText(id, quantity).toString();
}
@@ -483,7 +496,7 @@ public class Resources {
* @return CharSequence The string data associated with the resource, plus
* possibly styled text information, or def if id is 0 or not found.
*/
- public CharSequence getText(int id, CharSequence def) {
+ public CharSequence getText(@StringRes int id, CharSequence def) {
CharSequence res = id != 0 ? mAssets.getResourceText(id) : null;
return res != null ? res : def;
}
@@ -499,7 +512,7 @@ public class Resources {
*
* @return The styled text array associated with the resource.
*/
- public CharSequence[] getTextArray(int id) throws NotFoundException {
+ public CharSequence[] getTextArray(@ArrayRes int id) throws NotFoundException {
CharSequence[] res = mAssets.getResourceTextArray(id);
if (res != null) {
return res;
@@ -519,7 +532,8 @@ public class Resources {
*
* @return The string array associated with the resource.
*/
- public String[] getStringArray(int id) throws NotFoundException {
+ public String[] getStringArray(@ArrayRes int id)
+ throws NotFoundException {
String[] res = mAssets.getResourceStringArray(id);
if (res != null) {
return res;
@@ -539,7 +553,7 @@ public class Resources {
*
* @return The int array associated with the resource.
*/
- public int[] getIntArray(int id) throws NotFoundException {
+ public int[] getIntArray(@ArrayRes int id) throws NotFoundException {
int[] res = mAssets.getArrayIntResource(id);
if (res != null) {
return res;
@@ -561,7 +575,8 @@ public class Resources {
* Be sure to call {@link TypedArray#recycle() TypedArray.recycle()}
* when done with it.
*/
- public TypedArray obtainTypedArray(int id) throws NotFoundException {
+ public TypedArray obtainTypedArray(@ArrayRes int id)
+ throws NotFoundException {
int len = mAssets.getArraySize(id);
if (len < 0) {
throw new NotFoundException("Array resource ID #0x"
@@ -592,7 +607,7 @@ public class Resources {
* @see #getDimensionPixelOffset
* @see #getDimensionPixelSize
*/
- public float getDimension(int id) throws NotFoundException {
+ public float getDimension(@DimenRes int id) throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
@@ -627,7 +642,7 @@ public class Resources {
* @see #getDimension
* @see #getDimensionPixelSize
*/
- public int getDimensionPixelOffset(int id) throws NotFoundException {
+ public int getDimensionPixelOffset(@DimenRes int id) throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
@@ -664,7 +679,7 @@ public class Resources {
* @see #getDimension
* @see #getDimensionPixelOffset
*/
- public int getDimensionPixelSize(int id) throws NotFoundException {
+ public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
@@ -698,7 +713,7 @@ public class Resources {
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*/
- public float getFraction(int id, int base, int pbase) {
+ public float getFraction(@FractionRes int id, int base, int pbase) {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
@@ -748,7 +763,7 @@ public class Resources {
*/
@Deprecated
@Nullable
- public Drawable getDrawable(int id) throws NotFoundException {
+ public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
final Drawable d = getDrawable(id, null);
if (d != null && d.canApplyTheme()) {
Log.w(TAG, "Drawable " + getResourceName(id) + " has unresolved theme "
@@ -773,7 +788,7 @@ public class Resources {
* not exist.
*/
@Nullable
- public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
+ public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -821,7 +836,7 @@ public class Resources {
*/
@Deprecated
@Nullable
- public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
+ public Drawable getDrawableForDensity(@DrawableRes int id, int density) throws NotFoundException {
return getDrawableForDensity(id, density, null);
}
@@ -840,7 +855,7 @@ public class Resources {
* not exist.
*/
@Nullable
- public Drawable getDrawableForDensity(int id, int density, @Nullable Theme theme) {
+ public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -884,7 +899,7 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
*/
- public Movie getMovie(int id) throws NotFoundException {
+ public Movie getMovie(@RawRes int id) throws NotFoundException {
InputStream is = openRawResource(id);
Movie movie = Movie.decodeStream(is);
try {
@@ -897,20 +912,44 @@ public class Resources {
}
/**
- * Return a color integer associated with a particular resource ID.
- * If the resource holds a complex
- * {@link android.content.res.ColorStateList}, then the default color from
- * the set is returned.
+ * Returns a color integer associated with a particular resource ID. If the
+ * resource holds a complex {@link ColorStateList}, then the default color
+ * from the set is returned.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
*
- * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A single color value in the form 0xAARRGGBB.
+ * @deprecated Use {@link #getColor(int, Theme)} instead.
+ */
+ @ColorInt
+ @Deprecated
+ public int getColor(@ColorRes int id) throws NotFoundException {
+ return getColor(id, null);
+ }
+
+ /**
+ * Returns a themed color integer associated with a particular resource ID.
+ * If the resource holds a complex {@link ColorStateList}, then the default
+ * color from the set is returned.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @param theme The theme used to style the color attributes, may be
+ * {@code null}.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
*
- * @return Returns a single color value in the form 0xAARRGGBB.
+ * @return A single color value in the form 0xAARRGGBB.
*/
- public int getColor(int id) throws NotFoundException {
+ @ColorInt
+ public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -919,40 +958,78 @@ public class Resources {
}
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
- && value.type <= TypedValue.TYPE_LAST_INT) {
+ && value.type <= TypedValue.TYPE_LAST_INT) {
mTmpValue = value;
return value.data;
} else if (value.type != TypedValue.TYPE_STRING) {
throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
}
mTmpValue = null;
}
- ColorStateList csl = loadColorStateList(value, id);
+
+ final ColorStateList csl = loadColorStateList(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
+
return csl.getDefaultColor();
}
/**
- * Return a color state list associated with a particular resource ID. The
- * resource may contain either a single raw color value, or a complex
- * {@link android.content.res.ColorStateList} holding multiple possible colors.
+ * Returns a color state list associated with a particular resource ID. The
+ * resource may contain either a single raw color value or a complex
+ * {@link ColorStateList} holding multiple possible colors.
*
* @param id The desired resource identifier of a {@link ColorStateList},
- * as generated by the aapt tool. This integer encodes the package, type, and resource
- * entry. The value 0 is an invalid identifier.
+ * as generated by the aapt tool. This integer encodes the
+ * package, type, and resource entry. The value 0 is an invalid
+ * identifier.
*
- * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A ColorStateList object containing either a single solid color
+ * or multiple colors that can be selected based on a state.
+ * @deprecated Use {@link #getColorStateList(int, Theme)} instead.
+ */
+ @Nullable
+ @Deprecated
+ public ColorStateList getColorStateList(@ColorRes int id) throws NotFoundException {
+ final ColorStateList csl = getColorStateList(id, null);
+ if (csl != null && csl.canApplyTheme()) {
+ Log.w(TAG, "ColorStateList " + getResourceName(id) + " has "
+ + "unresolved theme attributes! Consider using "
+ + "Resources.getColorStateList(int, Theme) or "
+ + "Context.getColorStateList(int).", new RuntimeException());
+ }
+ return csl;
+ }
+
+ /**
+ * Returns a themed color state list associated with a particular resource
+ * ID. The resource may contain either a single raw color value or a
+ * complex {@link ColorStateList} holding multiple possible colors.
*
- * @return Returns a ColorStateList object containing either a single
- * solid color or multiple colors that can be selected based on a state.
+ * @param id The desired resource identifier of a {@link ColorStateList},
+ * as generated by the aapt tool. This integer encodes the
+ * package, type, and resource entry. The value 0 is an invalid
+ * identifier.
+ * @param theme The theme used to style the color attributes, may be
+ * {@code null}.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A themed ColorStateList object containing either a single solid
+ * color or multiple colors that can be selected based on a state.
*/
- public ColorStateList getColorStateList(int id) throws NotFoundException {
+ @Nullable
+ public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
+ throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -963,12 +1040,14 @@ public class Resources {
}
getValue(id, value, true);
}
- ColorStateList res = loadColorStateList(value, id);
+
+ final ColorStateList res = loadColorStateList(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
+
return res;
}
@@ -985,7 +1064,7 @@ public class Resources {
*
* @return Returns the boolean value contained in the resource.
*/
- public boolean getBoolean(int id) throws NotFoundException {
+ public boolean getBoolean(@BoolRes int id) throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
@@ -1013,7 +1092,7 @@ public class Resources {
*
* @return Returns the integer value contained in the resource.
*/
- public int getInteger(int id) throws NotFoundException {
+ public int getInteger(@IntegerRes int id) throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
@@ -1078,7 +1157,7 @@ public class Resources {
*
* @see #getXml
*/
- public XmlResourceParser getLayout(int id) throws NotFoundException {
+ public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
@@ -1102,7 +1181,7 @@ public class Resources {
*
* @see #getXml
*/
- public XmlResourceParser getAnimation(int id) throws NotFoundException {
+ public XmlResourceParser getAnimation(@AnimRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "anim");
}
@@ -1127,7 +1206,7 @@ public class Resources {
*
* @see android.util.AttributeSet
*/
- public XmlResourceParser getXml(int id) throws NotFoundException {
+ public XmlResourceParser getXml(@XmlRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "xml");
}
@@ -1145,7 +1224,7 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
*/
- public InputStream openRawResource(int id) throws NotFoundException {
+ public InputStream openRawResource(@RawRes int id) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -1177,7 +1256,8 @@ public class Resources {
*
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*/
- public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+ public InputStream openRawResource(@RawRes int id, TypedValue value)
+ throws NotFoundException {
getValue(id, value, true);
try {
@@ -1212,7 +1292,8 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
*/
- public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+ public AssetFileDescriptor openRawResourceFd(@RawRes int id)
+ throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -1257,7 +1338,7 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*
*/
- public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+ public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
@@ -1280,8 +1361,8 @@ public class Resources {
* not exist.
* @see #getValue(String, TypedValue, boolean)
*/
- public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
- throws NotFoundException {
+ public void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
+ boolean resolveRefs) throws NotFoundException {
boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
if (found) {
return;
@@ -1640,7 +1721,7 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID
* does not exist.
*/
- public Drawable getDrawable(int id) throws NotFoundException {
+ public Drawable getDrawable(@DrawableRes int id) throws NotFoundException {
return Resources.this.getDrawable(id, this);
}
@@ -1843,11 +1924,10 @@ public class Resources {
clearDrawableCachesLocked(mDrawableCache, configChanges);
clearDrawableCachesLocked(mColorDrawableCache, configChanges);
+ mColorStateListCache.onConfigurationChange(configChanges);
mAnimatorCache.onConfigurationChange(configChanges);
mStateListAnimatorCache.onConfigurationChange(configChanges);
- mColorStateListCache.clear();
-
flushLayoutCache();
}
synchronized (sSync) {
@@ -2046,7 +2126,7 @@ public class Resources {
*
* @hide
*/
- public static boolean resourceHasPackage(int resid) {
+ public static boolean resourceHasPackage(@AnyRes int resid) {
return (resid >>> 24) != 0;
}
@@ -2064,7 +2144,7 @@ public class Resources {
* @see #getResourceTypeName
* @see #getResourceEntryName
*/
- public String getResourceName(int resid) throws NotFoundException {
+ public String getResourceName(@AnyRes int resid) throws NotFoundException {
String str = mAssets.getResourceName(resid);
if (str != null) return str;
throw new NotFoundException("Unable to find resource ID #0x"
@@ -2083,7 +2163,7 @@ public class Resources {
*
* @see #getResourceName
*/
- public String getResourcePackageName(int resid) throws NotFoundException {
+ public String getResourcePackageName(@AnyRes int resid) throws NotFoundException {
String str = mAssets.getResourcePackageName(resid);
if (str != null) return str;
throw new NotFoundException("Unable to find resource ID #0x"
@@ -2102,7 +2182,7 @@ public class Resources {
*
* @see #getResourceName
*/
- public String getResourceTypeName(int resid) throws NotFoundException {
+ public String getResourceTypeName(@AnyRes int resid) throws NotFoundException {
String str = mAssets.getResourceTypeName(resid);
if (str != null) return str;
throw new NotFoundException("Unable to find resource ID #0x"
@@ -2121,7 +2201,7 @@ public class Resources {
*
* @see #getResourceName
*/
- public String getResourceEntryName(int resid) throws NotFoundException {
+ public String getResourceEntryName(@AnyRes int resid) throws NotFoundException {
String str = mAssets.getResourceEntryName(resid);
if (str != null) return str;
throw new NotFoundException("Unable to find resource ID #0x"
@@ -2425,6 +2505,9 @@ public class Resources {
final String themeKey = theme == null ? "" : theme.mKey;
LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey);
if (themedCache == null) {
+ // Clean out the caches before we add more. This shouldn't
+ // happen very often.
+ pruneCaches(caches);
themedCache = new LongSparseArray<WeakReference<ConstantState>>(1);
caches.put(themeKey, themedCache);
}
@@ -2434,12 +2517,46 @@ public class Resources {
}
/**
+ * Prunes empty caches from the cache map.
+ *
+ * @param caches The map of caches to prune.
+ */
+ private void pruneCaches(ArrayMap<String,
+ LongSparseArray<WeakReference<ConstantState>>> caches) {
+ final int N = caches.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final LongSparseArray<WeakReference<ConstantState>> cache = caches.valueAt(i);
+ if (pruneCache(cache)) {
+ caches.removeAt(i);
+ }
+ }
+ }
+
+ /**
+ * Prunes obsolete weak references from a cache, returning {@code true} if
+ * the cache is empty and should be removed.
+ *
+ * @param cache The cache of weak references to prune.
+ * @return {@code true} if the cache is empty and should be removed.
+ */
+ private boolean pruneCache(LongSparseArray<WeakReference<ConstantState>> cache) {
+ final int N = cache.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final WeakReference entry = cache.valueAt(i);
+ if (entry == null || entry.get() == null) {
+ cache.removeAt(i);
+ }
+ }
+ return cache.size() == 0;
+ }
+
+ /**
* Loads a drawable from XML or resources stream.
*/
private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
if (value.string == null) {
throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
- + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
+ + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
}
final String file = value.string.toString();
@@ -2530,7 +2647,8 @@ public class Resources {
return null;
}
- /*package*/ ColorStateList loadColorStateList(TypedValue value, int id)
+ @Nullable
+ ColorStateList loadColorStateList(TypedValue value, int id, Theme theme)
throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
@@ -2544,101 +2662,107 @@ public class Resources {
ColorStateList csl;
- if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
- value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
-
- csl = sPreloadedColorStateLists.get(key);
- if (csl != null) {
- return csl;
+ // Handle inline color definitions.
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ final ColorStateListFactory factory = sPreloadedColorStateLists.get(key);
+ if (factory != null) {
+ return factory.newInstance();
}
csl = ColorStateList.valueOf(value.data);
+
if (mPreloading) {
if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
"color")) {
- sPreloadedColorStateLists.put(key, csl);
+ sPreloadedColorStateLists.put(key, csl.getFactory());
}
}
return csl;
}
- csl = getCachedColorStateList(key);
+ final ConfigurationBoundResourceCache<ColorStateList> cache = mColorStateListCache;
+
+ csl = cache.get(key, theme);
if (csl != null) {
return csl;
}
- csl = sPreloadedColorStateLists.get(key);
+ final ColorStateListFactory factory = sPreloadedColorStateLists.get(key);
+ if (factory != null) {
+ csl = factory.newInstance(this, theme);
+ }
+
+ if (csl == null) {
+ csl = loadColorStateListForCookie(value, id, theme);
+ }
+
if (csl != null) {
- return csl;
+ if (mPreloading) {
+ if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
+ "color")) {
+ sPreloadedColorStateLists.put(key, csl.getFactory());
+ }
+ } else {
+ cache.put(key, theme, csl.getFactory());
+ }
}
+ return csl;
+ }
+
+ private ColorStateList loadColorStateListForCookie(TypedValue value, int id, Theme theme) {
if (value.string == null) {
- throw new NotFoundException(
- "Resource is not a ColorStateList (color or path): " + value);
+ throw new UnsupportedOperationException(
+ "Can't convert to color state list: type=0x" + value.type);
}
-
+
final String file = value.string.toString();
+ if (TRACE_FOR_MISS_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ Log.d(TAG, "Loading framework color state list #" + Integer.toHexString(id)
+ + ": " + name + " at " + file);
+ }
+ }
+ }
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading color state list for cookie " + value.assetCookie + ": " + file);
+ }
+
+ final ColorStateList csl;
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
if (file.endsWith(".xml")) {
- Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
final XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "colorstatelist");
- csl = ColorStateList.createFromXml(this, rp);
+ file, id, value.assetCookie, "colorstatelist");
+ csl = ColorStateList.createFromXml(this, rp, theme);
rp.close();
} catch (Exception e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from color state list resource ID #0x"
- + Integer.toHexString(id));
+ final NotFoundException rnf = new NotFoundException(
+ "File " + file + " from color state list resource ID #0x"
+ + Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
- Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
} else {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
throw new NotFoundException(
"File " + file + " from drawable resource ID #0x"
- + Integer.toHexString(id) + ": .xml extension required");
- }
-
- if (csl != null) {
- if (mPreloading) {
- if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
- "color")) {
- sPreloadedColorStateLists.put(key, csl);
- }
- } else {
- synchronized (mAccessLock) {
- //Log.i(TAG, "Saving cached color state list @ #" +
- // Integer.toHexString(key.intValue())
- // + " in " + this + ": " + csl);
- mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl));
- }
- }
+ + Integer.toHexString(id) + ": .xml extension required");
}
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return csl;
}
- private ColorStateList getCachedColorStateList(long key) {
- synchronized (mAccessLock) {
- WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
- if (wr != null) { // we have the key
- ColorStateList entry = wr.get();
- if (entry != null) {
- //Log.i(TAG, "Returning cached color state list @ #" +
- // Integer.toHexString(((Integer)key).intValue())
- // + " in " + this + ": " + entry);
- return entry;
- } else { // our entry has been purged
- mColorStateListCache.delete(key);
- }
- }
- }
- return null;
- }
-
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
synchronized (mAccessLock) {
@@ -2715,6 +2839,20 @@ public class Resources {
}
}
+ /**
+ * Obtains styled attributes from the theme, if available, or unstyled
+ * resources if the theme is null.
+ *
+ * @hide
+ */
+ public static TypedArray obtainAttributes(
+ Resources res, Theme theme, AttributeSet set, int[] attrs) {
+ if (theme == null) {
+ return res.obtainAttributes(set, attrs);
+ }
+ return theme.obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
private Resources() {
mAssets = AssetManager.getSystem();
// NOTE: Intentionally leaving this uninitialized (all values set
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index 4ae3825..9548d49 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -16,27 +16,23 @@
package android.content.res;
-import android.os.IBinder;
+import java.util.Objects;
/** @hide */
public final class ResourcesKey {
- final String mResDir;
- final float mScale;
+ private final String mResDir;
+ private final float mScale;
private final int mHash;
- private final IBinder mToken;
public final int mDisplayId;
- public final Configuration mOverrideConfiguration = new Configuration();
+ public final Configuration mOverrideConfiguration;
public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration,
- float scale, IBinder token) {
+ float scale) {
mResDir = resDir;
mDisplayId = displayId;
- if (overrideConfiguration != null) {
- mOverrideConfiguration.setTo(overrideConfiguration);
- }
+ mOverrideConfiguration = overrideConfiguration;
mScale = scale;
- mToken = token;
int hash = 17;
hash = 31 * hash + (mResDir == null ? 0 : mResDir.hashCode());
@@ -48,7 +44,8 @@ public final class ResourcesKey {
}
public boolean hasOverrideConfiguration() {
- return !Configuration.EMPTY.equals(mOverrideConfiguration);
+ return mOverrideConfiguration != null
+ && !Configuration.EMPTY.equals(mOverrideConfiguration);
}
@Override
@@ -63,17 +60,9 @@ public final class ResourcesKey {
}
ResourcesKey peer = (ResourcesKey) obj;
- if ((mResDir == null) && (peer.mResDir != null)) {
- return false;
- }
- if ((mResDir != null) && (peer.mResDir == null)) {
+ if (!Objects.equals(mResDir, peer.mResDir)) {
return false;
}
- if ((mResDir != null) && (peer.mResDir != null)) {
- if (!mResDir.equals(peer.mResDir)) {
- return false;
- }
- }
if (mDisplayId != peer.mDisplayId) {
return false;
}
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 9652db7..5cfc41f 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -328,7 +328,7 @@ final class StringBlock {
String name = color.substring(1);
int colorRes = res.getIdentifier(name, "color", "android");
if (colorRes != 0) {
- ColorStateList colors = res.getColorStateList(colorRes);
+ ColorStateList colors = res.getColorStateList(colorRes, null);
if (foreground) {
return new TextAppearanceSpan(null, 0, 0, colors, null);
} else {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 02602fb..1fca920 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -16,11 +16,13 @@
package android.content.res;
+import android.annotation.AnyRes;
+import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
+import android.os.StrictMode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
@@ -73,7 +75,9 @@ public class TypedArray {
/*package*/ TypedValue mValue = new TypedValue();
/**
- * Return the number of values in this array.
+ * Returns the number of values in this array.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int length() {
if (mRecycled) {
@@ -85,6 +89,8 @@ public class TypedArray {
/**
* Return the number of indices in the array that actually have data.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getIndexCount() {
if (mRecycled) {
@@ -95,13 +101,14 @@ public class TypedArray {
}
/**
- * Return an index in the array that has data.
+ * Returns an index in the array that has data.
*
* @param at The index you would like to returned, ranging from 0 to
- * {@link #getIndexCount()}.
+ * {@link #getIndexCount()}.
*
* @return The index at the given offset, which can be used with
- * {@link #getValue} and related APIs.
+ * {@link #getValue} and related APIs.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getIndex(int at) {
if (mRecycled) {
@@ -112,7 +119,9 @@ public class TypedArray {
}
/**
- * Return the Resources object this array was loaded from.
+ * Returns the Resources object this array was loaded from.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public Resources getResources() {
if (mRecycled) {
@@ -123,12 +132,17 @@ public class TypedArray {
}
/**
- * Retrieve the styled string value for the attribute at <var>index</var>.
+ * Retrieves the styled string value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a string, this method will attempt to coerce
+ * it to a string.
*
* @param index Index of attribute to retrieve.
*
- * @return CharSequence holding string data. May be styled. Returns
- * null if the attribute is not defined.
+ * @return CharSequence holding string data. May be styled. Returns
+ * {@code null} if the attribute is not defined or could not be
+ * coerced to a string.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public CharSequence getText(int index) {
if (mRecycled) {
@@ -144,23 +158,28 @@ public class TypedArray {
return loadStringValueAt(index);
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
+ StrictMode.noteResourceMismatch(v);
return v.coerceToString();
}
- Log.w(Resources.TAG, "getString of bad type: 0x"
- + Integer.toHexString(type));
- return null;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type));
}
/**
- * Retrieve the string value for the attribute at <var>index</var>.
+ * Retrieves the string value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a string, this method will attempt to coerce
+ * it to a string.
*
* @param index Index of attribute to retrieve.
*
- * @return String holding string data. Any styling information is
- * removed. Returns null if the attribute is not defined.
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined or could
+ * not be coerced to a string.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public String getString(int index) {
if (mRecycled) {
@@ -176,19 +195,19 @@ public class TypedArray {
return loadStringValueAt(index).toString();
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
- CharSequence cs = v.coerceToString();
+ StrictMode.noteResourceMismatch(v);
+ final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
- Log.w(Resources.TAG, "getString of bad type: 0x"
- + Integer.toHexString(type));
- return null;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type));
}
/**
- * Retrieve the string value for the attribute at <var>index</var>, but
+ * Retrieves the string value for the attribute at <var>index</var>, but
* only if that string comes from an immediate value in an XML file. That
* is, this does not allow references to string resources, string
* attributes, or conversions from other types. As such, this method
@@ -197,9 +216,10 @@ public class TypedArray {
*
* @param index Index of attribute to retrieve.
*
- * @return String holding string data. Any styling information is
- * removed. Returns null if the attribute is not defined or is not
- * an immediate string value.
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined or is not
+ * an immediate string value.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public String getNonResourceString(int index) {
if (mRecycled) {
@@ -220,16 +240,17 @@ public class TypedArray {
}
/**
- * @hide
- * Retrieve the string value for the attribute at <var>index</var> that is
+ * Retrieves the string value for the attribute at <var>index</var> that is
* not allowed to change with the given configurations.
*
* @param index Index of attribute to retrieve.
* @param allowedChangingConfigs Bit mask of configurations from
- * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
+ * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
*
- * @return String holding string data. Any styling information is
- * removed. Returns null if the attribute is not defined.
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @hide
*/
public String getNonConfigurationString(int index, int allowedChangingConfigs) {
if (mRecycled) {
@@ -248,24 +269,33 @@ public class TypedArray {
return loadStringValueAt(index).toString();
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
- CharSequence cs = v.coerceToString();
+ StrictMode.noteResourceMismatch(v);
+ final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
- Log.w(Resources.TAG, "getString of bad type: 0x"
- + Integer.toHexString(type));
- return null;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getNonConfigurationString of bad type: 0x"
+ + Integer.toHexString(type));
}
/**
* Retrieve the boolean value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is an integer value, this method will return whether
+ * it is equal to zero. If the attribute is not a boolean or integer value,
+ * this method will attempt to coerce it to an integer using
+ * {@link Integer#decode(String)} and return whether it is equal to zero.
*
* @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined.
+ * @param defValue Value to return if the attribute is not defined or
+ * cannot be coerced to an integer.
*
- * @return Attribute boolean value, or defValue if not defined.
+ * @return Boolean value of the attribute, or defValue if the attribute was
+ * not defined or could not be coerced to an integer.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean getBoolean(int index, boolean defValue) {
if (mRecycled) {
@@ -278,28 +308,33 @@ public class TypedArray {
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA] != 0;
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to boolean: " + v);
- return XmlUtils.convertValueToBoolean(
- v.coerceToString(), defValue);
+ StrictMode.noteResourceMismatch(v);
+ return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue);
}
- Log.w(Resources.TAG, "getBoolean of bad type: 0x"
- + Integer.toHexString(type));
- return defValue;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type));
}
/**
* Retrieve the integer value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not an integer, this method will attempt to coerce
+ * it to an integer using {@link Integer#decode(String)}.
*
* @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined.
+ * @param defValue Value to return if the attribute is not defined or
+ * cannot be coerced to an integer.
*
- * @return Attribute int value, or defValue if not defined.
+ * @return Integer value of the attribute, or defValue if the attribute was
+ * not defined or could not be coerced to an integer.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getInt(int index, int defValue) {
if (mRecycled) {
@@ -312,27 +347,31 @@ public class TypedArray {
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to int: " + v);
- return XmlUtils.convertValueToInt(
- v.coerceToString(), defValue);
+ StrictMode.noteResourceMismatch(v);
+ return XmlUtils.convertValueToInt(v.coerceToString(), defValue);
}
- Log.w(Resources.TAG, "getInt of bad type: 0x"
- + Integer.toHexString(type));
- return defValue;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getInt of bad type: 0x" + Integer.toHexString(type));
}
/**
* Retrieve the float value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a float or an integer, this method will attempt
+ * to coerce it to a float using {@link Float#parseFloat(String)}.
*
* @param index Index of attribute to retrieve.
*
- * @return Attribute float value, or defValue if not defined..
+ * @return Attribute float value, or defValue if the attribute was
+ * not defined or could not be coerced to a float.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public float getFloat(int index, float defValue) {
if (mRecycled) {
@@ -347,21 +386,21 @@ public class TypedArray {
} else if (type == TypedValue.TYPE_FLOAT) {
return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]);
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to float: " + v);
- CharSequence str = v.coerceToString();
+ final CharSequence str = v.coerceToString();
if (str != null) {
+ StrictMode.noteResourceMismatch(v);
return Float.parseFloat(str.toString());
}
}
- Log.w(Resources.TAG, "getFloat of bad type: 0x"
- + Integer.toHexString(type));
- return defValue;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type));
}
/**
@@ -369,14 +408,21 @@ public class TypedArray {
* the attribute references a color resource holding a complex
* {@link android.content.res.ColorStateList}, then the default color from
* the set is returned.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not an integer color or color state list.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute color value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer color or color state list.
*/
- public int getColor(int index, int defValue) {
+ @ColorInt
+ public int getColor(int index, @ColorInt int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
@@ -387,18 +433,21 @@ public class TypedArray {
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_STRING) {
final TypedValue value = mValue;
if (getValueAt(index, value)) {
- ColorStateList csl = mResources.loadColorStateList(
- value, value.resourceId);
+ final ColorStateList csl = mResources.loadColorStateList(
+ value, value.resourceId, mTheme);
return csl.getDefaultColor();
}
return defValue;
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ final TypedValue value = mValue;
+ getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to color: type=0x"
@@ -408,12 +457,22 @@ public class TypedArray {
/**
* Retrieve the ColorStateList for the attribute at <var>index</var>.
* The value may be either a single solid color or a reference to
- * a color or complex {@link android.content.res.ColorStateList} description.
+ * a color or complex {@link android.content.res.ColorStateList}
+ * description.
+ * <p>
+ * This method will return {@code null} if the attribute is not defined or
+ * is not an integer color or color state list.
*
* @param index Index of attribute to retrieve.
*
- * @return ColorStateList for the attribute, or null if not defined.
+ * @return ColorStateList for the attribute, or {@code null} if not
+ * defined.
+ * @throws RuntimeException if the attribute if the TypedArray has already
+ * been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer color or color state list.
*/
+ @Nullable
public ColorStateList getColorStateList(int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
@@ -422,21 +481,28 @@ public class TypedArray {
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
- return mResources.loadColorStateList(value, value.resourceId);
+ return mResources.loadColorStateList(value, value.resourceId, mTheme);
}
return null;
}
/**
* Retrieve the integer value for the attribute at <var>index</var>.
+ * <p>
+ * Unlike {@link #getInt(int, int)}, this method will throw an exception if
+ * the attribute is defined but is not an integer.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute integer value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
*/
public int getInteger(int index, int defValue) {
if (mRecycled) {
@@ -449,10 +515,13 @@ public class TypedArray {
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ final TypedValue value = mValue;
+ getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to integer: type=0x"
@@ -460,17 +529,23 @@ public class TypedArray {
}
/**
- * Retrieve a dimensional unit attribute at <var>index</var>. Unit
+ * Retrieve a dimensional unit attribute at <var>index</var>. Unit
* conversions are based on the current {@link DisplayMetrics}
* associated with the resources this {@link TypedArray} object
* came from.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric, or defValue if not defined.
+ * metric, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
*
* @see #getDimensionPixelOffset
* @see #getDimensionPixelSize
@@ -487,9 +562,12 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ final TypedValue value = mValue;
+ getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -502,13 +580,19 @@ public class TypedArray {
* {@link #getDimension}, except the returned value is converted to
* integer pixels for you. An offset conversion involves simply
* truncating the base value to an integer.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels, or defValue if not defined.
+ * metric and truncated to integer pixels, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
*
* @see #getDimension
* @see #getDimensionPixelSize
@@ -525,9 +609,12 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelOffset(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ final TypedValue value = mValue;
+ getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -541,13 +628,19 @@ public class TypedArray {
* integer pixels for use as a size. A size conversion involves
* rounding the base value, and ensuring that a non-zero base value
* is at least one pixel in size.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels, or defValue if not defined.
+ * metric and truncated to integer pixels, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a dimension.
*
* @see #getDimension
* @see #getDimensionPixelOffset
@@ -566,7 +659,10 @@ public class TypedArray {
return TypedValue.complexToDimensionPixelSize(
data[index+AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ final TypedValue value = mValue;
+ getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -578,12 +674,18 @@ public class TypedArray {
* {@link android.view.ViewGroup}'s layout_width and layout_height
* attributes. This is only here for performance reasons; applications
* should use {@link #getDimensionPixelSize}.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension or integer (enum).
*
* @param index Index of the attribute to retrieve.
* @param name Textual name of attribute for error reporting.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels.
+ * metric and truncated to integer pixels.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a dimension or integer (enum).
*/
public int getLayoutDimension(int index, String name) {
if (mRecycled) {
@@ -600,10 +702,13 @@ public class TypedArray {
return TypedValue.complexToDimensionPixelSize(
data[index+AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ final TypedValue value = mValue;
+ getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
- throw new RuntimeException(getPositionDescription()
+ throw new UnsupportedOperationException(getPositionDescription()
+ ": You must supply a " + name + " attribute.");
}
@@ -615,10 +720,11 @@ public class TypedArray {
*
* @param index Index of the attribute to retrieve.
* @param defValue The default value to return if this attribute is not
- * default or contains the wrong type of data.
+ * default or contains the wrong type of data.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels.
+ * metric and truncated to integer pixels.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getLayoutDimension(int index, int defValue) {
if (mRecycled) {
@@ -633,14 +739,14 @@ public class TypedArray {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
}
return defValue;
}
/**
- * Retrieve a fractional unit attribute at <var>index</var>.
+ * Retrieves a fractional unit attribute at <var>index</var>.
*
* @param index Index of attribute to retrieve.
* @param base The base value of this fraction. In other words, a
@@ -652,7 +758,10 @@ public class TypedArray {
* not a resource.
*
* @return Attribute fractional value multiplied by the appropriate
- * base value, or defValue if not defined.
+ * base value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a fraction.
*/
public float getFraction(int index, int base, int pbase, float defValue) {
if (mRecycled) {
@@ -668,7 +777,10 @@ public class TypedArray {
return TypedValue.complexToFraction(
data[index+AssetManager.STYLE_DATA], base, pbase);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ final TypedValue value = mValue;
+ getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to fraction: type=0x"
@@ -676,7 +788,7 @@ public class TypedArray {
}
/**
- * Retrieve the resource identifier for the attribute at
+ * Retrieves the resource identifier for the attribute at
* <var>index</var>. Note that attribute resource as resolved when
* the overall {@link TypedArray} object is retrieved. As a
* result, this function will return the resource identifier of the
@@ -688,7 +800,9 @@ public class TypedArray {
* not a resource.
*
* @return Attribute resource identifier, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
+ @AnyRes
public int getResourceId(int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
@@ -706,13 +820,15 @@ public class TypedArray {
}
/**
- * Retrieve the theme attribute resource identifier for the attribute at
+ * Retrieves the theme attribute resource identifier for the attribute at
* <var>index</var>.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or not a
- * resource.
+ * resource.
+ *
* @return Theme attribute resource identifier, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
* @hide
*/
public int getThemeAttributeId(int index, int defValue) {
@@ -730,10 +846,16 @@ public class TypedArray {
/**
* Retrieve the Drawable for the attribute at <var>index</var>.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a color or drawable resource.
*
* @param index Index of attribute to retrieve.
*
- * @return Drawable for the attribute, or null if not defined.
+ * @return Drawable for the attribute, or {@code null} if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a color or drawable resource.
*/
@Nullable
public Drawable getDrawable(int index) {
@@ -744,7 +866,8 @@ public class TypedArray {
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
return mResources.loadDrawable(value, value.resourceId, mTheme);
}
@@ -756,10 +879,15 @@ public class TypedArray {
* This gets the resource ID of the selected attribute, and uses
* {@link Resources#getTextArray Resources.getTextArray} of the owning
* Resources object to retrieve its String[].
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a text array resource.
*
* @param index Index of attribute to retrieve.
*
- * @return CharSequence[] for the attribute, or null if not defined.
+ * @return CharSequence[] for the attribute, or {@code null} if not
+ * defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public CharSequence[] getTextArray(int index) {
if (mRecycled) {
@@ -780,7 +908,8 @@ public class TypedArray {
* @param outValue TypedValue object in which to place the attribute's
* data.
*
- * @return Returns true if the value was retrieved, else false.
+ * @return {@code true} if the value was retrieved, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean getValue(int index, TypedValue outValue) {
if (mRecycled) {
@@ -794,7 +923,9 @@ public class TypedArray {
* Returns the type of attribute at the specified index.
*
* @param index Index of attribute whose type to retrieve.
+ *
* @return Attribute type.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getType(int index) {
if (mRecycled) {
@@ -814,6 +945,7 @@ public class TypedArray {
* @param index Index of attribute to retrieve.
*
* @return True if the attribute has a value, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean hasValue(int index) {
if (mRecycled) {
@@ -834,6 +966,7 @@ public class TypedArray {
* @param index Index of attribute to retrieve.
*
* @return True if the attribute has a value or is empty, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean hasValueOrEmpty(int index) {
if (mRecycled) {
@@ -857,6 +990,7 @@ public class TypedArray {
* @return Returns a TypedValue object if the attribute is defined,
* containing its data; otherwise returns null. (You will not
* receive a TypedValue whose type is TYPE_NULL.)
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public TypedValue peekValue(int index) {
if (mRecycled) {
@@ -872,6 +1006,9 @@ public class TypedArray {
/**
* Returns a message about the parser state suitable for printing error messages.
+ *
+ * @return Human-readable description of current parser state.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public String getPositionDescription() {
if (mRecycled) {
@@ -882,8 +1019,10 @@ public class TypedArray {
}
/**
- * Recycle the TypedArray, to be re-used by a later caller. After calling
+ * Recycles the TypedArray, to be re-used by a later caller. After calling
* this function you must not ever touch the typed array again.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public void recycle() {
if (mRecycled) {
@@ -908,9 +1047,19 @@ public class TypedArray {
* @return an array of length {@link #getIndexCount()} populated with theme
* attributes, or null if there are no theme attributes in the typed
* array
+ * @throws RuntimeException if the TypedArray has already been recycled.
* @hide
*/
+ @Nullable
public int[] extractThemeAttrs() {
+ return extractThemeAttrs(null);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public int[] extractThemeAttrs(@Nullable int[] scrap) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
@@ -922,6 +1071,7 @@ public class TypedArray {
for (int i = 0; i < N; i++) {
final int index = i * AssetManager.STYLE_NUM_ENTRIES;
if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
+ // Not an attribute, ignore.
continue;
}
@@ -930,13 +1080,20 @@ public class TypedArray {
final int attr = data[index + AssetManager.STYLE_DATA];
if (attr == 0) {
- // This attribute is useless!
+ // Useless data, ignore.
continue;
}
+ // Ensure we have a usable attribute array.
if (attrs == null) {
- attrs = new int[N];
+ if (scrap != null && scrap.length == N) {
+ attrs = scrap;
+ Arrays.fill(attrs, 0);
+ } else {
+ attrs = new int[N];
+ }
}
+
attrs[i] = attr;
}
@@ -949,9 +1106,14 @@ public class TypedArray {
*
* @return Returns a mask of the changing configuration parameters, as
* defined by {@link android.content.pm.ActivityInfo}.
+ * @throws RuntimeException if the TypedArray has already been recycled.
* @see android.content.pm.ActivityInfo
*/
public int getChangingConfigurations() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
int changingConfig = 0;
final int[] data = mData;
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index c125544..e61664c 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -16,8 +16,6 @@
package android.database;
-import org.apache.commons.codec.binary.Hex;
-
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
@@ -416,11 +414,33 @@ public class DatabaseUtils {
* @return the collation key in hex format
*/
public static String getHexCollationKey(String name) {
- byte [] arr = getCollationKeyInBytes(name);
- char[] keys = Hex.encodeHex(arr);
+ byte[] arr = getCollationKeyInBytes(name);
+ char[] keys = encodeHex(arr);
return new String(keys, 0, getKeyLen(arr) * 2);
}
+
+ /**
+ * Used building output as Hex
+ */
+ private static final char[] DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ private static char[] encodeHex(byte[] input) {
+ int l = input.length;
+ char[] out = new char[l << 1];
+
+ // two characters form the hex value.
+ for (int i = 0, j = 0; i < l; i++) {
+ out[j++] = DIGITS[(0xF0 & input[i]) >>> 4 ];
+ out[j++] = DIGITS[ 0x0F & input[i] ];
+ }
+
+ return out;
+ }
+
private static int getKeyLen(byte[] arr) {
if (arr[arr.length - 1] != 0) {
return arr.length;
diff --git a/core/java/android/gesture/GestureLibraries.java b/core/java/android/gesture/GestureLibraries.java
index 6d6c156..611d9ab 100644
--- a/core/java/android/gesture/GestureLibraries.java
+++ b/core/java/android/gesture/GestureLibraries.java
@@ -16,6 +16,7 @@
package android.gesture;
+import android.annotation.RawRes;
import android.util.Log;
import static android.gesture.GestureConstants.*;
import android.content.Context;
@@ -44,7 +45,7 @@ public final class GestureLibraries {
return fromFile(context.getFileStreamPath(name));
}
- public static GestureLibrary fromRawResource(Context context, int resourceId) {
+ public static GestureLibrary fromRawResource(Context context, @RawRes int resourceId) {
return new ResourceGestureLibrary(context, resourceId);
}
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
index e1a2a25..e0d454c 100644
--- a/core/java/android/gesture/GestureOverlayView.java
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -16,6 +16,7 @@
package android.gesture;
+import android.annotation.ColorInt;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -204,18 +205,20 @@ public class GestureOverlayView extends FrameLayout {
mOrientation = orientation;
}
- public void setGestureColor(int color) {
+ public void setGestureColor(@ColorInt int color) {
mCertainGestureColor = color;
}
- public void setUncertainGestureColor(int color) {
+ public void setUncertainGestureColor(@ColorInt int color) {
mUncertainGestureColor = color;
}
+ @ColorInt
public int getUncertainGestureColor() {
return mUncertainGestureColor;
}
+ @ColorInt
public int getGestureColor() {
return mCertainGestureColor;
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 310ab76..49f6513 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -44,7 +44,6 @@ import android.view.SurfaceHolder;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index 2bc3dd4..9aede01 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -81,4 +81,6 @@ interface ICameraService
int clientUid,
// Container for an ICamera object
out BinderHolder device);
+
+ int setTorchMode(String CameraId, boolean enabled, IBinder clientBinder);
}
diff --git a/core/java/android/hardware/ICameraServiceListener.aidl b/core/java/android/hardware/ICameraServiceListener.aidl
index c548496..49278b6 100644
--- a/core/java/android/hardware/ICameraServiceListener.aidl
+++ b/core/java/android/hardware/ICameraServiceListener.aidl
@@ -23,4 +23,6 @@ interface ICameraServiceListener
* Keep up-to-date with frameworks/av/include/camera/ICameraServiceListener.h
*/
void onStatusChanged(int status, int cameraId);
+
+ void onTorchStatusChanged(int status, String cameraId);
}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index fa5e9d2..39f4cca 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -833,4 +833,96 @@ public final class Sensor {
+ ", type=" + mType + ", maxRange=" + mMaxRange + ", resolution=" + mResolution
+ ", power=" + mPower + ", minDelay=" + mMinDelay + "}";
}
+
+ /**
+ * Sets the Type associated with the sensor.
+ * NOTE: to be used only by native bindings in SensorManager.
+ *
+ * This allows interned static strings to be used across all representations of the Sensor. If
+ * a sensor type is not referenced here, it will still be interned by the native SensorManager.
+ *
+ * @return {@code true} if the StringType was successfully set, {@code false} otherwise.
+ */
+ private boolean setType(int value) {
+ mType = value;
+ switch (mType) {
+ case TYPE_ACCELEROMETER:
+ mStringType = STRING_TYPE_ACCELEROMETER;
+ return true;
+ case TYPE_AMBIENT_TEMPERATURE:
+ mStringType = STRING_TYPE_AMBIENT_TEMPERATURE;
+ return true;
+ case TYPE_GAME_ROTATION_VECTOR:
+ mStringType = STRING_TYPE_GAME_ROTATION_VECTOR;
+ return true;
+ case TYPE_GEOMAGNETIC_ROTATION_VECTOR:
+ mStringType = STRING_TYPE_GEOMAGNETIC_ROTATION_VECTOR;
+ return true;
+ case TYPE_GLANCE_GESTURE:
+ mStringType = STRING_TYPE_GLANCE_GESTURE;
+ return true;
+ case TYPE_GRAVITY:
+ mStringType = STRING_TYPE_GRAVITY;
+ return true;
+ case TYPE_GYROSCOPE:
+ mStringType = STRING_TYPE_GYROSCOPE;
+ return true;
+ case TYPE_GYROSCOPE_UNCALIBRATED:
+ mStringType = STRING_TYPE_GYROSCOPE_UNCALIBRATED;
+ return true;
+ case TYPE_HEART_RATE:
+ mStringType = STRING_TYPE_HEART_RATE;
+ return true;
+ case TYPE_LIGHT:
+ mStringType = STRING_TYPE_LIGHT;
+ return true;
+ case TYPE_LINEAR_ACCELERATION:
+ mStringType = STRING_TYPE_LINEAR_ACCELERATION;
+ return true;
+ case TYPE_MAGNETIC_FIELD:
+ mStringType = STRING_TYPE_MAGNETIC_FIELD;
+ return true;
+ case TYPE_MAGNETIC_FIELD_UNCALIBRATED:
+ mStringType = STRING_TYPE_MAGNETIC_FIELD_UNCALIBRATED;
+ return true;
+ case TYPE_PICK_UP_GESTURE:
+ mStringType = STRING_TYPE_PICK_UP_GESTURE;
+ return true;
+ case TYPE_PRESSURE:
+ mStringType = STRING_TYPE_PRESSURE;
+ return true;
+ case TYPE_PROXIMITY:
+ mStringType = STRING_TYPE_PROXIMITY;
+ return true;
+ case TYPE_RELATIVE_HUMIDITY:
+ mStringType = STRING_TYPE_RELATIVE_HUMIDITY;
+ return true;
+ case TYPE_ROTATION_VECTOR:
+ mStringType = STRING_TYPE_ROTATION_VECTOR;
+ return true;
+ case TYPE_SIGNIFICANT_MOTION:
+ mStringType = STRING_TYPE_SIGNIFICANT_MOTION;
+ return true;
+ case TYPE_STEP_COUNTER:
+ mStringType = STRING_TYPE_STEP_COUNTER;
+ return true;
+ case TYPE_STEP_DETECTOR:
+ mStringType = STRING_TYPE_STEP_DETECTOR;
+ return true;
+ case TYPE_TILT_DETECTOR:
+ mStringType = SENSOR_STRING_TYPE_TILT_DETECTOR;
+ return true;
+ case TYPE_WAKE_GESTURE:
+ mStringType = STRING_TYPE_WAKE_GESTURE;
+ return true;
+ case TYPE_ORIENTATION:
+ mStringType = STRING_TYPE_ORIENTATION;
+ return true;
+ case TYPE_TEMPERATURE:
+ mStringType = STRING_TYPE_TEMPERATURE;
+ return true;
+ default:
+ return false;
+ }
+ }
}
diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java
index 91ef6ca..84e9912 100644
--- a/core/java/android/hardware/camera2/CameraAccessException.java
+++ b/core/java/android/hardware/camera2/CameraAccessException.java
@@ -28,16 +28,14 @@ import android.util.AndroidException;
*/
public class CameraAccessException extends AndroidException {
/**
- * The camera device is in use already
- * @hide
+ * The camera device is in use already.
*/
public static final int CAMERA_IN_USE = 4;
/**
- * The system-wide limit for number of open cameras has been reached,
- * and more camera devices cannot be opened until previous instances are
- * closed.
- * @hide
+ * The system-wide limit for number of open cameras or camera resources has
+ * been reached, and more camera devices cannot be opened or torch mode
+ * cannot be turned on until previous instances are closed.
*/
public static final int MAX_CAMERAS_IN_USE = 5;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 7cf8fb0..a0217c2 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -638,6 +638,44 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<android.hardware.camera2.params.HighSpeedVideoConfiguration[]>("android.control.availableHighSpeedVideoConfigurations", android.hardware.camera2.params.HighSpeedVideoConfiguration[].class);
/**
+ * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock}</p>
+ * <p>LIMITED or FULL devices will always list <code>true</code></p>
+ * <p>This key is available on all devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_LOCK
+ */
+ @PublicKey
+ public static final Key<Boolean> CONTROL_AE_LOCK_AVAILABLE =
+ new Key<Boolean>("android.control.aeLockAvailable", boolean.class);
+
+ /**
+ * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}</p>
+ * <p>LIMITED or FULL devices will always list <code>true</code></p>
+ * <p>This key is available on all devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AWB_LOCK
+ */
+ @PublicKey
+ public static final Key<Boolean> CONTROL_AWB_LOCK_AVAILABLE =
+ new Key<Boolean>("android.control.awbLockAvailable", boolean.class);
+
+ /**
+ * <p>List of control modes for {@link CaptureRequest#CONTROL_MODE android.control.mode} that are supported by this camera
+ * device.</p>
+ * <p>This list contains control modes that can be set for the camera device.
+ * LEGACY mode devices will always support AUTO mode. LIMITED and FULL
+ * devices will always support OFF, AUTO modes.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#CONTROL_MODE android.control.mode}</p>
+ * <p>This key is available on all devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_MODE
+ */
+ @PublicKey
+ public static final Key<int[]> CONTROL_AVAILABLE_MODES =
+ new Key<int[]>("android.control.availableModes", int[].class);
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; all devices will list FAST.</p>
@@ -889,10 +927,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <ul>
* <li>{@link #LENS_FACING_FRONT FRONT}</li>
* <li>{@link #LENS_FACING_BACK BACK}</li>
+ * <li>{@link #LENS_FACING_EXTERNAL EXTERNAL}</li>
* </ul></p>
* <p>This key is available on all devices.</p>
* @see #LENS_FACING_FRONT
* @see #LENS_FACING_BACK
+ * @see #LENS_FACING_EXTERNAL
*/
@PublicKey
public static final Key<Integer> LENS_FACING =
@@ -1069,8 +1109,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* android.scaler.availableInputOutputFormatsMap. When using an
* input stream, there must be at least one output stream
* configured to to receive the reprocessed images.</p>
+ * <p>When an input stream and some output streams are used in a reprocessing request,
+ * only the input buffer will be used to produce these output stream buffers, and a
+ * new sensor image will not be captured.</p>
* <p>For example, for Zero Shutter Lag (ZSL) still capture use case, the input
- * stream image format will be RAW_OPAQUE, the associated output stream image format
+ * stream image format will be OPAQUE, the associated output stream image format
* should be JPEG.</p>
* <p><b>Range of valid values:</b><br></p>
* <p>0 or 1.</p>
@@ -1080,8 +1123,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
- * @hide
*/
+ @PublicKey
public static final Key<Integer> REQUEST_MAX_NUM_INPUT_STREAMS =
new Key<Integer>("android.request.maxNumInputStreams", int.class);
@@ -1157,8 +1200,10 @@ 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_OPAQUE_REPROCESSING OPAQUE_REPROCESSING}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS READ_SENSOR_SETTINGS}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING YUV_REPROCESSING}</li>
* </ul></p>
* <p>This key is available on all devices.</p>
*
@@ -1167,8 +1212,10 @@ 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_OPAQUE_REPROCESSING
* @see #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS
* @see #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING
*/
@PublicKey
public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES =
@@ -1345,10 +1392,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>The mapping of image formats that are supported by this
* camera device for input streams, to their corresponding output formats.</p>
* <p>All camera devices with at least 1
- * android.request.maxNumInputStreams will have at least one
+ * {@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} will have at least one
* available input format.</p>
* <p>The camera device will support the following map of formats,
- * if its dependent capability is supported:</p>
+ * if its dependent capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}) is supported:</p>
* <table>
* <thead>
* <tr>
@@ -1359,45 +1406,42 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* </thead>
* <tbody>
* <tr>
- * <td align="left">RAW_OPAQUE</td>
+ * <td align="left">OPAQUE</td>
* <td align="left">JPEG</td>
- * <td align="left">ZSL</td>
+ * <td align="left">OPAQUE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">RAW_OPAQUE</td>
+ * <td align="left">OPAQUE</td>
* <td align="left">YUV_420_888</td>
- * <td align="left">ZSL</td>
- * </tr>
- * <tr>
- * <td align="left">RAW_OPAQUE</td>
- * <td align="left">RAW16</td>
- * <td align="left">RAW</td>
+ * <td align="left">OPAQUE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">RAW16</td>
* <td align="left">YUV_420_888</td>
- * <td align="left">RAW</td>
+ * <td align="left">JPEG</td>
+ * <td align="left">YUV_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">RAW16</td>
- * <td align="left">JPEG</td>
- * <td align="left">RAW</td>
+ * <td align="left">YUV_420_888</td>
+ * <td align="left">YUV_420_888</td>
+ * <td align="left">YUV_REPROCESSING</td>
* </tr>
* </tbody>
* </table>
- * <p>For ZSL-capable camera devices, using the RAW_OPAQUE format
+ * <p>OPAQUE refers to a device-internal format that is not directly application-visible.
+ * An OPAQUE input or output surface can be acquired by
+ * OpaqueImageRingBufferQueue#getInputSurface() or
+ * OpaqueImageRingBufferQueue#getOutputSurface().
+ * For a OPAQUE_REPROCESSING-capable camera device, using the OPAQUE format
* as either input or output will never hurt maximum frame rate (i.e.
- * StreamConfigurationMap#getOutputStallDuration(int,Size)
- * for a <code>format =</code> RAW_OPAQUE is always 0).</p>
+ * StreamConfigurationMap#getOutputStallDuration(klass,Size) is always 0),
+ * where klass is android.media.OpaqueImageRingBufferQueue.class.</p>
* <p>Attempting to configure an input stream with output streams not
* listed as available in this map is not valid.</p>
* <p>TODO: typedef to ReprocessFormatMap</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
- * <p><b>Full capability</b> -
- * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
- * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
- * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
* @hide
*/
public static final Key<int[]> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP =
@@ -1899,6 +1943,23 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<Integer>("android.sensor.info.timestampSource", int.class);
/**
+ * <p>Whether the RAW images output from this camera device are subject to
+ * lens shading correction.</p>
+ * <p>If TRUE, all images produced by the camera device in the RAW image formats will
+ * have lens shading correction already applied to it. If FALSE, the images will
+ * not be adjusted for lens shading correction.
+ * See {@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_RAW android.request.maxNumOutputRaw} for a list of RAW image formats.</p>
+ * <p>This key will be <code>null</code> for all devices do not report this information.
+ * Devices with RAW capability will always report this information in this key.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_RAW
+ */
+ @PublicKey
+ public static final Key<Boolean> SENSOR_INFO_LENS_SHADING_APPLIED =
+ new Key<Boolean>("android.sensor.info.lensShadingApplied", boolean.class);
+
+ /**
* <p>The standard reference illuminant used as the scene light source when
* calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1},
* {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and
@@ -2189,6 +2250,22 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<int[]>("android.sensor.availableTestPatternModes", int[].class);
/**
+ * <p>List of lens shading modes for {@link CaptureRequest#SHADING_MODE android.shading.mode} that are supported by this camera device.</p>
+ * <p>This list contains lens shading modes that can be set for the camera device.
+ * Camera devices that support the MANUAL_POST_PROCESSING capability will always
+ * list OFF and FAST mode. This includes all FULL level devices.
+ * LEGACY devices will always only support FAST mode.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#SHADING_MODE android.shading.mode}</p>
+ * <p>This key is available on all devices.</p>
+ *
+ * @see CaptureRequest#SHADING_MODE
+ */
+ @PublicKey
+ public static final Key<int[]> SHADING_AVAILABLE_MODES =
+ new Key<int[]>("android.shading.availableModes", int[].class);
+
+ /**
* <p>List of face detection modes for {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} that are
* supported by this camera device.</p>
* <p>OFF is always supported.</p>
@@ -2232,6 +2309,23 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<boolean[]>("android.statistics.info.availableHotPixelMapModes", boolean[].class);
/**
+ * <p>List of lens shading map output modes for {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} that
+ * are supported by this camera device.</p>
+ * <p>If no lens shading map output is available for this camera device, this key will
+ * contain only OFF.</p>
+ * <p>ON is always supported on devices with the RAW capability.
+ * LEGACY mode devices will always only support OFF.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode}</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
+ */
+ @PublicKey
+ public static final Key<byte[]> STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES =
+ new Key<byte[]>("android.statistics.info.availableLensShadingMapModes", byte[].class);
+
+ /**
* <p>Maximum number of supported points in the
* tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p>
* <p>If the actual number of points provided by the application (in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}*) is
@@ -2255,8 +2349,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
/**
* <p>List of tonemapping modes for {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} that are supported by this camera
* device.</p>
- * <p>Camera devices that support the MANUAL_POST_PROCESSING capability will always list
- * CONTRAST_CURVE and FAST. This includes all FULL level devices.</p>
+ * <p>Camera devices that support the MANUAL_POST_PROCESSING capability will always contain
+ * at least one of below mode combinations:</p>
+ * <ul>
+ * <li>CONTRAST_CURVE and FAST</li>
+ * <li>GAMMA_VALUE, PRESET_CURVE, and FAST</li>
+ * </ul>
+ * <p>This includes all FULL level devices.</p>
* <p><b>Range of valid values:</b><br>
* Any value listed in {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
@@ -2394,6 +2493,83 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
public static final Key<Integer> SYNC_MAX_LATENCY =
new Key<Integer>("android.sync.maxLatency", int.class);
+ /**
+ * <p>The available depth dataspace stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).</p>
+ * <p>These are output stream configurations for use with
+ * dataSpace HAL_DATASPACE_DEPTH. The configurations are
+ * listed as <code>(format, width, height, input?)</code> tuples.</p>
+ * <p>Only devices that support depth output for at least
+ * the HAL_PIXEL_FORMAT_Y16 dense depth map may include
+ * this entry.</p>
+ * <p>A device that also supports the HAL_PIXEL_FORMAT_BLOB
+ * sparse depth point cloud must report a single entry for
+ * the format in this list as <code>(HAL_PIXEL_FORMAT_BLOB,
+ * android.depth.maxDepthSamples, 1, OUTPUT)</code> in addition to
+ * the entries for HAL_PIXEL_FORMAT_Y16.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * 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 CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.depth.availableDepthStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for depth output formats.</p>
+ * <p>This should correspond to the frame duration when only that
+ * stream is active, with all processing (typically in android.*.mode)
+ * set to either OFF or FAST.</p>
+ * <p>When multiple streams are used in a request, the minimum frame
+ * duration will be max(individual stream min durations).</p>
+ * <p>The minimum frame duration of a stream (of a particular format, size)
+ * is the same regardless of whether the stream is input or output.</p>
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+ * android.scaler.availableStallDurations for more details about
+ * calculating the max frame rate.</p>
+ * <p>(Keep in sync with
+ * StreamConfigurationMap#getOutputMinFrameDuration)</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * 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 CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDepthMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * format/size combination for depth streams.</p>
+ * <p>A stall duration is how much extra time would get added
+ * to the normal minimum frame duration for a repeating request
+ * that has streams with non-zero stall.</p>
+ * <p>This functions similarly to
+ * android.scaler.availableStallDurations for depth
+ * streams.</p>
+ * <p>All depth output stream formats may have a nonzero stall
+ * duration.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * 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 CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.depth.availableDepthStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index bec9489..fd4cf3c 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -17,7 +17,7 @@
package android.hardware.camera2;
import android.hardware.camera2.params.StreamConfigurationMap;
-import android.graphics.ImageFormat;
+import android.hardware.camera2.params.OutputConfiguration;
import android.os.Handler;
import android.view.Surface;
@@ -381,6 +381,20 @@ public abstract class CameraDevice implements AutoCloseable {
throws CameraAccessException;
/**
+ * <p>Create a new camera capture session by providing the target output set of Surfaces and
+ * its corresponding surface configuration to the camera device.</p>
+ *
+ * @see #createCaptureSession
+ * @see OutputConfiguration
+ *
+ * @hide
+ */
+ public abstract void createCaptureSessionByOutputConfiguration(
+ List<OutputConfiguration> outputConfigurations,
+ CameraCaptureSession.StateCallback callback, Handler handler)
+ throws CameraAccessException;
+
+ /**
* <p>Create a {@link CaptureRequest.Builder} for new capture requests,
* initialized with template for a target use case. The settings are chosen
* to be the best options for the specific camera device, so it is not
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index a25b94a..b513379 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -27,6 +27,7 @@ import android.hardware.camera2.utils.CameraServiceBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.hardware.camera2.utils.BinderHolder;
import android.os.IBinder;
+import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -109,8 +110,11 @@ public final class CameraManager {
* 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}.
+ * @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}.
+ *
+ * @throws IllegalArgumentException if the handler is {@code null} but the current thread has
+ * no looper.
*/
public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) {
if (handler == null) {
@@ -138,6 +142,52 @@ public final class CameraManager {
}
/**
+ * Register a callback to be notified about torch mode status.
+ *
+ * <p>Registering the same callback again will replace the handler with the
+ * new one provided.</p>
+ *
+ * <p>The first time a callback is registered, it is immediately called
+ * with the torch mode 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 torch mode status 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}.
+ *
+ * @throws IllegalArgumentException if the handler is {@code null} but the current thread has
+ * no looper.
+ */
+ public void registerTorchCallback(TorchCallback callback, Handler handler) {
+ if (handler == null) {
+ Looper looper = Looper.myLooper();
+ if (looper == null) {
+ throw new IllegalArgumentException(
+ "No handler given, and current thread has no looper!");
+ }
+ handler = new Handler(looper);
+ }
+ CameraManagerGlobal.get().registerTorchCallback(callback, handler);
+ }
+
+ /**
+ * Remove a previously-added callback; the callback will no longer receive torch mode status
+ * callbacks.
+ *
+ * <p>Removing a callback that isn't registered has no effect.</p>
+ *
+ * @param callback The callback to remove from the notification list
+ */
+ public void unregisterTorchCallback(TorchCallback callback) {
+ CameraManagerGlobal.get().unregisterTorchCallback(callback);
+ }
+
+ /**
* <p>Query the capabilities of a camera device. These capabilities are
* immutable for a given camera.</p>
*
@@ -384,6 +434,49 @@ public final class CameraManager {
}
/**
+ * Set the flash unit's torch mode of the camera of the given ID without opening the camera
+ * device.
+ *
+ * <p>Use {@link #getCameraIdList} to get the list of available camera devices and use
+ * {@link #getCameraCharacteristics} to check whether the camera device has a flash unit.
+ * Note that even if a camera device has a flash unit, turning on the torch mode may fail
+ * if the camera device or other camera resources needed to turn on the torch mode are in use.
+ * </p>
+ *
+ * <p> If {@link #setTorchMode} is called to turn on or off the torch mode successfully,
+ * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked.
+ * However, even if turning on the torch mode is successful, the application does not have the
+ * exclusive ownership of the flash unit or the camera device. The torch mode will be turned
+ * off and becomes unavailable when the camera device that the flash unit belongs to becomes
+ * unavailable or when other camera resources to keep the torch on become unavailable (
+ * {@link CameraManager.TorchCallback#onTorchModeUnavailable} will be invoked). Also,
+ * other applications are free to call {@link #setTorchMode} to turn off the torch mode (
+ * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked). If the latest
+ * application that turned on the torch mode exits, the torch mode will be turned off.
+ *
+ * @param cameraId
+ * The unique identifier of the camera device that the flash unit belongs to.
+ * @param enabled
+ * The desired state of the torch mode for the target camera device. Set to
+ * {@code true} to turn on the torch mode. Set to {@code false} to turn off the
+ * torch mode.
+ *
+ * @throws CameraAccessException if it failed to access the flash unit.
+ * {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device
+ * is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if
+ * other camera resources needed to turn on the torch mode are in use.
+ * {@link CameraAccessException#CAMERA_DISCONNECTED} will be thrown if camera
+ * service is not available.
+ *
+ * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
+ * or previously available camera device, or the camera device doesn't have a
+ * flash unit.
+ */
+ public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException {
+ CameraManagerGlobal.get().setTorchMode(cameraId, enabled);
+ }
+
+ /**
* A callback for camera devices becoming available or
* unavailable to open.
*
@@ -428,6 +521,63 @@ public final class CameraManager {
}
/**
+ * A callback for camera flash torch modes becoming unavailable, disabled, or enabled.
+ *
+ * <p>The torch mode becomes unavailable when the camera device it belongs to becomes
+ * unavailable or other camera resouces it needs become busy due to other higher priority
+ * camera activities. The torch mode becomes disabled when it was turned off or when the camera
+ * device it belongs to is no longer in use and other camera resources it needs are no longer
+ * busy. A camera's torch mode is turned off when an application calls {@link #setTorchMode} to
+ * turn off the camera's torch mode, or when an application turns on another camera's torch mode
+ * if keeping multiple torch modes on simultaneously is not supported. The torch mode becomes
+ * enabled when it is turned on via {@link #setTorchMode}.</p>
+ *
+ * <p>The torch mode is available to set via {@link #setTorchMode} only when it's in a disabled
+ * or enabled state.</p>
+ *
+ * <p>Extend this callback and pass an instance of the subclass to
+ * {@link CameraManager#registerTorchCallback} to be notified of such status changes.
+ * </p>
+ *
+ * @see registerTorchCallback
+ */
+ public static abstract class TorchCallback {
+ /**
+ * A camera's torch mode has become unavailable to set via {@link #setTorchMode}.
+ *
+ * <p>If torch mode was previously turned on by calling {@link #setTorchMode}, it will be
+ * turned off before {@link CameraManager.TorchCallback#onTorchModeUnavailable} is
+ * invoked. {@link #setTorchMode} will fail until the torch mode has entered a disabled or
+ * enabled state again.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param cameraId The unique identifier of the camera whose torch mode has become
+ * unavailable.
+ */
+ public void onTorchModeUnavailable(String cameraId) {
+ // default empty implementation
+ }
+
+ /**
+ * A camera's torch mode has become enabled or disabled and can be changed via
+ * {@link #setTorchMode}.
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param cameraId The unique identifier of the camera whose torch mode has been changed.
+ *
+ * @param enabled The state that the torch mode of the camera has been changed to.
+ * {@code true} when the torch mode has become on and available to be turned
+ * off. {@code false} when the torch mode has becomes off and available to
+ * be turned on.
+ */
+ public void onTorchModeChanged(String cameraId, boolean enabled) {
+ // default empty implementation
+ }
+ }
+
+ /**
* 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>
@@ -583,6 +733,27 @@ public final class CameraManager {
private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
new ArrayMap<AvailabilityCallback, Handler>();
+ // Keep up-to-date with ICameraServiceListener.h
+
+ // torch mode has become not available to set via setTorchMode().
+ public static final int TORCH_STATUS_NOT_AVAILABLE = 0;
+ // torch mode is off and available to be turned on via setTorchMode().
+ public static final int TORCH_STATUS_AVAILABLE_OFF = 1;
+ // torch mode is on and available to be turned off via setTorchMode().
+ public static final int TORCH_STATUS_AVAILABLE_ON = 2;
+
+ // End enums shared with ICameraServiceListener.h
+
+ // torch client binder to set the torch mode with.
+ private Binder mTorchClientBinder = new Binder();
+
+ // Camera ID -> Torch status map
+ private final ArrayMap<String, Integer> mTorchStatus = new ArrayMap<String, Integer>();
+
+ // Registered torch callbacks and their handlers
+ private final ArrayMap<TorchCallback, Handler> mTorchCallbackMap =
+ new ArrayMap<TorchCallback, Handler>();
+
private final Object mLock = new Object();
// Access only through getCameraService to deal with binder death
@@ -668,15 +839,46 @@ public final class CameraManager {
}
}
+ public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException {
+ synchronized(mLock) {
+
+ if (cameraId == null) {
+ throw new IllegalArgumentException("cameraId was null");
+ }
+
+ ICameraService cameraService = getCameraService();
+ if (cameraService == null) {
+ throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable");
+ }
+
+ try {
+ int status = cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder);
+ } catch(CameraRuntimeException e) {
+ int problem = e.getReason();
+ switch (problem) {
+ case CameraAccessException.CAMERA_ERROR:
+ throw new IllegalArgumentException(
+ "the camera device doesn't have a flash unit.");
+ default:
+ throw e.asChecked();
+ }
+ } catch (RemoteException e) {
+ throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+ "Camera service is currently unavailable");
+ }
+ }
+ }
+
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());
+ case CameraAccessException.CAMERA_DISCONNECTED:
+ String errorMsg = CameraAccessException.getDefaultMessage(problem);
+ Log.w(TAG, msg + ": " + errorMsg);
+ break;
+ default:
+ throw new IllegalStateException(msg, e.asChecked());
}
}
@@ -701,6 +903,17 @@ public final class CameraManager {
}
}
+ private boolean validTorchStatus(int status) {
+ switch (status) {
+ case TORCH_STATUS_NOT_AVAILABLE:
+ case TORCH_STATUS_AVAILABLE_ON:
+ case TORCH_STATUS_AVAILABLE_OFF:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private void postSingleUpdate(final AvailabilityCallback callback, final Handler handler,
final String id, final int status) {
if (isAvailable(status)) {
@@ -722,6 +935,32 @@ public final class CameraManager {
}
}
+ private void postSingleTorchUpdate(final TorchCallback callback, final Handler handler,
+ final String id, final int status) {
+ switch(status) {
+ case TORCH_STATUS_AVAILABLE_ON:
+ case TORCH_STATUS_AVAILABLE_OFF:
+ handler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ callback.onTorchModeChanged(id, status ==
+ TORCH_STATUS_AVAILABLE_ON);
+ }
+ });
+ break;
+ default:
+ handler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ callback.onTorchModeUnavailable(id);
+ }
+ });
+ break;
+ }
+ }
+
/**
* Send the state of all known cameras to the provided listener, to initialize
* the listener's knowledge of camera state.
@@ -791,6 +1030,44 @@ public final class CameraManager {
}
} // onStatusChangedLocked
+ private void updateTorchCallbackLocked(TorchCallback callback, Handler handler) {
+ for (int i = 0; i < mTorchStatus.size(); i++) {
+ String id = mTorchStatus.keyAt(i);
+ Integer status = mTorchStatus.valueAt(i);
+ postSingleTorchUpdate(callback, handler, id, status);
+ }
+ }
+
+ private void onTorchStatusChangedLocked(int status, String id) {
+ if (DEBUG) {
+ Log.v(TAG,
+ String.format("Camera id %s has torch status changed to 0x%x", id, status));
+ }
+
+ if (!validTorchStatus(status)) {
+ Log.e(TAG, String.format("Ignoring invalid device %s torch status 0x%x", id,
+ status));
+ return;
+ }
+
+ Integer oldStatus = mTorchStatus.put(id, status);
+ if (oldStatus != null && oldStatus == status) {
+ if (DEBUG) {
+ Log.v(TAG, String.format(
+ "Torch status changed to 0x%x, which is what it already was",
+ status));
+ }
+ return;
+ }
+
+ final int callbackCount = mTorchCallbackMap.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final Handler handler = mTorchCallbackMap.valueAt(i);
+ final TorchCallback callback = mTorchCallbackMap.keyAt(i);
+ postSingleTorchUpdate(callback, handler, id, status);
+ }
+ } // onTorchStatusChangedLocked
+
/**
* Register a callback to be notified about camera device availability with the
* global listener singleton.
@@ -820,6 +1097,22 @@ public final class CameraManager {
}
}
+ public void registerTorchCallback(TorchCallback callback, Handler handler) {
+ synchronized(mLock) {
+ Handler oldHandler = mTorchCallbackMap.put(callback, handler);
+ // For new callbacks, provide initial torch information
+ if (oldHandler == null) {
+ updateTorchCallbackLocked(callback, handler);
+ }
+ }
+ }
+
+ public void unregisterTorchCallback(TorchCallback callback) {
+ synchronized(mLock) {
+ mTorchCallbackMap.remove(callback);
+ }
+ }
+
/**
* Callback from camera service notifying the process about camera availability changes
*/
@@ -830,6 +1123,13 @@ public final class CameraManager {
}
}
+ @Override
+ public void onTorchStatusChanged(int status, String cameraId) throws RemoteException {
+ synchronized (mLock) {
+ onTorchStatusChangedLocked(status, cameraId);
+ }
+ }
+
/**
* Listener for camera service death.
*
@@ -844,9 +1144,9 @@ public final class CameraManager {
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.
+ // Tell listeners that the cameras and torch modes 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.
@@ -854,6 +1154,11 @@ public final class CameraManager {
String cameraId = mDeviceStatus.keyAt(i);
onStatusChangedLocked(STATUS_PRESENT, cameraId);
}
+ for (int i = 0; i < mTorchStatus.size(); i++) {
+ String cameraId = mTorchStatus.keyAt(i);
+ onTorchStatusChangedLocked(TORCH_STATUS_AVAILABLE_OFF, cameraId);
+ }
+
}
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 1b10858..7f901c8 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -288,6 +288,13 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int LENS_FACING_BACK = 1;
+ /**
+ * <p>The camera device is an external camera, and has no fixed facing relative to the
+ * device's screen.</p>
+ * @see CameraCharacteristics#LENS_FACING
+ */
+ public static final int LENS_FACING_EXTERNAL = 2;
+
//
// Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
//
@@ -367,13 +374,19 @@ public abstract class CameraMetadata<TKey> {
* The camera device supports basic manual control of the image post-processing
* stages. This means the following controls are guaranteed to be supported:</p>
* <ul>
- * <li>Manual tonemap control<ul>
+ * <li>
+ * <p>Manual tonemap control</p>
+ * <ul>
* <li>{@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}</li>
* <li>{@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</li>
* <li>{@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</li>
+ * <li>android.tonemap.gamma</li>
+ * <li>android.tonemap.presetCurve</li>
* </ul>
* </li>
- * <li>Manual white balance control<ul>
+ * <li>
+ * <p>Manual white balance control</p>
+ * <ul>
* <li>{@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}</li>
* <li>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}</li>
* </ul>
@@ -432,23 +445,40 @@ public abstract class CameraMetadata<TKey> {
public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3;
/**
- * <p>The camera device supports the Zero Shutter Lag use case.</p>
+ * <p>The camera device supports the Zero Shutter Lag reprocessing use case.</p>
* <ul>
- * <li>At least one input stream can be used.</li>
- * <li>RAW_OPAQUE is supported as an output/input format</li>
- * <li>Using RAW_OPAQUE does not cause a frame rate drop
+ * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
+ * <li>OPAQUE is supported as an output/input format, that is,
+ * StreamConfigurationMap#getOutputSizes(klass) and
+ * StreamConfigurationMap#getInputSizes(klass) return non empty Size[] and have common
+ * sizes, where klass is android.media.OpaqueImageRingBufferQueue.class. See
+ * android.scaler.availableInputOutputFormatsMap for detailed information about
+ * OPAQUE format.</li>
+ * <li>android.scaler.availableInputOutputFormatsMap has the required map entries.</li>
+ * <li>Using OPAQUE does not cause a frame rate drop
* relative to the sensor's maximum capture rate (at that
- * resolution).</li>
- * <li>RAW_OPAQUE will be reprocessable into both YUV_420_888
+ * resolution), see android.scaler.availableInputOutputFormatsMap for more details.</li>
+ * <li>OPAQUE will be reprocessable into both YUV_420_888
* and JPEG formats.</li>
- * <li>The maximum available resolution for RAW_OPAQUE streams
+ * <li>The maximum available resolution for OPAQUE streams
* (both input/output) will match the maximum available
* resolution of JPEG streams.</li>
+ * <li>Only below controls are effective for reprocessing requests and
+ * will be present in capture results, other controls in reprocess
+ * requests will be ignored by the camera device.<ul>
+ * <li>android.jpeg.*</li>
+ * <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li>
+ * <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li>
+ * </ul>
+ * </li>
* </ul>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
- * @hide
*/
- public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4;
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING = 4;
/**
* <p>The camera device supports accurately reporting the sensor settings for many of
@@ -508,6 +538,45 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6;
+ /**
+ * <p>The camera device supports the YUV420_888 reprocessing use case, similar as
+ * OPAQUE_REPROCESSING, This capability requires the camera device to support the
+ * following:</p>
+ * <ul>
+ * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
+ * <li>YUV420_888 is supported as a common format for both input and output, that is,
+ * StreamConfigurationMap#getOutputSizes(YUV420_888) and
+ * StreamConfigurationMap#getInputSizes(YUV420_888) return non empty Size[] and have
+ * common sizes.</li>
+ * <li>android.scaler.availableInputOutputFormatsMap has the required map entries.</li>
+ * <li>Using YUV420_888 does not cause a frame rate drop
+ * relative to the sensor's maximum capture rate (at that
+ * resolution), see android.scaler.availableInputOutputFormatsMap for more details.</li>
+ * <li>YUV420_888 will be reprocessable into both YUV_420_888
+ * and JPEG formats.</li>
+ * <li>The maximum available resolution for YUV420_888 streams
+ * (both input/output) will match the maximum available
+ * resolution of JPEG streams.</li>
+ * <li>Only the below controls are effective for reprocessing requests and will be
+ * present in capture results. The reprocess requests are from the original capture
+ * results that are assocaited with the intermidate YUV420_888 output buffers.
+ * All other controls in the reprocess requests will be ignored by the camera device.<ul>
+ * <li>android.jpeg.*</li>
+ * <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li>
+ * <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li>
+ * <li>{@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7;
+
//
// Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
//
@@ -966,6 +1035,14 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1;
+ /**
+ * <p>The camera device will cancel any currently active or completed
+ * precapture metering sequence, the auto-exposure routine will return to its
+ * initial state.</p>
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ */
+ public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2;
+
//
// Enumeration values for CaptureRequest#CONTROL_AF_MODE
//
@@ -1823,6 +1900,13 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2;
+ /**
+ * <p>MINIMAL noise reduction is applied without reducing frame rate relative to
+ * sensor output. </p>
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ */
+ public static final int NOISE_REDUCTION_MODE_MINIMAL = 3;
+
//
// Enumeration values for CaptureRequest#SENSOR_TEST_PATTERN_MODE
//
@@ -2026,6 +2110,43 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int TONEMAP_MODE_HIGH_QUALITY = 2;
+ /**
+ * <p>Use the gamma value specified in android.tonemap.gamma to peform
+ * tonemapping.</p>
+ * <p>All color enhancement and tonemapping must be disabled, except
+ * for applying the tonemapping curve specified by android.tonemap.gamma.</p>
+ * <p>Must not slow down frame rate relative to raw sensor output.</p>
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final int TONEMAP_MODE_GAMMA_VALUE = 3;
+
+ /**
+ * <p>Use the preset tonemapping curve specified in
+ * android.tonemap.presetCurve to peform tonemapping.</p>
+ * <p>All color enhancement and tonemapping must be disabled, except
+ * for applying the tonemapping curve specified by
+ * android.tonemap.presetCurve.</p>
+ * <p>Must not slow down frame rate relative to raw sensor output.</p>
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final int TONEMAP_MODE_PRESET_CURVE = 4;
+
+ //
+ // Enumeration values for CaptureRequest#TONEMAP_PRESET_CURVE
+ //
+
+ /**
+ * <p>Tonemapping curve is defined by sRGB</p>
+ * @see CaptureRequest#TONEMAP_PRESET_CURVE
+ */
+ public static final int TONEMAP_PRESET_CURVE_SRGB = 0;
+
+ /**
+ * <p>Tonemapping curve is defined by ITU-R BT.709</p>
+ * @see CaptureRequest#TONEMAP_PRESET_CURVE
+ */
+ public static final int TONEMAP_PRESET_CURVE_REC709 = 1;
+
//
// Enumeration values for CaptureResult#CONTROL_AE_STATE
//
@@ -2073,7 +2194,10 @@ public abstract class CameraMetadata<TKey> {
* <p>AE has been asked to do a precapture sequence
* and is currently executing it.</p>
* <p>Precapture can be triggered through setting
- * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} to START.</p>
+ * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} to START. Currently
+ * active and completed (if it causes camera device internal AE lock) precapture
+ * metering sequence can be canceled through setting
+ * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} to CANCEL.</p>
* <p>Once PRECAPTURE completes, AE will transition to CONVERGED
* or FLASH_REQUIRED as appropriate. This is a transient
* state, the camera device may skip reporting this state in
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index b417496..7569ea5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -552,6 +552,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* in this matrix result metadata. The transform should keep the magnitude
* of the output color values within <code>[0, 1.0]</code> (assuming input color
* values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p>
+ * <p>The valid range of each matrix element varies on different devices, but
+ * values within [-1.5, 3.0] are guaranteed not to be clipped.</p>
* <p><b>Units</b>: Unitless scale factors</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
@@ -575,6 +577,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* TRANSFORM_MATRIX.</p>
* <p>The gains in the result metadata are the gains actually
* applied by the camera device to the current frame.</p>
+ * <p>The valid range of gains varies on different devices, but gains
+ * between [1.0, 3.0] are guaranteed not to be clipped. Even if a given
+ * device allows gains below 1.0, this is usually not recommended because
+ * this can create color artifacts.</p>
* <p><b>Units</b>: Unitless gain factors</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
@@ -724,7 +730,14 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}) and sensitivity ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity})
* parameters. The flash may be fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
* is ON_AUTO_FLASH/ON_AUTO_FLASH_REDEYE and the scene is too dark. If the
- * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.</p>
+ * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.
+ * Similarly, AE precapture trigger CANCEL has no effect when AE is already locked.</p>
+ * <p>When an AE precapture sequence is triggered, AE unlock will not be able to unlock
+ * the AE if AE is locked by the camera device internally during precapture metering
+ * sequence In other words, submitting requests with AE unlock has no effect for an
+ * ongoing precapture metering sequence. Otherwise, the precapture metering sequence
+ * will never succeed in a sequence of preview requests where AE lock is always set
+ * to <code>false</code>.</p>
* <p>Since the camera device has a pipeline of in-flight requests, the settings that
* get locked do not necessarily correspond to the settings that were present in the
* latest capture result received from the camera device, since additional captures
@@ -869,6 +882,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* included at all in the request settings. When included and
* set to START, the camera device will trigger the auto-exposure (AE)
* precapture metering sequence.</p>
+ * <p>When set to CANCEL, the camera device will cancel any active
+ * precapture metering trigger, and return to its initial AE state.
+ * If a precapture metering sequence is already completed, and the camera
+ * device has implicitly locked the AE for subsequent still capture, the
+ * CANCEL trigger will unlock the AE and return to its initial AE state.</p>
* <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
@@ -884,7 +902,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* 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>
+ * still capture request after the precapture sequence completes. Alternatively, for
+ * API level 23 or newer devices, the CANCEL can be used to unlock the camera device
+ * internally locked AE if the application doesn't submit a still capture request after
+ * the AE precapture trigger. Note that, the CANCEL was added in API level 23, and must not
+ * be used in devices that have earlier API levels.</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
@@ -897,6 +919,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <ul>
* <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE IDLE}</li>
* <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_START START}</li>
+ * <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL CANCEL}</li>
* </ul></p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Limited capability</b> -
@@ -909,6 +932,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_START
+ * @see #CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
*/
@PublicKey
public static final Key<Integer> CONTROL_AE_PRECAPTURE_TRIGGER =
@@ -1164,7 +1188,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>This control (except for MANUAL) is only effective if
* <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
* <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
- * contains ZSL. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
+ * contains OPAQUE_REPROCESSING. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
* contains MANUAL_SENSOR. Other intent values are always supported.</p>
* <p><b>Possible values:</b>
* <ul>
@@ -1249,10 +1273,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* update, as if this frame is never captured. This mode can be used in the scenario
* where the application doesn't want a 3A manual control capture to affect
* the subsequent auto 3A capture results.</p>
- * <p>LEGACY mode devices will only support AUTO and USE_SCENE_MODE modes.
- * LIMITED mode devices will only support OFF and OFF_KEEP_STATE if they
- * support the MANUAL_SENSOR and MANUAL_POST_PROCSESING capabilities.
- * FULL mode devices will always support OFF and OFF_KEEP_STATE.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #CONTROL_MODE_OFF OFF}</li>
@@ -1260,9 +1280,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #CONTROL_MODE_USE_SCENE_MODE USE_SCENE_MODE}</li>
* <li>{@link #CONTROL_MODE_OFF_KEEP_STATE OFF_KEEP_STATE}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_MODES android.control.availableModes}</p>
* <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AF_MODE
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_MODES
* @see #CONTROL_MODE_OFF
* @see #CONTROL_MODE_AUTO
* @see #CONTROL_MODE_USE_SCENE_MODE
@@ -1382,6 +1405,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* camera device will use the highest-quality enhancement algorithms,
* even if it slows down capture rate. FAST means the camera device will
* not slow down capture rate when applying edge enhancement.</p>
+ * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera
+ * device will apply FAST/HIGH_QUALITY YUV-domain edge enhancement, respectively.
+ * The camera device may adjust its internal noise reduction parameters for best
+ * image quality based on the {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}, if it is set.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #EDGE_MODE_OFF OFF}</li>
@@ -1397,6 +1424,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
*
* @see CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
* @see #EDGE_MODE_OFF
* @see #EDGE_MODE_FAST
* @see #EDGE_MODE_HIGH_QUALITY
@@ -1761,18 +1789,28 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>Mode of operation for the noise reduction algorithm.</p>
* <p>The noise reduction algorithm attempts to improve image quality by removing
- * excessive noise added by the capture process, especially in dark conditions.
- * OFF means no noise reduction will be applied by the camera device.</p>
+ * excessive noise added by the capture process, especially in dark conditions.</p>
+ * <p>OFF means no noise reduction will be applied by the camera device, for both raw and
+ * YUV domain.</p>
+ * <p>MINIMAL means that only sensor raw domain basic noise reduction is enabled ,to remove
+ * demosaicing or other processing artifacts. For YUV_REPROCESSING, MINIMAL is same as OFF.
+ * This mode is optional, may not be support by all devices. The application should check
+ * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes} before using it.</p>
* <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering
* will be applied. HIGH_QUALITY mode indicates that the camera device
* will use the highest-quality noise filtering algorithms,
* even if it slows down capture rate. FAST means the camera device will not
* slow down capture rate when applying noise filtering.</p>
+ * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera device
+ * will apply FAST/HIGH_QUALITY YUV domain noise reduction, respectively. The camera device
+ * may adjust the noise reduction parameters for best image quality based on the
+ * {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor} if it is set.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #NOISE_REDUCTION_MODE_OFF OFF}</li>
* <li>{@link #NOISE_REDUCTION_MODE_FAST FAST}</li>
* <li>{@link #NOISE_REDUCTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
+ * <li>{@link #NOISE_REDUCTION_MODE_MINIMAL MINIMAL}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes}</p>
@@ -1783,9 +1821,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
* @see #NOISE_REDUCTION_MODE_OFF
* @see #NOISE_REDUCTION_MODE_FAST
* @see #NOISE_REDUCTION_MODE_HIGH_QUALITY
+ * @see #NOISE_REDUCTION_MODE_MINIMAL
*/
@PublicKey
public static final Key<Integer> NOISE_REDUCTION_MODE =
@@ -2078,6 +2118,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #SHADING_MODE_FAST FAST}</li>
* <li>{@link #SHADING_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#SHADING_AVAILABLE_MODES android.shading.availableModes}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
@@ -2086,6 +2128,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AWB_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#SHADING_AVAILABLE_MODES
* @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see #SHADING_MODE_OFF
@@ -2148,12 +2191,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #STATISTICS_LENS_SHADING_MAP_MODE_OFF OFF}</li>
* <li>{@link #STATISTICS_LENS_SHADING_MAP_MODE_ON ON}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES android.statistics.info.availableLensShadingMapModes}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES
* @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF
* @see #STATISTICS_LENS_SHADING_MAP_MODE_ON
*/
@@ -2340,6 +2386,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #TONEMAP_MODE_CONTRAST_CURVE CONTRAST_CURVE}</li>
* <li>{@link #TONEMAP_MODE_FAST FAST}</li>
* <li>{@link #TONEMAP_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
+ * <li>{@link #TONEMAP_MODE_GAMMA_VALUE GAMMA_VALUE}</li>
+ * <li>{@link #TONEMAP_MODE_PRESET_CURVE PRESET_CURVE}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}</p>
@@ -2355,12 +2403,60 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
* @see #TONEMAP_MODE_HIGH_QUALITY
+ * @see #TONEMAP_MODE_GAMMA_VALUE
+ * @see #TONEMAP_MODE_PRESET_CURVE
*/
@PublicKey
public static final Key<Integer> TONEMAP_MODE =
new Key<Integer>("android.tonemap.mode", int.class);
/**
+ * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * GAMMA_VALUE</p>
+ * <p>The tonemap curve will be defined the following formula:
+ * * OUT = pow(IN, 1.0 / gamma)
+ * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+ * pow is the power function and gamma is the gamma value specified by this
+ * key.</p>
+ * <p>The same curve will be applied to all color channels. The camera device
+ * may clip the input gamma value to its supported range. The actual applied
+ * value will be returned in capture result.</p>
+ * <p>The valid range of gamma value varies on different devices, but values
+ * within [1.0, 5.0] are guaranteed not to be clipped.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ @PublicKey
+ public static final Key<Float> TONEMAP_GAMMA =
+ new Key<Float>("android.tonemap.gamma", float.class);
+
+ /**
+ * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * PRESET_CURVE</p>
+ * <p>The tonemap curve will be defined by specified standard.</p>
+ * <p>sRGB (approximated by 16 control points):</p>
+ * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p>Rec. 709 (approximated by 16 control points):</p>
+ * <p><img alt="Rec. 709 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p>
+ * <p>Note that above figures show a 16 control points approximation of preset
+ * curves. Camera devices may apply a different approximation to the curve.</p>
+ * <p><b>Possible values:</b>
+ * <ul>
+ * <li>{@link #TONEMAP_PRESET_CURVE_SRGB SRGB}</li>
+ * <li>{@link #TONEMAP_PRESET_CURVE_REC709 REC709}</li>
+ * </ul></p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#TONEMAP_MODE
+ * @see #TONEMAP_PRESET_CURVE_SRGB
+ * @see #TONEMAP_PRESET_CURVE_REC709
+ */
+ @PublicKey
+ public static final Key<Integer> TONEMAP_PRESET_CURVE =
+ new Key<Integer>("android.tonemap.presetCurve", int.class);
+
+ /**
* <p>This LED is nominally used to indicate to the user
* that the camera is powered on and may be streaming images back to the
* Application Processor. In certain rare circumstances, the OS may
@@ -2426,6 +2522,52 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
public static final Key<Boolean> BLACK_LEVEL_LOCK =
new Key<Boolean>("android.blackLevel.lock", boolean.class);
+ /**
+ * <p>The amount of exposure time increase factor applied to the original output
+ * frame by the application processing before sending for reprocessing.</p>
+ * <p>This is optional, and will be supported if the camera device supports YUV_REPROCESSING
+ * capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains YUV_REPROCESSING).</p>
+ * <p>For some YUV reprocessing use cases, the application may choose to filter the original
+ * output frames to effectively reduce the noise to the same level as a frame that was
+ * captured with longer exposure time. To be more specific, assuming the original captured
+ * images were captured with a sensitivity of S and an exposure time of T, the model in
+ * the camera device is that the amount of noise in the image would be approximately what
+ * would be expected if the original capture parameters had been a sensitivity of
+ * S/effectiveExposureFactor and an exposure time of T*effectiveExposureFactor, rather
+ * than S and T respectively. If the captured images were processed by the application
+ * before being sent for reprocessing, then the application may have used image processing
+ * algorithms and/or multi-frame image fusion to reduce the noise in the
+ * application-processed images (input images). By using the effectiveExposureFactor
+ * control, the application can communicate to the camera device the actual noise level
+ * improvement in the application-processed image. With this information, the camera
+ * device can select appropriate noise reduction and edge enhancement parameters to avoid
+ * excessive noise reduction ({@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}) and insufficient edge
+ * enhancement ({@link CaptureRequest#EDGE_MODE android.edge.mode}) being applied to the reprocessed frames.</p>
+ * <p>For example, for multi-frame image fusion use case, the application may fuse
+ * multiple output frames together to a final frame for reprocessing. When N image are
+ * fused into 1 image for reprocessing, the exposure time increase factor could be up to
+ * square root of N (based on a simple photon shot noise model). The camera device will
+ * adjust the reprocessing noise reduction and edge enhancement parameters accordingly to
+ * produce the best quality images.</p>
+ * <p>This is relative factor, 1.0 indicates the application hasn't processed the input
+ * buffer in a way that affects its effective exposure time.</p>
+ * <p>This control is only effective for YUV reprocessing capture request. For noise
+ * reduction reprocessing, it is only effective when <code>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} != OFF</code>.
+ * Similarly, for edge enhancement reprocessing, it is only effective when
+ * <code>{@link CaptureRequest#EDGE_MODE android.edge.mode} != OFF</code>.</p>
+ * <p><b>Units</b>: Relative exposure time increase factor.</p>
+ * <p><b>Range of valid values:</b><br>
+ * &gt;= 1.0</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ @PublicKey
+ public static final Key<Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR =
+ new Key<Float>("android.reprocess.effectiveExposureFactor", float.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index f17100d..b84dc2e 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -403,6 +403,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* in this matrix result metadata. The transform should keep the magnitude
* of the output color values within <code>[0, 1.0]</code> (assuming input color
* values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p>
+ * <p>The valid range of each matrix element varies on different devices, but
+ * values within [-1.5, 3.0] are guaranteed not to be clipped.</p>
* <p><b>Units</b>: Unitless scale factors</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
@@ -426,6 +428,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* TRANSFORM_MATRIX.</p>
* <p>The gains in the result metadata are the gains actually
* applied by the camera device to the current frame.</p>
+ * <p>The valid range of gains varies on different devices, but gains
+ * between [1.0, 3.0] are guaranteed not to be clipped. Even if a given
+ * device allows gains below 1.0, this is usually not recommended because
+ * this can create color artifacts.</p>
* <p><b>Units</b>: Unitless gain factors</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
@@ -575,7 +581,14 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}) and sensitivity ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity})
* parameters. The flash may be fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
* is ON_AUTO_FLASH/ON_AUTO_FLASH_REDEYE and the scene is too dark. If the
- * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.</p>
+ * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.
+ * Similarly, AE precapture trigger CANCEL has no effect when AE is already locked.</p>
+ * <p>When an AE precapture sequence is triggered, AE unlock will not be able to unlock
+ * the AE if AE is locked by the camera device internally during precapture metering
+ * sequence In other words, submitting requests with AE unlock has no effect for an
+ * ongoing precapture metering sequence. Otherwise, the precapture metering sequence
+ * will never succeed in a sequence of preview requests where AE lock is always set
+ * to <code>false</code>.</p>
* <p>Since the camera device has a pipeline of in-flight requests, the settings that
* get locked do not necessarily correspond to the settings that were present in the
* latest capture result received from the camera device, since additional captures
@@ -720,6 +733,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* included at all in the request settings. When included and
* set to START, the camera device will trigger the auto-exposure (AE)
* precapture metering sequence.</p>
+ * <p>When set to CANCEL, the camera device will cancel any active
+ * precapture metering trigger, and return to its initial AE state.
+ * If a precapture metering sequence is already completed, and the camera
+ * device has implicitly locked the AE for subsequent still capture, the
+ * CANCEL trigger will unlock the AE and return to its initial AE state.</p>
* <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
@@ -735,7 +753,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* 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>
+ * still capture request after the precapture sequence completes. Alternatively, for
+ * API level 23 or newer devices, the CANCEL can be used to unlock the camera device
+ * internally locked AE if the application doesn't submit a still capture request after
+ * the AE precapture trigger. Note that, the CANCEL was added in API level 23, and must not
+ * be used in devices that have earlier API levels.</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
@@ -748,6 +770,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <ul>
* <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE IDLE}</li>
* <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_START START}</li>
+ * <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL CANCEL}</li>
* </ul></p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Limited capability</b> -
@@ -760,6 +783,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_START
+ * @see #CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
*/
@PublicKey
public static final Key<Integer> CONTROL_AE_PRECAPTURE_TRIGGER =
@@ -892,11 +916,29 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <td align="center">Ready for high-quality capture</td>
* </tr>
* <tr>
- * <td align="center">Any state</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">aeLock is ON and aePrecaptureTrigger is START</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">LOCKED</td>
+ * <td align="center">aeLock is ON and aePrecaptureTrigger is CANCEL</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state (excluding LOCKED)</td>
* <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
* <td align="center">PRECAPTURE</td>
* <td align="center">Start AE precapture metering sequence</td>
* </tr>
+ * <tr>
+ * <td align="center">Any state (excluding LOCKED)</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Currently active precapture metering sequence is canceled</td>
+ * </tr>
* </tbody>
* </table>
* <p>For the above table, the camera device may skip reporting any state changes that happen
@@ -922,18 +964,30 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <td align="center">Values are already good, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state</td>
+ * <td align="center">Any state (excluding LOCKED)</td>
* <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
* <td align="center">FLASH_REQUIRED</td>
* <td align="center">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state</td>
+ * <td align="center">Any state (excluding LOCKED)</td>
* <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
* <td align="center">CONVERGED</td>
* <td align="center">Converged after a precapture sequence, transient states are skipped by camera device.</td>
* </tr>
* <tr>
+ * <td align="center">Any state (excluding LOCKED)</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Converged but too dark w/o flash after a precapture sequence is canceled, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state (excluding LOCKED)</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Converged after a precapture sequenceis canceled, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
* <td align="center">CONVERGED</td>
* <td align="center">Camera device finished AE scan</td>
* <td align="center">FLASH_REQUIRED</td>
@@ -1637,7 +1691,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>This control (except for MANUAL) is only effective if
* <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
* <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
- * contains ZSL. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
+ * contains OPAQUE_REPROCESSING. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
* contains MANUAL_SENSOR. Other intent values are always supported.</p>
* <p><b>Possible values:</b>
* <ul>
@@ -1865,10 +1919,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* update, as if this frame is never captured. This mode can be used in the scenario
* where the application doesn't want a 3A manual control capture to affect
* the subsequent auto 3A capture results.</p>
- * <p>LEGACY mode devices will only support AUTO and USE_SCENE_MODE modes.
- * LIMITED mode devices will only support OFF and OFF_KEEP_STATE if they
- * support the MANUAL_SENSOR and MANUAL_POST_PROCSESING capabilities.
- * FULL mode devices will always support OFF and OFF_KEEP_STATE.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #CONTROL_MODE_OFF OFF}</li>
@@ -1876,9 +1926,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #CONTROL_MODE_USE_SCENE_MODE USE_SCENE_MODE}</li>
* <li>{@link #CONTROL_MODE_OFF_KEEP_STATE OFF_KEEP_STATE}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_MODES android.control.availableModes}</p>
* <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AF_MODE
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_MODES
* @see #CONTROL_MODE_OFF
* @see #CONTROL_MODE_AUTO
* @see #CONTROL_MODE_USE_SCENE_MODE
@@ -1998,6 +2051,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* camera device will use the highest-quality enhancement algorithms,
* even if it slows down capture rate. FAST means the camera device will
* not slow down capture rate when applying edge enhancement.</p>
+ * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera
+ * device will apply FAST/HIGH_QUALITY YUV-domain edge enhancement, respectively.
+ * The camera device may adjust its internal noise reduction parameters for best
+ * image quality based on the {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}, if it is set.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #EDGE_MODE_OFF OFF}</li>
@@ -2013,6 +2070,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
* @see #EDGE_MODE_OFF
* @see #EDGE_MODE_FAST
* @see #EDGE_MODE_HIGH_QUALITY
@@ -2475,18 +2533,28 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>Mode of operation for the noise reduction algorithm.</p>
* <p>The noise reduction algorithm attempts to improve image quality by removing
- * excessive noise added by the capture process, especially in dark conditions.
- * OFF means no noise reduction will be applied by the camera device.</p>
+ * excessive noise added by the capture process, especially in dark conditions.</p>
+ * <p>OFF means no noise reduction will be applied by the camera device, for both raw and
+ * YUV domain.</p>
+ * <p>MINIMAL means that only sensor raw domain basic noise reduction is enabled ,to remove
+ * demosaicing or other processing artifacts. For YUV_REPROCESSING, MINIMAL is same as OFF.
+ * This mode is optional, may not be support by all devices. The application should check
+ * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes} before using it.</p>
* <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering
* will be applied. HIGH_QUALITY mode indicates that the camera device
* will use the highest-quality noise filtering algorithms,
* even if it slows down capture rate. FAST means the camera device will not
* slow down capture rate when applying noise filtering.</p>
+ * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera device
+ * will apply FAST/HIGH_QUALITY YUV domain noise reduction, respectively. The camera device
+ * may adjust the noise reduction parameters for best image quality based on the
+ * {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor} if it is set.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #NOISE_REDUCTION_MODE_OFF OFF}</li>
* <li>{@link #NOISE_REDUCTION_MODE_FAST FAST}</li>
* <li>{@link #NOISE_REDUCTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
+ * <li>{@link #NOISE_REDUCTION_MODE_MINIMAL MINIMAL}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes}</p>
@@ -2497,9 +2565,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
* @see #NOISE_REDUCTION_MODE_OFF
* @see #NOISE_REDUCTION_MODE_FAST
* @see #NOISE_REDUCTION_MODE_HIGH_QUALITY
+ * @see #NOISE_REDUCTION_MODE_MINIMAL
*/
@PublicKey
public static final Key<Integer> NOISE_REDUCTION_MODE =
@@ -2984,6 +3054,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #SHADING_MODE_FAST FAST}</li>
* <li>{@link #SHADING_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#SHADING_AVAILABLE_MODES android.shading.availableModes}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
@@ -2992,6 +3064,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AWB_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#SHADING_AVAILABLE_MODES
* @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see #SHADING_MODE_OFF
@@ -3155,7 +3228,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>The shading map is a low-resolution floating-point map
* that lists the coefficients used to correct for vignetting, for each
- * Bayer color channel.</p>
+ * Bayer color channel of RAW image data.</p>
* <p>The least shaded section of the image should have a gain factor
* of 1; all other sections should have gains above 1.</p>
* <p>When {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} = TRANSFORM_MATRIX, the map
@@ -3191,8 +3264,20 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" />
* <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p>
* <p>As a visualization only, inverting the full-color map to recover an
- * image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p>
+ * image of a gray wall (using bicubic interpolation for visual quality)
+ * as captured by the sensor gives:</p>
* <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
+ * <p>Note that the RAW image data might be subject to lens shading
+ * correction not reported on this map. Query
+ * {@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied} to see if RAW image data has subject
+ * to lens shading correction. If {@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied}
+ * is TRUE, the RAW image data is subject to partial or full lens shading
+ * correction. In the case full lens shading correction is applied to RAW
+ * images, the gain factor map reported in this key will contain all 1.0 gains.
+ * In other words, the map reported in this key is the remaining lens shading
+ * that needs to be applied on the RAW image to get images without lens shading
+ * artifacts. See {@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_RAW android.request.maxNumOutputRaw} for a list of RAW image
+ * formats.</p>
* <p><b>Range of valid values:</b><br>
* Each gain factor is &gt;= 1</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
@@ -3202,6 +3287,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_RAW
+ * @see CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED
* @hide
*/
public static final Key<float[]> STATISTICS_LENS_SHADING_MAP =
@@ -3339,12 +3426,15 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #STATISTICS_LENS_SHADING_MAP_MODE_OFF OFF}</li>
* <li>{@link #STATISTICS_LENS_SHADING_MAP_MODE_ON ON}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES android.statistics.info.availableLensShadingMapModes}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES
* @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF
* @see #STATISTICS_LENS_SHADING_MAP_MODE_ON
*/
@@ -3531,6 +3621,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #TONEMAP_MODE_CONTRAST_CURVE CONTRAST_CURVE}</li>
* <li>{@link #TONEMAP_MODE_FAST FAST}</li>
* <li>{@link #TONEMAP_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
+ * <li>{@link #TONEMAP_MODE_GAMMA_VALUE GAMMA_VALUE}</li>
+ * <li>{@link #TONEMAP_MODE_PRESET_CURVE PRESET_CURVE}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}</p>
@@ -3546,12 +3638,60 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
* @see #TONEMAP_MODE_HIGH_QUALITY
+ * @see #TONEMAP_MODE_GAMMA_VALUE
+ * @see #TONEMAP_MODE_PRESET_CURVE
*/
@PublicKey
public static final Key<Integer> TONEMAP_MODE =
new Key<Integer>("android.tonemap.mode", int.class);
/**
+ * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * GAMMA_VALUE</p>
+ * <p>The tonemap curve will be defined the following formula:
+ * * OUT = pow(IN, 1.0 / gamma)
+ * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+ * pow is the power function and gamma is the gamma value specified by this
+ * key.</p>
+ * <p>The same curve will be applied to all color channels. The camera device
+ * may clip the input gamma value to its supported range. The actual applied
+ * value will be returned in capture result.</p>
+ * <p>The valid range of gamma value varies on different devices, but values
+ * within [1.0, 5.0] are guaranteed not to be clipped.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ @PublicKey
+ public static final Key<Float> TONEMAP_GAMMA =
+ new Key<Float>("android.tonemap.gamma", float.class);
+
+ /**
+ * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * PRESET_CURVE</p>
+ * <p>The tonemap curve will be defined by specified standard.</p>
+ * <p>sRGB (approximated by 16 control points):</p>
+ * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p>Rec. 709 (approximated by 16 control points):</p>
+ * <p><img alt="Rec. 709 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p>
+ * <p>Note that above figures show a 16 control points approximation of preset
+ * curves. Camera devices may apply a different approximation to the curve.</p>
+ * <p><b>Possible values:</b>
+ * <ul>
+ * <li>{@link #TONEMAP_PRESET_CURVE_SRGB SRGB}</li>
+ * <li>{@link #TONEMAP_PRESET_CURVE_REC709 REC709}</li>
+ * </ul></p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#TONEMAP_MODE
+ * @see #TONEMAP_PRESET_CURVE_SRGB
+ * @see #TONEMAP_PRESET_CURVE_REC709
+ */
+ @PublicKey
+ public static final Key<Integer> TONEMAP_PRESET_CURVE =
+ new Key<Integer>("android.tonemap.presetCurve", int.class);
+
+ /**
* <p>This LED is nominally used to indicate to the user
* that the camera is powered on and may be streaming images back to the
* Application Processor. In certain rare circumstances, the OS may
@@ -3657,6 +3797,52 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
public static final Key<Long> SYNC_FRAME_NUMBER =
new Key<Long>("android.sync.frameNumber", long.class);
+ /**
+ * <p>The amount of exposure time increase factor applied to the original output
+ * frame by the application processing before sending for reprocessing.</p>
+ * <p>This is optional, and will be supported if the camera device supports YUV_REPROCESSING
+ * capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains YUV_REPROCESSING).</p>
+ * <p>For some YUV reprocessing use cases, the application may choose to filter the original
+ * output frames to effectively reduce the noise to the same level as a frame that was
+ * captured with longer exposure time. To be more specific, assuming the original captured
+ * images were captured with a sensitivity of S and an exposure time of T, the model in
+ * the camera device is that the amount of noise in the image would be approximately what
+ * would be expected if the original capture parameters had been a sensitivity of
+ * S/effectiveExposureFactor and an exposure time of T*effectiveExposureFactor, rather
+ * than S and T respectively. If the captured images were processed by the application
+ * before being sent for reprocessing, then the application may have used image processing
+ * algorithms and/or multi-frame image fusion to reduce the noise in the
+ * application-processed images (input images). By using the effectiveExposureFactor
+ * control, the application can communicate to the camera device the actual noise level
+ * improvement in the application-processed image. With this information, the camera
+ * device can select appropriate noise reduction and edge enhancement parameters to avoid
+ * excessive noise reduction ({@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}) and insufficient edge
+ * enhancement ({@link CaptureRequest#EDGE_MODE android.edge.mode}) being applied to the reprocessed frames.</p>
+ * <p>For example, for multi-frame image fusion use case, the application may fuse
+ * multiple output frames together to a final frame for reprocessing. When N image are
+ * fused into 1 image for reprocessing, the exposure time increase factor could be up to
+ * square root of N (based on a simple photon shot noise model). The camera device will
+ * adjust the reprocessing noise reduction and edge enhancement parameters accordingly to
+ * produce the best quality images.</p>
+ * <p>This is relative factor, 1.0 indicates the application hasn't processed the input
+ * buffer in a way that affects its effective exposure time.</p>
+ * <p>This control is only effective for YUV reprocessing capture request. For noise
+ * reduction reprocessing, it is only effective when <code>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} != OFF</code>.
+ * Similarly, for edge enhancement reprocessing, it is only effective when
+ * <code>{@link CaptureRequest#EDGE_MODE android.edge.mode} != OFF</code>.</p>
+ * <p><b>Units</b>: Relative exposure time increase factor.</p>
+ * <p><b>Range of valid values:</b><br>
+ * &gt;= 1.0</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ @PublicKey
+ public static final Key<Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR =
+ new Key<Float>("android.reprocess.effectiveExposureFactor", float.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
index 50a58ed..01f2396 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -16,7 +16,7 @@
package android.hardware.camera2;
-import android.view.Surface;
+import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.CaptureRequest;
@@ -66,7 +66,7 @@ interface ICameraDeviceUser
int deleteStream(int streamId);
// non-negative value is the stream ID. negative value is status_t
- int createStream(int width, int height, int format, in Surface surface);
+ int createStream(in OutputConfiguration outputConfiguration);
int createDefaultRequest(int templateId, out CameraMetadataNative request);
diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index 5bc7f71..e87a2f8 100644
--- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -21,11 +21,9 @@ import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.dispatch.ArgumentReplacingDispatcher;
import android.hardware.camera2.dispatch.BroadcastDispatcher;
-import android.hardware.camera2.dispatch.Dispatchable;
import android.hardware.camera2.dispatch.DuckTypingDispatcher;
import android.hardware.camera2.dispatch.HandlerDispatcher;
import android.hardware.camera2.dispatch.InvokeDispatcher;
-import android.hardware.camera2.dispatch.NullDispatcher;
import android.hardware.camera2.utils.TaskDrainer;
import android.hardware.camera2.utils.TaskSingleDrainer;
import android.os.Handler;
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index ec450bd..38f8e39 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -28,6 +28,7 @@ import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.hardware.camera2.utils.LongParcelable;
@@ -78,7 +79,8 @@ public class CameraDeviceImpl extends CameraDevice {
private int mRepeatingRequestId = REQUEST_ID_NONE;
private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>();
// Map stream IDs to Surfaces
- private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>();
+ private final SparseArray<OutputConfiguration> mConfiguredOutputs =
+ new SparseArray<OutputConfiguration>();
private final String mCameraId;
private final CameraCharacteristics mCharacteristics;
@@ -314,7 +316,11 @@ public class CameraDeviceImpl extends CameraDevice {
public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
// Leave this here for backwards compatibility with older code using this directly
- configureOutputsChecked(outputs);
+ ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>(outputs.size());
+ for (Surface s : outputs) {
+ outputConfigs.add(new OutputConfiguration(s));
+ }
+ configureOutputsChecked(outputConfigs);
}
/**
@@ -333,28 +339,30 @@ public class CameraDeviceImpl extends CameraDevice {
*
* @throws CameraAccessException if there were any unexpected problems during configuration
*/
- public boolean configureOutputsChecked(List<Surface> outputs) throws CameraAccessException {
+ public boolean configureOutputsChecked(List<OutputConfiguration> outputs)
+ throws CameraAccessException {
// Treat a null input the same an empty list
if (outputs == null) {
- outputs = new ArrayList<Surface>();
+ outputs = new ArrayList<OutputConfiguration>();
}
boolean success = false;
synchronized(mInterfaceLock) {
checkIfCameraClosedOrInError();
-
- HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create
- List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete
+ // Streams to create
+ HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs);
+ // Streams to delete
+ List<Integer> deleteList = new ArrayList<Integer>();
// Determine which streams need to be created, which to be deleted
for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
int streamId = mConfiguredOutputs.keyAt(i);
- Surface s = mConfiguredOutputs.valueAt(i);
+ OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i);
- if (!outputs.contains(s)) {
+ if (!outputs.contains(outConfig)) {
deleteList.add(streamId);
} else {
- addSet.remove(s); // Don't create a stream previously created
+ addSet.remove(outConfig); // Don't create a stream previously created
}
}
@@ -372,11 +380,11 @@ public class CameraDeviceImpl extends CameraDevice {
}
// Add all new streams
- for (Surface s : addSet) {
- // TODO: remove width,height,format since we are ignoring
- // it.
- int streamId = mRemoteDevice.createStream(0, 0, 0, s);
- mConfiguredOutputs.put(streamId, s);
+ for (OutputConfiguration outConfig : outputs) {
+ if (addSet.contains(outConfig)) {
+ int streamId = mRemoteDevice.createStream(outConfig);
+ mConfiguredOutputs.put(streamId, outConfig);
+ }
}
try {
@@ -417,6 +425,18 @@ public class CameraDeviceImpl extends CameraDevice {
public void createCaptureSession(List<Surface> outputs,
CameraCaptureSession.StateCallback callback, Handler handler)
throws CameraAccessException {
+ List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
+ for (Surface surface : outputs) {
+ outConfigurations.add(new OutputConfiguration(surface));
+ }
+ createCaptureSessionByOutputConfiguration(outConfigurations, callback, handler);
+ }
+
+ @Override
+ public void createCaptureSessionByOutputConfiguration(
+ List<OutputConfiguration> outputConfigurations,
+ CameraCaptureSession.StateCallback callback, Handler handler)
+ throws CameraAccessException {
synchronized(mInterfaceLock) {
if (DEBUG) {
Log.d(TAG, "createCaptureSession");
@@ -434,7 +454,8 @@ public class CameraDeviceImpl extends CameraDevice {
boolean configureSuccess = true;
CameraAccessException pendingException = null;
try {
- configureSuccess = configureOutputsChecked(outputs); // and then block until IDLE
+ // configure outputs and then block until IDLE
+ configureSuccess = configureOutputsChecked(outputConfigurations);
} catch (CameraAccessException e) {
configureSuccess = false;
pendingException = e;
@@ -443,10 +464,14 @@ public class CameraDeviceImpl extends CameraDevice {
}
}
+ List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size());
+ for (OutputConfiguration config : outputConfigurations) {
+ outSurfaces.add(config.getSurface());
+ }
// Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
CameraCaptureSessionImpl newSession =
new CameraCaptureSessionImpl(mNextSessionId++,
- outputs, callback, handler, this, mDeviceHandler,
+ outSurfaces, callback, handler, this, mDeviceHandler,
configureSuccess);
// TODO: wait until current session closes, then create the new session
diff --git a/core/java/android/hardware/camera2/legacy/BurstHolder.java b/core/java/android/hardware/camera2/legacy/BurstHolder.java
index b9c89f8..e7b3682 100644
--- a/core/java/android/hardware/camera2/legacy/BurstHolder.java
+++ b/core/java/android/hardware/camera2/legacy/BurstHolder.java
@@ -17,10 +17,6 @@
package android.hardware.camera2.legacy;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.util.Log;
-import android.view.Surface;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index fcf172c..70f3463 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -26,6 +26,7 @@ import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.os.ConditionVariable;
@@ -504,7 +505,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
}
@Override
- public int createStream(int width, int height, int format, Surface surface) {
+ public int createStream(OutputConfiguration outputConfiguration) {
if (DEBUG) {
Log.d(TAG, "createStream called.");
}
@@ -518,8 +519,12 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
Log.e(TAG, "Cannot create stream, beginConfigure hasn't been called yet.");
return CameraBinderDecorator.INVALID_OPERATION;
}
+ if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) {
+ Log.e(TAG, "Cannot create stream, stream rotation is not supported.");
+ return CameraBinderDecorator.INVALID_OPERATION;
+ }
int id = ++mSurfaceIdCounter;
- mSurfaces.put(id, surface);
+ mSurfaces.put(id, outputConfiguration.getSurface());
return id;
}
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 367a078..b5a019d 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -292,6 +292,10 @@ public class LegacyCameraDevice implements AutoCloseable {
Log.e(TAG, "configureOutputs - null outputs are not allowed");
return BAD_VALUE;
}
+ if (!output.isValid()) {
+ Log.e(TAG, "configureOutputs - invalid output surfaces are not allowed");
+ return BAD_VALUE;
+ }
StreamConfigurationMap streamConfigurations = mStaticCharacteristics.
get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
@@ -522,7 +526,7 @@ public class LegacyCameraDevice implements AutoCloseable {
* @return the width and height of the surface
*
* @throws NullPointerException if the {@code surface} was {@code null}
- * @throws IllegalStateException if the {@code surface} was invalid
+ * @throws BufferQueueAbandonedException if the {@code surface} was invalid
*/
public static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
diff --git a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
index 7e0c01b..4b7cfbf 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
@@ -60,7 +60,7 @@ public class LegacyExceptionUtils {
case CameraBinderDecorator.NO_ERROR: {
return CameraBinderDecorator.NO_ERROR;
}
- case CameraBinderDecorator.ENODEV: {
+ case CameraBinderDecorator.BAD_VALUE: {
throw new BufferQueueAbandonedException();
}
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
index 6215a8f..e576beb 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyFaceDetectMapper.java
@@ -33,7 +33,6 @@ import android.util.Size;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import static android.hardware.camera2.CaptureRequest.*;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 347db05..8776418 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -42,7 +42,6 @@ import android.util.SizeF;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import static com.android.internal.util.Preconditions.*;
@@ -474,6 +473,15 @@ public class LegacyMetadataMapper {
m.set(CONTROL_AE_COMPENSATION_STEP, ParamsUtils.createRational(step));
}
+
+ /*
+ * control.aeLockAvailable
+ */
+ {
+ boolean aeLockAvailable = p.isAutoExposureLockSupported();
+
+ m.set(CONTROL_AE_LOCK_AVAILABLE, aeLockAvailable);
+ }
}
@@ -571,6 +579,16 @@ public class LegacyMetadataMapper {
Log.v(TAG, "mapControlAwb - control.awbAvailableModes set to " +
ListUtils.listToString(awbAvail));
}
+
+
+ /*
+ * control.awbLockAvailable
+ */
+ {
+ boolean awbLockAvailable = p.isAutoWhiteBalanceLockSupported();
+
+ m.set(CONTROL_AWB_LOCK_AVAILABLE, awbLockAvailable);
+ }
}
}
@@ -618,17 +636,44 @@ public class LegacyMetadataMapper {
/*
* android.control.availableSceneModes
*/
+ int maxNumDetectedFaces = p.getMaxNumDetectedFaces();
List<String> sceneModes = p.getSupportedSceneModes();
List<Integer> supportedSceneModes =
ArrayUtils.convertStringListToIntList(sceneModes, sLegacySceneModes, sSceneModes);
- if (supportedSceneModes == null) { // camera1 doesn't support scene mode settings
- supportedSceneModes = new ArrayList<Integer>();
- supportedSceneModes.add(CONTROL_SCENE_MODE_DISABLED); // disabled is always available
+
+ // Special case where the only scene mode listed is AUTO => no scene mode
+ if (sceneModes != null && sceneModes.size() == 1 &&
+ sceneModes.get(0) == Parameters.SCENE_MODE_AUTO) {
+ supportedSceneModes = null;
}
- if (p.getMaxNumDetectedFaces() > 0) { // always supports FACE_PRIORITY when face detecting
- supportedSceneModes.add(CONTROL_SCENE_MODE_FACE_PRIORITY);
+
+ boolean sceneModeSupported = true;
+ if (supportedSceneModes == null && maxNumDetectedFaces == 0) {
+ sceneModeSupported = false;
}
- m.set(CONTROL_AVAILABLE_SCENE_MODES, ArrayUtils.toIntArray(supportedSceneModes));
+
+ if (sceneModeSupported) {
+ if (supportedSceneModes == null) {
+ supportedSceneModes = new ArrayList<Integer>();
+ }
+ if (maxNumDetectedFaces > 0) { // always supports FACE_PRIORITY when face detecting
+ supportedSceneModes.add(CONTROL_SCENE_MODE_FACE_PRIORITY);
+ }
+ // Remove all DISABLED occurrences
+ if (supportedSceneModes.contains(CONTROL_SCENE_MODE_DISABLED)) {
+ while(supportedSceneModes.remove(new Integer(CONTROL_SCENE_MODE_DISABLED))) {}
+ }
+ m.set(CONTROL_AVAILABLE_SCENE_MODES, ArrayUtils.toIntArray(supportedSceneModes));
+ } else {
+ m.set(CONTROL_AVAILABLE_SCENE_MODES, new int[] {CONTROL_SCENE_MODE_DISABLED});
+ }
+
+ /*
+ * android.control.availableModes
+ */
+ m.set(CONTROL_AVAILABLE_MODES, sceneModeSupported ?
+ new int[] { CONTROL_MODE_AUTO, CONTROL_MODE_USE_SCENE_MODE } :
+ new int[] { CONTROL_MODE_AUTO });
}
private static void mapLens(CameraMetadataNative m, Camera.Parameters p) {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
index 61f7b8b..3688610 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
@@ -34,7 +34,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
-import static com.android.internal.util.Preconditions.*;
import static android.hardware.camera2.CaptureRequest.*;
/**
diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
index 3b10eb5..9e9a6fe 100644
--- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java
+++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
@@ -22,8 +22,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.Camera;
import android.hardware.Camera.Area;
-import android.hardware.camera2.legacy.ParameterUtils.MeteringData;
-import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.utils.ListUtils;
diff --git a/core/java/android/hardware/camera2/legacy/PerfMeasurement.java b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
index b930ec2..53278c7 100644
--- a/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
+++ b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
@@ -20,7 +20,6 @@ import android.os.SystemClock;
import android.util.Log;
import java.io.BufferedWriter;
-import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
diff --git a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
index 0699ffb..e19ebf2 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHandlerThread.java
@@ -96,15 +96,15 @@ public class RequestHandlerThread extends HandlerThread {
// Blocks until thread is idling
public void waitUntilIdle() {
Handler handler = waitAndGetHandler();
- Looper looper = handler.getLooper();
- if (looper.isIdling()) {
+ MessageQueue queue = handler.getLooper().getQueue();
+ if (queue.isIdle()) {
return;
}
mIdle.close();
- looper.getQueue().addIdleHandler(mIdleHandler);
+ queue.addIdleHandler(mIdleHandler);
// Ensure that the idle handler gets run even if the looper already went idle
handler.sendEmptyMessage(MSG_POKE_IDLE_HANDLER);
- if (looper.isIdling()) {
+ if (queue.isIdle()) {
return;
}
mIdle.block();
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
index edd8e4e..9b628fb 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -17,7 +17,6 @@
package android.hardware.camera2.legacy;
import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.impl.CameraMetadataNative;
import android.util.Log;
import android.view.Surface;
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index f1f2f0c..691798f 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -16,13 +16,11 @@
package android.hardware.camera2.legacy;
-import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraDeviceImpl;
-import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.LongParcelable;
import android.hardware.camera2.utils.SizeAreaComparator;
import android.hardware.camera2.impl.CameraMetadataNative;
@@ -38,7 +36,6 @@ import android.view.Surface;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@@ -85,7 +82,7 @@ public class RequestThreadManager {
private static final int PREVIEW_FRAME_TIMEOUT = 1000; // ms
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 int REQUEST_COMPLETE_TIMEOUT = JPEG_FRAME_TIMEOUT;
private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
private boolean mPreviewRunning = false;
@@ -501,6 +498,10 @@ public class RequestThreadManager {
return;
}
for(Surface s : surfaces) {
+ if (s == null || !s.isValid()) {
+ Log.w(TAG, "Jpeg surface is invalid, skipping...");
+ continue;
+ }
try {
LegacyCameraDevice.setSurfaceFormat(s, LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB);
} catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
index 22b87ef..d89518b 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
@@ -25,9 +25,6 @@ import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.ArrayList;
-import static android.hardware.camera2.impl.CameraMetadataNative.*;
-import static android.hardware.camera2.marshal.MarshalHelpers.*;
-
/**
* Marshal any array {@code T}.
*
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
index 1fd6a1d..0b7a4bf 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableParcelable.java
@@ -25,9 +25,6 @@ import android.util.Log;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
-import static android.hardware.camera2.impl.CameraMetadataNative.*;
-import static android.hardware.camera2.marshal.MarshalHelpers.*;
-
/**
* Marshal any {@code T extends Parcelable} to/from any native type
*
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java
index 189b597..090dd48 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePrimitive.java
@@ -23,8 +23,6 @@ import android.util.Rational;
import static android.hardware.camera2.impl.CameraMetadataNative.*;
import static android.hardware.camera2.marshal.MarshalHelpers.*;
-import static com.android.internal.util.Preconditions.*;
-
import java.nio.ByteBuffer;
/**
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java
index 8512804..64763e7 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableRange.java
@@ -27,9 +27,6 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
-import static android.hardware.camera2.impl.CameraMetadataNative.*;
-import static android.hardware.camera2.marshal.MarshalHelpers.*;
-
/**
* Marshal {@link Range} to/from any native type
*/
diff --git a/core/java/android/hardware/camera2/params/LensShadingMap.java b/core/java/android/hardware/camera2/params/LensShadingMap.java
index 9bbc33a..d6b84f2 100644
--- a/core/java/android/hardware/camera2/params/LensShadingMap.java
+++ b/core/java/android/hardware/camera2/params/LensShadingMap.java
@@ -19,7 +19,6 @@ package android.hardware.camera2.params;
import static com.android.internal.util.Preconditions.*;
import static android.hardware.camera2.params.RggbChannelVector.*;
-import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.utils.HashCodeHelpers;
@@ -238,6 +237,51 @@ public final class LensShadingMap {
return HashCodeHelpers.hashCode(mRows, mColumns, elemsHash);
}
+ /**
+ * Return the LensShadingMap as a string representation.
+ *
+ * <p> {@code "LensShadingMap{R:([%f, %f, ... %f], ... [%f, %f, ... %f]), G_even:([%f, %f, ...
+ * %f], ... [%f, %f, ... %f]), G_odd:([%f, %f, ... %f], ... [%f, %f, ... %f]), B:([%f, %f, ...
+ * %f], ... [%f, %f, ... %f])}"},
+ * where each {@code %f} represents one gain factor and each {@code [%f, %f, ... %f]} represents
+ * a row of the lens shading map</p>
+ *
+ * @return string representation of {@link LensShadingMap}
+ */
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+ str.append("LensShadingMap{");
+
+ final String channelPrefix[] = {"R:(", "G_even:(", "G_odd:(", "B:("};
+
+ for (int ch = 0; ch < COUNT; ch++) {
+ str.append(channelPrefix[ch]);
+
+ for (int r = 0; r < mRows; r++) {
+ str.append("[");
+ for (int c = 0; c < mColumns; c++) {
+ float gain = getGainFactor(ch, c, r);
+ str.append(gain);
+ if (c < mColumns - 1) {
+ str.append(", ");
+ }
+ }
+ str.append("]");
+ if (r < mRows - 1) {
+ str.append(", ");
+ }
+ }
+
+ str.append(")");
+ if (ch < COUNT - 1) {
+ str.append(", ");
+ }
+ }
+
+ str.append("}");
+ return str.toString();
+ }
private final int mRows;
private final int mColumns;
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.aidl b/core/java/android/hardware/camera2/params/OutputConfiguration.aidl
new file mode 100644
index 0000000..0921cd8
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.params;
+
+/** @hide */
+parcelable OutputConfiguration;
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
new file mode 100644
index 0000000..0a4ed39
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.hardware.camera2.params;
+
+import android.hardware.camera2.CameraDevice;
+import android.util.Log;
+import android.view.Surface;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import static com.android.internal.util.Preconditions.*;
+
+/**
+ * A class for describing camera output, which contains a {@link Surface} and its specific
+ * configuration for creating capture session.
+ *
+ * @see CameraDevice#createCaptureSession
+ *
+ * @hide
+ */
+public final class OutputConfiguration implements Parcelable {
+
+ /**
+ * Rotation constant: 0 degree rotation (no rotation)
+ */
+ public static final int ROTATION_0 = 0;
+
+ /**
+ * Rotation constant: 90 degree counterclockwise rotation.
+ */
+ public static final int ROTATION_90 = 1;
+
+ /**
+ * Rotation constant: 180 degree counterclockwise rotation.
+ */
+ public static final int ROTATION_180 = 2;
+
+ /**
+ * Rotation constant: 270 degree counterclockwise rotation.
+ */
+ public static final int ROTATION_270 = 3;
+
+ /**
+ * Create a new immutable SurfaceConfiguration instance.
+ *
+ * @param surface
+ * A Surface for camera to output to.
+ *
+ * <p>This constructor creates a default configuration</p>
+ *
+ */
+ public OutputConfiguration(Surface surface) {
+ checkNotNull(surface, "Surface must not be null");
+ mSurface = surface;
+ mRotation = ROTATION_0;
+ }
+
+ /**
+ * Create a new immutable SurfaceConfiguration instance.
+ *
+ * <p>This constructor takes an argument for desired camera rotation</p>
+ *
+ * @param surface
+ * A Surface for camera to output to.
+ * @param rotation
+ * The desired rotation to be applied on camera output. Value must be one of
+ * ROTATION_[0, 90, 180, 270]. Note that when the rotation is 90 or 270 degree,
+ * application should make sure corresponding surface size has width and height
+ * transposed corresponding to the width and height without rotation. For example,
+ * if application needs camera to capture 1280x720 picture and rotate it by 90 degree,
+ * application should set rotation to {@code ROTATION_90} and make sure the
+ * corresponding Surface size is 720x1280. Note that {@link CameraDevice} might
+ * throw {@code IllegalArgumentException} if device cannot perform such rotation.
+ *
+ */
+ public OutputConfiguration(Surface surface, int rotation) {
+ checkNotNull(surface, "Surface must not be null");
+ checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
+ mSurface = surface;
+ mRotation = rotation;
+ }
+
+ /**
+ * Create an OutputConfiguration from Parcel.
+ */
+ private OutputConfiguration(Parcel source) {
+ int rotation = source.readInt();
+ Surface surface = Surface.CREATOR.createFromParcel(source);
+ checkNotNull(surface, "Surface must not be null");
+ checkArgumentInRange(rotation, ROTATION_0, ROTATION_270, "Rotation constant");
+ mSurface = surface;
+ mRotation = rotation;
+ }
+
+ /**
+ * Get the {@link Surface} associated with this {@link OutputConfiguration}.
+ *
+ * @return the {@link Surface} associated with this {@link OutputConfiguration}.
+ */
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+ /**
+ * Get the rotation associated with this {@link OutputConfiguration}.
+ *
+ * @return the rotation associated with this {@link OutputConfiguration}.
+ * Value will be one of ROTATION_[0, 90, 180, 270]
+ */
+ public int getRotation() {
+ return mRotation;
+ }
+
+ public static final Parcelable.Creator<OutputConfiguration> CREATOR =
+ new Parcelable.Creator<OutputConfiguration>() {
+ @Override
+ public OutputConfiguration createFromParcel(Parcel source) {
+ try {
+ OutputConfiguration outputConfiguration = new OutputConfiguration(source);
+ return outputConfiguration;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating OutputConfiguration from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public OutputConfiguration[] newArray(int size) {
+ return new OutputConfiguration[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (dest == null) {
+ throw new IllegalArgumentException("dest must not be null");
+ }
+ dest.writeInt(mRotation);
+ mSurface.writeToParcel(dest, flags);
+ }
+
+ private static final String TAG = "OutputConfiguration";
+ private final Surface mSurface;
+ private final int mRotation;
+}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index 479c842..f5304f8 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -26,7 +26,6 @@ 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;
import android.util.Size;
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 0051ef5..d9f9c1e 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -17,10 +17,10 @@
package android.hardware.display;
import android.content.Context;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManager.DisplayListener;
import android.media.projection.MediaProjection;
import android.media.projection.IMediaProjection;
-import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -196,11 +196,11 @@ public final class DisplayManagerGlobal {
* Gets information about a logical display without applying any compatibility metrics.
*
* @param displayId The logical display id.
- * @param IBinder the activity token for this display.
+ * @param configuration the configuration.
* @return The display object, or null if there is no display with the given id.
*/
- public Display getRealDisplay(int displayId, IBinder token) {
- return getCompatibleDisplay(displayId, new DisplayAdjustments(token));
+ public Display getRealDisplay(int displayId, Configuration configuration) {
+ return getCompatibleDisplay(displayId, new DisplayAdjustments(configuration));
}
public void registerDisplayListener(DisplayListener listener, Handler handler) {
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 4ddf10f..d354666 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -15,7 +15,6 @@
*/
package android.hardware.display;
-import android.os.IBinder;
import android.view.Display;
import android.view.Surface;
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index e5995b0..444f020 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -27,8 +27,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Vibrator;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
new file mode 100644
index 0000000..32930a7
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -0,0 +1,1308 @@
+/**
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.List;
+import java.util.Arrays;
+
+/**
+ * The RadioManager class allows to control a broadcast radio tuner present on the device.
+ * It provides data structures and methods to query for available radio modules, list their
+ * properties and open an interface to control tuning operations and receive callbacks when
+ * asynchronous operations complete or events occur.
+ * @hide
+ */
+@SystemApi
+public class RadioManager {
+
+ /** Method return status: successful operation */
+ public static final int STATUS_OK = 0;
+ /** Method return status: unspecified error */
+ public static final int STATUS_ERROR = Integer.MIN_VALUE;
+ /** Method return status: permission denied */
+ public static final int STATUS_PERMISSION_DENIED = -1;
+ /** Method return status: initialization failure */
+ public static final int STATUS_NO_INIT = -19;
+ /** Method return status: invalid argument provided */
+ public static final int STATUS_BAD_VALUE = -22;
+ /** Method return status: cannot reach service */
+ public static final int STATUS_DEAD_OBJECT = -32;
+ /** Method return status: invalid or out of sequence operation */
+ public static final int STATUS_INVALID_OPERATION = -38;
+ /** Method return status: time out before operation completion */
+ public static final int STATUS_TIMED_OUT = -110;
+
+
+ // keep in sync with radio_class_t in /system/core/incluse/system/radio.h
+ /** Radio module class supporting FM (including HD radio) and AM */
+ public static final int CLASS_AM_FM = 0;
+ /** Radio module class supporting satellite radio */
+ public static final int CLASS_SAT = 1;
+ /** Radio module class supporting Digital terrestrial radio */
+ public static final int CLASS_DT = 2;
+
+ // keep in sync with radio_band_t in /system/core/incluse/system/radio.h
+ /** AM radio band (LW/MW/SW).
+ * @see BandDescriptor */
+ public static final int BAND_AM = 0;
+ /** FM radio band.
+ * @see BandDescriptor */
+ public static final int BAND_FM = 1;
+ /** FM HD radio or DRM band.
+ * @see BandDescriptor */
+ public static final int BAND_FM_HD = 2;
+ /** AM HD radio or DRM band.
+ * @see BandDescriptor */
+ public static final int BAND_AM_HD = 3;
+
+ // keep in sync with radio_region_t in /system/core/incluse/system/radio.h
+ /** Africa, Europe.
+ * @see BandDescriptor */
+ public static final int REGION_ITU_1 = 0;
+ /** Americas.
+ * @see BandDescriptor */
+ public static final int REGION_ITU_2 = 1;
+ /** Russia.
+ * @see BandDescriptor */
+ public static final int REGION_OIRT = 2;
+ /** Japan.
+ * @see BandDescriptor */
+ public static final int REGION_JAPAN = 3;
+ /** Korea.
+ * @see BandDescriptor */
+ public static final int REGION_KOREA = 4;
+
+ /*****************************************************************************
+ * Lists properties, options and radio bands supported by a given broadcast radio module.
+ * Each module has a unique ID used to address it when calling RadioManager APIs.
+ * Module properties are returned by {@link #listModules(List <ModuleProperties>)} method.
+ ****************************************************************************/
+ public static class ModuleProperties implements Parcelable {
+
+ private final int mId;
+ private final int mClassId;
+ private final String mImplementor;
+ private final String mProduct;
+ private final String mVersion;
+ private final String mSerial;
+ private final int mNumTuners;
+ private final int mNumAudioSources;
+ private final boolean mIsCaptureSupported;
+ private final BandDescriptor[] mBands;
+
+ ModuleProperties(int id, int classId, String implementor, String product, String version,
+ String serial, int numTuners, int numAudioSources, boolean isCaptureSupported,
+ BandDescriptor[] bands) {
+ mId = id;
+ mClassId = classId;
+ mImplementor = implementor;
+ mProduct = product;
+ mVersion = version;
+ mSerial = serial;
+ mNumTuners = numTuners;
+ mNumAudioSources = numAudioSources;
+ mIsCaptureSupported = isCaptureSupported;
+ mBands = bands;
+ }
+
+
+ /** Unique module identifier provided by the native service.
+ * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}.
+ * @return the radio module unique identifier.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /** Module class identifier: {@link #CLASS_AM_FM}, {@link #CLASS_SAT}, {@link #CLASS_DT}
+ * @return the radio module class identifier.
+ */
+ public int getClassId() {
+ return mClassId;
+ }
+
+ /** Human readable broadcast radio module implementor
+ * @return the name of the radio module implementator.
+ */
+ public String getImplementor() {
+ return mImplementor;
+ }
+
+ /** Human readable broadcast radio module product name
+ * @return the radio module product name.
+ */
+ public String getProduct() {
+ return mProduct;
+ }
+
+ /** Human readable broadcast radio module version number
+ * @return the radio module version.
+ */
+ public String getVersion() {
+ return mVersion;
+ }
+
+ /** Radio module serial number.
+ * Can be used for subscription services.
+ * @return the radio module serial number.
+ */
+ public String getSerial() {
+ return mSerial;
+ }
+
+ /** Number of tuners available.
+ * This is the number of tuners that can be open simultaneously.
+ * @return the number of tuners supported.
+ */
+ public int getNumTuners() {
+ return mNumTuners;
+ }
+
+ /** Number tuner audio sources available. Must be less or equal to getNumTuners().
+ * When more than one tuner is supported, one is usually for playback and has one
+ * associated audio source and the other is for pre scanning and building a
+ * program list.
+ * @return the number of audio sources available.
+ */
+ public int getNumAudioSources() {
+ return mNumAudioSources;
+ }
+
+ /** {@code true} if audio capture is possible from radio tuner output.
+ * This indicates if routing to audio devices not connected to the same HAL as the FM radio
+ * is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be implemented.
+ * @return {@code true} if audio capture is possible, {@code false} otherwise.
+ */
+ public boolean isCaptureSupported() {
+ return mIsCaptureSupported;
+ }
+
+ /** List of descriptors for all bands supported by this module.
+ * @return an array of {@link BandDescriptor}.
+ */
+ public BandDescriptor[] getBands() {
+ return mBands;
+ }
+
+ private ModuleProperties(Parcel in) {
+ mId = in.readInt();
+ mClassId = in.readInt();
+ mImplementor = in.readString();
+ mProduct = in.readString();
+ mVersion = in.readString();
+ mSerial = in.readString();
+ mNumTuners = in.readInt();
+ mNumAudioSources = in.readInt();
+ mIsCaptureSupported = in.readInt() == 1;
+ Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader());
+ mBands = new BandDescriptor[tmp.length];
+ for (int i = 0; i < tmp.length; i++) {
+ mBands[i] = (BandDescriptor) tmp[i];
+ }
+ }
+
+ public static final Parcelable.Creator<ModuleProperties> CREATOR
+ = new Parcelable.Creator<ModuleProperties>() {
+ public ModuleProperties createFromParcel(Parcel in) {
+ return new ModuleProperties(in);
+ }
+
+ public ModuleProperties[] newArray(int size) {
+ return new ModuleProperties[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mId);
+ dest.writeInt(mClassId);
+ dest.writeString(mImplementor);
+ dest.writeString(mProduct);
+ dest.writeString(mVersion);
+ dest.writeString(mSerial);
+ dest.writeInt(mNumTuners);
+ dest.writeInt(mNumAudioSources);
+ dest.writeInt(mIsCaptureSupported ? 1 : 0);
+ dest.writeParcelableArray(mBands, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "ModuleProperties [mId=" + mId + ", mClassId=" + mClassId
+ + ", mImplementor=" + mImplementor + ", mProduct=" + mProduct
+ + ", mVersion=" + mVersion + ", mSerial=" + mSerial
+ + ", mNumTuners=" + mNumTuners
+ + ", mNumAudioSources=" + mNumAudioSources
+ + ", mIsCaptureSupported=" + mIsCaptureSupported
+ + ", mBands=" + Arrays.toString(mBands) + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mId;
+ result = prime * result + mClassId;
+ result = prime * result + ((mImplementor == null) ? 0 : mImplementor.hashCode());
+ result = prime * result + ((mProduct == null) ? 0 : mProduct.hashCode());
+ result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+ result = prime * result + ((mSerial == null) ? 0 : mSerial.hashCode());
+ result = prime * result + mNumTuners;
+ result = prime * result + mNumAudioSources;
+ result = prime * result + (mIsCaptureSupported ? 1 : 0);
+ result = prime * result + Arrays.hashCode(mBands);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof ModuleProperties))
+ return false;
+ ModuleProperties other = (ModuleProperties) obj;
+ if (mId != other.getId())
+ return false;
+ if (mClassId != other.getClassId())
+ return false;
+ if (mImplementor == null) {
+ if (other.getImplementor() != null)
+ return false;
+ } else if (!mImplementor.equals(other.getImplementor()))
+ return false;
+ if (mProduct == null) {
+ if (other.getProduct() != null)
+ return false;
+ } else if (!mProduct.equals(other.getProduct()))
+ return false;
+ if (mVersion == null) {
+ if (other.getVersion() != null)
+ return false;
+ } else if (!mVersion.equals(other.getVersion()))
+ return false;
+ if (mSerial == null) {
+ if (other.getSerial() != null)
+ return false;
+ } else if (!mSerial.equals(other.getSerial()))
+ return false;
+ if (mNumTuners != other.getNumTuners())
+ return false;
+ if (mNumAudioSources != other.getNumAudioSources())
+ return false;
+ if (mIsCaptureSupported != other.isCaptureSupported())
+ return false;
+ if (!Arrays.equals(mBands, other.getBands()))
+ return false;
+ return true;
+ }
+ }
+
+ /** Radio band descriptor: an element in ModuleProperties bands array.
+ * It is either an instance of {@link FmBandDescriptor} or {@link AmBandDescriptor} */
+ public static class BandDescriptor implements Parcelable {
+
+ private final int mRegion;
+ private final int mType;
+ private final int mLowerLimit;
+ private final int mUpperLimit;
+ private final int mSpacing;
+
+ BandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing) {
+ mRegion = region;
+ mType = type;
+ mLowerLimit = lowerLimit;
+ mUpperLimit = upperLimit;
+ mSpacing = spacing;
+ }
+
+ /** Region this band applies to. E.g. {@link #REGION_ITU_1}
+ * @return the region this band is associated to.
+ */
+ public int getRegion() {
+ return mRegion;
+ }
+ /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
+ * <ul>
+ * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li>
+ * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li>
+ * </ul>
+ * @return the band type.
+ */
+ public int getType() {
+ return mType;
+ }
+ /** Lower band limit expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the lower band limit.
+ */
+ public int getLowerLimit() {
+ return mLowerLimit;
+ }
+ /** Upper band limit expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the upper band limit.
+ */
+ public int getUpperLimit() {
+ return mUpperLimit;
+ }
+ /** Channel spacing in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the channel spacing.
+ */
+ public int getSpacing() {
+ return mSpacing;
+ }
+
+ private BandDescriptor(Parcel in) {
+ mRegion = in.readInt();
+ mType = in.readInt();
+ mLowerLimit = in.readInt();
+ mUpperLimit = in.readInt();
+ mSpacing = in.readInt();
+ }
+
+ public static final Parcelable.Creator<BandDescriptor> CREATOR
+ = new Parcelable.Creator<BandDescriptor>() {
+ public BandDescriptor createFromParcel(Parcel in) {
+ return new BandDescriptor(in);
+ }
+
+ public BandDescriptor[] newArray(int size) {
+ return new BandDescriptor[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRegion);
+ dest.writeInt(mType);
+ dest.writeInt(mLowerLimit);
+ dest.writeInt(mUpperLimit);
+ dest.writeInt(mSpacing);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "BandDescriptor [mRegion=" + mRegion + ", mType=" + mType + ", mLowerLimit="
+ + mLowerLimit + ", mUpperLimit=" + mUpperLimit + ", mSpacing=" + mSpacing + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mRegion;
+ result = prime * result + mType;
+ result = prime * result + mLowerLimit;
+ result = prime * result + mUpperLimit;
+ result = prime * result + mSpacing;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof BandDescriptor))
+ return false;
+ BandDescriptor other = (BandDescriptor) obj;
+ if (mRegion != other.getRegion())
+ return false;
+ if (mType != other.getType())
+ return false;
+ if (mLowerLimit != other.getLowerLimit())
+ return false;
+ if (mUpperLimit != other.getUpperLimit())
+ return false;
+ if (mSpacing != other.getSpacing())
+ return false;
+ return true;
+ }
+ }
+
+ /** FM band descriptor
+ * @see #BAND_FM
+ * @see #BAND_FM_HD */
+ public static class FmBandDescriptor extends BandDescriptor {
+ private final boolean mStereo;
+ private final boolean mRds;
+ private final boolean mTa;
+ private final boolean mAf;
+
+ FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ boolean stereo, boolean rds, boolean ta, boolean af) {
+ super(region, type, lowerLimit, upperLimit, spacing);
+ mStereo = stereo;
+ mRds = rds;
+ mTa = ta;
+ mAf = af;
+ }
+
+ /** Stereo is supported
+ * @return {@code true} if stereo is supported, {@code false} otherwise.
+ */
+ public boolean isStereoSupported() {
+ return mStereo;
+ }
+ /** RDS or RBDS(if region is ITU2) is supported
+ * @return {@code true} if RDS or RBDS is supported, {@code false} otherwise.
+ */
+ public boolean isRdsSupported() {
+ return mRds;
+ }
+ /** Traffic announcement is supported
+ * @return {@code true} if TA is supported, {@code false} otherwise.
+ */
+ public boolean isTaSupported() {
+ return mTa;
+ }
+ /** Alternate Frequency Switching is supported
+ * @return {@code true} if AF switching is supported, {@code false} otherwise.
+ */
+ public boolean isAfSupported() {
+ return mAf;
+ }
+
+ /* Parcelable implementation */
+ private FmBandDescriptor(Parcel in) {
+ super(in);
+ mStereo = in.readByte() == 1;
+ mRds = in.readByte() == 1;
+ mTa = in.readByte() == 1;
+ mAf = in.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator<FmBandDescriptor> CREATOR
+ = new Parcelable.Creator<FmBandDescriptor>() {
+ public FmBandDescriptor createFromParcel(Parcel in) {
+ return new FmBandDescriptor(in);
+ }
+
+ public FmBandDescriptor[] newArray(int size) {
+ return new FmBandDescriptor[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mStereo ? 1 : 0));
+ dest.writeByte((byte) (mRds ? 1 : 0));
+ dest.writeByte((byte) (mTa ? 1 : 0));
+ dest.writeByte((byte) (mAf ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "FmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo
+ + ", mRds=" + mRds + ", mTa=" + mTa + ", mAf=" + mAf + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (mStereo ? 1 : 0);
+ result = prime * result + (mRds ? 1 : 0);
+ result = prime * result + (mTa ? 1 : 0);
+ result = prime * result + (mAf ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof FmBandDescriptor))
+ return false;
+ FmBandDescriptor other = (FmBandDescriptor) obj;
+ if (mStereo != other.isStereoSupported())
+ return false;
+ if (mRds != other.isRdsSupported())
+ return false;
+ if (mTa != other.isTaSupported())
+ return false;
+ if (mAf != other.isAfSupported())
+ return false;
+ return true;
+ }
+ }
+
+ /** AM band descriptor.
+ * @see #BAND_AM */
+ public static class AmBandDescriptor extends BandDescriptor {
+
+ private final boolean mStereo;
+
+ AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ boolean stereo) {
+ super(region, type, lowerLimit, upperLimit, spacing);
+ mStereo = stereo;
+ }
+
+ /** Stereo is supported
+ * @return {@code true} if stereo is supported, {@code false} otherwise.
+ */
+ public boolean isStereoSupported() {
+ return mStereo;
+ }
+
+ private AmBandDescriptor(Parcel in) {
+ super(in);
+ mStereo = in.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator<AmBandDescriptor> CREATOR
+ = new Parcelable.Creator<AmBandDescriptor>() {
+ public AmBandDescriptor createFromParcel(Parcel in) {
+ return new AmBandDescriptor(in);
+ }
+
+ public AmBandDescriptor[] newArray(int size) {
+ return new AmBandDescriptor[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mStereo ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "AmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (mStereo ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof AmBandDescriptor))
+ return false;
+ AmBandDescriptor other = (AmBandDescriptor) obj;
+ if (mStereo != other.isStereoSupported())
+ return false;
+ return true;
+ }
+ }
+
+
+ /** Radio band configuration. */
+ public static class BandConfig implements Parcelable {
+
+ final BandDescriptor mDescriptor;
+
+ BandConfig(BandDescriptor descriptor) {
+ mDescriptor = descriptor;
+ }
+
+ BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) {
+ mDescriptor = new BandDescriptor(region, type, lowerLimit, upperLimit, spacing);
+ }
+
+ private BandConfig(Parcel in) {
+ mDescriptor = new BandDescriptor(in);
+ }
+
+ BandDescriptor getDescriptor() {
+ return mDescriptor;
+ }
+
+ /** Region this band applies to. E.g. {@link #REGION_ITU_1}
+ * @return the region associated with this band.
+ */
+ public int getRegion() {
+ return mDescriptor.getRegion();
+ }
+ /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
+ * <ul>
+ * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li>
+ * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li>
+ * </ul>
+ * @return the band type.
+ */
+ public int getType() {
+ return mDescriptor.getType();
+ }
+ /** Lower band limit expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the lower band limit.
+ */
+ public int getLowerLimit() {
+ return mDescriptor.getLowerLimit();
+ }
+ /** Upper band limit expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the upper band limit.
+ */
+ public int getUpperLimit() {
+ return mDescriptor.getUpperLimit();
+ }
+ /** Channel spacing in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the channel spacing.
+ */
+ public int getSpacing() {
+ return mDescriptor.getSpacing();
+ }
+
+
+ public static final Parcelable.Creator<BandConfig> CREATOR
+ = new Parcelable.Creator<BandConfig>() {
+ public BandConfig createFromParcel(Parcel in) {
+ return new BandConfig(in);
+ }
+
+ public BandConfig[] newArray(int size) {
+ return new BandConfig[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mDescriptor.writeToParcel(dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "BandConfig [ " + mDescriptor.toString() + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mDescriptor.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof BandConfig))
+ return false;
+ BandConfig other = (BandConfig) obj;
+ if (mDescriptor != other.getDescriptor())
+ return false;
+ return true;
+ }
+ }
+
+ /** FM band configuration.
+ * @see #BAND_FM
+ * @see #BAND_FM_HD */
+ public static class FmBandConfig extends BandConfig {
+ private final boolean mStereo;
+ private final boolean mRds;
+ private final boolean mTa;
+ private final boolean mAf;
+
+ FmBandConfig(FmBandDescriptor descriptor) {
+ super((BandDescriptor)descriptor);
+ mStereo = descriptor.isStereoSupported();
+ mRds = descriptor.isRdsSupported();
+ mTa = descriptor.isTaSupported();
+ mAf = descriptor.isAfSupported();
+ }
+
+ FmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ boolean stereo, boolean rds, boolean ta, boolean af) {
+ super(region, type, lowerLimit, upperLimit, spacing);
+ mStereo = stereo;
+ mRds = rds;
+ mTa = ta;
+ mAf = af;
+ }
+
+ /** Get stereo enable state
+ * @return the enable state.
+ */
+ public boolean getStereo() {
+ return mStereo;
+ }
+
+ /** Get RDS or RBDS(if region is ITU2) enable state
+ * @return the enable state.
+ */
+ public boolean getRds() {
+ return mRds;
+ }
+
+ /** Get Traffic announcement enable state
+ * @return the enable state.
+ */
+ public boolean getTa() {
+ return mTa;
+ }
+
+ /** Get Alternate Frequency Switching enable state
+ * @return the enable state.
+ */
+ public boolean getAf() {
+ return mAf;
+ }
+
+ private FmBandConfig(Parcel in) {
+ super(in);
+ mStereo = in.readByte() == 1;
+ mRds = in.readByte() == 1;
+ mTa = in.readByte() == 1;
+ mAf = in.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator<FmBandConfig> CREATOR
+ = new Parcelable.Creator<FmBandConfig>() {
+ public FmBandConfig createFromParcel(Parcel in) {
+ return new FmBandConfig(in);
+ }
+
+ public FmBandConfig[] newArray(int size) {
+ return new FmBandConfig[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mStereo ? 1 : 0));
+ dest.writeByte((byte) (mRds ? 1 : 0));
+ dest.writeByte((byte) (mTa ? 1 : 0));
+ dest.writeByte((byte) (mAf ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "FmBandConfig [" + super.toString()
+ + ", mStereo=" + mStereo + ", mRds=" + mRds + ", mTa=" + mTa
+ + ", mAf=" + mAf + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (mStereo ? 1 : 0);
+ result = prime * result + (mRds ? 1 : 0);
+ result = prime * result + (mTa ? 1 : 0);
+ result = prime * result + (mAf ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof FmBandConfig))
+ return false;
+ FmBandConfig other = (FmBandConfig) obj;
+ if (mStereo != other.mStereo)
+ return false;
+ if (mRds != other.mRds)
+ return false;
+ if (mTa != other.mTa)
+ return false;
+ if (mAf != other.mAf)
+ return false;
+ return true;
+ }
+
+ /**
+ * Builder class for {@link FmBandConfig} objects.
+ */
+ public static class Builder {
+ private final BandDescriptor mDescriptor;
+ private boolean mStereo;
+ private boolean mRds;
+ private boolean mTa;
+ private boolean mAf;
+
+ /**
+ * Constructs a new Builder with the defaults from an {@link FmBandDescriptor} .
+ * @param descriptor the FmBandDescriptor defaults are read from .
+ */
+ public Builder(FmBandDescriptor descriptor) {
+ mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(),
+ descriptor.getLowerLimit(), descriptor.getUpperLimit(),
+ descriptor.getSpacing());
+ mStereo = descriptor.isStereoSupported();
+ mRds = descriptor.isRdsSupported();
+ mTa = descriptor.isTaSupported();
+ mAf = descriptor.isAfSupported();
+ }
+
+ /**
+ * Constructs a new Builder from a given {@link FmBandConfig}
+ * @param config the FmBandConfig object whose data will be reused in the new Builder.
+ */
+ public Builder(FmBandConfig config) {
+ mDescriptor = new BandDescriptor(config.getRegion(), config.getType(),
+ config.getLowerLimit(), config.getUpperLimit(), config.getSpacing());
+ mStereo = config.getStereo();
+ mRds = config.getRds();
+ mTa = config.getTa();
+ mAf = config.getAf();
+ }
+
+ /**
+ * Combines all of the parameters that have been set and return a new
+ * {@link FmBandConfig} object.
+ * @return a new {@link FmBandConfig} object
+ */
+ public FmBandConfig build() {
+ FmBandConfig config = new FmBandConfig(mDescriptor.getRegion(),
+ mDescriptor.getType(), mDescriptor.getLowerLimit(),
+ mDescriptor.getUpperLimit(), mDescriptor.getSpacing(),
+ mStereo, mRds, mTa, mAf);
+ return config;
+ }
+
+ /** Set stereo enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setStereo(boolean state) {
+ mStereo = state;
+ return this;
+ }
+
+ /** Set RDS or RBDS(if region is ITU2) enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setRds(boolean state) {
+ mRds = state;
+ return this;
+ }
+
+ /** Set Traffic announcement enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setTa(boolean state) {
+ mTa = state;
+ return this;
+ }
+
+ /** Set Alternate Frequency Switching enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setAf(boolean state) {
+ mAf = state;
+ return this;
+ }
+ };
+ }
+
+ /** AM band configuration.
+ * @see #BAND_AM */
+ public static class AmBandConfig extends BandConfig {
+ private final boolean mStereo;
+
+ AmBandConfig(AmBandDescriptor descriptor) {
+ super((BandDescriptor)descriptor);
+ mStereo = descriptor.isStereoSupported();
+ }
+
+ AmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing,
+ boolean stereo) {
+ super(region, type, lowerLimit, upperLimit, spacing);
+ mStereo = stereo;
+ }
+
+ /** Get stereo enable state
+ * @return the enable state.
+ */
+ public boolean getStereo() {
+ return mStereo;
+ }
+
+ private AmBandConfig(Parcel in) {
+ super(in);
+ mStereo = in.readByte() == 1;
+ }
+
+ public static final Parcelable.Creator<AmBandConfig> CREATOR
+ = new Parcelable.Creator<AmBandConfig>() {
+ public AmBandConfig createFromParcel(Parcel in) {
+ return new AmBandConfig(in);
+ }
+
+ public AmBandConfig[] newArray(int size) {
+ return new AmBandConfig[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeByte((byte) (mStereo ? 1 : 0));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "AmBandConfig [" + super.toString()
+ + ", mStereo=" + mStereo + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + (mStereo ? 1 : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (!(obj instanceof AmBandConfig))
+ return false;
+ AmBandConfig other = (AmBandConfig) obj;
+ if (mStereo != other.getStereo())
+ return false;
+ return true;
+ }
+
+ /**
+ * Builder class for {@link AmBandConfig} objects.
+ */
+ public static class Builder {
+ private final BandDescriptor mDescriptor;
+ private boolean mStereo;
+
+ /**
+ * Constructs a new Builder with the defaults from an {@link AmBandDescriptor} .
+ * @param descriptor the FmBandDescriptor defaults are read from .
+ */
+ public Builder(AmBandDescriptor descriptor) {
+ mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(),
+ descriptor.getLowerLimit(), descriptor.getUpperLimit(),
+ descriptor.getSpacing());
+ mStereo = descriptor.isStereoSupported();
+ }
+
+ /**
+ * Constructs a new Builder from a given {@link AmBandConfig}
+ * @param config the FmBandConfig object whose data will be reused in the new Builder.
+ */
+ public Builder(AmBandConfig config) {
+ mDescriptor = new BandDescriptor(config.getRegion(), config.getType(),
+ config.getLowerLimit(), config.getUpperLimit(), config.getSpacing());
+ mStereo = config.getStereo();
+ }
+
+ /**
+ * Combines all of the parameters that have been set and return a new
+ * {@link AmBandConfig} object.
+ * @return a new {@link AmBandConfig} object
+ */
+ public AmBandConfig build() {
+ AmBandConfig config = new AmBandConfig(mDescriptor.getRegion(),
+ mDescriptor.getType(), mDescriptor.getLowerLimit(),
+ mDescriptor.getUpperLimit(), mDescriptor.getSpacing(),
+ mStereo);
+ return config;
+ }
+
+ /** Set stereo enable state
+ * @param state The new enable state.
+ * @return the same Builder instance.
+ */
+ public Builder setStereo(boolean state) {
+ mStereo = state;
+ return this;
+ }
+ };
+ }
+
+ /** Radio program information returned by
+ * {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */
+ public static class ProgramInfo implements Parcelable {
+
+ private final int mChannel;
+ private final int mSubChannel;
+ private final boolean mTuned;
+ private final boolean mStereo;
+ private final boolean mDigital;
+ private final int mSignalStrength;
+ private final RadioMetadata mMetadata;
+
+ ProgramInfo(int channel, int subChannel, boolean tuned, boolean stereo,
+ boolean digital, int signalStrength, RadioMetadata metadata) {
+ mChannel = channel;
+ mSubChannel = subChannel;
+ mTuned = tuned;
+ mStereo = stereo;
+ mDigital = digital;
+ mSignalStrength = signalStrength;
+ mMetadata = metadata;
+ }
+
+ /** Main channel expressed in units according to band type.
+ * Currently all defined band types express channels as frequency in kHz
+ * @return the program channel
+ */
+ public int getChannel() {
+ return mChannel;
+ }
+ /** Sub channel ID. E.g 1 for HD radio HD1
+ * @return the program sub channel
+ */
+ public int getSubChannel() {
+ return mSubChannel;
+ }
+ /** {@code true} if the tuner is currently tuned on a valid station
+ * @return {@code true} if currently tuned, {@code false} otherwise.
+ */
+ public boolean isTuned() {
+ return mTuned;
+ }
+ /** {@code true} if the received program is stereo
+ * @return {@code true} if stereo, {@code false} otherwise.
+ */
+ public boolean isStereo() {
+ return mStereo;
+ }
+ /** {@code true} if the received program is digital (e.g HD radio)
+ * @return {@code true} if digital, {@code false} otherwise.
+ */
+ public boolean isDigital() {
+ return mDigital;
+ }
+ /** Signal strength indicator from 0 (no signal) to 100 (excellent)
+ * @return the signal strength indication.
+ */
+ public int getSignalStrength() {
+ return mSignalStrength;
+ }
+ /** Metadata currently received from this station.
+ * null if no metadata have been received
+ * @return current meta data received from this program.
+ */
+ public RadioMetadata getMetadata() {
+ return mMetadata;
+ }
+
+ private ProgramInfo(Parcel in) {
+ mChannel = in.readInt();
+ mSubChannel = in.readInt();
+ mTuned = in.readByte() == 1;
+ mStereo = in.readByte() == 1;
+ mDigital = in.readByte() == 1;
+ mSignalStrength = in.readInt();
+ if (in.readByte() == 1) {
+ mMetadata = RadioMetadata.CREATOR.createFromParcel(in);
+ } else {
+ mMetadata = null;
+ }
+ }
+
+ public static final Parcelable.Creator<ProgramInfo> CREATOR
+ = new Parcelable.Creator<ProgramInfo>() {
+ public ProgramInfo createFromParcel(Parcel in) {
+ return new ProgramInfo(in);
+ }
+
+ public ProgramInfo[] newArray(int size) {
+ return new ProgramInfo[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mChannel);
+ dest.writeInt(mSubChannel);
+ dest.writeByte((byte)(mTuned ? 1 : 0));
+ dest.writeByte((byte)(mStereo ? 1 : 0));
+ dest.writeByte((byte)(mDigital ? 1 : 0));
+ dest.writeInt(mSignalStrength);
+ if (mMetadata == null) {
+ dest.writeByte((byte)0);
+ } else {
+ dest.writeByte((byte)1);
+ mMetadata.writeToParcel(dest, flags);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "ProgramInfo [mChannel=" + mChannel + ", mSubChannel=" + mSubChannel
+ + ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
+ + ", mSignalStrength=" + mSignalStrength
+ + ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString()))
+ + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mChannel;
+ result = prime * result + mSubChannel;
+ result = prime * result + (mTuned ? 1 : 0);
+ result = prime * result + (mStereo ? 1 : 0);
+ result = prime * result + (mDigital ? 1 : 0);
+ result = prime * result + mSignalStrength;
+ result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof ProgramInfo))
+ return false;
+ ProgramInfo other = (ProgramInfo) obj;
+ if (mChannel != other.getChannel())
+ return false;
+ if (mSubChannel != other.getSubChannel())
+ return false;
+ if (mTuned != other.isTuned())
+ return false;
+ if (mStereo != other.isStereo())
+ return false;
+ if (mDigital != other.isDigital())
+ return false;
+ if (mSignalStrength != other.getSignalStrength())
+ return false;
+ if (mMetadata == null) {
+ if (other.getMetadata() != null)
+ return false;
+ } else if (!mMetadata.equals(other.getMetadata()))
+ return false;
+ return true;
+ }
+ }
+
+
+ /**
+ * Returns a list of descriptors for all broadcast radio modules present on the device.
+ * @param modules An List of {@link ModuleProperties} where the list will be returned.
+ * @return
+ * <ul>
+ * <li>{@link #STATUS_OK} in case of success, </li>
+ * <li>{@link #STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link #STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link #STATUS_BAD_VALUE} if modules is null, </li>
+ * <li>{@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails, </li>
+ * </ul>
+ */
+ public native int listModules(List <ModuleProperties> modules);
+
+ /**
+ * Open an interface to control a tuner on a given broadcast radio module.
+ * Optionally selects and applies the configuration passed as "config" argument.
+ * @param moduleId radio module identifier {@link ModuleProperties#getId()}. Mandatory.
+ * @param config desired band and configuration to apply when enabling the hardware module.
+ * optional, can be null.
+ * @param withAudio {@code true} to request a tuner with an audio source.
+ * This tuner is intended for live listening or recording or a radio program.
+ * If {@code false}, the tuner can only be used to retrieve program informations.
+ * @param callback {@link RadioTuner.Callback} interface. Mandatory.
+ * @param handler the Handler on which the callbacks will be received.
+ * Can be null if default handler is OK.
+ * @return a valid {@link RadioTuner} interface in case of success or null in case of error.
+ */
+ public RadioTuner openTuner(int moduleId, BandConfig config, boolean withAudio,
+ RadioTuner.Callback callback, Handler handler) {
+ if (callback == null) {
+ return null;
+ }
+ RadioModule module = new RadioModule(moduleId, config, withAudio, callback, handler);
+ if (module != null) {
+ if (!module.initCheck()) {
+ module = null;
+ }
+ }
+ return (RadioTuner)module;
+ }
+
+ private final Context mContext;
+
+ /**
+ * @hide
+ */
+ public RadioManager(Context context) {
+ mContext = context;
+ }
+}
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
new file mode 100644
index 0000000..8b1851b
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.radio;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.ContentResolver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Contains meta data about a radio program such as station name, song title, artist etc...
+ * @hide
+ */
+@SystemApi
+public final class RadioMetadata implements Parcelable {
+ private static final String TAG = "RadioMetadata";
+
+ /**
+ * The RDS Program Information.
+ */
+ public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI";
+
+ /**
+ * The RDS Program Service.
+ */
+ public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS";
+
+ /**
+ * The RDS PTY.
+ */
+ public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
+
+ /**
+ * The RBDS PTY.
+ */
+ public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
+
+ /**
+ * The RBDS Radio Text.
+ */
+ public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
+
+ /**
+ * The song title.
+ */
+ public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
+
+ /**
+ * The artist name.
+ */
+ public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
+
+ /**
+ * The album name.
+ */
+ public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM";
+
+ /**
+ * The music genre.
+ */
+ public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
+
+ /**
+ * The radio station icon {@link Bitmap}.
+ */
+ public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
+
+ /**
+ * The artwork for the song/album {@link Bitmap}.
+ */
+ public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
+
+
+ private static final int METADATA_TYPE_INVALID = -1;
+ private static final int METADATA_TYPE_INT = 0;
+ private static final int METADATA_TYPE_TEXT = 1;
+ private static final int METADATA_TYPE_BITMAP = 2;
+
+ private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
+
+ static {
+ METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
+ METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
+ }
+
+ // keep in sync with: system/media/radio/include/system/radio_metadata.h
+ private static final int NATIVE_KEY_INVALID = -1;
+ private static final int NATIVE_KEY_RDS_PI = 0;
+ private static final int NATIVE_KEY_RDS_PS = 1;
+ private static final int NATIVE_KEY_RDS_PTY = 2;
+ private static final int NATIVE_KEY_RBDS_PTY = 3;
+ private static final int NATIVE_KEY_RDS_RT = 4;
+ private static final int NATIVE_KEY_TITLE = 5;
+ private static final int NATIVE_KEY_ARTIST = 6;
+ private static final int NATIVE_KEY_ALBUM = 7;
+ private static final int NATIVE_KEY_GENRE = 8;
+ private static final int NATIVE_KEY_ICON = 9;
+ private static final int NATIVE_KEY_ART = 10;
+
+ private static final SparseArray<String> NATIVE_KEY_MAPPING;
+
+ static {
+ NATIVE_KEY_MAPPING = new SparseArray<String>();
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
+ NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
+ }
+
+ private final Bundle mBundle;
+
+ RadioMetadata() {
+ mBundle = new Bundle();
+ }
+
+ private RadioMetadata(Bundle bundle) {
+ mBundle = new Bundle(bundle);
+ }
+
+ private RadioMetadata(Parcel in) {
+ mBundle = in.readBundle();
+ }
+
+ /**
+ * Returns {@code true} if the given key is contained in the meta data
+ *
+ * @param key a String key
+ * @return {@code true} if the key exists in this meta data, {@code false} otherwise
+ */
+ public boolean containsKey(String key) {
+ return mBundle.containsKey(key);
+ }
+
+ /**
+ * Returns the text value associated with the given key as a String, or null
+ * if the key is not found in the meta data.
+ *
+ * @param key The key the value is stored under
+ * @return a String value, or null
+ */
+ public String getString(String key) {
+ return mBundle.getString(key);
+ }
+
+ /**
+ * Returns the value associated with the given key,
+ * or 0 if the key is not found in the meta data.
+ *
+ * @param key The key the value is stored under
+ * @return an int value
+ */
+ public int getInt(String key) {
+ return mBundle.getInt(key, 0);
+ }
+
+ /**
+ * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
+ *
+ * @param key The key the value is stored under
+ * @return a {@link Bitmap} or null
+ */
+ public Bitmap getBitmap(String key) {
+ Bitmap bmp = null;
+ try {
+ bmp = mBundle.getParcelable(key);
+ } catch (Exception e) {
+ // ignore, value was not a bitmap
+ Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
+ }
+ return bmp;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBundle(mBundle);
+ }
+
+ /**
+ * Returns the number of fields in this meta data.
+ *
+ * @return the number of fields in the meta data.
+ */
+ public int size() {
+ return mBundle.size();
+ }
+
+ /**
+ * Returns a Set containing the Strings used as keys in this meta data.
+ *
+ * @return a Set of String keys
+ */
+ public Set<String> keySet() {
+ return mBundle.keySet();
+ }
+
+ /**
+ * Helper for getting the String key used by {@link RadioMetadata} from the
+ * corrsponding native integer key.
+ *
+ * @param editorKey The key used by the editor
+ * @return the key used by this class or null if no mapping exists
+ * @hide
+ */
+ public static String getKeyFromNativeKey(int nativeKey) {
+ return NATIVE_KEY_MAPPING.get(nativeKey, null);
+ }
+
+ public static final Parcelable.Creator<RadioMetadata> CREATOR =
+ new Parcelable.Creator<RadioMetadata>() {
+ @Override
+ public RadioMetadata createFromParcel(Parcel in) {
+ return new RadioMetadata(in);
+ }
+
+ @Override
+ public RadioMetadata[] newArray(int size) {
+ return new RadioMetadata[size];
+ }
+ };
+
+ /**
+ * Use to build RadioMetadata objects.
+ */
+ public static final class Builder {
+ private final Bundle mBundle;
+
+ /**
+ * Create an empty Builder. Any field that should be included in the
+ * {@link RadioMetadata} must be added.
+ */
+ public Builder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Create a Builder using a {@link RadioMetadata} instance to set the
+ * initial values. All fields in the source meta data will be included in
+ * the new meta data. Fields can be overwritten by adding the same key.
+ *
+ * @param source
+ */
+ public Builder(RadioMetadata source) {
+ mBundle = new Bundle(source.mBundle);
+ }
+
+ /**
+ * Create a Builder using a {@link RadioMetadata} instance to set
+ * initial values, but replace bitmaps with a scaled down copy if they
+ * are larger than maxBitmapSize.
+ *
+ * @param source The original meta data to copy.
+ * @param maxBitmapSize The maximum height/width for bitmaps contained
+ * in the meta data.
+ * @hide
+ */
+ public Builder(RadioMetadata source, int maxBitmapSize) {
+ this(source);
+ for (String key : mBundle.keySet()) {
+ Object value = mBundle.get(key);
+ if (value != null && value instanceof Bitmap) {
+ Bitmap bmp = (Bitmap) value;
+ if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
+ putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
+ }
+ }
+ }
+ }
+
+ /**
+ * Put a String value into the meta data. Custom keys may be used, but if
+ * the METADATA_KEYs defined in this class are used they may only be one
+ * of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_RDS_PI}</li>
+ * <li>{@link #METADATA_KEY_RDS_PS}</li>
+ * <li>{@link #METADATA_KEY_RDS_RT}</li>
+ * <li>{@link #METADATA_KEY_TITLE}</li>
+ * <li>{@link #METADATA_KEY_ARTIST}</li>
+ * <li>{@link #METADATA_KEY_ALBUM}</li>
+ * <li>{@link #METADATA_KEY_GENRE}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The String value to store
+ * @return the same Builder instance
+ */
+ public Builder putString(String key, String value) {
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a String");
+ }
+ mBundle.putString(key, value);
+ return this;
+ }
+
+ /**
+ * Put an int value into the meta data. Custom keys may be used, but if
+ * the METADATA_KEYs defined in this class are used they may only be one
+ * of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_RDS_PTY}</li>
+ * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
+ * </ul>
+ *
+ * @param key The key for referencing this value
+ * @param value The int value to store
+ * @return the same Builder instance
+ */
+ public Builder putInt(String key, int value) {
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a long");
+ }
+ mBundle.putInt(key, value);
+ return this;
+ }
+
+ /**
+ * Put a {@link Bitmap} into the meta data. Custom keys may be used, but
+ * if the METADATA_KEYs defined in this class are used they may only be
+ * one of the following:
+ * <ul>
+ * <li>{@link #METADATA_KEY_ICON}</li>
+ * <li>{@link #METADATA_KEY_ART}</li>
+ * </ul>
+ * <p>
+ *
+ * @param key The key for referencing this value
+ * @param value The Bitmap to store
+ * @return the same Builder instance
+ */
+ public Builder putBitmap(String key, Bitmap value) {
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+ throw new IllegalArgumentException("The " + key
+ + " key cannot be used to put a Bitmap");
+ }
+ mBundle.putParcelable(key, value);
+ return this;
+ }
+
+ /**
+ * Creates a {@link RadioMetadata} instance with the specified fields.
+ *
+ * @return a new {@link RadioMetadata} object
+ */
+ public RadioMetadata build() {
+ return new RadioMetadata(mBundle);
+ }
+
+ private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
+ float maxSizeF = maxSize;
+ float widthScale = maxSizeF / bmp.getWidth();
+ float heightScale = maxSizeF / bmp.getHeight();
+ float scale = Math.min(widthScale, heightScale);
+ int height = (int) (bmp.getHeight() * scale);
+ int width = (int) (bmp.getWidth() * scale);
+ return Bitmap.createScaledBitmap(bmp, width, height, true);
+ }
+ }
+
+ int putIntFromNative(int nativeKey, int value) {
+ String key = getKeyFromNativeKey(nativeKey);
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) {
+ return -1;
+ }
+ mBundle.putInt(key, value);
+ return 0;
+ }
+
+ int putStringFromNative(int nativeKey, String value) {
+ String key = getKeyFromNativeKey(nativeKey);
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+ return -1;
+ }
+ mBundle.putString(key, value);
+ return 0;
+ }
+
+ int putBitmapFromNative(int nativeKey, byte[] value) {
+ String key = getKeyFromNativeKey(nativeKey);
+ if (!METADATA_KEYS_TYPE.containsKey(key) ||
+ METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+ return -1;
+ }
+ Bitmap bmp = null;
+ try {
+ bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
+ } catch (Exception e) {
+ } finally {
+ if (bmp == null) {
+ return -1;
+ }
+ mBundle.putParcelable(key, bmp);
+ return 0;
+ }
+ }
+}
diff --git a/core/java/android/hardware/radio/RadioModule.java b/core/java/android/hardware/radio/RadioModule.java
new file mode 100644
index 0000000..15916ae
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioModule.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+
+/**
+ * A RadioModule implements the RadioTuner interface for a broadcast radio tuner physically
+ * present on the device and exposed by the radio HAL.
+ *
+ * @hide
+ */
+public class RadioModule extends RadioTuner {
+ private long mNativeContext = 0;
+ private int mId;
+ private NativeEventHandlerDelegate mEventHandlerDelegate;
+
+ RadioModule(int moduleId, RadioManager.BandConfig config, boolean withAudio,
+ RadioTuner.Callback callback, Handler handler) {
+ mId = moduleId;
+ mEventHandlerDelegate = new NativeEventHandlerDelegate(callback, handler);
+ native_setup(new WeakReference<RadioModule>(this), config, withAudio);
+ }
+ private native void native_setup(Object module_this,
+ RadioManager.BandConfig config, boolean withAudio);
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+ private native void native_finalize();
+
+ boolean initCheck() {
+ return mNativeContext != 0;
+ }
+
+ // RadioTuner implementation
+ public native void close();
+
+ public native int setConfiguration(RadioManager.BandConfig config);
+
+ public native int getConfiguration(RadioManager.BandConfig[] config);
+
+ public native int setMute(boolean mute);
+
+ public native boolean getMute();
+
+ public native int step(int direction, boolean skipSubChannel);
+
+ public native int scan(int direction, boolean skipSubChannel);
+
+ public native int tune(int channel, int subChannel);
+
+ public native int cancel();
+
+ public native int getProgramInformation(RadioManager.ProgramInfo[] info);
+
+ public native boolean isAntennaConnected();
+
+ public native boolean hasControl();
+
+
+ /* keep in sync with radio_event_type_t in system/core/include/system/radio.h */
+ static final int EVENT_HW_FAILURE = 0;
+ static final int EVENT_CONFIG = 1;
+ static final int EVENT_ANTENNA = 2;
+ static final int EVENT_TUNED = 3;
+ static final int EVENT_METADATA = 4;
+ static final int EVENT_TA = 5;
+ static final int EVENT_AF_SWITCH = 6;
+ static final int EVENT_CONTROL = 100;
+ static final int EVENT_SERVER_DIED = 101;
+
+ private class NativeEventHandlerDelegate {
+ private final Handler mHandler;
+
+ NativeEventHandlerDelegate(final RadioTuner.Callback callback,
+ Handler handler) {
+ // find the looper for our new event handler
+ Looper looper;
+ if (handler != null) {
+ looper = handler.getLooper();
+ } else {
+ looper = Looper.getMainLooper();
+ }
+
+ // construct the event handler with this looper
+ if (looper != null) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_HW_FAILURE:
+ if (callback != null) {
+ callback.onError(RadioTuner.ERROR_HARDWARE_FAILURE);
+ }
+ break;
+ case EVENT_CONFIG: {
+ RadioManager.BandConfig config = (RadioManager.BandConfig)msg.obj;
+ switch(msg.arg1) {
+ case RadioManager.STATUS_OK:
+ if (callback != null) {
+ callback.onConfigurationChanged(config);
+ }
+ break;
+ default:
+ if (callback != null) {
+ callback.onError(RadioTuner.ERROR_CONFIG);
+ }
+ break;
+ }
+ } break;
+ case EVENT_ANTENNA:
+ if (callback != null) {
+ callback.onAntennaState(msg.arg2 == 1);
+ }
+ break;
+ case EVENT_AF_SWITCH:
+ case EVENT_TUNED: {
+ RadioManager.ProgramInfo info = (RadioManager.ProgramInfo)msg.obj;
+ switch (msg.arg1) {
+ case RadioManager.STATUS_OK:
+ if (callback != null) {
+ callback.onProgramInfoChanged(info);
+ }
+ break;
+ case RadioManager.STATUS_TIMED_OUT:
+ if (callback != null) {
+ callback.onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ }
+ break;
+ case RadioManager.STATUS_INVALID_OPERATION:
+ default:
+ if (callback != null) {
+ callback.onError(RadioTuner.ERROR_CANCELLED);
+ }
+ break;
+ }
+ } break;
+ case EVENT_METADATA: {
+ RadioMetadata metadata = (RadioMetadata)msg.obj;
+ if (callback != null) {
+ callback.onMetadataChanged(metadata);
+ }
+ } break;
+ case EVENT_TA:
+ if (callback != null) {
+ callback.onTrafficAnnouncement(msg.arg2 == 1);
+ }
+ break;
+ case EVENT_CONTROL:
+ if (callback != null) {
+ callback.onControlChanged(msg.arg2 == 1);
+ }
+ break;
+ case EVENT_SERVER_DIED:
+ if (callback != null) {
+ callback.onError(RadioTuner.ERROR_SERVER_DIED);
+ }
+ break;
+ default:
+ // Should not happen
+ break;
+ }
+ }
+ };
+ } else {
+ mHandler = null;
+ }
+ }
+
+ Handler handler() {
+ return mHandler;
+ }
+ }
+
+
+ @SuppressWarnings("unused")
+ private static void postEventFromNative(Object module_ref,
+ int what, int arg1, int arg2, Object obj) {
+ RadioModule module = (RadioModule)((WeakReference)module_ref).get();
+ if (module == null) {
+ return;
+ }
+
+ NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate;
+ if (delegate != null) {
+ Handler handler = delegate.handler();
+ if (handler != null) {
+ Message m = handler.obtainMessage(what, arg1, arg2, obj);
+ handler.sendMessage(m);
+ }
+ }
+ }
+}
+
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
new file mode 100644
index 0000000..376900a
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+
+/**
+ * RadioTuner interface provides methods to control a radio tuner on the device: selecting and
+ * configuring the active band, muting/unmuting, scanning and tuning, etc...
+ *
+ * Obtain a RadioTuner interface by calling {@link RadioManager#openTuner(int,
+ * RadioManager.BandConfig, boolean, RadioTuner.Callback, Handler)}.
+ * @hide
+ */
+@SystemApi
+public abstract class RadioTuner {
+
+ /** Scanning direction UP for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */
+ public static final int DIRECTION_UP = 0;
+
+ /** Scanning directions DOWN for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */
+ public static final int DIRECTION_DOWN = 1;
+
+ /**
+ * Close the tuner interface. The {@link Callback} callback will not be called
+ * anymore and associated resources will be released.
+ * Must be called when the tuner is not needed to make hardware resources available to others.
+ * */
+ public abstract void close();
+
+ /**
+ * Set the active band configuration for this module.
+ * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed
+ * in the ModuleProperties of the module with the specified ID.
+ * @param config The desired band configuration (FmBandConfig or AmBandConfig).
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int setConfiguration(RadioManager.BandConfig config);
+
+ /**
+ * Get current configuration.
+ * @param config a BandConfig array of lengh 1 where the configuration is returned.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int getConfiguration(RadioManager.BandConfig[] config);
+
+
+ /**
+ * Set mute state. When muted, the radio tuner audio source is not available for playback on
+ * any audio device. when unmuted, the radio tuner audio source is output as a media source
+ * and renderd over the audio device selected for media use case.
+ * The radio tuner audio source is muted by default when the tuner is first attached.
+ * Only effective if the tuner is attached with audio enabled.
+ *
+ * @param mute the requested mute state.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int setMute(boolean mute);
+
+ /**
+ * Get mute state.
+ *
+ * @return {@code true} if the radio tuner audio source is muted or a problem occured
+ * retrieving the mute state, {@code false} otherwise.
+ */
+ public abstract boolean getMute();
+
+ /**
+ * Step up or down by one channel spacing.
+ * The operation is asynchronous and {@link Callback}
+ * onProgramInfoChanged() will be called when step completes or
+ * onError() when cancelled or timeout.
+ * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+ * @param skipSubChannel indicates to skip sub channels when the configuration currently
+ * selected supports sub channel (e.g HD Radio). N/A otherwise.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int step(int direction, boolean skipSubChannel);
+
+ /**
+ * Scan up or down to next valid station.
+ * The operation is asynchronous and {@link Callback}
+ * onProgramInfoChanged() will be called when scan completes or
+ * onError() when cancelled or timeout.
+ * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+ * @param skipSubChannel indicates to skip sub channels when the configuration currently
+ * selected supports sub channel (e.g HD Radio). N/A otherwise.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int scan(int direction, boolean skipSubChannel);
+
+ /**
+ * Tune to a specific frequency.
+ * The operation is asynchronous and {@link Callback}
+ * onProgramInfoChanged() will be called when tune completes or
+ * onError() when cancelled or timeout.
+ * @param channel the specific channel or frequency to tune to.
+ * @param subChannel the specific sub-channel to tune to. N/A if the selected configuration
+ * does not support cub channels.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int tune(int channel, int subChannel);
+
+ /**
+ * Cancel a pending scan or tune operation.
+ * If an operation is pending, {@link Callback} onError() will be called with
+ * {@link #ERROR_CANCELLED}.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int cancel();
+
+ /**
+ * Get current station information.
+ * @param info a ProgramInfo array of lengh 1 where the information is returned.
+ * @return
+ * <ul>
+ * <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+ * <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+ * <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+ * <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+ * <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+ * <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+ * service fails, </li>
+ * </ul>
+ */
+ public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
+
+ /**
+ * Get current antenna connection state for current configuration.
+ * Only valid if a configuration has been applied.
+ * @return {@code true} if the antenna is connected, {@code false} otherwise.
+ */
+ public abstract boolean isAntennaConnected();
+
+ /**
+ * Indicates if this client actually controls the tuner.
+ * Control is always granted after
+ * {@link RadioManager#openTuner(int,
+ * RadioManager.BandConfig, boolean, Callback, Handler)}
+ * returns a non null tuner interface.
+ * Control is lost when another client opens an interface on the same tuner.
+ * When this happens, {@link Callback#onControlChanged(boolean)} is received.
+ * The client can either wait for control to be returned (which is indicated by the same
+ * callback) or close and reopen the tuner interface.
+ * @return {@code true} if this interface controls the tuner,
+ * {@code false} otherwise or if a problem occured retrieving the state.
+ */
+ public abstract boolean hasControl();
+
+ /** Indicates a failure of radio IC or driver.
+ * The application must close and re open the tuner */
+ public static final int ERROR_HARDWARE_FAILURE = 0;
+ /** Indicates a failure of the radio service.
+ * The application must close and re open the tuner */
+ public static final int ERROR_SERVER_DIED = 1;
+ /** A pending seek or tune operation was cancelled */
+ public static final int ERROR_CANCELLED = 2;
+ /** A pending seek or tune operation timed out */
+ public static final int ERROR_SCAN_TIMEOUT = 3;
+ /** The requested configuration could not be applied */
+ public static final int ERROR_CONFIG = 4;
+
+ /**
+ * Callback provided by the client application when opening a {@link RadioTuner}
+ * to receive asynchronous operation results, updates and error notifications.
+ */
+ public static abstract class Callback {
+ /**
+ * onError() is called when an error occured while performing an asynchronous
+ * operation of when the hardware or system service experiences a problem.
+ * status is one of {@link #ERROR_HARDWARE_FAILURE}, {@link #ERROR_SERVER_DIED},
+ * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT},
+ * {@link #ERROR_CONFIG}
+ */
+ public void onError(int status) {}
+ /**
+ * onConfigurationChanged() is called upon successful completion of
+ * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}
+ * or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)}
+ */
+ public void onConfigurationChanged(RadioManager.BandConfig config) {}
+ /**
+ * onProgramInfoChanged() is called upon successful completion of
+ * {@link RadioTuner#step(int, boolean)}, {@link RadioTuner#scan(int, boolean)},
+ * {@link RadioTuner#tune(int, int)} or when a switching to alternate frequency occurs.
+ * Note that if metadata only are updated, {@link #onMetadataChanged(RadioMetadata)} will
+ * be called.
+ */
+ public void onProgramInfoChanged(RadioManager.ProgramInfo info) {}
+ /**
+ * onMetadataChanged() is called when new meta data are received on current program.
+ * Meta data are also received in {@link RadioManager.ProgramInfo} when
+ * {@link #onProgramInfoChanged(RadioManager.ProgramInfo)} is called.
+ */
+ public void onMetadataChanged(RadioMetadata metadata) {}
+ /**
+ * onTrafficAnnouncement() is called when a traffic announcement starts and stops.
+ */
+ public void onTrafficAnnouncement(boolean active) {}
+ /**
+ * onAntennaState() is called when the antenna is connected or disconnected.
+ */
+ public void onAntennaState(boolean connected) {}
+ /**
+ * onControlChanged() is called when the client loses or gains control of the radio tuner.
+ * The control is always granted after a successful call to
+ * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}.
+ * If another client opens the same tuner, onControlChanged() will be called with
+ * control set to {@code false} to indicate loss of control.
+ * At this point, RadioTuner APIs other than getters will return
+ * {@link RadioManager#STATUS_INVALID_OPERATION}.
+ * When the other client releases the tuner, onControlChanged() will be called
+ * with control set to {@code true}.
+ */
+ public void onControlChanged(boolean control) {}
+ }
+
+}
+
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 1a8723d..e23a2bb 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -16,13 +16,10 @@
package android.hardware.soundtrigger;
-import android.content.Context;
-import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.lang.ref.WeakReference;
-import java.util.UUID;
/**
* The SoundTriggerModule provides APIs to control sound models and sound detection
diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java
index d90e06e..1a42319 100644
--- a/core/java/android/hardware/usb/UsbDevice.java
+++ b/core/java/android/hardware/usb/UsbDevice.java
@@ -40,6 +40,7 @@ import android.os.Parcelable;
public class UsbDevice implements Parcelable {
private static final String TAG = "UsbDevice";
+ private static final boolean DEBUG = false;
private final String mName;
private final String mManufacturerName;
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index f64ef87..f283051 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -68,6 +68,8 @@ public class UsbManager {
* accessory function is enabled
* <li> {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the
* audio source function is enabled
+ * <li> {@link #USB_FUNCTION_MIDI} boolean extra indicating whether the
+ * MIDI function is enabled
* </ul>
*
* {@hide}
@@ -188,6 +190,14 @@ public class UsbManager {
public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source";
/**
+ * Name of the MIDI USB function.
+ * Used in extras for the {@link #ACTION_USB_STATE} broadcast
+ *
+ * {@hide}
+ */
+ public static final String USB_FUNCTION_MIDI = "midi";
+
+ /**
* Name of the Accessory USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index f218b65..481fc2f 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -19,6 +19,7 @@ package android.inputmethodservice;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.annotation.DrawableRes;
import android.app.ActivityManager;
import android.app.Dialog;
import android.content.Context;
@@ -1178,7 +1179,7 @@ public class InputMethodService extends AbstractInputMethodService {
return isExtractViewShown() ? View.GONE : View.INVISIBLE;
}
- public void showStatusIcon(int iconResId) {
+ public void showStatusIcon(@DrawableRes int iconResId) {
mStatusIcon = iconResId;
mImm.showStatusIcon(mToken, getPackageName(), iconResId);
}
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index 4fe54c0..45f1889 100644
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -18,6 +18,7 @@ package android.inputmethodservice;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.XmlRes;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -519,7 +520,8 @@ public class Keyboard {
* @param width sets width of keyboard
* @param height sets height of keyboard
*/
- public Keyboard(Context context, int xmlLayoutResId, int modeId, int width, int height) {
+ public Keyboard(Context context, @XmlRes int xmlLayoutResId, int modeId, int width,
+ int height) {
mDisplayWidth = width;
mDisplayHeight = height;
@@ -540,7 +542,7 @@ public class Keyboard {
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
* @param modeId keyboard mode identifier
*/
- public Keyboard(Context context, int xmlLayoutResId, int modeId) {
+ public Keyboard(Context context, @XmlRes int xmlLayoutResId, int modeId) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
mDisplayWidth = dm.widthPixels;
mDisplayHeight = dm.heightPixels;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 0832d81..34a0727 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -25,7 +25,6 @@ import android.content.Intent;
import android.net.NetworkUtils;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -38,7 +37,6 @@ 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;
@@ -504,6 +502,8 @@ public class ConnectivityManager {
return "MOBILE_EMERGENCY";
case TYPE_PROXY:
return "PROXY";
+ case TYPE_VPN:
+ return "VPN";
default:
return Integer.toString(type);
}
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index 6159e1e..87c063f 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -17,7 +17,6 @@
package android.net;
import android.net.NetworkUtils;
-import android.os.Parcelable;
import android.os.Parcel;
import android.text.TextUtils;
import android.util.Log;
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index d8852f8..3c09978 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -33,6 +33,7 @@ import android.os.ResultReceiver;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
import com.android.internal.net.VpnProfile;
/**
@@ -114,6 +115,8 @@ interface IConnectivityManager
LegacyVpnInfo getLegacyVpnInfo();
+ VpnInfo[] getAllVpnInfo();
+
boolean updateLockdownVpn();
void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index dfe2413..ab57c9b 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -26,7 +26,6 @@ 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;
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 74d4ac2..24aaf0d 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -21,15 +21,12 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* A Utility class for handling for communicating between bearer-specific
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 64d0fcf..e47220b 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -21,12 +21,9 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import android.util.SparseArray;
-import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
/**
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 5a09b46..7838b47 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -19,8 +19,6 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.concurrent.atomic.AtomicInteger;
-
/**
* Defines a request for a network, made through {@link NetworkRequest.Builder} and used
* to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 2afe578..0766253 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -19,6 +19,7 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.util.Slog;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -42,6 +43,7 @@ import java.util.Objects;
* @hide
*/
public class NetworkStats implements Parcelable {
+ private static final String TAG = "NetworkStats";
/** {@link #iface} value when interface details unavailable. */
public static final String IFACE_ALL = null;
/** {@link #uid} value when UID details unavailable. */
@@ -783,4 +785,162 @@ public class NetworkStats implements Parcelable {
public void foundNonMonotonic(
NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
}
+
+ /**
+ * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface.
+ *
+ * This method should only be called on delta NetworkStats. Do not call this method on a
+ * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may
+ * change over time.
+ *
+ * This method performs adjustments for one active VPN package and one VPN iface at a time.
+ *
+ * It is possible for the VPN software to use multiple underlying networks. This method
+ * only migrates traffic for the primary underlying network.
+ *
+ * @param tunUid uid of the VPN application
+ * @param tunIface iface of the vpn tunnel
+ * @param underlyingIface the primary underlying network iface used by the VPN application
+ * @return true if it successfully adjusts the accounting for VPN, false otherwise
+ */
+ public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) {
+ Entry tunIfaceTotal = new Entry();
+ Entry underlyingIfaceTotal = new Entry();
+
+ tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal);
+
+ // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app.
+ // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression.
+ // Negative stats should be avoided.
+ Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal);
+ if (pool.isEmpty()) {
+ return true;
+ }
+ Entry moved = addTrafficToApplications(tunIface, underlyingIface, tunIfaceTotal, pool);
+ deductTrafficFromVpnApp(tunUid, underlyingIface, moved);
+
+ if (!moved.isEmpty()) {
+ Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved="
+ + moved);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Initializes the data used by the migrateTun() method.
+ *
+ * This is the first pass iteration which does the following work:
+ * (1) Adds up all the traffic through tun0.
+ * (2) Adds up all the traffic through the tunUid's underlyingIface
+ * (both foreground and background).
+ */
+ private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface,
+ Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
+ Entry recycle = new Entry();
+ for (int i = 0; i < size; i++) {
+ getValues(i, recycle);
+ if (recycle.uid == UID_ALL) {
+ throw new IllegalStateException(
+ "Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
+ }
+
+ if (recycle.uid == tunUid && recycle.tag == TAG_NONE
+ && Objects.equals(underlyingIface, recycle.iface)) {
+ underlyingIfaceTotal.add(recycle);
+ }
+
+ if (recycle.tag == TAG_NONE && Objects.equals(tunIface, recycle.iface)) {
+ // Add up all tunIface traffic.
+ tunIfaceTotal.add(recycle);
+ }
+ }
+ }
+
+ private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) {
+ Entry pool = new Entry();
+ pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes);
+ pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets);
+ pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes);
+ pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets);
+ pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations);
+ return pool;
+ }
+
+ private Entry addTrafficToApplications(String tunIface, String underlyingIface,
+ Entry tunIfaceTotal, Entry pool) {
+ Entry moved = new Entry();
+ Entry tmpEntry = new Entry();
+ tmpEntry.iface = underlyingIface;
+ for (int i = 0; i < size; i++) {
+ if (Objects.equals(iface[i], tunIface)) {
+ if (tunIfaceTotal.rxBytes > 0) {
+ tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes;
+ } else {
+ tmpEntry.rxBytes = 0;
+ }
+ if (tunIfaceTotal.rxPackets > 0) {
+ tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets;
+ } else {
+ tmpEntry.rxPackets = 0;
+ }
+ if (tunIfaceTotal.txBytes > 0) {
+ tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes;
+ } else {
+ tmpEntry.txBytes = 0;
+ }
+ if (tunIfaceTotal.txPackets > 0) {
+ tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets;
+ } else {
+ tmpEntry.txPackets = 0;
+ }
+ if (tunIfaceTotal.operations > 0) {
+ tmpEntry.operations =
+ pool.operations * operations[i] / tunIfaceTotal.operations;
+ } else {
+ tmpEntry.operations = 0;
+ }
+ tmpEntry.uid = uid[i];
+ tmpEntry.tag = tag[i];
+ tmpEntry.set = set[i];
+ combineValues(tmpEntry);
+ if (tag[i] == TAG_NONE) {
+ moved.add(tmpEntry);
+ }
+ }
+ }
+ return moved;
+ }
+
+ private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
+ // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
+ // the TAG_NONE traffic.
+ int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE);
+ if (idxVpnBackground != -1) {
+ tunSubtract(idxVpnBackground, this, moved);
+ }
+
+ int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE);
+ if (idxVpnForeground != -1) {
+ tunSubtract(idxVpnForeground, this, moved);
+ }
+ }
+
+ private static void tunSubtract(int i, NetworkStats left, Entry right) {
+ long rxBytes = Math.min(left.rxBytes[i], right.rxBytes);
+ left.rxBytes[i] -= rxBytes;
+ right.rxBytes -= rxBytes;
+
+ long rxPackets = Math.min(left.rxPackets[i], right.rxPackets);
+ left.rxPackets[i] -= rxPackets;
+ right.rxPackets -= rxPackets;
+
+ long txBytes = Math.min(left.txBytes[i], right.txBytes);
+ left.txBytes[i] -= txBytes;
+ right.txBytes -= txBytes;
+
+ long txPackets = Math.min(left.txPackets[i], right.txPackets);
+ left.txPackets[i] -= txPackets;
+ right.txPackets -= txPackets;
+ }
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d2a2997..8003afb 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -56,6 +56,30 @@ public class NetworkUtils {
/**
* Start the DHCP client daemon, in order to have it request addresses
+ * for the named interface. This returns {@code true} if the DHCPv4 daemon
+ * starts, {@code false} otherwise. This call blocks until such time as a
+ * result is available or the default discovery timeout has been reached.
+ * Callers should check {@link #getDhcpResults} to determine whether DHCP
+ * succeeded or failed, and if it succeeded, to fetch the {@link DhcpResults}.
+ * @param interfaceName the name of the interface to configure
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean startDhcp(String interfaceName);
+
+ /**
+ * Initiate renewal on the DHCP client daemon for the named interface. This
+ * returns {@code true} if the DHCPv4 daemon has been notified, {@code false}
+ * otherwise. This call blocks until such time as a result is available or
+ * the default renew timeout has been reached. Callers should check
+ * {@link #getDhcpResults} to determine whether DHCP succeeded or failed,
+ * and if it succeeded, to fetch the {@link DhcpResults}.
+ * @param interfaceName the name of the interface to configure
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean startDhcpRenew(String interfaceName);
+
+ /**
+ * Start the DHCP client daemon, in order to have it request addresses
* for the named interface, and then configure the interface with those
* addresses. This call blocks until it obtains a result (either success
* or failure) from the daemon.
@@ -64,17 +88,31 @@ public class NetworkUtils {
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcp(String interfaceName, DhcpResults dhcpResults);
+ public static boolean runDhcp(String interfaceName, DhcpResults dhcpResults) {
+ return startDhcp(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
+ }
/**
- * Initiate renewal on the Dhcp client daemon. This call blocks until it obtains
+ * Initiate renewal on the DHCP client daemon. This call blocks until it obtains
* a result (either success or failure) from the daemon.
* @param interfaceName the name of the interface to configure
* @param dhcpResults if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults);
+ public static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults) {
+ return startDhcpRenew(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
+ }
+
+ /**
+ * Fetch results from the DHCP client daemon. This call returns {@code true} if
+ * if there are results available to be read, {@code false} otherwise.
+ * @param interfaceName the name of the interface to configure
+ * @param dhcpResults if the request succeeds, this object is filled in with
+ * the IP address information.
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean getDhcpResults(String interfaceName, DhcpResults dhcpResults);
/**
* Shut down the DHCP client daemon.
diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java
index 8626d08..9bdf4f6 100644
--- a/core/java/android/net/PacProxySelector.java
+++ b/core/java/android/net/PacProxySelector.java
@@ -16,7 +16,6 @@
package android.net;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -74,8 +73,8 @@ public class PacProxySelector extends ProxySelector {
}
try {
response = mProxyService.resolvePacFile(uri.getHost(), urlString);
- } catch (RemoteException e) {
- e.printStackTrace();
+ } catch (Exception e) {
+ Log.e(TAG, "Error resolving PAC File", e);
}
if (response == null) {
return mDefaultList;
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index a3cad77..2c90909 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -21,8 +21,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-import org.apache.http.client.HttpClient;
-
import java.net.InetSocketAddress;
import java.net.URLConnection;
import java.util.List;
@@ -31,8 +29,9 @@ import java.util.Locale;
/**
* Describes a proxy configuration.
*
- * Proxy configurations are already integrated within the Apache HTTP stack.
- * So {@link URLConnection} and {@link HttpClient} will use them automatically.
+ * Proxy configurations are already integrated within the {@code java.net} and
+ * Apache HTTP stack. So {@link URLConnection} and Apache's {@code HttpClient} will use
+ * them automatically.
*
* 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}.
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 6654577..27096b1 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -159,6 +159,8 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
* 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.
+ *
+ * @removed
*/
@Deprecated
public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 37ee961..7f1b179 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -21,7 +21,6 @@ import android.os.Parcelable;
import android.os.Parcel;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 2099c3f..bf3d9aa 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -366,6 +366,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
public String toSafeString() {
String scheme = getScheme();
String ssp = getSchemeSpecificPart();
+ String authority = null;
if (scheme != null) {
if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
|| scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
@@ -384,6 +385,9 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
}
return builder.toString();
+ } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) {
+ ssp = null;
+ authority = "//" + getAuthority() + "/...";
}
}
// Not a sensitive scheme, but let's still be conservative about
@@ -397,6 +401,9 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
if (ssp != null) {
builder.append(ssp);
}
+ if (authority != null) {
+ builder.append(authority);
+ }
return builder.toString();
}
@@ -1742,7 +1749,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
*
* @return normalized Uri (never null)
* @see {@link android.content.Intent#setData}
- * @see {@link #setNormalizedData}
+ * @see {@link android.content.Intent#setDataAndNormalize}
*/
public Uri normalizeScheme() {
String scheme = getScheme();
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
deleted file mode 100644
index a262076..0000000
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ /dev/null
@@ -1,527 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.http;
-
-import com.android.internal.http.HttpDateTime;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpEntityEnclosingRequest;
-import org.apache.http.HttpException;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.params.HttpClientParams;
-import org.apache.http.client.protocol.ClientContext;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.scheme.PlainSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.client.RequestWrapper;
-import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.HttpProtocolParams;
-import org.apache.http.protocol.BasicHttpContext;
-import org.apache.http.protocol.BasicHttpProcessor;
-import org.apache.http.protocol.HttpContext;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.SSLCertificateSocketFactory;
-import android.net.SSLSessionCache;
-import android.os.Looper;
-import android.util.Base64;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
-/**
- * Implementation of the Apache {@link DefaultHttpClient} that is configured with
- * reasonable default settings and registered schemes for Android.
- * Don't create this directly, use the {@link #newInstance} factory method.
- *
- * <p>This client processes cookies but does not retain them by default.
- * 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
- public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
-
- // Default connection and socket timeout of 60 seconds. Tweak to taste.
- private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000;
-
- private static final String TAG = "AndroidHttpClient";
-
- private static String[] textContentTypes = new String[] {
- "text/",
- "application/xml",
- "application/json"
- };
-
- /** Interceptor throws an exception if the executing thread is blocked */
- private static final HttpRequestInterceptor sThreadCheckInterceptor =
- new HttpRequestInterceptor() {
- public void process(HttpRequest request, HttpContext context) {
- // Prevent the HttpRequest from being sent on the main thread
- if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) {
- throw new RuntimeException("This thread forbids HTTP requests");
- }
- }
- };
-
- /**
- * Create a new HttpClient with reasonable defaults (which you can update).
- *
- * @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();
-
- // Turn off stale checking. Our connections break all the time anyway,
- // and it's not worth it to pay the penalty of checking every time.
- HttpConnectionParams.setStaleCheckingEnabled(params, false);
-
- HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT);
- HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT);
- HttpConnectionParams.setSocketBufferSize(params, 8192);
-
- // Don't handle redirects -- return them to the caller. Our code
- // often wants to re-POST after a redirect, which we must do ourselves.
- HttpClientParams.setRedirecting(params, false);
-
- // Use a session cache for SSL sockets
- SSLSessionCache sessionCache = context == null ? null : new SSLSessionCache(context);
-
- // Set the specified user agent and register standard protocols.
- HttpProtocolParams.setUserAgent(params, userAgent);
- SchemeRegistry schemeRegistry = new SchemeRegistry();
- schemeRegistry.register(new Scheme("http",
- PlainSocketFactory.getSocketFactory(), 80));
- schemeRegistry.register(new Scheme("https",
- SSLCertificateSocketFactory.getHttpSocketFactory(
- SOCKET_OPERATION_TIMEOUT, sessionCache), 443));
-
- ClientConnectionManager manager =
- new ThreadSafeClientConnManager(params, schemeRegistry);
-
- // We use a factory method to modify superclass initialization
- // parameters without the funny call-a-static-method dance.
- return new AndroidHttpClient(manager, params);
- }
-
- /**
- * 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 */);
- }
-
- private final HttpClient delegate;
-
- private RuntimeException mLeakedException = new IllegalStateException(
- "AndroidHttpClient created and never closed");
-
- private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
- this.delegate = new DefaultHttpClient(ccm, params) {
- @Override
- protected BasicHttpProcessor createHttpProcessor() {
- // Add interceptor to prevent making requests from main thread.
- BasicHttpProcessor processor = super.createHttpProcessor();
- processor.addRequestInterceptor(sThreadCheckInterceptor);
- processor.addRequestInterceptor(new CurlLogger());
-
- return processor;
- }
-
- @Override
- protected HttpContext createHttpContext() {
- // Same as DefaultHttpClient.createHttpContext() minus the
- // cookie store.
- HttpContext context = new BasicHttpContext();
- context.setAttribute(
- ClientContext.AUTHSCHEME_REGISTRY,
- getAuthSchemes());
- context.setAttribute(
- ClientContext.COOKIESPEC_REGISTRY,
- getCookieSpecs());
- context.setAttribute(
- ClientContext.CREDS_PROVIDER,
- getCredentialsProvider());
- return context;
- }
- };
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- if (mLeakedException != null) {
- Log.e(TAG, "Leak found", mLeakedException);
- mLeakedException = null;
- }
- }
-
- /**
- * Modifies a request to indicate to the server that we would like a
- * gzipped response. (Uses the "Accept-Encoding" HTTP header.)
- * @param request the request to modify
- * @see #getUngzippedContent
- */
- public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
- request.addHeader("Accept-Encoding", "gzip");
- }
-
- /**
- * Gets the input stream from a response entity. If the entity is gzipped
- * then this will get a stream over the uncompressed data.
- *
- * @param entity the entity whose content should be read
- * @return the input stream to read from
- * @throws IOException
- */
- public static InputStream getUngzippedContent(HttpEntity entity)
- throws IOException {
- InputStream responseStream = entity.getContent();
- if (responseStream == null) return responseStream;
- Header header = entity.getContentEncoding();
- if (header == null) return responseStream;
- String contentEncoding = header.getValue();
- if (contentEncoding == null) return responseStream;
- if (contentEncoding.contains("gzip")) responseStream
- = new GZIPInputStream(responseStream);
- return responseStream;
- }
-
- /**
- * Release resources associated with this client. You must call this,
- * or significant resources (sockets and memory) may be leaked.
- */
- public void close() {
- if (mLeakedException != null) {
- getConnectionManager().shutdown();
- mLeakedException = null;
- }
- }
-
- public HttpParams getParams() {
- return delegate.getParams();
- }
-
- public ClientConnectionManager getConnectionManager() {
- return delegate.getConnectionManager();
- }
-
- public HttpResponse execute(HttpUriRequest request) throws IOException {
- return delegate.execute(request);
- }
-
- public HttpResponse execute(HttpUriRequest request, HttpContext context)
- throws IOException {
- return delegate.execute(request, context);
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request)
- throws IOException {
- return delegate.execute(target, request);
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request,
- HttpContext context) throws IOException {
- return delegate.execute(target, request, context);
- }
-
- public <T> T execute(HttpUriRequest request,
- ResponseHandler<? extends T> responseHandler)
- throws IOException, ClientProtocolException {
- return delegate.execute(request, responseHandler);
- }
-
- public <T> T execute(HttpUriRequest request,
- ResponseHandler<? extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- return delegate.execute(request, responseHandler, context);
- }
-
- public <T> T execute(HttpHost target, HttpRequest request,
- ResponseHandler<? extends T> responseHandler) throws IOException,
- ClientProtocolException {
- return delegate.execute(target, request, responseHandler);
- }
-
- public <T> T execute(HttpHost target, HttpRequest request,
- ResponseHandler<? extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- return delegate.execute(target, request, responseHandler, context);
- }
-
- /**
- * Compress data to send to server.
- * Creates a Http Entity holding the gzipped data.
- * The data will not be compressed if it is too short.
- * @param data The bytes to compress
- * @return Entity holding the data
- */
- public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver)
- throws IOException {
- AbstractHttpEntity entity;
- if (data.length < getMinGzipSize(resolver)) {
- entity = new ByteArrayEntity(data);
- } else {
- ByteArrayOutputStream arr = new ByteArrayOutputStream();
- OutputStream zipper = new GZIPOutputStream(arr);
- zipper.write(data);
- zipper.close();
- entity = new ByteArrayEntity(arr.toByteArray());
- entity.setContentEncoding("gzip");
- }
- return entity;
- }
-
- /**
- * Retrieves the minimum size for compressing data.
- * Shorter data will not be compressed.
- */
- public static long getMinGzipSize(ContentResolver resolver) {
- return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant.
- }
-
- /* cURL logging support. */
-
- /**
- * Logging tag and level.
- */
- private static class LoggingConfiguration {
-
- private final String tag;
- private final int level;
-
- private LoggingConfiguration(String tag, int level) {
- this.tag = tag;
- this.level = level;
- }
-
- /**
- * Returns true if logging is turned on for this configuration.
- */
- private boolean isLoggable() {
- return Log.isLoggable(tag, level);
- }
-
- /**
- * Prints a message using this configuration.
- */
- private void println(String message) {
- Log.println(level, tag, message);
- }
- }
-
- /** cURL logging configuration. */
- private volatile LoggingConfiguration curlConfiguration;
-
- /**
- * Enables cURL request logging for this client.
- *
- * @param name to log messages with
- * @param level at which to log messages (see {@link android.util.Log})
- */
- public void enableCurlLogging(String name, int level) {
- if (name == null) {
- throw new NullPointerException("name");
- }
- if (level < Log.VERBOSE || level > Log.ASSERT) {
- throw new IllegalArgumentException("Level is out of range ["
- + Log.VERBOSE + ".." + Log.ASSERT + "]");
- }
-
- curlConfiguration = new LoggingConfiguration(name, level);
- }
-
- /**
- * Disables cURL logging for this client.
- */
- public void disableCurlLogging() {
- curlConfiguration = null;
- }
-
- /**
- * Logs cURL commands equivalent to requests.
- */
- private class CurlLogger implements HttpRequestInterceptor {
- public void process(HttpRequest request, HttpContext context)
- throws HttpException, IOException {
- LoggingConfiguration configuration = curlConfiguration;
- if (configuration != null
- && configuration.isLoggable()
- && request instanceof HttpUriRequest) {
- // Never print auth token -- we used to check ro.secure=0 to
- // enable that, but can't do that in unbundled code.
- configuration.println(toCurl((HttpUriRequest) request, false));
- }
- }
- }
-
- /**
- * Generates a cURL command equivalent to the given request.
- */
- private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
- StringBuilder builder = new StringBuilder();
-
- builder.append("curl ");
-
- // add in the method
- builder.append("-X ");
- builder.append(request.getMethod());
- builder.append(" ");
-
- for (Header header: request.getAllHeaders()) {
- if (!logAuthToken
- && (header.getName().equals("Authorization") ||
- header.getName().equals("Cookie"))) {
- continue;
- }
- builder.append("--header \"");
- builder.append(header.toString().trim());
- builder.append("\" ");
- }
-
- URI uri = request.getURI();
-
- // If this is a wrapped request, use the URI from the original
- // request instead. getURI() on the wrapper seems to return a
- // relative URI. We want an absolute URI.
- if (request instanceof RequestWrapper) {
- HttpRequest original = ((RequestWrapper) request).getOriginal();
- if (original instanceof HttpUriRequest) {
- uri = ((HttpUriRequest) original).getURI();
- }
- }
-
- builder.append("\"");
- builder.append(uri);
- builder.append("\"");
-
- if (request instanceof HttpEntityEnclosingRequest) {
- HttpEntityEnclosingRequest entityRequest =
- (HttpEntityEnclosingRequest) request;
- HttpEntity entity = entityRequest.getEntity();
- if (entity != null && entity.isRepeatable()) {
- if (entity.getContentLength() < 1024) {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- entity.writeTo(stream);
-
- if (isBinaryContent(request)) {
- String base64 = Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP);
- builder.insert(0, "echo '" + base64 + "' | base64 -d > /tmp/$$.bin; ");
- builder.append(" --data-binary @/tmp/$$.bin");
- } else {
- String entityString = stream.toString();
- builder.append(" --data-ascii \"")
- .append(entityString)
- .append("\"");
- }
- } else {
- builder.append(" [TOO MUCH DATA TO INCLUDE]");
- }
- }
- }
-
- return builder.toString();
- }
-
- private static boolean isBinaryContent(HttpUriRequest request) {
- Header[] headers;
- headers = request.getHeaders(Headers.CONTENT_ENCODING);
- if (headers != null) {
- for (Header header : headers) {
- if ("gzip".equalsIgnoreCase(header.getValue())) {
- return true;
- }
- }
- }
-
- headers = request.getHeaders(Headers.CONTENT_TYPE);
- if (headers != null) {
- for (Header header : headers) {
- for (String contentType : textContentTypes) {
- if (header.getValue().startsWith(contentType)) {
- return false;
- }
- }
- }
- }
- return true;
- }
-
- /**
- * Returns the date of the given HTTP date string. This method can identify
- * and parse the date formats emitted by common HTTP servers, such as
- * <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a>,
- * <a href="http://www.ietf.org/rfc/rfc0850.txt">RFC 850</a>,
- * <a href="http://www.ietf.org/rfc/rfc1036.txt">RFC 1036</a>,
- * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a> and
- * <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html">ANSI
- * C's asctime()</a>.
- *
- * @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
- * @throws IllegalArgumentException if {@code dateString} is not a date or
- * of an unsupported format.
- */
- public static long parseDate(String dateString) {
- return HttpDateTime.parse(dateString);
- }
-}
diff --git a/core/java/android/net/http/AndroidHttpClientConnection.java b/core/java/android/net/http/AndroidHttpClientConnection.java
deleted file mode 100644
index 6d48fce..0000000
--- a/core/java/android/net/http/AndroidHttpClientConnection.java
+++ /dev/null
@@ -1,460 +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.http;
-
-import org.apache.http.HttpConnection;
-import org.apache.http.HttpClientConnection;
-import org.apache.http.HttpConnectionMetrics;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpEntityEnclosingRequest;
-import org.apache.http.HttpException;
-import org.apache.http.HttpInetConnection;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.NoHttpResponseException;
-import org.apache.http.StatusLine;
-import org.apache.http.entity.BasicHttpEntity;
-import org.apache.http.entity.ContentLengthStrategy;
-import org.apache.http.impl.HttpConnectionMetricsImpl;
-import org.apache.http.impl.entity.EntitySerializer;
-import org.apache.http.impl.entity.StrictContentLengthStrategy;
-import org.apache.http.impl.io.ChunkedInputStream;
-import org.apache.http.impl.io.ContentLengthInputStream;
-import org.apache.http.impl.io.HttpRequestWriter;
-import org.apache.http.impl.io.IdentityInputStream;
-import org.apache.http.impl.io.SocketInputBuffer;
-import org.apache.http.impl.io.SocketOutputBuffer;
-import org.apache.http.io.HttpMessageWriter;
-import org.apache.http.io.SessionInputBuffer;
-import org.apache.http.io.SessionOutputBuffer;
-import org.apache.http.message.BasicLineParser;
-import org.apache.http.message.ParserCursor;
-import org.apache.http.params.CoreConnectionPNames;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-import org.apache.http.ParseException;
-import org.apache.http.util.CharArrayBuffer;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.SocketException;
-
-/**
- * A alternate class for (@link DefaultHttpClientConnection).
- * It has better performance than DefaultHttpClientConnection
- *
- * {@hide}
- */
-public class AndroidHttpClientConnection
- implements HttpInetConnection, HttpConnection {
-
- private SessionInputBuffer inbuffer = null;
- private SessionOutputBuffer outbuffer = null;
- private int maxHeaderCount;
- // store CoreConnectionPNames.MAX_LINE_LENGTH for performance
- private int maxLineLength;
-
- private final EntitySerializer entityserializer;
-
- private HttpMessageWriter requestWriter = null;
- private HttpConnectionMetricsImpl metrics = null;
- private volatile boolean open;
- private Socket socket = null;
-
- public AndroidHttpClientConnection() {
- this.entityserializer = new EntitySerializer(
- new StrictContentLengthStrategy());
- }
-
- /**
- * Bind socket and set HttpParams to AndroidHttpClientConnection
- * @param socket outgoing socket
- * @param params HttpParams
- * @throws IOException
- */
- public void bind(
- final Socket socket,
- final HttpParams params) throws IOException {
- if (socket == null) {
- throw new IllegalArgumentException("Socket may not be null");
- }
- if (params == null) {
- throw new IllegalArgumentException("HTTP parameters may not be null");
- }
- assertNotOpen();
- socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params));
- socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params));
-
- int linger = HttpConnectionParams.getLinger(params);
- if (linger >= 0) {
- socket.setSoLinger(linger > 0, linger);
- }
- this.socket = socket;
-
- int buffersize = HttpConnectionParams.getSocketBufferSize(params);
- this.inbuffer = new SocketInputBuffer(socket, buffersize, params);
- this.outbuffer = new SocketOutputBuffer(socket, buffersize, params);
-
- maxHeaderCount = params.getIntParameter(
- CoreConnectionPNames.MAX_HEADER_COUNT, -1);
- maxLineLength = params.getIntParameter(
- CoreConnectionPNames.MAX_LINE_LENGTH, -1);
-
- this.requestWriter = new HttpRequestWriter(outbuffer, null, params);
-
- this.metrics = new HttpConnectionMetricsImpl(
- inbuffer.getMetrics(),
- outbuffer.getMetrics());
-
- this.open = true;
- }
-
- @Override
- public String toString() {
- StringBuilder buffer = new StringBuilder();
- buffer.append(getClass().getSimpleName()).append("[");
- if (isOpen()) {
- buffer.append(getRemotePort());
- } else {
- buffer.append("closed");
- }
- buffer.append("]");
- return buffer.toString();
- }
-
-
- private void assertNotOpen() {
- if (this.open) {
- throw new IllegalStateException("Connection is already open");
- }
- }
-
- private void assertOpen() {
- if (!this.open) {
- throw new IllegalStateException("Connection is not open");
- }
- }
-
- public boolean isOpen() {
- // to make this method useful, we want to check if the socket is connected
- return (this.open && this.socket != null && this.socket.isConnected());
- }
-
- public InetAddress getLocalAddress() {
- if (this.socket != null) {
- return this.socket.getLocalAddress();
- } else {
- return null;
- }
- }
-
- public int getLocalPort() {
- if (this.socket != null) {
- return this.socket.getLocalPort();
- } else {
- return -1;
- }
- }
-
- public InetAddress getRemoteAddress() {
- if (this.socket != null) {
- return this.socket.getInetAddress();
- } else {
- return null;
- }
- }
-
- public int getRemotePort() {
- if (this.socket != null) {
- return this.socket.getPort();
- } else {
- return -1;
- }
- }
-
- public void setSocketTimeout(int timeout) {
- assertOpen();
- if (this.socket != null) {
- try {
- this.socket.setSoTimeout(timeout);
- } catch (SocketException ignore) {
- // It is not quite clear from the original documentation if there are any
- // other legitimate cases for a socket exception to be thrown when setting
- // SO_TIMEOUT besides the socket being already closed
- }
- }
- }
-
- public int getSocketTimeout() {
- if (this.socket != null) {
- try {
- return this.socket.getSoTimeout();
- } catch (SocketException ignore) {
- return -1;
- }
- } else {
- return -1;
- }
- }
-
- public void shutdown() throws IOException {
- this.open = false;
- Socket tmpsocket = this.socket;
- if (tmpsocket != null) {
- tmpsocket.close();
- }
- }
-
- public void close() throws IOException {
- if (!this.open) {
- return;
- }
- this.open = false;
- doFlush();
- try {
- try {
- this.socket.shutdownOutput();
- } catch (IOException ignore) {
- }
- try {
- this.socket.shutdownInput();
- } catch (IOException ignore) {
- }
- } catch (UnsupportedOperationException ignore) {
- // if one isn't supported, the other one isn't either
- }
- this.socket.close();
- }
-
- /**
- * Sends the request line and all headers over the connection.
- * @param request the request whose headers to send.
- * @throws HttpException
- * @throws IOException
- */
- public void sendRequestHeader(final HttpRequest request)
- throws HttpException, IOException {
- if (request == null) {
- throw new IllegalArgumentException("HTTP request may not be null");
- }
- assertOpen();
- this.requestWriter.write(request);
- this.metrics.incrementRequestCount();
- }
-
- /**
- * Sends the request entity over the connection.
- * @param request the request whose entity to send.
- * @throws HttpException
- * @throws IOException
- */
- public void sendRequestEntity(final HttpEntityEnclosingRequest request)
- throws HttpException, IOException {
- if (request == null) {
- throw new IllegalArgumentException("HTTP request may not be null");
- }
- assertOpen();
- if (request.getEntity() == null) {
- return;
- }
- this.entityserializer.serialize(
- this.outbuffer,
- request,
- request.getEntity());
- }
-
- protected void doFlush() throws IOException {
- this.outbuffer.flush();
- }
-
- public void flush() throws IOException {
- assertOpen();
- doFlush();
- }
-
- /**
- * Parses the response headers and adds them to the
- * given {@code headers} object, and returns the response StatusLine
- * @param headers store parsed header to headers.
- * @throws IOException
- * @return StatusLine
- * @see HttpClientConnection#receiveResponseHeader()
- */
- public StatusLine parseResponseHeader(Headers headers)
- throws IOException, ParseException {
- assertOpen();
-
- CharArrayBuffer current = new CharArrayBuffer(64);
-
- if (inbuffer.readLine(current) == -1) {
- throw new NoHttpResponseException("The target server failed to respond");
- }
-
- // Create the status line from the status string
- StatusLine statusline = BasicLineParser.DEFAULT.parseStatusLine(
- current, new ParserCursor(0, current.length()));
-
- if (HttpLog.LOGV) HttpLog.v("read: " + statusline);
- int statusCode = statusline.getStatusCode();
-
- // Parse header body
- CharArrayBuffer previous = null;
- int headerNumber = 0;
- while(true) {
- if (current == null) {
- current = new CharArrayBuffer(64);
- } else {
- // This must be he buffer used to parse the status
- current.clear();
- }
- int l = inbuffer.readLine(current);
- if (l == -1 || current.length() < 1) {
- break;
- }
- // Parse the header name and value
- // Check for folded headers first
- // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
- // discussion on folded headers
- char first = current.charAt(0);
- if ((first == ' ' || first == '\t') && previous != null) {
- // we have continuation folded header
- // so append value
- int start = 0;
- int length = current.length();
- while (start < length) {
- char ch = current.charAt(start);
- if (ch != ' ' && ch != '\t') {
- break;
- }
- start++;
- }
- if (maxLineLength > 0 &&
- previous.length() + 1 + current.length() - start >
- maxLineLength) {
- throw new IOException("Maximum line length limit exceeded");
- }
- previous.append(' ');
- previous.append(current, start, current.length() - start);
- } else {
- if (previous != null) {
- headers.parseHeader(previous);
- }
- headerNumber++;
- previous = current;
- current = null;
- }
- if (maxHeaderCount > 0 && headerNumber >= maxHeaderCount) {
- throw new IOException("Maximum header count exceeded");
- }
- }
-
- if (previous != null) {
- headers.parseHeader(previous);
- }
-
- if (statusCode >= 200) {
- this.metrics.incrementResponseCount();
- }
- return statusline;
- }
-
- /**
- * Return the next response entity.
- * @param headers contains values for parsing entity
- * @see HttpClientConnection#receiveResponseEntity(HttpResponse response)
- */
- public HttpEntity receiveResponseEntity(final Headers headers) {
- assertOpen();
- BasicHttpEntity entity = new BasicHttpEntity();
-
- long len = determineLength(headers);
- if (len == ContentLengthStrategy.CHUNKED) {
- entity.setChunked(true);
- entity.setContentLength(-1);
- entity.setContent(new ChunkedInputStream(inbuffer));
- } else if (len == ContentLengthStrategy.IDENTITY) {
- entity.setChunked(false);
- entity.setContentLength(-1);
- entity.setContent(new IdentityInputStream(inbuffer));
- } else {
- entity.setChunked(false);
- entity.setContentLength(len);
- entity.setContent(new ContentLengthInputStream(inbuffer, len));
- }
-
- String contentTypeHeader = headers.getContentType();
- if (contentTypeHeader != null) {
- entity.setContentType(contentTypeHeader);
- }
- String contentEncodingHeader = headers.getContentEncoding();
- if (contentEncodingHeader != null) {
- entity.setContentEncoding(contentEncodingHeader);
- }
-
- return entity;
- }
-
- private long determineLength(final Headers headers) {
- long transferEncoding = headers.getTransferEncoding();
- // We use Transfer-Encoding if present and ignore Content-Length.
- // RFC2616, 4.4 item number 3
- if (transferEncoding < Headers.NO_TRANSFER_ENCODING) {
- return transferEncoding;
- } else {
- long contentlen = headers.getContentLength();
- if (contentlen > Headers.NO_CONTENT_LENGTH) {
- return contentlen;
- } else {
- return ContentLengthStrategy.IDENTITY;
- }
- }
- }
-
- /**
- * Checks whether this connection has gone down.
- * Network connections may get closed during some time of inactivity
- * for several reasons. The next time a read is attempted on such a
- * connection it will throw an IOException.
- * This method tries to alleviate this inconvenience by trying to
- * find out if a connection is still usable. Implementations may do
- * that by attempting a read with a very small timeout. Thus this
- * method may block for a small amount of time before returning a result.
- * It is therefore an <i>expensive</i> operation.
- *
- * @return <code>true</code> if attempts to use this connection are
- * likely to succeed, or <code>false</code> if they are likely
- * to fail and this connection should be closed
- */
- public boolean isStale() {
- assertOpen();
- try {
- this.inbuffer.isDataAvailable(1);
- return false;
- } catch (IOException ex) {
- return true;
- }
- }
-
- /**
- * Returns a collection of connection metrcis
- * @return HttpConnectionMetrics
- */
- public HttpConnectionMetrics getMetrics() {
- return this.metrics;
- }
-}
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
deleted file mode 100644
index bf3fe02..0000000
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ /dev/null
@@ -1,279 +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.http;
-
-import com.android.org.conscrypt.SSLParametersImpl;
-import com.android.org.conscrypt.TrustManagerImpl;
-
-import android.util.Slog;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
-/**
- * Class responsible for all server certificate validation functionality
- *
- * {@hide}
- */
-public class CertificateChainValidator {
- private static final String TAG = "CertificateChainValidator";
-
- private static class NoPreloadHolder {
- /**
- * The singleton instance of the certificate chain validator.
- */
- private static final CertificateChainValidator sInstance = new CertificateChainValidator();
-
- /**
- * The singleton instance of the hostname verifier.
- */
- private static final HostnameVerifier sVerifier = HttpsURLConnection
- .getDefaultHostnameVerifier();
- }
-
- private X509TrustManager mTrustManager;
-
- /**
- * @return The singleton instance of the certificates chain validator
- */
- public static CertificateChainValidator getInstance() {
- return NoPreloadHolder.sInstance;
- }
-
- /**
- * Creates a new certificate chain validator. This is a private constructor.
- * If you need a Certificate chain validator, call getInstance().
- */
- private CertificateChainValidator() {
- try {
- TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509");
- tmf.init((KeyStore) null);
- for (TrustManager tm : tmf.getTrustManagers()) {
- if (tm instanceof X509TrustManager) {
- mTrustManager = (X509TrustManager) tm;
- }
- }
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("X.509 TrustManagerFactory must be available", e);
- } catch (KeyStoreException e) {
- throw new RuntimeException("X.509 TrustManagerFactory cannot be initialized", e);
- }
-
- if (mTrustManager == null) {
- throw new RuntimeException(
- "None of the X.509 TrustManagers are X509TrustManager");
- }
- }
-
- /**
- * Performs the handshake and server certificates validation
- * Notice a new chain will be rebuilt by tracing the issuer and subject
- * before calling checkServerTrusted().
- * And if the last traced certificate is self issued and it is expired, it
- * will be dropped.
- * @param sslSocket The secure connection socket
- * @param domain The website domain
- * @return An SSL error object if there is an error and null otherwise
- */
- public SslError doHandshakeAndValidateServerCertificates(
- HttpsConnection connection, SSLSocket sslSocket, String domain)
- throws IOException {
- // get a valid SSLSession, close the socket if we fail
- SSLSession sslSession = sslSocket.getSession();
- if (!sslSession.isValid()) {
- closeSocketThrowException(sslSocket, "failed to perform SSL handshake");
- }
-
- // retrieve the chain of the server peer certificates
- Certificate[] peerCertificates =
- sslSocket.getSession().getPeerCertificates();
-
- if (peerCertificates == null || peerCertificates.length == 0) {
- closeSocketThrowException(
- sslSocket, "failed to retrieve peer certificates");
- } else {
- // update the SSL certificate associated with the connection
- if (connection != null) {
- if (peerCertificates[0] != null) {
- connection.setCertificate(
- new SslCertificate((X509Certificate)peerCertificates[0]));
- }
- }
- }
-
- return verifyServerDomainAndCertificates((X509Certificate[]) peerCertificates, domain, "RSA");
- }
-
- /**
- * Similar to doHandshakeAndValidateServerCertificates but exposed to JNI for use
- * by Chromium HTTPS stack to validate the cert chain.
- * @param certChain The bytes for certificates in ASN.1 DER encoded certificates format.
- * @param domain The full website hostname and domain
- * @param authType The authentication type for the cert chain
- * @return An SSL error object if there is an error and null otherwise
- */
- public static SslError verifyServerCertificates(
- byte[][] certChain, String domain, String authType)
- throws IOException {
-
- if (certChain == null || certChain.length == 0) {
- throw new IllegalArgumentException("bad certificate chain");
- }
-
- X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
-
- try {
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- for (int i = 0; i < certChain.length; ++i) {
- serverCertificates[i] = (X509Certificate) cf.generateCertificate(
- new ByteArrayInputStream(certChain[i]));
- }
- } catch (CertificateException e) {
- throw new IOException("can't read certificate", e);
- }
-
- return verifyServerDomainAndCertificates(serverCertificates, domain, authType);
- }
-
- /**
- * Handles updates to credential storage.
- */
- public static void handleTrustStorageUpdate() {
- TrustManagerFactory tmf;
- try {
- tmf = TrustManagerFactory.getInstance("X.509");
- tmf.init((KeyStore) null);
- } catch (NoSuchAlgorithmException e) {
- Slog.w(TAG, "Couldn't find default X.509 TrustManagerFactory");
- return;
- } catch (KeyStoreException e) {
- Slog.w(TAG, "Couldn't initialize default X.509 TrustManagerFactory", e);
- return;
- }
-
- TrustManager[] tms = tmf.getTrustManagers();
- boolean sentUpdate = false;
- for (TrustManager tm : tms) {
- try {
- Method updateMethod = tm.getClass().getDeclaredMethod("handleTrustStorageUpdate");
- updateMethod.setAccessible(true);
- updateMethod.invoke(tm);
- sentUpdate = true;
- } catch (Exception e) {
- }
- }
- if (!sentUpdate) {
- Slog.w(TAG, "Didn't find a TrustManager to handle CA list update");
- }
- }
-
- /**
- * Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates.
- * Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs.
- * @param chain the cert chain in X509 cert format.
- * @param domain The full website hostname and domain
- * @param authType The authentication type for the cert chain
- * @return An SSL error object if there is an error and null otherwise
- */
- private static SslError verifyServerDomainAndCertificates(
- X509Certificate[] chain, String domain, String authType)
- throws IOException {
- // check if the first certificate in the chain is for this site
- X509Certificate currCertificate = chain[0];
- if (currCertificate == null) {
- throw new IllegalArgumentException("certificate for this site is null");
- }
-
- boolean valid = domain != null
- && !domain.isEmpty()
- && NoPreloadHolder.sVerifier.verify(domain,
- new DelegatingSSLSession.CertificateWrap(currCertificate));
- if (!valid) {
- if (HttpLog.LOGV) {
- HttpLog.v("certificate not for this host: " + domain);
- }
- return new SslError(SslError.SSL_IDMISMATCH, currCertificate);
- }
-
- try {
- X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultX509TrustManager();
- if (x509TrustManager instanceof TrustManagerImpl) {
- TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager;
- trustManager.checkServerTrusted(chain, authType, domain);
- } else {
- x509TrustManager.checkServerTrusted(chain, authType);
- }
- return null; // No errors.
- } catch (GeneralSecurityException e) {
- if (HttpLog.LOGV) {
- HttpLog.v("failed to validate the certificate chain, error: " +
- e.getMessage());
- }
- return new SslError(SslError.SSL_UNTRUSTED, currCertificate);
- }
- }
-
- /**
- * Returns the platform default {@link X509TrustManager}.
- */
- private X509TrustManager getTrustManager() {
- return mTrustManager;
- }
-
- private void closeSocketThrowException(
- SSLSocket socket, String errorMessage, String defaultErrorMessage)
- throws IOException {
- closeSocketThrowException(
- socket, errorMessage != null ? errorMessage : defaultErrorMessage);
- }
-
- private void closeSocketThrowException(SSLSocket socket,
- String errorMessage) throws IOException {
- if (HttpLog.LOGV) {
- HttpLog.v("validation error: " + errorMessage);
- }
-
- if (socket != null) {
- SSLSession session = socket.getSession();
- if (session != null) {
- session.invalidate();
- }
-
- socket.close();
- }
-
- throw new SSLHandshakeException(errorMessage);
- }
-}
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
deleted file mode 100644
index 831bd0e..0000000
--- a/core/java/android/net/http/Connection.java
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.http;
-
-import android.content.Context;
-import android.os.SystemClock;
-
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.util.LinkedList;
-
-import javax.net.ssl.SSLHandshakeException;
-
-import org.apache.http.ConnectionReuseStrategy;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpException;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpVersion;
-import org.apache.http.ParseException;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.protocol.ExecutionContext;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.BasicHttpContext;
-
-/**
- * {@hide}
- */
-abstract class Connection {
-
- /**
- * Allow a TCP connection 60 idle seconds before erroring out
- */
- static final int SOCKET_TIMEOUT = 60000;
-
- private static final int SEND = 0;
- private static final int READ = 1;
- private static final int DRAIN = 2;
- private static final int DONE = 3;
- private static final String[] states = {"SEND", "READ", "DRAIN", "DONE"};
-
- Context mContext;
-
- /** The low level connection */
- protected AndroidHttpClientConnection mHttpClientConnection = null;
-
- /**
- * The server SSL certificate associated with this connection
- * (null if the connection is not secure)
- * It would be nice to store the whole certificate chain, but
- * we want to keep things as light-weight as possible
- */
- protected SslCertificate mCertificate = null;
-
- /**
- * The host this connection is connected to. If using proxy,
- * this is set to the proxy address
- */
- HttpHost mHost;
-
- /** true if the connection can be reused for sending more requests */
- private boolean mCanPersist;
-
- /** context required by ConnectionReuseStrategy. */
- private HttpContext mHttpContext;
-
- /** set when cancelled */
- private static int STATE_NORMAL = 0;
- private static int STATE_CANCEL_REQUESTED = 1;
- private int mActive = STATE_NORMAL;
-
- /** The number of times to try to re-connect (if connect fails). */
- private final static int RETRY_REQUEST_LIMIT = 2;
-
- private static final int MIN_PIPE = 2;
- private static final int MAX_PIPE = 3;
-
- /**
- * Doesn't seem to exist anymore in the new HTTP client, so copied here.
- */
- private static final String HTTP_CONNECTION = "http.connection";
-
- RequestFeeder mRequestFeeder;
-
- /**
- * Buffer for feeding response blocks to webkit. One block per
- * connection reduces memory churn.
- */
- private byte[] mBuf;
-
- protected Connection(Context context, HttpHost host,
- RequestFeeder requestFeeder) {
- mContext = context;
- mHost = host;
- mRequestFeeder = requestFeeder;
-
- mCanPersist = false;
- mHttpContext = new BasicHttpContext(null);
- }
-
- HttpHost getHost() {
- return mHost;
- }
-
- /**
- * connection factory: returns an HTTP or HTTPS connection as
- * necessary
- */
- static Connection getConnection(
- Context context, HttpHost host, HttpHost proxy,
- RequestFeeder requestFeeder) {
-
- if (host.getSchemeName().equals("http")) {
- return new HttpConnection(context, host, requestFeeder);
- }
-
- // Otherwise, default to https
- return new HttpsConnection(context, host, proxy, requestFeeder);
- }
-
- /**
- * @return The server SSL certificate associated with this
- * connection (null if the connection is not secure)
- */
- /* package */ SslCertificate getCertificate() {
- return mCertificate;
- }
-
- /**
- * Close current network connection
- * Note: this runs in non-network thread
- */
- void cancel() {
- mActive = STATE_CANCEL_REQUESTED;
- closeConnection();
- if (HttpLog.LOGV) HttpLog.v(
- "Connection.cancel(): connection closed " + mHost);
- }
-
- /**
- * Process requests in queue
- * pipelines requests
- */
- void processRequests(Request firstRequest) {
- Request req = null;
- boolean empty;
- int error = EventHandler.OK;
- Exception exception = null;
-
- LinkedList<Request> pipe = new LinkedList<Request>();
-
- int minPipe = MIN_PIPE, maxPipe = MAX_PIPE;
- int state = SEND;
-
- while (state != DONE) {
- if (HttpLog.LOGV) HttpLog.v(
- states[state] + " pipe " + pipe.size());
-
- /* If a request was cancelled, give other cancel requests
- some time to go through so we don't uselessly restart
- connections */
- if (mActive == STATE_CANCEL_REQUESTED) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException x) { /* ignore */ }
- mActive = STATE_NORMAL;
- }
-
- switch (state) {
- case SEND: {
- if (pipe.size() == maxPipe) {
- state = READ;
- break;
- }
- /* get a request */
- if (firstRequest == null) {
- req = mRequestFeeder.getRequest(mHost);
- } else {
- req = firstRequest;
- firstRequest = null;
- }
- if (req == null) {
- state = DRAIN;
- break;
- }
- req.setConnection(this);
-
- /* Don't work on cancelled requests. */
- if (req.mCancelled) {
- if (HttpLog.LOGV) HttpLog.v(
- "processRequests(): skipping cancelled request "
- + req);
- req.complete();
- break;
- }
-
- if (mHttpClientConnection == null ||
- !mHttpClientConnection.isOpen()) {
- /* If this call fails, the address is bad or
- the net is down. Punt for now.
-
- FIXME: blow out entire queue here on
- connection failure if net up? */
-
- if (!openHttpConnection(req)) {
- state = DONE;
- break;
- }
- }
-
- /* we have a connection, let the event handler
- * know of any associated certificate,
- * potentially none.
- */
- req.mEventHandler.certificate(mCertificate);
-
- try {
- /* FIXME: don't increment failure count if old
- connection? There should not be a penalty for
- attempting to reuse an old connection */
- req.sendRequest(mHttpClientConnection);
- } catch (HttpException e) {
- exception = e;
- error = EventHandler.ERROR;
- } catch (IOException e) {
- exception = e;
- error = EventHandler.ERROR_IO;
- } catch (IllegalStateException e) {
- exception = e;
- error = EventHandler.ERROR_IO;
- }
- if (exception != null) {
- if (httpFailure(req, error, exception) &&
- !req.mCancelled) {
- /* retry request if not permanent failure
- or cancelled */
- pipe.addLast(req);
- }
- exception = null;
- state = clearPipe(pipe) ? DONE : SEND;
- minPipe = maxPipe = 1;
- break;
- }
-
- pipe.addLast(req);
- if (!mCanPersist) state = READ;
- break;
-
- }
- case DRAIN:
- case READ: {
- empty = !mRequestFeeder.haveRequest(mHost);
- int pipeSize = pipe.size();
- if (state != DRAIN && pipeSize < minPipe &&
- !empty && mCanPersist) {
- state = SEND;
- break;
- } else if (pipeSize == 0) {
- /* Done if no other work to do */
- state = empty ? DONE : SEND;
- break;
- }
-
- req = (Request)pipe.removeFirst();
- if (HttpLog.LOGV) HttpLog.v(
- "processRequests() reading " + req);
-
- try {
- req.readResponse(mHttpClientConnection);
- } catch (ParseException e) {
- exception = e;
- error = EventHandler.ERROR_IO;
- } catch (IOException e) {
- exception = e;
- error = EventHandler.ERROR_IO;
- } catch (IllegalStateException e) {
- exception = e;
- error = EventHandler.ERROR_IO;
- }
- if (exception != null) {
- if (httpFailure(req, error, exception) &&
- !req.mCancelled) {
- /* retry request if not permanent failure
- or cancelled */
- req.reset();
- pipe.addFirst(req);
- }
- exception = null;
- mCanPersist = false;
- }
- if (!mCanPersist) {
- if (HttpLog.LOGV) HttpLog.v(
- "processRequests(): no persist, closing " +
- mHost);
-
- closeConnection();
-
- mHttpContext.removeAttribute(HTTP_CONNECTION);
- clearPipe(pipe);
- minPipe = maxPipe = 1;
- state = SEND;
- }
- break;
- }
- }
- }
- }
-
- /**
- * After a send/receive failure, any pipelined requests must be
- * cleared back to the mRequest queue
- * @return true if mRequests is empty after pipe cleared
- */
- private boolean clearPipe(LinkedList<Request> pipe) {
- boolean empty = true;
- if (HttpLog.LOGV) HttpLog.v(
- "Connection.clearPipe(): clearing pipe " + pipe.size());
- synchronized (mRequestFeeder) {
- Request tReq;
- while (!pipe.isEmpty()) {
- tReq = (Request)pipe.removeLast();
- if (HttpLog.LOGV) HttpLog.v(
- "clearPipe() adding back " + mHost + " " + tReq);
- mRequestFeeder.requeueRequest(tReq);
- empty = false;
- }
- if (empty) empty = !mRequestFeeder.haveRequest(mHost);
- }
- return empty;
- }
-
- /**
- * @return true on success
- */
- private boolean openHttpConnection(Request req) {
-
- long now = SystemClock.uptimeMillis();
- int error = EventHandler.OK;
- Exception exception = null;
-
- try {
- // reset the certificate to null before opening a connection
- mCertificate = null;
- mHttpClientConnection = openConnection(req);
- if (mHttpClientConnection != null) {
- mHttpClientConnection.setSocketTimeout(SOCKET_TIMEOUT);
- mHttpContext.setAttribute(HTTP_CONNECTION,
- mHttpClientConnection);
- } else {
- // we tried to do SSL tunneling, failed,
- // and need to drop the request;
- // we have already informed the handler
- req.mFailCount = RETRY_REQUEST_LIMIT;
- return false;
- }
- } catch (UnknownHostException e) {
- if (HttpLog.LOGV) HttpLog.v("Failed to open connection");
- error = EventHandler.ERROR_LOOKUP;
- exception = e;
- } catch (IllegalArgumentException e) {
- if (HttpLog.LOGV) HttpLog.v("Illegal argument exception");
- error = EventHandler.ERROR_CONNECT;
- req.mFailCount = RETRY_REQUEST_LIMIT;
- exception = e;
- } catch (SSLConnectionClosedByUserException e) {
- // hack: if we have an SSL connection failure,
- // we don't want to reconnect
- req.mFailCount = RETRY_REQUEST_LIMIT;
- // no error message
- return false;
- } catch (SSLHandshakeException e) {
- // hack: if we have an SSL connection failure,
- // we don't want to reconnect
- req.mFailCount = RETRY_REQUEST_LIMIT;
- if (HttpLog.LOGV) HttpLog.v(
- "SSL exception performing handshake");
- error = EventHandler.ERROR_FAILED_SSL_HANDSHAKE;
- exception = e;
- } catch (IOException e) {
- error = EventHandler.ERROR_CONNECT;
- exception = e;
- }
-
- if (HttpLog.LOGV) {
- long now2 = SystemClock.uptimeMillis();
- HttpLog.v("Connection.openHttpConnection() " +
- (now2 - now) + " " + mHost);
- }
-
- if (error == EventHandler.OK) {
- return true;
- } else {
- if (req.mFailCount < RETRY_REQUEST_LIMIT) {
- // requeue
- mRequestFeeder.requeueRequest(req);
- req.mFailCount++;
- } else {
- httpFailure(req, error, exception);
- }
- return error == EventHandler.OK;
- }
- }
-
- /**
- * Helper. Calls the mEventHandler's error() method only if
- * request failed permanently. Increments mFailcount on failure.
- *
- * Increments failcount only if the network is believed to be
- * connected
- *
- * @return true if request can be retried (less than
- * RETRY_REQUEST_LIMIT failures have occurred).
- */
- private boolean httpFailure(Request req, int errorId, Exception e) {
- boolean ret = true;
-
- // e.printStackTrace();
- if (HttpLog.LOGV) HttpLog.v(
- "httpFailure() ******* " + e + " count " + req.mFailCount +
- " " + mHost + " " + req.getUri());
-
- if (++req.mFailCount >= RETRY_REQUEST_LIMIT) {
- ret = false;
- String error;
- if (errorId < 0) {
- error = getEventHandlerErrorString(errorId);
- } else {
- Throwable cause = e.getCause();
- error = cause != null ? cause.toString() : e.getMessage();
- }
- req.mEventHandler.error(errorId, error);
- req.complete();
- }
-
- closeConnection();
- mHttpContext.removeAttribute(HTTP_CONNECTION);
-
- return ret;
- }
-
- private static String getEventHandlerErrorString(int errorId) {
- switch (errorId) {
- case EventHandler.OK:
- return "OK";
-
- case EventHandler.ERROR:
- return "ERROR";
-
- case EventHandler.ERROR_LOOKUP:
- return "ERROR_LOOKUP";
-
- case EventHandler.ERROR_UNSUPPORTED_AUTH_SCHEME:
- return "ERROR_UNSUPPORTED_AUTH_SCHEME";
-
- case EventHandler.ERROR_AUTH:
- return "ERROR_AUTH";
-
- case EventHandler.ERROR_PROXYAUTH:
- return "ERROR_PROXYAUTH";
-
- case EventHandler.ERROR_CONNECT:
- return "ERROR_CONNECT";
-
- case EventHandler.ERROR_IO:
- return "ERROR_IO";
-
- case EventHandler.ERROR_TIMEOUT:
- return "ERROR_TIMEOUT";
-
- case EventHandler.ERROR_REDIRECT_LOOP:
- return "ERROR_REDIRECT_LOOP";
-
- case EventHandler.ERROR_UNSUPPORTED_SCHEME:
- return "ERROR_UNSUPPORTED_SCHEME";
-
- case EventHandler.ERROR_FAILED_SSL_HANDSHAKE:
- return "ERROR_FAILED_SSL_HANDSHAKE";
-
- case EventHandler.ERROR_BAD_URL:
- return "ERROR_BAD_URL";
-
- case EventHandler.FILE_ERROR:
- return "FILE_ERROR";
-
- case EventHandler.FILE_NOT_FOUND_ERROR:
- return "FILE_NOT_FOUND_ERROR";
-
- case EventHandler.TOO_MANY_REQUESTS_ERROR:
- return "TOO_MANY_REQUESTS_ERROR";
-
- default:
- return "UNKNOWN_ERROR";
- }
- }
-
- HttpContext getHttpContext() {
- return mHttpContext;
- }
-
- /**
- * Use same logic as ConnectionReuseStrategy
- * @see ConnectionReuseStrategy
- */
- private boolean keepAlive(HttpEntity entity,
- ProtocolVersion ver, int connType, final HttpContext context) {
- org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection)
- context.getAttribute(ExecutionContext.HTTP_CONNECTION);
-
- if (conn != null && !conn.isOpen())
- return false;
- // do NOT check for stale connection, that is an expensive operation
-
- if (entity != null) {
- if (entity.getContentLength() < 0) {
- if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
- // if the content length is not known and is not chunk
- // encoded, the connection cannot be reused
- return false;
- }
- }
- }
- // Check for 'Connection' directive
- if (connType == Headers.CONN_CLOSE) {
- return false;
- } else if (connType == Headers.CONN_KEEP_ALIVE) {
- return true;
- }
- // Resorting to protocol version default close connection policy
- return !ver.lessEquals(HttpVersion.HTTP_1_0);
- }
-
- void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) {
- mCanPersist = keepAlive(entity, ver, connType, mHttpContext);
- }
-
- void setCanPersist(boolean canPersist) {
- mCanPersist = canPersist;
- }
-
- boolean getCanPersist() {
- return mCanPersist;
- }
-
- /** typically http or https... set by subclass */
- abstract String getScheme();
- abstract void closeConnection();
- abstract AndroidHttpClientConnection openConnection(Request req) throws IOException;
-
- /**
- * Prints request queue to log, for debugging.
- * returns request count
- */
- public synchronized String toString() {
- return mHost.toString();
- }
-
- byte[] getBuf() {
- if (mBuf == null) mBuf = new byte[8192];
- return mBuf;
- }
-
-}
diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java
deleted file mode 100644
index d825530..0000000
--- a/core/java/android/net/http/ConnectionThread.java
+++ /dev/null
@@ -1,137 +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.http;
-
-import android.content.Context;
-import android.os.SystemClock;
-
-import java.lang.Thread;
-
-/**
- * {@hide}
- */
-class ConnectionThread extends Thread {
-
- static final int WAIT_TIMEOUT = 5000;
- static final int WAIT_TICK = 1000;
-
- // Performance probe
- long mCurrentThreadTime;
- long mTotalThreadTime;
-
- private boolean mWaiting;
- private volatile boolean mRunning = true;
- private Context mContext;
- private RequestQueue.ConnectionManager mConnectionManager;
- private RequestFeeder mRequestFeeder;
-
- private int mId;
- Connection mConnection;
-
- ConnectionThread(Context context,
- int id,
- RequestQueue.ConnectionManager connectionManager,
- RequestFeeder requestFeeder) {
- super();
- mContext = context;
- setName("http" + id);
- mId = id;
- mConnectionManager = connectionManager;
- mRequestFeeder = requestFeeder;
- }
-
- void requestStop() {
- synchronized (mRequestFeeder) {
- mRunning = false;
- mRequestFeeder.notify();
- }
- }
-
- /**
- * Loop until app shutdown. Runs connections in priority
- * order.
- */
- public void run() {
- android.os.Process.setThreadPriority(
- android.os.Process.THREAD_PRIORITY_DEFAULT +
- android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
-
- // these are used to get performance data. When it is not in the timing,
- // mCurrentThreadTime is 0. When it starts timing, mCurrentThreadTime is
- // first set to -1, it will be set to the current thread time when the
- // next request starts.
- mCurrentThreadTime = 0;
- mTotalThreadTime = 0;
-
- while (mRunning) {
- if (mCurrentThreadTime == -1) {
- mCurrentThreadTime = SystemClock.currentThreadTimeMillis();
- }
-
- Request request;
-
- /* Get a request to process */
- request = mRequestFeeder.getRequest();
-
- /* wait for work */
- if (request == null) {
- synchronized(mRequestFeeder) {
- if (HttpLog.LOGV) HttpLog.v("ConnectionThread: Waiting for work");
- mWaiting = true;
- try {
- mRequestFeeder.wait();
- } catch (InterruptedException e) {
- }
- mWaiting = false;
- if (mCurrentThreadTime != 0) {
- mCurrentThreadTime = SystemClock
- .currentThreadTimeMillis();
- }
- }
- } else {
- if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
- request.mHost + " " + request );
-
- mConnection = mConnectionManager.getConnection(mContext,
- request.mHost);
- mConnection.processRequests(request);
- if (mConnection.getCanPersist()) {
- if (!mConnectionManager.recycleConnection(mConnection)) {
- mConnection.closeConnection();
- }
- } else {
- mConnection.closeConnection();
- }
- mConnection = null;
-
- if (mCurrentThreadTime > 0) {
- long start = mCurrentThreadTime;
- mCurrentThreadTime = SystemClock.currentThreadTimeMillis();
- mTotalThreadTime += mCurrentThreadTime - start;
- }
- }
-
- }
- }
-
- public synchronized String toString() {
- String con = mConnection == null ? "" : mConnection.toString();
- String active = mWaiting ? "w" : "a";
- return "cid " + mId + " " + active + " " + con;
- }
-
-}
diff --git a/core/java/android/net/http/DelegatingSSLSession.java b/core/java/android/net/http/DelegatingSSLSession.java
deleted file mode 100644
index 98fbe21..0000000
--- a/core/java/android/net/http/DelegatingSSLSession.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.net.http;
-
-import java.security.Principal;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSessionContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.X509TrustManager;
-
-/**
- * This is only used when a {@code certificate} is available but usage
- * requires a {@link SSLSession}.
- *
- * @hide
- */
-public class DelegatingSSLSession implements SSLSession {
- protected DelegatingSSLSession() {
- }
-
- public static class CertificateWrap extends DelegatingSSLSession {
- private final Certificate mCertificate;
-
- public CertificateWrap(Certificate certificate) {
- mCertificate = certificate;
- }
-
- @Override
- public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
- return new Certificate[] { mCertificate };
- }
- }
-
-
- @Override
- public int getApplicationBufferSize() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getCipherSuite() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getCreationTime() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public byte[] getId() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getLastAccessedTime() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Certificate[] getLocalCertificates() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Principal getLocalPrincipal() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getPacketBufferSize() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public javax.security.cert.X509Certificate[] getPeerCertificateChain()
- throws SSLPeerUnverifiedException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getPeerHost() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getPeerPort() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getProtocol() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public SSLSessionContext getSessionContext() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object getValue(String name) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String[] getValueNames() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void invalidate() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isValid() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void putValue(String name, Object value) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void removeValue(String name) {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/core/java/android/net/http/EventHandler.java b/core/java/android/net/http/EventHandler.java
deleted file mode 100644
index 3fd471d..0000000
--- a/core/java/android/net/http/EventHandler.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.http;
-
-
-/**
- * Callbacks in this interface are made as an HTTP request is
- * processed. The normal order of callbacks is status(), headers(),
- * then multiple data() then endData(). handleSslErrorRequest(), if
- * there is an SSL certificate error. error() can occur anywhere
- * in the transaction.
- *
- * {@hide}
- */
-
-public interface EventHandler {
-
- /**
- * Error codes used in the error() callback. Positive error codes
- * are reserved for codes sent by http servers. Negative error
- * codes are connection/parsing failures, etc.
- */
-
- /** Success */
- public static final int OK = 0;
- /** Generic error */
- public static final int ERROR = -1;
- /** Server or proxy hostname lookup failed */
- public static final int ERROR_LOOKUP = -2;
- /** Unsupported authentication scheme (ie, not basic or digest) */
- public static final int ERROR_UNSUPPORTED_AUTH_SCHEME = -3;
- /** User authentication failed on server */
- public static final int ERROR_AUTH = -4;
- /** User authentication failed on proxy */
- public static final int ERROR_PROXYAUTH = -5;
- /** Could not connect to server */
- public static final int ERROR_CONNECT = -6;
- /** Failed to write to or read from server */
- public static final int ERROR_IO = -7;
- /** Connection timed out */
- public static final int ERROR_TIMEOUT = -8;
- /** Too many redirects */
- public static final int ERROR_REDIRECT_LOOP = -9;
- /** Unsupported URI scheme (ie, not http, https, etc) */
- public static final int ERROR_UNSUPPORTED_SCHEME = -10;
- /** Failed to perform SSL handshake */
- public static final int ERROR_FAILED_SSL_HANDSHAKE = -11;
- /** Bad URL */
- public static final int ERROR_BAD_URL = -12;
- /** Generic file error for file:/// loads */
- public static final int FILE_ERROR = -13;
- /** File not found error for file:/// loads */
- public static final int FILE_NOT_FOUND_ERROR = -14;
- /** Too many requests queued */
- public static final int TOO_MANY_REQUESTS_ERROR = -15;
-
- /**
- * Called after status line has been sucessfully processed.
- * @param major_version HTTP version advertised by server. major
- * is the part before the "."
- * @param minor_version HTTP version advertised by server. minor
- * is the part after the "."
- * @param code HTTP Status code. See RFC 2616.
- * @param reason_phrase Textual explanation sent by server
- */
- public void status(int major_version,
- int minor_version,
- int code,
- String reason_phrase);
-
- /**
- * Called after all headers are successfully processed.
- */
- public void headers(Headers headers);
-
- /**
- * An array containing all or part of the http body as read from
- * the server.
- * @param data A byte array containing the content
- * @param len The length of valid content in data
- *
- * Note: chunked and compressed encodings are handled within
- * android.net.http. Decoded data is passed through this
- * interface.
- */
- public void data(byte[] data, int len);
-
- /**
- * Called when the document is completely read. No more data()
- * callbacks will be made after this call
- */
- public void endData();
-
- /**
- * SSL certificate callback called before resource request is
- * made, which will be null for insecure connection.
- */
- public void certificate(SslCertificate certificate);
-
- /**
- * There was trouble.
- * @param id One of the error codes defined below
- * @param description of error
- */
- public void error(int id, String description);
-
- /**
- * SSL certificate error callback. Handles SSL error(s) on the way
- * up to the user. The callback has to make sure that restartConnection() is called,
- * otherwise the connection will be suspended indefinitely.
- * @return True if the callback can handle the error, which means it will
- * call restartConnection() to unblock the thread later,
- * otherwise return false.
- */
- public boolean handleSslErrorRequest(SslError error);
-
-}
diff --git a/core/java/android/net/http/Headers.java b/core/java/android/net/http/Headers.java
deleted file mode 100644
index 0f8b105..0000000
--- a/core/java/android/net/http/Headers.java
+++ /dev/null
@@ -1,521 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.http;
-
-import android.util.Log;
-
-import java.util.ArrayList;
-
-import org.apache.http.HeaderElement;
-import org.apache.http.entity.ContentLengthStrategy;
-import org.apache.http.message.BasicHeaderValueParser;
-import org.apache.http.message.ParserCursor;
-import org.apache.http.protocol.HTTP;
-import org.apache.http.util.CharArrayBuffer;
-
-/**
- * Manages received headers
- *
- * {@hide}
- */
-public final class Headers {
- private static final String LOGTAG = "Http";
-
- // header parsing constant
- /**
- * indicate HTTP 1.0 connection close after the response
- */
- public final static int CONN_CLOSE = 1;
- /**
- * indicate HTTP 1.1 connection keep alive
- */
- public final static int CONN_KEEP_ALIVE = 2;
-
- // initial values.
- public final static int NO_CONN_TYPE = 0;
- public final static long NO_TRANSFER_ENCODING = 0;
- public final static long NO_CONTENT_LENGTH = -1;
-
- // header strings
- public final static String TRANSFER_ENCODING = "transfer-encoding";
- public final static String CONTENT_LEN = "content-length";
- public final static String CONTENT_TYPE = "content-type";
- public final static String CONTENT_ENCODING = "content-encoding";
- public final static String CONN_DIRECTIVE = "connection";
-
- public final static String LOCATION = "location";
- public final static String PROXY_CONNECTION = "proxy-connection";
-
- public final static String WWW_AUTHENTICATE = "www-authenticate";
- public final static String PROXY_AUTHENTICATE = "proxy-authenticate";
- public final static String CONTENT_DISPOSITION = "content-disposition";
- public final static String ACCEPT_RANGES = "accept-ranges";
- public final static String EXPIRES = "expires";
- public final static String CACHE_CONTROL = "cache-control";
- public final static String LAST_MODIFIED = "last-modified";
- public final static String ETAG = "etag";
- public final static String SET_COOKIE = "set-cookie";
- public final static String PRAGMA = "pragma";
- public final static String REFRESH = "refresh";
- public final static String X_PERMITTED_CROSS_DOMAIN_POLICIES = "x-permitted-cross-domain-policies";
-
- // following hash are generated by String.hashCode()
- private final static int HASH_TRANSFER_ENCODING = 1274458357;
- private final static int HASH_CONTENT_LEN = -1132779846;
- private final static int HASH_CONTENT_TYPE = 785670158;
- private final static int HASH_CONTENT_ENCODING = 2095084583;
- private final static int HASH_CONN_DIRECTIVE = -775651618;
- private final static int HASH_LOCATION = 1901043637;
- private final static int HASH_PROXY_CONNECTION = 285929373;
- private final static int HASH_WWW_AUTHENTICATE = -243037365;
- private final static int HASH_PROXY_AUTHENTICATE = -301767724;
- private final static int HASH_CONTENT_DISPOSITION = -1267267485;
- private final static int HASH_ACCEPT_RANGES = 1397189435;
- private final static int HASH_EXPIRES = -1309235404;
- private final static int HASH_CACHE_CONTROL = -208775662;
- private final static int HASH_LAST_MODIFIED = 150043680;
- private final static int HASH_ETAG = 3123477;
- private final static int HASH_SET_COOKIE = 1237214767;
- private final static int HASH_PRAGMA = -980228804;
- private final static int HASH_REFRESH = 1085444827;
- private final static int HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES = -1345594014;
-
- // keep any headers that require direct access in a presized
- // string array
- private final static int IDX_TRANSFER_ENCODING = 0;
- private final static int IDX_CONTENT_LEN = 1;
- private final static int IDX_CONTENT_TYPE = 2;
- private final static int IDX_CONTENT_ENCODING = 3;
- private final static int IDX_CONN_DIRECTIVE = 4;
- private final static int IDX_LOCATION = 5;
- private final static int IDX_PROXY_CONNECTION = 6;
- private final static int IDX_WWW_AUTHENTICATE = 7;
- private final static int IDX_PROXY_AUTHENTICATE = 8;
- private final static int IDX_CONTENT_DISPOSITION = 9;
- private final static int IDX_ACCEPT_RANGES = 10;
- private final static int IDX_EXPIRES = 11;
- private final static int IDX_CACHE_CONTROL = 12;
- private final static int IDX_LAST_MODIFIED = 13;
- private final static int IDX_ETAG = 14;
- private final static int IDX_SET_COOKIE = 15;
- private final static int IDX_PRAGMA = 16;
- private final static int IDX_REFRESH = 17;
- private final static int IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES = 18;
-
- private final static int HEADER_COUNT = 19;
-
- /* parsed values */
- private long transferEncoding;
- private long contentLength; // Content length of the incoming data
- private int connectionType;
- private ArrayList<String> cookies = new ArrayList<String>(2);
-
- private String[] mHeaders = new String[HEADER_COUNT];
- private final static String[] sHeaderNames = {
- TRANSFER_ENCODING,
- CONTENT_LEN,
- CONTENT_TYPE,
- CONTENT_ENCODING,
- CONN_DIRECTIVE,
- LOCATION,
- PROXY_CONNECTION,
- WWW_AUTHENTICATE,
- PROXY_AUTHENTICATE,
- CONTENT_DISPOSITION,
- ACCEPT_RANGES,
- EXPIRES,
- CACHE_CONTROL,
- LAST_MODIFIED,
- ETAG,
- SET_COOKIE,
- PRAGMA,
- REFRESH,
- X_PERMITTED_CROSS_DOMAIN_POLICIES
- };
-
- // Catch-all for headers not explicitly handled
- private ArrayList<String> mExtraHeaderNames = new ArrayList<String>(4);
- private ArrayList<String> mExtraHeaderValues = new ArrayList<String>(4);
-
- public Headers() {
- transferEncoding = NO_TRANSFER_ENCODING;
- contentLength = NO_CONTENT_LENGTH;
- connectionType = NO_CONN_TYPE;
- }
-
- public void parseHeader(CharArrayBuffer buffer) {
- int pos = setLowercaseIndexOf(buffer, ':');
- if (pos == -1) {
- return;
- }
- String name = buffer.substringTrimmed(0, pos);
- if (name.length() == 0) {
- return;
- }
- pos++;
-
- String val = buffer.substringTrimmed(pos, buffer.length());
- if (HttpLog.LOGV) {
- HttpLog.v("hdr " + buffer.length() + " " + buffer);
- }
-
- switch (name.hashCode()) {
- case HASH_TRANSFER_ENCODING:
- if (name.equals(TRANSFER_ENCODING)) {
- mHeaders[IDX_TRANSFER_ENCODING] = val;
- HeaderElement[] encodings = BasicHeaderValueParser.DEFAULT
- .parseElements(buffer, new ParserCursor(pos,
- buffer.length()));
- // The chunked encoding must be the last one applied RFC2616,
- // 14.41
- int len = encodings.length;
- if (HTTP.IDENTITY_CODING.equalsIgnoreCase(val)) {
- transferEncoding = ContentLengthStrategy.IDENTITY;
- } else if ((len > 0)
- && (HTTP.CHUNK_CODING
- .equalsIgnoreCase(encodings[len - 1].getName()))) {
- transferEncoding = ContentLengthStrategy.CHUNKED;
- } else {
- transferEncoding = ContentLengthStrategy.IDENTITY;
- }
- }
- break;
- case HASH_CONTENT_LEN:
- if (name.equals(CONTENT_LEN)) {
- mHeaders[IDX_CONTENT_LEN] = val;
- try {
- contentLength = Long.parseLong(val);
- } catch (NumberFormatException e) {
- if (false) {
- Log.v(LOGTAG, "Headers.headers(): error parsing"
- + " content length: " + buffer.toString());
- }
- }
- }
- break;
- case HASH_CONTENT_TYPE:
- if (name.equals(CONTENT_TYPE)) {
- mHeaders[IDX_CONTENT_TYPE] = val;
- }
- break;
- case HASH_CONTENT_ENCODING:
- if (name.equals(CONTENT_ENCODING)) {
- mHeaders[IDX_CONTENT_ENCODING] = val;
- }
- break;
- case HASH_CONN_DIRECTIVE:
- if (name.equals(CONN_DIRECTIVE)) {
- mHeaders[IDX_CONN_DIRECTIVE] = val;
- setConnectionType(buffer, pos);
- }
- break;
- case HASH_LOCATION:
- if (name.equals(LOCATION)) {
- mHeaders[IDX_LOCATION] = val;
- }
- break;
- case HASH_PROXY_CONNECTION:
- if (name.equals(PROXY_CONNECTION)) {
- mHeaders[IDX_PROXY_CONNECTION] = val;
- setConnectionType(buffer, pos);
- }
- break;
- case HASH_WWW_AUTHENTICATE:
- if (name.equals(WWW_AUTHENTICATE)) {
- mHeaders[IDX_WWW_AUTHENTICATE] = val;
- }
- break;
- case HASH_PROXY_AUTHENTICATE:
- if (name.equals(PROXY_AUTHENTICATE)) {
- mHeaders[IDX_PROXY_AUTHENTICATE] = val;
- }
- break;
- case HASH_CONTENT_DISPOSITION:
- if (name.equals(CONTENT_DISPOSITION)) {
- mHeaders[IDX_CONTENT_DISPOSITION] = val;
- }
- break;
- case HASH_ACCEPT_RANGES:
- if (name.equals(ACCEPT_RANGES)) {
- mHeaders[IDX_ACCEPT_RANGES] = val;
- }
- break;
- case HASH_EXPIRES:
- if (name.equals(EXPIRES)) {
- mHeaders[IDX_EXPIRES] = val;
- }
- break;
- case HASH_CACHE_CONTROL:
- if (name.equals(CACHE_CONTROL)) {
- // In case where we receive more than one header, create a ',' separated list.
- // This should be ok, according to RFC 2616 chapter 4.2
- if (mHeaders[IDX_CACHE_CONTROL] != null &&
- mHeaders[IDX_CACHE_CONTROL].length() > 0) {
- mHeaders[IDX_CACHE_CONTROL] += (',' + val);
- } else {
- mHeaders[IDX_CACHE_CONTROL] = val;
- }
- }
- break;
- case HASH_LAST_MODIFIED:
- if (name.equals(LAST_MODIFIED)) {
- mHeaders[IDX_LAST_MODIFIED] = val;
- }
- break;
- case HASH_ETAG:
- if (name.equals(ETAG)) {
- mHeaders[IDX_ETAG] = val;
- }
- break;
- case HASH_SET_COOKIE:
- if (name.equals(SET_COOKIE)) {
- mHeaders[IDX_SET_COOKIE] = val;
- cookies.add(val);
- }
- break;
- case HASH_PRAGMA:
- if (name.equals(PRAGMA)) {
- mHeaders[IDX_PRAGMA] = val;
- }
- break;
- case HASH_REFRESH:
- if (name.equals(REFRESH)) {
- mHeaders[IDX_REFRESH] = val;
- }
- break;
- case HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES:
- if (name.equals(X_PERMITTED_CROSS_DOMAIN_POLICIES)) {
- mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = val;
- }
- break;
- default:
- mExtraHeaderNames.add(name);
- mExtraHeaderValues.add(val);
- }
- }
-
- public long getTransferEncoding() {
- return transferEncoding;
- }
-
- public long getContentLength() {
- return contentLength;
- }
-
- public int getConnectionType() {
- return connectionType;
- }
-
- public String getContentType() {
- return mHeaders[IDX_CONTENT_TYPE];
- }
-
- public String getContentEncoding() {
- return mHeaders[IDX_CONTENT_ENCODING];
- }
-
- public String getLocation() {
- return mHeaders[IDX_LOCATION];
- }
-
- public String getWwwAuthenticate() {
- return mHeaders[IDX_WWW_AUTHENTICATE];
- }
-
- public String getProxyAuthenticate() {
- return mHeaders[IDX_PROXY_AUTHENTICATE];
- }
-
- public String getContentDisposition() {
- return mHeaders[IDX_CONTENT_DISPOSITION];
- }
-
- public String getAcceptRanges() {
- return mHeaders[IDX_ACCEPT_RANGES];
- }
-
- public String getExpires() {
- return mHeaders[IDX_EXPIRES];
- }
-
- public String getCacheControl() {
- return mHeaders[IDX_CACHE_CONTROL];
- }
-
- public String getLastModified() {
- return mHeaders[IDX_LAST_MODIFIED];
- }
-
- public String getEtag() {
- return mHeaders[IDX_ETAG];
- }
-
- public ArrayList<String> getSetCookie() {
- return this.cookies;
- }
-
- public String getPragma() {
- return mHeaders[IDX_PRAGMA];
- }
-
- public String getRefresh() {
- return mHeaders[IDX_REFRESH];
- }
-
- public String getXPermittedCrossDomainPolicies() {
- return mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES];
- }
-
- public void setContentLength(long value) {
- this.contentLength = value;
- }
-
- public void setContentType(String value) {
- mHeaders[IDX_CONTENT_TYPE] = value;
- }
-
- public void setContentEncoding(String value) {
- mHeaders[IDX_CONTENT_ENCODING] = value;
- }
-
- public void setLocation(String value) {
- mHeaders[IDX_LOCATION] = value;
- }
-
- public void setWwwAuthenticate(String value) {
- mHeaders[IDX_WWW_AUTHENTICATE] = value;
- }
-
- public void setProxyAuthenticate(String value) {
- mHeaders[IDX_PROXY_AUTHENTICATE] = value;
- }
-
- public void setContentDisposition(String value) {
- mHeaders[IDX_CONTENT_DISPOSITION] = value;
- }
-
- public void setAcceptRanges(String value) {
- mHeaders[IDX_ACCEPT_RANGES] = value;
- }
-
- public void setExpires(String value) {
- mHeaders[IDX_EXPIRES] = value;
- }
-
- public void setCacheControl(String value) {
- mHeaders[IDX_CACHE_CONTROL] = value;
- }
-
- public void setLastModified(String value) {
- mHeaders[IDX_LAST_MODIFIED] = value;
- }
-
- public void setEtag(String value) {
- mHeaders[IDX_ETAG] = value;
- }
-
- public void setXPermittedCrossDomainPolicies(String value) {
- mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = value;
- }
-
- public interface HeaderCallback {
- public void header(String name, String value);
- }
-
- /**
- * Reports all non-null headers to the callback
- */
- public void getHeaders(HeaderCallback hcb) {
- for (int i = 0; i < HEADER_COUNT; i++) {
- String h = mHeaders[i];
- if (h != null) {
- hcb.header(sHeaderNames[i], h);
- }
- }
- int extraLen = mExtraHeaderNames.size();
- for (int i = 0; i < extraLen; i++) {
- if (false) {
- HttpLog.v("Headers.getHeaders() extra: " + i + " " +
- mExtraHeaderNames.get(i) + " " + mExtraHeaderValues.get(i));
- }
- hcb.header(mExtraHeaderNames.get(i),
- mExtraHeaderValues.get(i));
- }
-
- }
-
- private void setConnectionType(CharArrayBuffer buffer, int pos) {
- if (containsIgnoreCaseTrimmed(buffer, pos, HTTP.CONN_CLOSE)) {
- connectionType = CONN_CLOSE;
- } else if (containsIgnoreCaseTrimmed(
- buffer, pos, HTTP.CONN_KEEP_ALIVE)) {
- connectionType = CONN_KEEP_ALIVE;
- }
- }
-
-
- /**
- * Returns true if the buffer contains the given string. Ignores leading
- * whitespace and case.
- *
- * @param buffer to search
- * @param beginIndex index at which we should start
- * @param str to search for
- */
- static boolean containsIgnoreCaseTrimmed(CharArrayBuffer buffer,
- int beginIndex, final String str) {
- int len = buffer.length();
- char[] chars = buffer.buffer();
- while (beginIndex < len && HTTP.isWhitespace(chars[beginIndex])) {
- beginIndex++;
- }
- int size = str.length();
- boolean ok = len >= (beginIndex + size);
- for (int j=0; ok && (j < size); j++) {
- char a = chars[beginIndex + j];
- char b = str.charAt(j);
- if (a != b) {
- a = Character.toLowerCase(a);
- b = Character.toLowerCase(b);
- ok = a == b;
- }
- }
-
- return true;
- }
-
- /**
- * Returns index of first occurence ch. Lower cases characters leading up
- * to first occurrence of ch.
- */
- static int setLowercaseIndexOf(CharArrayBuffer buffer, final int ch) {
-
- int beginIndex = 0;
- int endIndex = buffer.length();
- char[] chars = buffer.buffer();
-
- for (int i = beginIndex; i < endIndex; i++) {
- char current = chars[i];
- if (current == ch) {
- return i;
- } else {
- chars[i] = Character.toLowerCase(current);
- }
- }
- return -1;
- }
-}
diff --git a/core/java/android/net/http/HttpAuthHeader.java b/core/java/android/net/http/HttpAuthHeader.java
deleted file mode 100644
index 3abac23..0000000
--- a/core/java/android/net/http/HttpAuthHeader.java
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.http;
-
-import java.util.Locale;
-
-/**
- * HttpAuthHeader: a class to store HTTP authentication-header parameters.
- * For more information, see: RFC 2617: HTTP Authentication.
- *
- * {@hide}
- */
-public class HttpAuthHeader {
- /**
- * Possible HTTP-authentication header tokens to search for:
- */
- public final static String BASIC_TOKEN = "Basic";
- public final static String DIGEST_TOKEN = "Digest";
-
- private final static String REALM_TOKEN = "realm";
- private final static String NONCE_TOKEN = "nonce";
- private final static String STALE_TOKEN = "stale";
- private final static String OPAQUE_TOKEN = "opaque";
- private final static String QOP_TOKEN = "qop";
- private final static String ALGORITHM_TOKEN = "algorithm";
-
- /**
- * An authentication scheme. We currently support two different schemes:
- * HttpAuthHeader.BASIC - basic, and
- * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
- */
- private int mScheme;
-
- public static final int UNKNOWN = 0;
- public static final int BASIC = 1;
- public static final int DIGEST = 2;
-
- /**
- * A flag, indicating that the previous request from the client was
- * rejected because the nonce value was stale. If stale is TRUE
- * (case-insensitive), the client may wish to simply retry the request
- * with a new encrypted response, without reprompting the user for a
- * new username and password.
- */
- private boolean mStale;
-
- /**
- * A string to be displayed to users so they know which username and
- * password to use.
- */
- private String mRealm;
-
- /**
- * A server-specified data string which should be uniquely generated
- * each time a 401 response is made.
- */
- private String mNonce;
-
- /**
- * A string of data, specified by the server, which should be returned
- * by the client unchanged in the Authorization header of subsequent
- * requests with URIs in the same protection space.
- */
- private String mOpaque;
-
- /**
- * This directive is optional, but is made so only for backward
- * compatibility with RFC 2069 [6]; it SHOULD be used by all
- * implementations compliant with this version of the Digest scheme.
- * If present, it is a quoted string of one or more tokens indicating
- * the "quality of protection" values supported by the server. The
- * value "auth" indicates authentication; the value "auth-int"
- * indicates authentication with integrity protection.
- */
- private String mQop;
-
- /**
- * A string indicating a pair of algorithms used to produce the digest
- * and a checksum. If this is not present it is assumed to be "MD5".
- */
- private String mAlgorithm;
-
- /**
- * Is this authentication request a proxy authentication request?
- */
- private boolean mIsProxy;
-
- /**
- * Username string we get from the user.
- */
- private String mUsername;
-
- /**
- * Password string we get from the user.
- */
- private String mPassword;
-
- /**
- * Creates a new HTTP-authentication header object from the
- * input header string.
- * The header string is assumed to contain parameters of at
- * most one authentication-scheme (ensured by the caller).
- */
- public HttpAuthHeader(String header) {
- if (header != null) {
- parseHeader(header);
- }
- }
-
- /**
- * @return True iff this is a proxy authentication header.
- */
- public boolean isProxy() {
- return mIsProxy;
- }
-
- /**
- * Marks this header as a proxy authentication header.
- */
- public void setProxy() {
- mIsProxy = true;
- }
-
- /**
- * @return The username string.
- */
- public String getUsername() {
- return mUsername;
- }
-
- /**
- * Sets the username string.
- */
- public void setUsername(String username) {
- mUsername = username;
- }
-
- /**
- * @return The password string.
- */
- public String getPassword() {
- return mPassword;
- }
-
- /**
- * Sets the password string.
- */
- public void setPassword(String password) {
- mPassword = password;
- }
-
- /**
- * @return True iff this is the BASIC-authentication request.
- */
- public boolean isBasic () {
- return mScheme == BASIC;
- }
-
- /**
- * @return True iff this is the DIGEST-authentication request.
- */
- public boolean isDigest() {
- return mScheme == DIGEST;
- }
-
- /**
- * @return The authentication scheme requested. We currently
- * support two schemes:
- * HttpAuthHeader.BASIC - basic, and
- * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
- */
- public int getScheme() {
- return mScheme;
- }
-
- /**
- * @return True if indicating that the previous request from
- * the client was rejected because the nonce value was stale.
- */
- public boolean getStale() {
- return mStale;
- }
-
- /**
- * @return The realm value or null if there is none.
- */
- public String getRealm() {
- return mRealm;
- }
-
- /**
- * @return The nonce value or null if there is none.
- */
- public String getNonce() {
- return mNonce;
- }
-
- /**
- * @return The opaque value or null if there is none.
- */
- public String getOpaque() {
- return mOpaque;
- }
-
- /**
- * @return The QOP ("quality-of_protection") value or null if
- * there is none. The QOP value is always lower-case.
- */
- public String getQop() {
- return mQop;
- }
-
- /**
- * @return The name of the algorithm used or null if there is
- * none. By default, MD5 is used.
- */
- public String getAlgorithm() {
- return mAlgorithm;
- }
-
- /**
- * @return True iff the authentication scheme requested by the
- * server is supported; currently supported schemes:
- * BASIC,
- * DIGEST (only algorithm="md5", no qop or qop="auth).
- */
- public boolean isSupportedScheme() {
- // it is a good idea to enforce non-null realms!
- if (mRealm != null) {
- if (mScheme == BASIC) {
- return true;
- } else {
- if (mScheme == DIGEST) {
- return
- mAlgorithm.equals("md5") &&
- (mQop == null || mQop.equals("auth"));
- }
- }
- }
-
- return false;
- }
-
- /**
- * Parses the header scheme name and then scheme parameters if
- * the scheme is supported.
- */
- private void parseHeader(String header) {
- if (HttpLog.LOGV) {
- HttpLog.v("HttpAuthHeader.parseHeader(): header: " + header);
- }
-
- if (header != null) {
- String parameters = parseScheme(header);
- if (parameters != null) {
- // if we have a supported scheme
- if (mScheme != UNKNOWN) {
- parseParameters(parameters);
- }
- }
- }
- }
-
- /**
- * Parses the authentication scheme name. If we have a Digest
- * scheme, sets the algorithm value to the default of MD5.
- * @return The authentication scheme parameters string to be
- * parsed later (if the scheme is supported) or null if failed
- * to parse the scheme (the header value is null?).
- */
- private String parseScheme(String header) {
- if (header != null) {
- int i = header.indexOf(' ');
- if (i >= 0) {
- String scheme = header.substring(0, i).trim();
- if (scheme.equalsIgnoreCase(DIGEST_TOKEN)) {
- mScheme = DIGEST;
-
- // md5 is the default algorithm!!!
- mAlgorithm = "md5";
- } else {
- if (scheme.equalsIgnoreCase(BASIC_TOKEN)) {
- mScheme = BASIC;
- }
- }
-
- return header.substring(i + 1);
- }
- }
-
- return null;
- }
-
- /**
- * Parses a comma-separated list of authentification scheme
- * parameters.
- */
- private void parseParameters(String parameters) {
- if (HttpLog.LOGV) {
- HttpLog.v("HttpAuthHeader.parseParameters():" +
- " parameters: " + parameters);
- }
-
- if (parameters != null) {
- int i;
- do {
- i = parameters.indexOf(',');
- if (i < 0) {
- // have only one parameter
- parseParameter(parameters);
- } else {
- parseParameter(parameters.substring(0, i));
- parameters = parameters.substring(i + 1);
- }
- } while (i >= 0);
- }
- }
-
- /**
- * Parses a single authentication scheme parameter. The parameter
- * string is expected to follow the format: PARAMETER=VALUE.
- */
- private void parseParameter(String parameter) {
- if (parameter != null) {
- // here, we are looking for the 1st occurence of '=' only!!!
- int i = parameter.indexOf('=');
- if (i >= 0) {
- String token = parameter.substring(0, i).trim();
- String value =
- trimDoubleQuotesIfAny(parameter.substring(i + 1).trim());
-
- if (HttpLog.LOGV) {
- HttpLog.v("HttpAuthHeader.parseParameter():" +
- " token: " + token +
- " value: " + value);
- }
-
- if (token.equalsIgnoreCase(REALM_TOKEN)) {
- mRealm = value;
- } else {
- if (mScheme == DIGEST) {
- parseParameter(token, value);
- }
- }
- }
- }
- }
-
- /**
- * If the token is a known parameter name, parses and initializes
- * the token value.
- */
- private void parseParameter(String token, String value) {
- if (token != null && value != null) {
- if (token.equalsIgnoreCase(NONCE_TOKEN)) {
- mNonce = value;
- return;
- }
-
- if (token.equalsIgnoreCase(STALE_TOKEN)) {
- parseStale(value);
- return;
- }
-
- if (token.equalsIgnoreCase(OPAQUE_TOKEN)) {
- mOpaque = value;
- return;
- }
-
- if (token.equalsIgnoreCase(QOP_TOKEN)) {
- mQop = value.toLowerCase(Locale.ROOT);
- return;
- }
-
- if (token.equalsIgnoreCase(ALGORITHM_TOKEN)) {
- mAlgorithm = value.toLowerCase(Locale.ROOT);
- return;
- }
- }
- }
-
- /**
- * Parses and initializes the 'stale' paramer value. Any value
- * different from case-insensitive "true" is considered "false".
- */
- private void parseStale(String value) {
- if (value != null) {
- if (value.equalsIgnoreCase("true")) {
- mStale = true;
- }
- }
- }
-
- /**
- * Trims double-quotes around a parameter value if there are any.
- * @return The string value without the outermost pair of double-
- * quotes or null if the original value is null.
- */
- static private String trimDoubleQuotesIfAny(String value) {
- if (value != null) {
- int len = value.length();
- if (len > 2 &&
- value.charAt(0) == '\"' && value.charAt(len - 1) == '\"') {
- return value.substring(1, len - 1);
- }
- }
-
- return value;
- }
-}
diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java
deleted file mode 100644
index edf8fed3..0000000
--- a/core/java/android/net/http/HttpConnection.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.http;
-
-import android.content.Context;
-
-import java.net.Socket;
-import java.io.IOException;
-
-import org.apache.http.HttpHost;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpConnectionParams;
-
-/**
- * A requestConnection connecting to a normal (non secure) http server
- *
- * {@hide}
- */
-class HttpConnection extends Connection {
-
- HttpConnection(Context context, HttpHost host,
- RequestFeeder requestFeeder) {
- super(context, host, requestFeeder);
- }
-
- /**
- * Opens the connection to a http server
- *
- * @return the opened low level connection
- * @throws IOException if the connection fails for any reason.
- */
- @Override
- AndroidHttpClientConnection openConnection(Request req) throws IOException {
-
- // Update the certificate info (connection not secure - set to null)
- EventHandler eventHandler = req.getEventHandler();
- mCertificate = null;
- eventHandler.certificate(mCertificate);
-
- AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
- BasicHttpParams params = new BasicHttpParams();
- Socket sock = new Socket(mHost.getHostName(), mHost.getPort());
- params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
- conn.bind(sock, params);
- return conn;
- }
-
- /**
- * Closes the low level connection.
- *
- * If an exception is thrown then it is assumed that the
- * connection will have been closed (to the extent possible)
- * anyway and the caller does not need to take any further action.
- *
- */
- void closeConnection() {
- try {
- if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
- mHttpClientConnection.close();
- }
- } catch (IOException e) {
- if (HttpLog.LOGV) HttpLog.v(
- "closeConnection(): failed closing connection " +
- mHost);
- e.printStackTrace();
- }
- }
-
- /**
- * Restart a secure connection suspended waiting for user interaction.
- */
- void restartConnection(boolean abort) {
- // not required for plain http connections
- }
-
- String getScheme() {
- return "http";
- }
-}
diff --git a/core/java/android/net/http/HttpResponseCache.java b/core/java/android/net/http/HttpResponseCache.java
index c6c22e7..188287f 100644
--- a/core/java/android/net/http/HttpResponseCache.java
+++ b/core/java/android/net/http/HttpResponseCache.java
@@ -35,8 +35,8 @@ import java.util.Map;
* Caches HTTP and HTTPS responses to the filesystem so they may be reused,
* saving time and bandwidth. This class supports {@link
* java.net.HttpURLConnection} and {@link javax.net.ssl.HttpsURLConnection};
- * there is no platform-provided cache for {@link
- * org.apache.http.impl.client.DefaultHttpClient} or {@link AndroidHttpClient}.
+ * there is no platform-provided cache for {@code DefaultHttpClient} or
+ * {@code AndroidHttpClient}.
*
* <h3>Installing an HTTP response cache</h3>
* Enable caching of all of your application's HTTP requests by installing the
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
deleted file mode 100644
index a8674de..0000000
--- a/core/java/android/net/http/HttpsConnection.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.http;
-
-import android.content.Context;
-import android.util.Log;
-import com.android.org.conscrypt.FileClientSessionCache;
-import com.android.org.conscrypt.OpenSSLContextImpl;
-import com.android.org.conscrypt.SSLClientSessionCache;
-import org.apache.http.Header;
-import org.apache.http.HttpException;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpStatus;
-import org.apache.http.ParseException;
-import org.apache.http.ProtocolVersion;
-import org.apache.http.StatusLine;
-import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.params.HttpConnectionParams;
-import org.apache.http.params.HttpParams;
-
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import java.io.File;
-import java.io.IOException;
-import java.net.Socket;
-import java.security.KeyManagementException;
-import java.security.cert.X509Certificate;
-import java.util.Locale;
-
-/**
- * A Connection connecting to a secure http server or tunneling through
- * a http proxy server to a https server.
- *
- * @hide
- */
-public class HttpsConnection extends Connection {
-
- /**
- * SSL socket factory
- */
- private static SSLSocketFactory mSslSocketFactory = null;
-
- static {
- // This initialization happens in the zygote. It triggers some
- // lazy initialization that can will benefit later invocations of
- // initializeEngine().
- initializeEngine(null);
- }
-
- /**
- * @hide
- *
- * @param sessionDir directory to cache SSL sessions
- */
- public static void initializeEngine(File sessionDir) {
- try {
- SSLClientSessionCache cache = null;
- if (sessionDir != null) {
- Log.d("HttpsConnection", "Caching SSL sessions in "
- + sessionDir + ".");
- cache = FileClientSessionCache.usingDirectory(sessionDir);
- }
-
- OpenSSLContextImpl sslContext = OpenSSLContextImpl.getPreferred();
-
- // here, trust managers is a single trust-all manager
- TrustManager[] trustManagers = new TrustManager[] {
- new X509TrustManager() {
- public X509Certificate[] getAcceptedIssuers() {
- return null;
- }
-
- public void checkClientTrusted(
- X509Certificate[] certs, String authType) {
- }
-
- public void checkServerTrusted(
- X509Certificate[] certs, String authType) {
- }
- }
- };
-
- sslContext.engineInit(null, trustManagers, null);
- sslContext.engineGetClientSessionContext().setPersistentCache(cache);
-
- synchronized (HttpsConnection.class) {
- mSslSocketFactory = sslContext.engineGetSocketFactory();
- }
- } catch (KeyManagementException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private synchronized static SSLSocketFactory getSocketFactory() {
- return mSslSocketFactory;
- }
-
- /**
- * Object to wait on when suspending the SSL connection
- */
- private Object mSuspendLock = new Object();
-
- /**
- * True if the connection is suspended pending the result of asking the
- * user about an error.
- */
- private boolean mSuspended = false;
-
- /**
- * True if the connection attempt should be aborted due to an ssl
- * error.
- */
- private boolean mAborted = false;
-
- // Used when connecting through a proxy.
- private HttpHost mProxyHost;
-
- /**
- * Contructor for a https connection.
- */
- HttpsConnection(Context context, HttpHost host, HttpHost proxy,
- RequestFeeder requestFeeder) {
- super(context, host, requestFeeder);
- mProxyHost = proxy;
- }
-
- /**
- * Sets the server SSL certificate associated with this
- * connection.
- * @param certificate The SSL certificate
- */
- /* package */ void setCertificate(SslCertificate certificate) {
- mCertificate = certificate;
- }
-
- /**
- * Opens the connection to a http server or proxy.
- *
- * @return the opened low level connection
- * @throws IOException if the connection fails for any reason.
- */
- @Override
- AndroidHttpClientConnection openConnection(Request req) throws IOException {
- SSLSocket sslSock = null;
-
- if (mProxyHost != null) {
- // If we have a proxy set, we first send a CONNECT request
- // to the proxy; if the proxy returns 200 OK, we negotiate
- // a secure connection to the target server via the proxy.
- // If the request fails, we drop it, but provide the event
- // handler with the response status and headers. The event
- // handler is then responsible for cancelling the load or
- // issueing a new request.
- AndroidHttpClientConnection proxyConnection = null;
- Socket proxySock = null;
- try {
- proxySock = new Socket
- (mProxyHost.getHostName(), mProxyHost.getPort());
-
- proxySock.setSoTimeout(60 * 1000);
-
- proxyConnection = new AndroidHttpClientConnection();
- HttpParams params = new BasicHttpParams();
- HttpConnectionParams.setSocketBufferSize(params, 8192);
-
- proxyConnection.bind(proxySock, params);
- } catch(IOException e) {
- if (proxyConnection != null) {
- proxyConnection.close();
- }
-
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage =
- "failed to establish a connection to the proxy";
- }
-
- throw new IOException(errorMessage);
- }
-
- StatusLine statusLine = null;
- int statusCode = 0;
- Headers headers = new Headers();
- try {
- BasicHttpRequest proxyReq = new BasicHttpRequest
- ("CONNECT", mHost.toHostString());
-
- // add all 'proxy' headers from the original request, we also need
- // to add 'host' header unless we want proxy to answer us with a
- // 400 Bad Request
- for (Header h : req.mHttpRequest.getAllHeaders()) {
- String headerName = h.getName().toLowerCase(Locale.ROOT);
- if (headerName.startsWith("proxy") || headerName.equals("keep-alive")
- || headerName.equals("host")) {
- proxyReq.addHeader(h);
- }
- }
-
- proxyConnection.sendRequestHeader(proxyReq);
- proxyConnection.flush();
-
- // it is possible to receive informational status
- // codes prior to receiving actual headers;
- // all those status codes are smaller than OK 200
- // a loop is a standard way of dealing with them
- do {
- statusLine = proxyConnection.parseResponseHeader(headers);
- statusCode = statusLine.getStatusCode();
- } while (statusCode < HttpStatus.SC_OK);
- } catch (ParseException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage =
- "failed to send a CONNECT request";
- }
-
- throw new IOException(errorMessage);
- } catch (HttpException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage =
- "failed to send a CONNECT request";
- }
-
- throw new IOException(errorMessage);
- } catch (IOException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage =
- "failed to send a CONNECT request";
- }
-
- throw new IOException(errorMessage);
- }
-
- if (statusCode == HttpStatus.SC_OK) {
- try {
- sslSock = (SSLSocket) getSocketFactory().createSocket(
- proxySock, mHost.getHostName(), mHost.getPort(), true);
- } catch(IOException e) {
- if (sslSock != null) {
- sslSock.close();
- }
-
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage =
- "failed to create an SSL socket";
- }
- throw new IOException(errorMessage);
- }
- } else {
- // if the code is not OK, inform the event handler
- ProtocolVersion version = statusLine.getProtocolVersion();
-
- req.mEventHandler.status(version.getMajor(),
- version.getMinor(),
- statusCode,
- statusLine.getReasonPhrase());
- req.mEventHandler.headers(headers);
- req.mEventHandler.endData();
-
- proxyConnection.close();
-
- // here, we return null to indicate that the original
- // request needs to be dropped
- return null;
- }
- } else {
- // if we do not have a proxy, we simply connect to the host
- try {
- sslSock = (SSLSocket) getSocketFactory().createSocket(
- mHost.getHostName(), mHost.getPort());
- sslSock.setSoTimeout(SOCKET_TIMEOUT);
- } catch(IOException e) {
- if (sslSock != null) {
- sslSock.close();
- }
-
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "failed to create an SSL socket";
- }
-
- throw new IOException(errorMessage);
- }
- }
-
- // do handshake and validate server certificates
- SslError error = CertificateChainValidator.getInstance().
- doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());
-
- // Inform the user if there is a problem
- if (error != null) {
- // handleSslErrorRequest may immediately unsuspend if it wants to
- // allow the certificate anyway.
- // So we mark the connection as suspended, call handleSslErrorRequest
- // then check if we're still suspended and only wait if we actually
- // need to.
- synchronized (mSuspendLock) {
- mSuspended = true;
- }
- // don't hold the lock while calling out to the event handler
- boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
- if(!canHandle) {
- throw new IOException("failed to handle "+ error);
- }
- synchronized (mSuspendLock) {
- if (mSuspended) {
- try {
- // Put a limit on how long we are waiting; if the timeout
- // expires (which should never happen unless you choose
- // to ignore the SSL error dialog for a very long time),
- // we wake up the thread and abort the request. This is
- // to prevent us from stalling the network if things go
- // very bad.
- mSuspendLock.wait(10 * 60 * 1000);
- if (mSuspended) {
- // mSuspended is true if we have not had a chance to
- // restart the connection yet (ie, the wait timeout
- // has expired)
- mSuspended = false;
- mAborted = true;
- if (HttpLog.LOGV) {
- HttpLog.v("HttpsConnection.openConnection():" +
- " SSL timeout expired and request was cancelled!!!");
- }
- }
- } catch (InterruptedException e) {
- // ignore
- }
- }
- if (mAborted) {
- // The user decided not to use this unverified connection
- // so close it immediately.
- sslSock.close();
- throw new SSLConnectionClosedByUserException("connection closed by the user");
- }
- }
- }
-
- // All went well, we have an open, verified connection.
- AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
- BasicHttpParams params = new BasicHttpParams();
- params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
- conn.bind(sslSock, params);
-
- return conn;
- }
-
- /**
- * Closes the low level connection.
- *
- * If an exception is thrown then it is assumed that the connection will
- * have been closed (to the extent possible) anyway and the caller does not
- * need to take any further action.
- *
- */
- @Override
- void closeConnection() {
- // if the connection has been suspended due to an SSL error
- if (mSuspended) {
- // wake up the network thread
- restartConnection(false);
- }
-
- try {
- if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
- mHttpClientConnection.close();
- }
- } catch (IOException e) {
- if (HttpLog.LOGV)
- HttpLog.v("HttpsConnection.closeConnection():" +
- " failed closing connection " + mHost);
- e.printStackTrace();
- }
- }
-
- /**
- * Restart a secure connection suspended waiting for user interaction.
- */
- void restartConnection(boolean proceed) {
- if (HttpLog.LOGV) {
- HttpLog.v("HttpsConnection.restartConnection():" +
- " proceed: " + proceed);
- }
-
- synchronized (mSuspendLock) {
- if (mSuspended) {
- mSuspended = false;
- mAborted = !proceed;
- mSuspendLock.notify();
- }
- }
- }
-
- @Override
- String getScheme() {
- return "https";
- }
-}
-
-/**
- * Simple exception we throw if the SSL connection is closed by the user.
- *
- * {@hide}
- */
-class SSLConnectionClosedByUserException extends SSLException {
-
- public SSLConnectionClosedByUserException(String reason) {
- super(reason);
- }
-}
diff --git a/core/java/android/net/http/IdleCache.java b/core/java/android/net/http/IdleCache.java
deleted file mode 100644
index fda6009..0000000
--- a/core/java/android/net/http/IdleCache.java
+++ /dev/null
@@ -1,175 +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.
- */
-
-/**
- * Hangs onto idle live connections for a little while
- */
-
-package android.net.http;
-
-import org.apache.http.HttpHost;
-
-import android.os.SystemClock;
-
-/**
- * {@hide}
- */
-class IdleCache {
-
- class Entry {
- HttpHost mHost;
- Connection mConnection;
- long mTimeout;
- };
-
- private final static int IDLE_CACHE_MAX = 8;
-
- /* Allow five consecutive empty queue checks before shutdown */
- private final static int EMPTY_CHECK_MAX = 5;
-
- /* six second timeout for connections */
- private final static int TIMEOUT = 6 * 1000;
- private final static int CHECK_INTERVAL = 2 * 1000;
- private Entry[] mEntries = new Entry[IDLE_CACHE_MAX];
-
- private int mCount = 0;
-
- private IdleReaper mThread = null;
-
- /* stats */
- private int mCached = 0;
- private int mReused = 0;
-
- IdleCache() {
- for (int i = 0; i < IDLE_CACHE_MAX; i++) {
- mEntries[i] = new Entry();
- }
- }
-
- /**
- * Caches connection, if there is room.
- * @return true if connection cached
- */
- synchronized boolean cacheConnection(
- HttpHost host, Connection connection) {
-
- boolean ret = false;
-
- if (HttpLog.LOGV) {
- HttpLog.v("IdleCache size " + mCount + " host " + host);
- }
-
- if (mCount < IDLE_CACHE_MAX) {
- long time = SystemClock.uptimeMillis();
- for (int i = 0; i < IDLE_CACHE_MAX; i++) {
- Entry entry = mEntries[i];
- if (entry.mHost == null) {
- entry.mHost = host;
- entry.mConnection = connection;
- entry.mTimeout = time + TIMEOUT;
- mCount++;
- if (HttpLog.LOGV) mCached++;
- ret = true;
- if (mThread == null) {
- mThread = new IdleReaper();
- mThread.start();
- }
- break;
- }
- }
- }
- return ret;
- }
-
- synchronized Connection getConnection(HttpHost host) {
- Connection ret = null;
-
- if (mCount > 0) {
- for (int i = 0; i < IDLE_CACHE_MAX; i++) {
- Entry entry = mEntries[i];
- HttpHost eHost = entry.mHost;
- if (eHost != null && eHost.equals(host)) {
- ret = entry.mConnection;
- entry.mHost = null;
- entry.mConnection = null;
- mCount--;
- if (HttpLog.LOGV) mReused++;
- break;
- }
- }
- }
- return ret;
- }
-
- synchronized void clear() {
- for (int i = 0; mCount > 0 && i < IDLE_CACHE_MAX; i++) {
- Entry entry = mEntries[i];
- if (entry.mHost != null) {
- entry.mHost = null;
- entry.mConnection.closeConnection();
- entry.mConnection = null;
- mCount--;
- }
- }
- }
-
- private synchronized void clearIdle() {
- if (mCount > 0) {
- long time = SystemClock.uptimeMillis();
- for (int i = 0; i < IDLE_CACHE_MAX; i++) {
- Entry entry = mEntries[i];
- if (entry.mHost != null && time > entry.mTimeout) {
- entry.mHost = null;
- entry.mConnection.closeConnection();
- entry.mConnection = null;
- mCount--;
- }
- }
- }
- }
-
- private class IdleReaper extends Thread {
-
- public void run() {
- int check = 0;
-
- setName("IdleReaper");
- android.os.Process.setThreadPriority(
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- synchronized (IdleCache.this) {
- while (check < EMPTY_CHECK_MAX) {
- try {
- IdleCache.this.wait(CHECK_INTERVAL);
- } catch (InterruptedException ex) {
- }
- if (mCount == 0) {
- check++;
- } else {
- check = 0;
- clearIdle();
- }
- }
- mThread = null;
- }
- if (HttpLog.LOGV) {
- HttpLog.v("IdleCache IdleReaper shutdown: cached " + mCached +
- " reused " + mReused);
- mCached = 0;
- mReused = 0;
- }
- }
- }
-}
diff --git a/core/java/android/net/http/LoggingEventHandler.java b/core/java/android/net/http/LoggingEventHandler.java
deleted file mode 100644
index bdafa0b..0000000
--- a/core/java/android/net/http/LoggingEventHandler.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * A test EventHandler: Logs everything received
- */
-
-package android.net.http;
-
-import android.net.http.Headers;
-
-/**
- * {@hide}
- */
-public class LoggingEventHandler implements EventHandler {
-
- public void requestSent() {
- HttpLog.v("LoggingEventHandler:requestSent()");
- }
-
- public void status(int major_version,
- int minor_version,
- int code, /* Status-Code value */
- String reason_phrase) {
- if (HttpLog.LOGV) {
- HttpLog.v("LoggingEventHandler:status() major: " + major_version +
- " minor: " + minor_version +
- " code: " + code +
- " reason: " + reason_phrase);
- }
- }
-
- public void headers(Headers headers) {
- if (HttpLog.LOGV) {
- HttpLog.v("LoggingEventHandler:headers()");
- HttpLog.v(headers.toString());
- }
- }
-
- public void locationChanged(String newLocation, boolean permanent) {
- if (HttpLog.LOGV) {
- HttpLog.v("LoggingEventHandler: locationChanged() " + newLocation +
- " permanent " + permanent);
- }
- }
-
- public void data(byte[] data, int len) {
- if (HttpLog.LOGV) {
- HttpLog.v("LoggingEventHandler: data() " + len + " bytes");
- }
- // HttpLog.v(new String(data, 0, len));
- }
- public void endData() {
- if (HttpLog.LOGV) {
- HttpLog.v("LoggingEventHandler: endData() called");
- }
- }
-
- public void certificate(SslCertificate certificate) {
- if (HttpLog.LOGV) {
- HttpLog.v("LoggingEventHandler: certificate(): " + certificate);
- }
- }
-
- public void error(int id, String description) {
- if (HttpLog.LOGV) {
- HttpLog.v("LoggingEventHandler: error() called Id:" + id +
- " description " + description);
- }
- }
-
- public boolean handleSslErrorRequest(SslError error) {
- if (HttpLog.LOGV) {
- HttpLog.v("LoggingEventHandler: handleSslErrorRequest():" + error);
- }
- // return false so that the caller thread won't wait forever
- return false;
- }
-}
diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java
deleted file mode 100644
index 76d7bb9..0000000
--- a/core/java/android/net/http/Request.java
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.http;
-
-import java.io.EOFException;
-import java.io.InputStream;
-import java.io.IOException;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.zip.GZIPInputStream;
-
-import org.apache.http.entity.InputStreamEntity;
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpEntityEnclosingRequest;
-import org.apache.http.HttpException;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpStatus;
-import org.apache.http.ParseException;
-import org.apache.http.ProtocolVersion;
-
-import org.apache.http.StatusLine;
-import org.apache.http.message.BasicHttpRequest;
-import org.apache.http.message.BasicHttpEntityEnclosingRequest;
-import org.apache.http.protocol.RequestContent;
-
-/**
- * Represents an HTTP request for a given host.
- *
- * {@hide}
- */
-
-class Request {
-
- /** The eventhandler to call as the request progresses */
- EventHandler mEventHandler;
-
- private Connection mConnection;
-
- /** The Apache http request */
- BasicHttpRequest mHttpRequest;
-
- /** The path component of this request */
- String mPath;
-
- /** Host serving this request */
- HttpHost mHost;
-
- /** Set if I'm using a proxy server */
- HttpHost mProxyHost;
-
- /** True if request has been cancelled */
- volatile boolean mCancelled = false;
-
- int mFailCount = 0;
-
- // This will be used to set the Range field if we retry a connection. This
- // is http/1.1 feature.
- private int mReceivedBytes = 0;
-
- private InputStream mBodyProvider;
- private int mBodyLength;
-
- private final static String HOST_HEADER = "Host";
- private final static String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
- private final static String CONTENT_LENGTH_HEADER = "content-length";
-
- /* Used to synchronize waitUntilComplete() requests */
- private final Object mClientResource = new Object();
-
- /** True if loading should be paused **/
- private boolean mLoadingPaused = false;
-
- /**
- * Processor used to set content-length and transfer-encoding
- * headers.
- */
- private static RequestContent requestContentProcessor =
- new RequestContent();
-
- /**
- * Instantiates a new Request.
- * @param method GET/POST/PUT
- * @param host The server that will handle this request
- * @param path path part of URI
- * @param bodyProvider InputStream providing HTTP body, null if none
- * @param bodyLength length of body, must be 0 if bodyProvider is null
- * @param eventHandler request will make progress callbacks on
- * this interface
- * @param headers reqeust headers
- */
- Request(String method, HttpHost host, HttpHost proxyHost, String path,
- InputStream bodyProvider, int bodyLength,
- EventHandler eventHandler,
- Map<String, String> headers) {
- mEventHandler = eventHandler;
- mHost = host;
- mProxyHost = proxyHost;
- mPath = path;
- mBodyProvider = bodyProvider;
- mBodyLength = bodyLength;
-
- if (bodyProvider == null && !"POST".equalsIgnoreCase(method)) {
- mHttpRequest = new BasicHttpRequest(method, getUri());
- } else {
- mHttpRequest = new BasicHttpEntityEnclosingRequest(
- method, getUri());
- // it is ok to have null entity for BasicHttpEntityEnclosingRequest.
- // By using BasicHttpEntityEnclosingRequest, it will set up the
- // correct content-length, content-type and content-encoding.
- if (bodyProvider != null) {
- setBodyProvider(bodyProvider, bodyLength);
- }
- }
- addHeader(HOST_HEADER, getHostPort());
-
- /* FIXME: if webcore will make the root document a
- high-priority request, we can ask for gzip encoding only on
- high priority reqs (saving the trouble for images, etc) */
- addHeader(ACCEPT_ENCODING_HEADER, "gzip");
- addHeaders(headers);
- }
-
- /**
- * @param pause True if the load should be paused.
- */
- synchronized void setLoadingPaused(boolean pause) {
- mLoadingPaused = pause;
-
- // Wake up the paused thread if we're unpausing the load.
- if (!mLoadingPaused) {
- notify();
- }
- }
-
- /**
- * @param connection Request served by this connection
- */
- void setConnection(Connection connection) {
- mConnection = connection;
- }
-
- /* package */ EventHandler getEventHandler() {
- return mEventHandler;
- }
-
- /**
- * Add header represented by given pair to request. Header will
- * be formatted in request as "name: value\r\n".
- * @param name of header
- * @param value of header
- */
- void addHeader(String name, String value) {
- if (name == null) {
- String damage = "Null http header name";
- HttpLog.e(damage);
- throw new NullPointerException(damage);
- }
- if (value == null || value.length() == 0) {
- String damage = "Null or empty value for header \"" + name + "\"";
- HttpLog.e(damage);
- throw new RuntimeException(damage);
- }
- mHttpRequest.addHeader(name, value);
- }
-
- /**
- * Add all headers in given map to this request. This is a helper
- * method: it calls addHeader for each pair in the map.
- */
- void addHeaders(Map<String, String> headers) {
- if (headers == null) {
- return;
- }
-
- Entry<String, String> entry;
- Iterator<Entry<String, String>> i = headers.entrySet().iterator();
- while (i.hasNext()) {
- entry = i.next();
- addHeader(entry.getKey(), entry.getValue());
- }
- }
-
- /**
- * Send the request line and headers
- */
- void sendRequest(AndroidHttpClientConnection httpClientConnection)
- throws HttpException, IOException {
-
- if (mCancelled) return; // don't send cancelled requests
-
- if (HttpLog.LOGV) {
- HttpLog.v("Request.sendRequest() " + mHost.getSchemeName() + "://" + getHostPort());
- // HttpLog.v(mHttpRequest.getRequestLine().toString());
- if (false) {
- Iterator i = mHttpRequest.headerIterator();
- while (i.hasNext()) {
- Header header = (Header)i.next();
- HttpLog.v(header.getName() + ": " + header.getValue());
- }
- }
- }
-
- requestContentProcessor.process(mHttpRequest,
- mConnection.getHttpContext());
- httpClientConnection.sendRequestHeader(mHttpRequest);
- if (mHttpRequest instanceof HttpEntityEnclosingRequest) {
- httpClientConnection.sendRequestEntity(
- (HttpEntityEnclosingRequest) mHttpRequest);
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v("Request.requestSent() " + mHost.getSchemeName() + "://" + getHostPort() + mPath);
- }
- }
-
-
- /**
- * Receive a single http response.
- *
- * @param httpClientConnection the request to receive the response for.
- */
- void readResponse(AndroidHttpClientConnection httpClientConnection)
- throws IOException, ParseException {
-
- if (mCancelled) return; // don't send cancelled requests
-
- StatusLine statusLine = null;
- boolean hasBody = false;
- httpClientConnection.flush();
- int statusCode = 0;
-
- Headers header = new Headers();
- do {
- statusLine = httpClientConnection.parseResponseHeader(header);
- statusCode = statusLine.getStatusCode();
- } while (statusCode < HttpStatus.SC_OK);
- if (HttpLog.LOGV) HttpLog.v(
- "Request.readResponseStatus() " +
- statusLine.toString().length() + " " + statusLine);
-
- ProtocolVersion v = statusLine.getProtocolVersion();
- mEventHandler.status(v.getMajor(), v.getMinor(),
- statusCode, statusLine.getReasonPhrase());
- mEventHandler.headers(header);
- HttpEntity entity = null;
- hasBody = canResponseHaveBody(mHttpRequest, statusCode);
-
- if (hasBody)
- entity = httpClientConnection.receiveResponseEntity(header);
-
- // restrict the range request to the servers claiming that they are
- // accepting ranges in bytes
- boolean supportPartialContent = "bytes".equalsIgnoreCase(header
- .getAcceptRanges());
-
- if (entity != null) {
- InputStream is = entity.getContent();
-
- // process gzip content encoding
- Header contentEncoding = entity.getContentEncoding();
- InputStream nis = null;
- byte[] buf = null;
- int count = 0;
- try {
- if (contentEncoding != null &&
- contentEncoding.getValue().equals("gzip")) {
- nis = new GZIPInputStream(is);
- } else {
- nis = is;
- }
-
- /* accumulate enough data to make it worth pushing it
- * up the stack */
- buf = mConnection.getBuf();
- int len = 0;
- int lowWater = buf.length / 2;
- while (len != -1) {
- synchronized(this) {
- while (mLoadingPaused) {
- // Put this (network loading) thread to sleep if WebCore
- // has asked us to. This can happen with plugins for
- // example, if we are streaming data but the plugin has
- // filled its internal buffers.
- try {
- wait();
- } catch (InterruptedException e) {
- HttpLog.e("Interrupted exception whilst "
- + "network thread paused at WebCore's request."
- + " " + e.getMessage());
- }
- }
- }
-
- len = nis.read(buf, count, buf.length - count);
-
- if (len != -1) {
- count += len;
- if (supportPartialContent) mReceivedBytes += len;
- }
- if (len == -1 || count >= lowWater) {
- if (HttpLog.LOGV) HttpLog.v("Request.readResponse() " + count);
- mEventHandler.data(buf, count);
- count = 0;
- }
- }
- } catch (EOFException e) {
- /* InflaterInputStream throws an EOFException when the
- server truncates gzipped content. Handle this case
- as we do truncated non-gzipped content: no error */
- if (count > 0) {
- // if there is uncommited content, we should commit them
- mEventHandler.data(buf, count);
- }
- if (HttpLog.LOGV) HttpLog.v( "readResponse() handling " + e);
- } catch(IOException e) {
- // don't throw if we have a non-OK status code
- if (statusCode == HttpStatus.SC_OK
- || statusCode == HttpStatus.SC_PARTIAL_CONTENT) {
- if (supportPartialContent && count > 0) {
- // if there is uncommited content, we should commit them
- // as we will continue the request
- mEventHandler.data(buf, count);
- }
- throw e;
- }
- } finally {
- if (nis != null) {
- nis.close();
- }
- }
- }
- mConnection.setCanPersist(entity, statusLine.getProtocolVersion(),
- header.getConnectionType());
- mEventHandler.endData();
- complete();
-
- if (HttpLog.LOGV) HttpLog.v("Request.readResponse(): done " +
- mHost.getSchemeName() + "://" + getHostPort() + mPath);
- }
-
- /**
- * Data will not be sent to or received from server after cancel()
- * call. Does not close connection--use close() below for that.
- *
- * Called by RequestHandle from non-network thread
- */
- synchronized void cancel() {
- if (HttpLog.LOGV) {
- HttpLog.v("Request.cancel(): " + getUri());
- }
-
- // Ensure that the network thread is not blocked by a hanging request from WebCore to
- // pause the load.
- mLoadingPaused = false;
- notify();
-
- mCancelled = true;
- if (mConnection != null) {
- mConnection.cancel();
- }
- }
-
- String getHostPort() {
- String myScheme = mHost.getSchemeName();
- int myPort = mHost.getPort();
-
- // Only send port when we must... many servers can't deal with it
- if (myPort != 80 && myScheme.equals("http") ||
- myPort != 443 && myScheme.equals("https")) {
- return mHost.toHostString();
- } else {
- return mHost.getHostName();
- }
- }
-
- String getUri() {
- if (mProxyHost == null ||
- mHost.getSchemeName().equals("https")) {
- return mPath;
- }
- return mHost.getSchemeName() + "://" + getHostPort() + mPath;
- }
-
- /**
- * for debugging
- */
- public String toString() {
- return mPath;
- }
-
-
- /**
- * If this request has been sent once and failed, it must be reset
- * before it can be sent again.
- */
- void reset() {
- /* clear content-length header */
- mHttpRequest.removeHeaders(CONTENT_LENGTH_HEADER);
-
- if (mBodyProvider != null) {
- try {
- mBodyProvider.reset();
- } catch (IOException ex) {
- if (HttpLog.LOGV) HttpLog.v(
- "failed to reset body provider " +
- getUri());
- }
- setBodyProvider(mBodyProvider, mBodyLength);
- }
-
- if (mReceivedBytes > 0) {
- // reset the fail count as we continue the request
- mFailCount = 0;
- // set the "Range" header to indicate that the retry will continue
- // instead of restarting the request
- HttpLog.v("*** Request.reset() to range:" + mReceivedBytes);
- mHttpRequest.setHeader("Range", "bytes=" + mReceivedBytes + "-");
- }
- }
-
- /**
- * Pause thread request completes. Used for synchronous requests,
- * and testing
- */
- void waitUntilComplete() {
- synchronized (mClientResource) {
- try {
- if (HttpLog.LOGV) HttpLog.v("Request.waitUntilComplete()");
- mClientResource.wait();
- if (HttpLog.LOGV) HttpLog.v("Request.waitUntilComplete() done waiting");
- } catch (InterruptedException e) {
- }
- }
- }
-
- void complete() {
- synchronized (mClientResource) {
- mClientResource.notifyAll();
- }
- }
-
- /**
- * Decide whether a response comes with an entity.
- * The implementation in this class is based on RFC 2616.
- * Unknown methods and response codes are supposed to
- * indicate responses with an entity.
- * <br/>
- * Derived executors can override this method to handle
- * methods and response codes not specified in RFC 2616.
- *
- * @param request the request, to obtain the executed method
- * @param response the response, to obtain the status code
- */
-
- private static boolean canResponseHaveBody(final HttpRequest request,
- final int status) {
-
- if ("HEAD".equalsIgnoreCase(request.getRequestLine().getMethod())) {
- return false;
- }
- return status >= HttpStatus.SC_OK
- && status != HttpStatus.SC_NO_CONTENT
- && status != HttpStatus.SC_NOT_MODIFIED;
- }
-
- /**
- * Supply an InputStream that provides the body of a request. It's
- * not great that the caller must also provide the length of the data
- * returned by that InputStream, but the client needs to know up
- * front, and I'm not sure how to get this out of the InputStream
- * itself without a costly readthrough. I'm not sure skip() would
- * do what we want. If you know a better way, please let me know.
- */
- private void setBodyProvider(InputStream bodyProvider, int bodyLength) {
- if (!bodyProvider.markSupported()) {
- throw new IllegalArgumentException(
- "bodyProvider must support mark()");
- }
- // Mark beginning of stream
- bodyProvider.mark(Integer.MAX_VALUE);
-
- ((BasicHttpEntityEnclosingRequest)mHttpRequest).setEntity(
- new InputStreamEntity(bodyProvider, bodyLength));
- }
-
-
- /**
- * Handles SSL error(s) on the way down from the user (the user
- * has already provided their feedback).
- */
- public void handleSslErrorResponse(boolean proceed) {
- HttpsConnection connection = (HttpsConnection)(mConnection);
- if (connection != null) {
- connection.restartConnection(proceed);
- }
- }
-
- /**
- * Helper: calls error() on eventhandler with appropriate message
- * This should not be called before the mConnection is set.
- */
- void error(int errorId, int resourceId) {
- mEventHandler.error(
- errorId,
- mConnection.mContext.getText(
- resourceId).toString());
- }
-
-}
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
deleted file mode 100644
index f23f69c..0000000
--- a/core/java/android/net/http/RequestHandle.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.http;
-
-import android.net.ParseException;
-import android.net.WebAddress;
-import junit.framework.Assert;
-import android.webkit.CookieManager;
-
-import org.apache.commons.codec.binary.Base64;
-
-import java.io.InputStream;
-import java.lang.Math;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Random;
-
-/**
- * RequestHandle: handles a request session that may include multiple
- * redirects, HTTP authentication requests, etc.
- *
- * {@hide}
- */
-public class RequestHandle {
-
- private String mUrl;
- private WebAddress mUri;
- private String mMethod;
- private Map<String, String> mHeaders;
- private RequestQueue mRequestQueue;
- private Request mRequest;
- private InputStream mBodyProvider;
- private int mBodyLength;
- private int mRedirectCount = 0;
- // Used only with synchronous requests.
- private Connection mConnection;
-
- private final static String AUTHORIZATION_HEADER = "Authorization";
- private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
-
- public final static int MAX_REDIRECT_COUNT = 16;
-
- /**
- * Creates a new request session.
- */
- public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
- String method, Map<String, String> headers,
- InputStream bodyProvider, int bodyLength, Request request) {
-
- if (headers == null) {
- headers = new HashMap<String, String>();
- }
- mHeaders = headers;
- mBodyProvider = bodyProvider;
- mBodyLength = bodyLength;
- mMethod = method == null? "GET" : method;
-
- mUrl = url;
- mUri = uri;
-
- mRequestQueue = requestQueue;
-
- mRequest = request;
- }
-
- /**
- * Creates a new request session with a given Connection. This connection
- * is used during a synchronous load to handle this request.
- */
- public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
- String method, Map<String, String> headers,
- InputStream bodyProvider, int bodyLength, Request request,
- Connection conn) {
- this(requestQueue, url, uri, method, headers, bodyProvider, bodyLength,
- request);
- mConnection = conn;
- }
-
- /**
- * Cancels this request
- */
- public void cancel() {
- if (mRequest != null) {
- mRequest.cancel();
- }
- }
-
- /**
- * Pauses the loading of this request. For example, called from the WebCore thread
- * when the plugin can take no more data.
- */
- public void pauseRequest(boolean pause) {
- if (mRequest != null) {
- mRequest.setLoadingPaused(pause);
- }
- }
-
- /**
- * Handles SSL error(s) on the way down from the user (the user
- * has already provided their feedback).
- */
- public void handleSslErrorResponse(boolean proceed) {
- if (mRequest != null) {
- mRequest.handleSslErrorResponse(proceed);
- }
- }
-
- /**
- * @return true if we've hit the max redirect count
- */
- public boolean isRedirectMax() {
- return mRedirectCount >= MAX_REDIRECT_COUNT;
- }
-
- public int getRedirectCount() {
- return mRedirectCount;
- }
-
- public void setRedirectCount(int count) {
- mRedirectCount = count;
- }
-
- /**
- * Create and queue a redirect request.
- *
- * @param redirectTo URL to redirect to
- * @param statusCode HTTP status code returned from original request
- * @param cacheHeaders Cache header for redirect URL
- * @return true if setup succeeds, false otherwise (redirect loop
- * count exceeded, body provider unable to rewind on 307 redirect)
- */
- public boolean setupRedirect(String redirectTo, int statusCode,
- Map<String, String> cacheHeaders) {
- if (HttpLog.LOGV) {
- HttpLog.v("RequestHandle.setupRedirect(): redirectCount " +
- mRedirectCount);
- }
-
- // be careful and remove authentication headers, if any
- mHeaders.remove(AUTHORIZATION_HEADER);
- mHeaders.remove(PROXY_AUTHORIZATION_HEADER);
-
- if (++mRedirectCount == MAX_REDIRECT_COUNT) {
- // Way too many redirects -- fail out
- if (HttpLog.LOGV) HttpLog.v(
- "RequestHandle.setupRedirect(): too many redirects " +
- mRequest);
- mRequest.error(EventHandler.ERROR_REDIRECT_LOOP,
- com.android.internal.R.string.httpErrorRedirectLoop);
- return false;
- }
-
- if (mUrl.startsWith("https:") && redirectTo.startsWith("http:")) {
- // implement http://www.w3.org/Protocols/rfc2616/rfc2616-sec15.html#sec15.1.3
- if (HttpLog.LOGV) {
- HttpLog.v("blowing away the referer on an https -> http redirect");
- }
- mHeaders.remove("Referer");
- }
-
- mUrl = redirectTo;
- try {
- mUri = new WebAddress(mUrl);
- } catch (ParseException e) {
- e.printStackTrace();
- }
-
- // update the "Cookie" header based on the redirected url
- mHeaders.remove("Cookie");
- String cookie = CookieManager.getInstance().getCookie(mUri);
- if (cookie != null && cookie.length() > 0) {
- mHeaders.put("Cookie", cookie);
- }
-
- if ((statusCode == 302 || statusCode == 303) && mMethod.equals("POST")) {
- if (HttpLog.LOGV) {
- HttpLog.v("replacing POST with GET on redirect to " + redirectTo);
- }
- mMethod = "GET";
- }
- /* Only repost content on a 307. If 307, reset the body
- provider so we can replay the body */
- if (statusCode == 307) {
- try {
- if (mBodyProvider != null) mBodyProvider.reset();
- } catch (java.io.IOException ex) {
- if (HttpLog.LOGV) {
- HttpLog.v("setupRedirect() failed to reset body provider");
- }
- return false;
- }
-
- } else {
- mHeaders.remove("Content-Type");
- mBodyProvider = null;
- }
-
- // Update the cache headers for this URL
- mHeaders.putAll(cacheHeaders);
-
- createAndQueueNewRequest();
- return true;
- }
-
- /**
- * Create and queue an HTTP authentication-response (basic) request.
- */
- public void setupBasicAuthResponse(boolean isProxy, String username, String password) {
- String response = computeBasicAuthResponse(username, password);
- if (HttpLog.LOGV) {
- HttpLog.v("setupBasicAuthResponse(): response: " + response);
- }
- mHeaders.put(authorizationHeader(isProxy), "Basic " + response);
- setupAuthResponse();
- }
-
- /**
- * Create and queue an HTTP authentication-response (digest) request.
- */
- public void setupDigestAuthResponse(boolean isProxy,
- String username,
- String password,
- String realm,
- String nonce,
- String QOP,
- String algorithm,
- String opaque) {
-
- String response = computeDigestAuthResponse(
- username, password, realm, nonce, QOP, algorithm, opaque);
- if (HttpLog.LOGV) {
- HttpLog.v("setupDigestAuthResponse(): response: " + response);
- }
- mHeaders.put(authorizationHeader(isProxy), "Digest " + response);
- setupAuthResponse();
- }
-
- private void setupAuthResponse() {
- try {
- if (mBodyProvider != null) mBodyProvider.reset();
- } catch (java.io.IOException ex) {
- if (HttpLog.LOGV) {
- HttpLog.v("setupAuthResponse() failed to reset body provider");
- }
- }
- createAndQueueNewRequest();
- }
-
- /**
- * @return HTTP request method (GET, PUT, etc).
- */
- public String getMethod() {
- return mMethod;
- }
-
- /**
- * @return Basic-scheme authentication response: BASE64(username:password).
- */
- public static String computeBasicAuthResponse(String username, String password) {
- Assert.assertNotNull(username);
- Assert.assertNotNull(password);
-
- // encode username:password to base64
- return new String(Base64.encodeBase64((username + ':' + password).getBytes()));
- }
-
- public void waitUntilComplete() {
- mRequest.waitUntilComplete();
- }
-
- public void processRequest() {
- if (mConnection != null) {
- mConnection.processRequests(mRequest);
- }
- }
-
- /**
- * @return Digest-scheme authentication response.
- */
- private String computeDigestAuthResponse(String username,
- String password,
- String realm,
- String nonce,
- String QOP,
- String algorithm,
- String opaque) {
-
- Assert.assertNotNull(username);
- Assert.assertNotNull(password);
- Assert.assertNotNull(realm);
-
- String A1 = username + ":" + realm + ":" + password;
- String A2 = mMethod + ":" + mUrl;
-
- // because we do not preemptively send authorization headers, nc is always 1
- String nc = "00000001";
- String cnonce = computeCnonce();
- String digest = computeDigest(A1, A2, nonce, QOP, nc, cnonce);
-
- String response = "";
- response += "username=" + doubleQuote(username) + ", ";
- response += "realm=" + doubleQuote(realm) + ", ";
- response += "nonce=" + doubleQuote(nonce) + ", ";
- response += "uri=" + doubleQuote(mUrl) + ", ";
- response += "response=" + doubleQuote(digest) ;
-
- if (opaque != null) {
- response += ", opaque=" + doubleQuote(opaque);
- }
-
- if (algorithm != null) {
- response += ", algorithm=" + algorithm;
- }
-
- if (QOP != null) {
- response += ", qop=" + QOP + ", nc=" + nc + ", cnonce=" + doubleQuote(cnonce);
- }
-
- return response;
- }
-
- /**
- * @return The right authorization header (dependeing on whether it is a proxy or not).
- */
- public static String authorizationHeader(boolean isProxy) {
- if (!isProxy) {
- return AUTHORIZATION_HEADER;
- } else {
- return PROXY_AUTHORIZATION_HEADER;
- }
- }
-
- /**
- * @return Double-quoted MD5 digest.
- */
- private String computeDigest(
- String A1, String A2, String nonce, String QOP, String nc, String cnonce) {
- if (HttpLog.LOGV) {
- HttpLog.v("computeDigest(): QOP: " + QOP);
- }
-
- if (QOP == null) {
- return KD(H(A1), nonce + ":" + H(A2));
- } else {
- if (QOP.equalsIgnoreCase("auth")) {
- return KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" + QOP + ":" + H(A2));
- }
- }
-
- return null;
- }
-
- /**
- * @return MD5 hash of concat(secret, ":", data).
- */
- private String KD(String secret, String data) {
- return H(secret + ":" + data);
- }
-
- /**
- * @return MD5 hash of param.
- */
- private String H(String param) {
- if (param != null) {
- try {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
-
- byte[] d = md5.digest(param.getBytes());
- if (d != null) {
- return bufferToHex(d);
- }
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- }
-
- return null;
- }
-
- /**
- * @return HEX buffer representation.
- */
- private String bufferToHex(byte[] buffer) {
- final char hexChars[] =
- { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
-
- if (buffer != null) {
- int length = buffer.length;
- if (length > 0) {
- StringBuilder hex = new StringBuilder(2 * length);
-
- for (int i = 0; i < length; ++i) {
- byte l = (byte) (buffer[i] & 0x0F);
- byte h = (byte)((buffer[i] & 0xF0) >> 4);
-
- hex.append(hexChars[h]);
- hex.append(hexChars[l]);
- }
-
- return hex.toString();
- } else {
- return "";
- }
- }
-
- return null;
- }
-
- /**
- * Computes a random cnonce value based on the current time.
- */
- private String computeCnonce() {
- Random rand = new Random();
- int nextInt = rand.nextInt();
- nextInt = (nextInt == Integer.MIN_VALUE) ?
- Integer.MAX_VALUE : Math.abs(nextInt);
- return Integer.toString(nextInt, 16);
- }
-
- /**
- * "Double-quotes" the argument.
- */
- private String doubleQuote(String param) {
- if (param != null) {
- return "\"" + param + "\"";
- }
-
- return null;
- }
-
- /**
- * Creates and queues new request.
- */
- private void createAndQueueNewRequest() {
- // mConnection is non-null if and only if the requests are synchronous.
- if (mConnection != null) {
- RequestHandle newHandle = mRequestQueue.queueSynchronousRequest(
- mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
- mBodyProvider, mBodyLength);
- mRequest = newHandle.mRequest;
- mConnection = newHandle.mConnection;
- newHandle.processRequest();
- return;
- }
- mRequest = mRequestQueue.queueRequest(
- mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
- mBodyProvider,
- mBodyLength).mRequest;
- }
-}
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
deleted file mode 100644
index 7d2da1b..0000000
--- a/core/java/android/net/http/RequestQueue.java
+++ /dev/null
@@ -1,542 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * High level HTTP Interface
- * Queues requests as necessary
- */
-
-package android.net.http;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.Proxy;
-import android.net.WebAddress;
-import android.util.Log;
-
-import java.io.InputStream;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.ListIterator;
-import java.util.Map;
-
-import org.apache.http.HttpHost;
-
-/**
- * {@hide}
- */
-public class RequestQueue implements RequestFeeder {
-
-
- /**
- * Requests, indexed by HttpHost (scheme, host, port)
- */
- private final LinkedHashMap<HttpHost, LinkedList<Request>> mPending;
- private final Context mContext;
- private final ActivePool mActivePool;
- private final ConnectivityManager mConnectivityManager;
-
- private HttpHost mProxyHost = null;
- private BroadcastReceiver mProxyChangeReceiver;
-
- /* default simultaneous connection count */
- private static final int CONNECTION_COUNT = 4;
-
- /**
- * This class maintains active connection threads
- */
- class ActivePool implements ConnectionManager {
- /** Threads used to process requests */
- ConnectionThread[] mThreads;
-
- IdleCache mIdleCache;
-
- private int mTotalRequest;
- private int mTotalConnection;
- private int mConnectionCount;
-
- ActivePool(int connectionCount) {
- mIdleCache = new IdleCache();
- mConnectionCount = connectionCount;
- mThreads = new ConnectionThread[mConnectionCount];
-
- for (int i = 0; i < mConnectionCount; i++) {
- mThreads[i] = new ConnectionThread(
- mContext, i, this, RequestQueue.this);
- }
- }
-
- void startup() {
- for (int i = 0; i < mConnectionCount; i++) {
- mThreads[i].start();
- }
- }
-
- void shutdown() {
- for (int i = 0; i < mConnectionCount; i++) {
- mThreads[i].requestStop();
- }
- }
-
- void startConnectionThread() {
- synchronized (RequestQueue.this) {
- RequestQueue.this.notify();
- }
- }
-
- public void startTiming() {
- for (int i = 0; i < mConnectionCount; i++) {
- ConnectionThread rt = mThreads[i];
- rt.mCurrentThreadTime = -1;
- rt.mTotalThreadTime = 0;
- }
- mTotalRequest = 0;
- mTotalConnection = 0;
- }
-
- public void stopTiming() {
- int totalTime = 0;
- for (int i = 0; i < mConnectionCount; i++) {
- ConnectionThread rt = mThreads[i];
- if (rt.mCurrentThreadTime != -1) {
- totalTime += rt.mTotalThreadTime;
- }
- rt.mCurrentThreadTime = 0;
- }
- Log.d("Http", "Http thread used " + totalTime + " ms " + " for "
- + mTotalRequest + " requests and " + mTotalConnection
- + " new connections");
- }
-
- void logState() {
- StringBuilder dump = new StringBuilder();
- for (int i = 0; i < mConnectionCount; i++) {
- dump.append(mThreads[i] + "\n");
- }
- HttpLog.v(dump.toString());
- }
-
-
- public HttpHost getProxyHost() {
- return mProxyHost;
- }
-
- /**
- * Turns off persistence on all live connections
- */
- void disablePersistence() {
- for (int i = 0; i < mConnectionCount; i++) {
- Connection connection = mThreads[i].mConnection;
- if (connection != null) connection.setCanPersist(false);
- }
- mIdleCache.clear();
- }
-
- /* Linear lookup -- okay for small thread counts. Might use
- private HashMap<HttpHost, LinkedList<ConnectionThread>> mActiveMap;
- if this turns out to be a hotspot */
- ConnectionThread getThread(HttpHost host) {
- synchronized(RequestQueue.this) {
- for (int i = 0; i < mThreads.length; i++) {
- ConnectionThread ct = mThreads[i];
- Connection connection = ct.mConnection;
- if (connection != null && connection.mHost.equals(host)) {
- return ct;
- }
- }
- }
- return null;
- }
-
- public Connection getConnection(Context context, HttpHost host) {
- host = RequestQueue.this.determineHost(host);
- Connection con = mIdleCache.getConnection(host);
- if (con == null) {
- mTotalConnection++;
- con = Connection.getConnection(mContext, host, mProxyHost,
- RequestQueue.this);
- }
- return con;
- }
- public boolean recycleConnection(Connection connection) {
- return mIdleCache.cacheConnection(connection.getHost(), connection);
- }
-
- }
-
- /**
- * A RequestQueue class instance maintains a set of queued
- * requests. It orders them, makes the requests against HTTP
- * servers, and makes callbacks to supplied eventHandlers as data
- * is read. It supports request prioritization, connection reuse
- * and pipelining.
- *
- * @param context application context
- */
- public RequestQueue(Context context) {
- this(context, CONNECTION_COUNT);
- }
-
- /**
- * A RequestQueue class instance maintains a set of queued
- * requests. It orders them, makes the requests against HTTP
- * servers, and makes callbacks to supplied eventHandlers as data
- * is read. It supports request prioritization, connection reuse
- * and pipelining.
- *
- * @param context application context
- * @param connectionCount The number of simultaneous connections
- */
- public RequestQueue(Context context, int connectionCount) {
- mContext = context;
-
- mPending = new LinkedHashMap<HttpHost, LinkedList<Request>>(32);
-
- mActivePool = new ActivePool(connectionCount);
- mActivePool.startup();
-
- mConnectivityManager = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
- }
-
- /**
- * Enables data state and proxy tracking
- */
- public synchronized void enablePlatformNotifications() {
- if (HttpLog.LOGV) HttpLog.v("RequestQueue.enablePlatformNotifications() network");
-
- if (mProxyChangeReceiver == null) {
- mProxyChangeReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context ctx, Intent intent) {
- setProxyConfig();
- }
- };
- mContext.registerReceiver(mProxyChangeReceiver,
- new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
- }
- // we need to resample the current proxy setup
- setProxyConfig();
- }
-
- /**
- * If platform notifications have been enabled, call this method
- * to disable before destroying RequestQueue
- */
- public synchronized void disablePlatformNotifications() {
- if (HttpLog.LOGV) HttpLog.v("RequestQueue.disablePlatformNotifications() network");
-
- if (mProxyChangeReceiver != null) {
- mContext.unregisterReceiver(mProxyChangeReceiver);
- mProxyChangeReceiver = null;
- }
- }
-
- /**
- * Because our IntentReceiver can run within a different thread,
- * synchronize setting the proxy
- */
- private synchronized void setProxyConfig() {
- NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
- if (info != null && info.getType() == ConnectivityManager.TYPE_WIFI) {
- mProxyHost = null;
- } else {
- String host = Proxy.getHost(mContext);
- if (HttpLog.LOGV) HttpLog.v("RequestQueue.setProxyConfig " + host);
- if (host == null) {
- mProxyHost = null;
- } else {
- mActivePool.disablePersistence();
- mProxyHost = new HttpHost(host, Proxy.getPort(mContext), "http");
- }
- }
- }
-
- /**
- * used by webkit
- * @return proxy host if set, null otherwise
- */
- public HttpHost getProxyHost() {
- return mProxyHost;
- }
-
- /**
- * Queues an HTTP request
- * @param url The url to load.
- * @param method "GET" or "POST."
- * @param headers A hashmap of http headers.
- * @param eventHandler The event handler for handling returned
- * data. Callbacks will be made on the supplied instance.
- * @param bodyProvider InputStream providing HTTP body, null if none
- * @param bodyLength length of body, must be 0 if bodyProvider is null
- */
- public RequestHandle queueRequest(
- String url, String method,
- Map<String, String> headers, EventHandler eventHandler,
- InputStream bodyProvider, int bodyLength) {
- WebAddress uri = new WebAddress(url);
- return queueRequest(url, uri, method, headers, eventHandler,
- bodyProvider, bodyLength);
- }
-
- /**
- * Queues an HTTP request
- * @param url The url to load.
- * @param uri The uri of the url to load.
- * @param method "GET" or "POST."
- * @param headers A hashmap of http headers.
- * @param eventHandler The event handler for handling returned
- * data. Callbacks will be made on the supplied instance.
- * @param bodyProvider InputStream providing HTTP body, null if none
- * @param bodyLength length of body, must be 0 if bodyProvider is null
- */
- public RequestHandle queueRequest(
- String url, WebAddress uri, String method, Map<String, String> headers,
- EventHandler eventHandler,
- InputStream bodyProvider, int bodyLength) {
-
- if (HttpLog.LOGV) HttpLog.v("RequestQueue.queueRequest " + uri);
-
- // Ensure there is an eventHandler set
- if (eventHandler == null) {
- eventHandler = new LoggingEventHandler();
- }
-
- /* Create and queue request */
- Request req;
- HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
-
- // set up request
- req = new Request(method, httpHost, mProxyHost, uri.getPath(), bodyProvider,
- bodyLength, eventHandler, headers);
-
- queueRequest(req, false);
-
- mActivePool.mTotalRequest++;
-
- // dump();
- mActivePool.startConnectionThread();
-
- return new RequestHandle(
- this, url, uri, method, headers, bodyProvider, bodyLength,
- req);
- }
-
- private static class SyncFeeder implements RequestFeeder {
- // This is used in the case where the request fails and needs to be
- // requeued into the RequestFeeder.
- private Request mRequest;
- SyncFeeder() {
- }
- public Request getRequest() {
- Request r = mRequest;
- mRequest = null;
- return r;
- }
- public Request getRequest(HttpHost host) {
- return getRequest();
- }
- public boolean haveRequest(HttpHost host) {
- return mRequest != null;
- }
- public void requeueRequest(Request r) {
- mRequest = r;
- }
- }
-
- public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
- String method, Map<String, String> headers,
- EventHandler eventHandler, InputStream bodyProvider,
- int bodyLength) {
- if (HttpLog.LOGV) {
- HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
- }
-
- HttpHost host = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
-
- Request req = new Request(method, host, mProxyHost, uri.getPath(),
- bodyProvider, bodyLength, eventHandler, headers);
-
- // Open a new connection that uses our special RequestFeeder
- // implementation.
- host = determineHost(host);
- Connection conn = Connection.getConnection(mContext, host, mProxyHost,
- new SyncFeeder());
-
- // TODO: I would like to process the request here but LoadListener
- // needs a RequestHandle to process some messages.
- return new RequestHandle(this, url, uri, method, headers, bodyProvider,
- bodyLength, req, conn);
-
- }
-
- // Chooses between the proxy and the request's host.
- private HttpHost determineHost(HttpHost host) {
- // There used to be a comment in ConnectionThread about t-mob's proxy
- // being really bad about https. But, HttpsConnection actually looks
- // for a proxy and connects through it anyway. I think that this check
- // is still valid because if a site is https, we will use
- // HttpsConnection rather than HttpConnection if the proxy address is
- // not secure.
- return (mProxyHost == null || "https".equals(host.getSchemeName()))
- ? host : mProxyHost;
- }
-
- /**
- * @return true iff there are any non-active requests pending
- */
- synchronized boolean requestsPending() {
- return !mPending.isEmpty();
- }
-
-
- /**
- * debug tool: prints request queue to log
- */
- synchronized void dump() {
- HttpLog.v("dump()");
- StringBuilder dump = new StringBuilder();
- int count = 0;
- Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter;
-
- // mActivePool.log(dump);
-
- if (!mPending.isEmpty()) {
- iter = mPending.entrySet().iterator();
- while (iter.hasNext()) {
- Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
- String hostName = entry.getKey().getHostName();
- StringBuilder line = new StringBuilder("p" + count++ + " " + hostName + " ");
-
- LinkedList<Request> reqList = entry.getValue();
- ListIterator reqIter = reqList.listIterator(0);
- while (iter.hasNext()) {
- Request request = (Request)iter.next();
- line.append(request + " ");
- }
- dump.append(line);
- dump.append("\n");
- }
- }
- HttpLog.v(dump.toString());
- }
-
- /*
- * RequestFeeder implementation
- */
- public synchronized Request getRequest() {
- Request ret = null;
-
- if (!mPending.isEmpty()) {
- ret = removeFirst(mPending);
- }
- if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest() => " + ret);
- return ret;
- }
-
- /**
- * @return a request for given host if possible
- */
- public synchronized Request getRequest(HttpHost host) {
- Request ret = null;
-
- if (mPending.containsKey(host)) {
- LinkedList<Request> reqList = mPending.get(host);
- ret = reqList.removeFirst();
- if (reqList.isEmpty()) {
- mPending.remove(host);
- }
- }
- if (HttpLog.LOGV) HttpLog.v("RequestQueue.getRequest(" + host + ") => " + ret);
- return ret;
- }
-
- /**
- * @return true if a request for this host is available
- */
- public synchronized boolean haveRequest(HttpHost host) {
- return mPending.containsKey(host);
- }
-
- /**
- * Put request back on head of queue
- */
- public void requeueRequest(Request request) {
- queueRequest(request, true);
- }
-
- /**
- * This must be called to cleanly shutdown RequestQueue
- */
- public void shutdown() {
- mActivePool.shutdown();
- }
-
- protected synchronized void queueRequest(Request request, boolean head) {
- HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
- LinkedList<Request> reqList;
- if (mPending.containsKey(host)) {
- reqList = mPending.get(host);
- } else {
- reqList = new LinkedList<Request>();
- mPending.put(host, reqList);
- }
- if (head) {
- reqList.addFirst(request);
- } else {
- reqList.add(request);
- }
- }
-
-
- public void startTiming() {
- mActivePool.startTiming();
- }
-
- public void stopTiming() {
- mActivePool.stopTiming();
- }
-
- /* helper */
- private Request removeFirst(LinkedHashMap<HttpHost, LinkedList<Request>> requestQueue) {
- Request ret = null;
- Iterator<Map.Entry<HttpHost, LinkedList<Request>>> iter = requestQueue.entrySet().iterator();
- if (iter.hasNext()) {
- Map.Entry<HttpHost, LinkedList<Request>> entry = iter.next();
- LinkedList<Request> reqList = entry.getValue();
- ret = reqList.removeFirst();
- if (reqList.isEmpty()) {
- requestQueue.remove(entry.getKey());
- }
- }
- return ret;
- }
-
- /**
- * This interface is exposed to each connection
- */
- interface ConnectionManager {
- HttpHost getProxyHost();
- Connection getConnection(Context context, HttpHost host);
- boolean recycleConnection(Connection connection);
- }
-}
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index bb36c20..eb4ceda 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -22,8 +22,6 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
-import javax.net.ssl.SSLParameters;
-import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509TrustManager;
/**
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 7785f2b..47e8e69 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -301,7 +301,7 @@ public abstract class AsyncTask<Params, Progress, Result> {
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
- throw new RuntimeException("An error occured while executing doInBackground()",
+ throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index 1b02141..c373308 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -16,12 +16,12 @@
package android.os;
+import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.Log;
import java.io.Serializable;
import java.util.ArrayList;
-import java.util.Map;
import java.util.Set;
/**
@@ -63,7 +63,7 @@ public class BaseBundle {
* inside of the Bundle.
* @param capacity Initial size of the ArrayMap.
*/
- BaseBundle(ClassLoader loader, int capacity) {
+ BaseBundle(@Nullable ClassLoader loader, int capacity) {
mMap = capacity > 0 ?
new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
mClassLoader = loader == null ? getClass().getClassLoader() : loader;
@@ -276,6 +276,7 @@ public class BaseBundle {
* @param key a String key
* @return an Object, or null
*/
+ @Nullable
public Object get(String key) {
unparcel();
return mMap.get(key);
@@ -307,7 +308,7 @@ public class BaseBundle {
*
* @param map a Map
*/
- void putAll(Map map) {
+ void putAll(ArrayMap map) {
unparcel();
mMap.putAll(map);
}
@@ -327,9 +328,9 @@ public class BaseBundle {
* 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
+ * @param value a boolean
*/
- public void putBoolean(String key, boolean value) {
+ public void putBoolean(@Nullable String key, boolean value) {
unparcel();
mMap.put(key, value);
}
@@ -341,7 +342,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a byte
*/
- void putByte(String key, byte value) {
+ void putByte(@Nullable String key, byte value) {
unparcel();
mMap.put(key, value);
}
@@ -351,9 +352,9 @@ public class BaseBundle {
* any existing value for the given key.
*
* @param key a String, or null
- * @param value a char, or null
+ * @param value a char
*/
- void putChar(String key, char value) {
+ void putChar(@Nullable String key, char value) {
unparcel();
mMap.put(key, value);
}
@@ -365,7 +366,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a short
*/
- void putShort(String key, short value) {
+ void putShort(@Nullable String key, short value) {
unparcel();
mMap.put(key, value);
}
@@ -375,9 +376,9 @@ public class BaseBundle {
* any existing value for the given key.
*
* @param key a String, or null
- * @param value an int, or null
+ * @param value an int
*/
- public void putInt(String key, int value) {
+ public void putInt(@Nullable String key, int value) {
unparcel();
mMap.put(key, value);
}
@@ -389,7 +390,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a long
*/
- public void putLong(String key, long value) {
+ public void putLong(@Nullable String key, long value) {
unparcel();
mMap.put(key, value);
}
@@ -401,7 +402,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a float
*/
- void putFloat(String key, float value) {
+ void putFloat(@Nullable String key, float value) {
unparcel();
mMap.put(key, value);
}
@@ -413,7 +414,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a double
*/
- public void putDouble(String key, double value) {
+ public void putDouble(@Nullable String key, double value) {
unparcel();
mMap.put(key, value);
}
@@ -425,7 +426,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a String, or null
*/
- public void putString(String key, String value) {
+ public void putString(@Nullable String key, @Nullable String value) {
unparcel();
mMap.put(key, value);
}
@@ -437,7 +438,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a CharSequence, or null
*/
- void putCharSequence(String key, CharSequence value) {
+ void putCharSequence(@Nullable String key, @Nullable CharSequence value) {
unparcel();
mMap.put(key, value);
}
@@ -449,7 +450,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value an ArrayList<Integer> object, or null
*/
- void putIntegerArrayList(String key, ArrayList<Integer> value) {
+ void putIntegerArrayList(@Nullable String key, @Nullable ArrayList<Integer> value) {
unparcel();
mMap.put(key, value);
}
@@ -461,7 +462,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value an ArrayList<String> object, or null
*/
- void putStringArrayList(String key, ArrayList<String> value) {
+ void putStringArrayList(@Nullable String key, @Nullable ArrayList<String> value) {
unparcel();
mMap.put(key, value);
}
@@ -473,7 +474,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value an ArrayList<CharSequence> object, or null
*/
- void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
+ void putCharSequenceArrayList(@Nullable String key, @Nullable ArrayList<CharSequence> value) {
unparcel();
mMap.put(key, value);
}
@@ -485,7 +486,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a Serializable object, or null
*/
- void putSerializable(String key, Serializable value) {
+ void putSerializable(@Nullable String key, @Nullable Serializable value) {
unparcel();
mMap.put(key, value);
}
@@ -497,7 +498,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a boolean array object, or null
*/
- public void putBooleanArray(String key, boolean[] value) {
+ public void putBooleanArray(@Nullable String key, @Nullable boolean[] value) {
unparcel();
mMap.put(key, value);
}
@@ -509,7 +510,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a byte array object, or null
*/
- void putByteArray(String key, byte[] value) {
+ void putByteArray(@Nullable String key, @Nullable byte[] value) {
unparcel();
mMap.put(key, value);
}
@@ -521,7 +522,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a short array object, or null
*/
- void putShortArray(String key, short[] value) {
+ void putShortArray(@Nullable String key, @Nullable short[] value) {
unparcel();
mMap.put(key, value);
}
@@ -533,7 +534,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a char array object, or null
*/
- void putCharArray(String key, char[] value) {
+ void putCharArray(@Nullable String key, @Nullable char[] value) {
unparcel();
mMap.put(key, value);
}
@@ -545,7 +546,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value an int array object, or null
*/
- public void putIntArray(String key, int[] value) {
+ public void putIntArray(@Nullable String key, @Nullable int[] value) {
unparcel();
mMap.put(key, value);
}
@@ -557,7 +558,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a long array object, or null
*/
- public void putLongArray(String key, long[] value) {
+ public void putLongArray(@Nullable String key, @Nullable long[] value) {
unparcel();
mMap.put(key, value);
}
@@ -569,7 +570,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a float array object, or null
*/
- void putFloatArray(String key, float[] value) {
+ void putFloatArray(@Nullable String key, @Nullable float[] value) {
unparcel();
mMap.put(key, value);
}
@@ -581,7 +582,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a double array object, or null
*/
- public void putDoubleArray(String key, double[] value) {
+ public void putDoubleArray(@Nullable String key, @Nullable double[] value) {
unparcel();
mMap.put(key, value);
}
@@ -593,7 +594,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a String array object, or null
*/
- public void putStringArray(String key, String[] value) {
+ public void putStringArray(@Nullable String key, @Nullable String[] value) {
unparcel();
mMap.put(key, value);
}
@@ -605,7 +606,7 @@ public class BaseBundle {
* @param key a String, or null
* @param value a CharSequence array object, or null
*/
- void putCharSequenceArray(String key, CharSequence[] value) {
+ void putCharSequenceArray(@Nullable String key, @Nullable CharSequence[] value) {
unparcel();
mMap.put(key, value);
}
@@ -914,7 +915,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a String value, or null
*/
- public String getString(String key) {
+ @Nullable
+ public String getString(@Nullable String key) {
unparcel();
final Object o = mMap.get(key);
try {
@@ -936,7 +938,7 @@ public class BaseBundle {
* @return the String value associated with the given key, or defaultValue
* if no valid String object is currently mapped to that key.
*/
- public String getString(String key, String defaultValue) {
+ public String getString(@Nullable String key, String defaultValue) {
final String s = getString(key);
return (s == null) ? defaultValue : s;
}
@@ -949,7 +951,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a CharSequence value, or null
*/
- CharSequence getCharSequence(String key) {
+ @Nullable
+ CharSequence getCharSequence(@Nullable String key) {
unparcel();
final Object o = mMap.get(key);
try {
@@ -971,7 +974,7 @@ public class BaseBundle {
* @return the CharSequence value associated with the given key, or defaultValue
* if no valid CharSequence object is currently mapped to that key.
*/
- CharSequence getCharSequence(String key, CharSequence defaultValue) {
+ CharSequence getCharSequence(@Nullable String key, CharSequence defaultValue) {
final CharSequence cs = getCharSequence(key);
return (cs == null) ? defaultValue : cs;
}
@@ -984,7 +987,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a Serializable value, or null
*/
- Serializable getSerializable(String key) {
+ @Nullable
+ Serializable getSerializable(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1006,7 +1010,8 @@ public class BaseBundle {
* @param key a String, or null
* @return an ArrayList<String> value, or null
*/
- ArrayList<Integer> getIntegerArrayList(String key) {
+ @Nullable
+ ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1028,7 +1033,8 @@ public class BaseBundle {
* @param key a String, or null
* @return an ArrayList<String> value, or null
*/
- ArrayList<String> getStringArrayList(String key) {
+ @Nullable
+ ArrayList<String> getStringArrayList(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1050,7 +1056,8 @@ public class BaseBundle {
* @param key a String, or null
* @return an ArrayList<CharSequence> value, or null
*/
- ArrayList<CharSequence> getCharSequenceArrayList(String key) {
+ @Nullable
+ ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1072,7 +1079,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a boolean[] value, or null
*/
- public boolean[] getBooleanArray(String key) {
+ @Nullable
+ public boolean[] getBooleanArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1094,7 +1102,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a byte[] value, or null
*/
- byte[] getByteArray(String key) {
+ @Nullable
+ byte[] getByteArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1116,7 +1125,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a short[] value, or null
*/
- short[] getShortArray(String key) {
+ @Nullable
+ short[] getShortArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1138,7 +1148,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a char[] value, or null
*/
- char[] getCharArray(String key) {
+ @Nullable
+ char[] getCharArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1160,7 +1171,8 @@ public class BaseBundle {
* @param key a String, or null
* @return an int[] value, or null
*/
- public int[] getIntArray(String key) {
+ @Nullable
+ public int[] getIntArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1182,7 +1194,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a long[] value, or null
*/
- public long[] getLongArray(String key) {
+ @Nullable
+ public long[] getLongArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1204,7 +1217,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a float[] value, or null
*/
- float[] getFloatArray(String key) {
+ @Nullable
+ float[] getFloatArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1226,7 +1240,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a double[] value, or null
*/
- public double[] getDoubleArray(String key) {
+ @Nullable
+ public double[] getDoubleArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1248,7 +1263,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a String[] value, or null
*/
- public String[] getStringArray(String key) {
+ @Nullable
+ public String[] getStringArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1270,7 +1286,8 @@ public class BaseBundle {
* @param key a String, or null
* @return a CharSequence[] value, or null
*/
- CharSequence[] getCharSequenceArray(String key) {
+ @Nullable
+ CharSequence[] getCharSequenceArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 537e993..bd5a392 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -162,7 +162,15 @@ public class BatteryManager {
*/
public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5;
- private IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+ private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+
+ /**
+ * @removed Was previously made visible by accident.
+ */
+ public BatteryManager() {
+ mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(
+ ServiceManager.getService("batteryproperties"));
+ }
/**
* Query a battery property from the batteryproperties service.
@@ -174,12 +182,7 @@ public class BatteryManager {
long ret;
if (mBatteryPropertiesRegistrar == null) {
- IBinder b = ServiceManager.getService("batteryproperties");
- mBatteryPropertiesRegistrar =
- IBatteryPropertiesRegistrar.Stub.asInterface(b);
-
- if (mBatteryPropertiesRegistrar == null)
- return Long.MIN_VALUE;
+ return Long.MIN_VALUE;
}
try {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index cd45cfb..d96a0e9 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -30,6 +30,7 @@ import android.content.pm.ApplicationInfo;
import android.telephony.SignalStrength;
import android.text.format.DateFormat;
import android.util.Printer;
+import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
@@ -85,10 +86,10 @@ public abstract class BatteryStats implements Parcelable {
*/
public static final int WIFI_SCAN = 6;
- /**
- * A constant indicating a wifi multicast timer
- */
- public static final int WIFI_MULTICAST_ENABLED = 7;
+ /**
+ * A constant indicating a wifi multicast timer
+ */
+ public static final int WIFI_MULTICAST_ENABLED = 7;
/**
* A constant indicating a video turn on timer
@@ -352,7 +353,9 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getWifiRunningTime(long elapsedRealtimeUs, int which);
public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
+ public abstract int getWifiScanCount(int which);
public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
+ public abstract int getWifiBatchedScanCount(int csphBin, int which);
public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
public abstract long getAudioTurnedOnTime(long elapsedRealtimeUs, int which);
public abstract long getVideoTurnedOnTime(long elapsedRealtimeUs, int which);
@@ -438,14 +441,14 @@ public abstract class BatteryStats implements Parcelable {
public abstract boolean isActive();
/**
- * Returns the total time (in 1/100 sec) spent executing in user code.
+ * Returns the total time (in milliseconds) spent executing in user code.
*
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
public abstract long getUserTime(int which);
/**
- * Returns the total time (in 1/100 sec) spent executing in system code.
+ * Returns the total time (in milliseconds) spent executing in system code.
*
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
*/
@@ -473,14 +476,14 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getNumAnrs(int which);
/**
- * Returns the cpu time spent in microseconds while the process was in the foreground.
+ * Returns the cpu time (milliseconds) spent 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
*/
public abstract long getForegroundTime(int which);
/**
- * Returns the approximate cpu time spent in microseconds, at a certain CPU speed.
+ * Returns the approximate cpu time (in milliseconds) spent at a certain CPU speed.
* @param speedStep the index of the CPU speed. This is not the actual speed of the
* CPU.
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
@@ -542,6 +545,297 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ public static final class LevelStepTracker {
+ public long mLastStepTime = -1;
+ public int mNumStepDurations;
+ public final long[] mStepDurations;
+
+ public LevelStepTracker(int maxLevelSteps) {
+ mStepDurations = new long[maxLevelSteps];
+ }
+
+ public LevelStepTracker(int numSteps, long[] steps) {
+ mNumStepDurations = numSteps;
+ mStepDurations = new long[numSteps];
+ System.arraycopy(steps, 0, mStepDurations, 0, numSteps);
+ }
+
+ public long getDurationAt(int index) {
+ return mStepDurations[index] & STEP_LEVEL_TIME_MASK;
+ }
+
+ public int getLevelAt(int index) {
+ return (int)((mStepDurations[index] & STEP_LEVEL_LEVEL_MASK)
+ >> STEP_LEVEL_LEVEL_SHIFT);
+ }
+
+ public int getInitModeAt(int index) {
+ return (int)((mStepDurations[index] & STEP_LEVEL_INITIAL_MODE_MASK)
+ >> STEP_LEVEL_INITIAL_MODE_SHIFT);
+ }
+
+ public int getModModeAt(int index) {
+ return (int)((mStepDurations[index] & STEP_LEVEL_MODIFIED_MODE_MASK)
+ >> STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ }
+
+ private void appendHex(long val, int topOffset, StringBuilder out) {
+ boolean hasData = false;
+ while (topOffset >= 0) {
+ int digit = (int)( (val>>topOffset) & 0xf );
+ topOffset -= 4;
+ if (!hasData && digit == 0) {
+ continue;
+ }
+ hasData = true;
+ if (digit >= 0 && digit <= 9) {
+ out.append((char)('0' + digit));
+ } else {
+ out.append((char)('a' + digit - 10));
+ }
+ }
+ }
+
+ public void encodeEntryAt(int index, StringBuilder out) {
+ long item = mStepDurations[index];
+ long duration = item & STEP_LEVEL_TIME_MASK;
+ int level = (int)((item & STEP_LEVEL_LEVEL_MASK)
+ >> STEP_LEVEL_LEVEL_SHIFT);
+ int initMode = (int)((item & STEP_LEVEL_INITIAL_MODE_MASK)
+ >> STEP_LEVEL_INITIAL_MODE_SHIFT);
+ int modMode = (int)((item & STEP_LEVEL_MODIFIED_MODE_MASK)
+ >> STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ switch ((initMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+ case Display.STATE_OFF: out.append('f'); break;
+ case Display.STATE_ON: out.append('o'); break;
+ case Display.STATE_DOZE: out.append('d'); break;
+ case Display.STATE_DOZE_SUSPEND: out.append('z'); break;
+ }
+ if ((initMode&STEP_LEVEL_MODE_POWER_SAVE) != 0) {
+ out.append('p');
+ }
+ switch ((modMode&STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+ case Display.STATE_OFF: out.append('F'); break;
+ case Display.STATE_ON: out.append('O'); break;
+ case Display.STATE_DOZE: out.append('D'); break;
+ case Display.STATE_DOZE_SUSPEND: out.append('Z'); break;
+ }
+ if ((modMode&STEP_LEVEL_MODE_POWER_SAVE) != 0) {
+ out.append('P');
+ }
+ out.append('-');
+ appendHex(level, 4, out);
+ out.append('-');
+ appendHex(duration, STEP_LEVEL_LEVEL_SHIFT-4, out);
+ }
+
+ public void decodeEntryAt(int index, String value) {
+ final int N = value.length();
+ int i = 0;
+ char c;
+ long out = 0;
+ while (i < N && (c=value.charAt(i)) != '-') {
+ i++;
+ switch (c) {
+ case 'f': out |= (((long)Display.STATE_OFF-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
+ break;
+ case 'o': out |= (((long)Display.STATE_ON-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
+ break;
+ case 'd': out |= (((long)Display.STATE_DOZE-1)<<STEP_LEVEL_INITIAL_MODE_SHIFT);
+ break;
+ case 'z': out |= (((long)Display.STATE_DOZE_SUSPEND-1)
+ << STEP_LEVEL_INITIAL_MODE_SHIFT);
+ break;
+ case 'p': out |= (((long)STEP_LEVEL_MODE_POWER_SAVE)
+ << STEP_LEVEL_INITIAL_MODE_SHIFT);
+ break;
+ case 'F': out |= (((long)Display.STATE_OFF-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ case 'O': out |= (((long)Display.STATE_ON-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ case 'D': out |= (((long)Display.STATE_DOZE-1)<<STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ case 'Z': out |= (((long)Display.STATE_DOZE_SUSPEND-1)
+ << STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ case 'P': out |= (((long)STEP_LEVEL_MODE_POWER_SAVE)
+ << STEP_LEVEL_MODIFIED_MODE_SHIFT);
+ break;
+ }
+ }
+ i++;
+ long level = 0;
+ while (i < N && (c=value.charAt(i)) != '-') {
+ i++;
+ level <<= 4;
+ if (c >= '0' && c <= '9') {
+ level += c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ level += c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ level += c - 'A' + 10;
+ }
+ }
+ i++;
+ out |= (level << STEP_LEVEL_LEVEL_SHIFT) & STEP_LEVEL_LEVEL_MASK;
+ long duration = 0;
+ while (i < N && (c=value.charAt(i)) != '-') {
+ i++;
+ duration <<= 4;
+ if (c >= '0' && c <= '9') {
+ duration += c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ duration += c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ duration += c - 'A' + 10;
+ }
+ }
+ mStepDurations[index] = out | (duration & STEP_LEVEL_TIME_MASK);
+ }
+
+ public void init() {
+ mLastStepTime = -1;
+ mNumStepDurations = 0;
+ }
+
+ public void clearTime() {
+ mLastStepTime = -1;
+ }
+
+ public long computeTimePerLevel() {
+ final long[] steps = mStepDurations;
+ final int numSteps = mNumStepDurations;
+
+ // For now we'll do a simple average across all steps.
+ if (numSteps <= 0) {
+ return -1;
+ }
+ long total = 0;
+ for (int i=0; i<numSteps; i++) {
+ total += steps[i] & STEP_LEVEL_TIME_MASK;
+ }
+ return total / numSteps;
+ /*
+ long[] buckets = new long[numSteps];
+ int numBuckets = 0;
+ int numToAverage = 4;
+ int i = 0;
+ while (i < numSteps) {
+ long totalTime = 0;
+ int num = 0;
+ for (int j=0; j<numToAverage && (i+j)<numSteps; j++) {
+ totalTime += steps[i+j] & STEP_LEVEL_TIME_MASK;
+ num++;
+ }
+ buckets[numBuckets] = totalTime / num;
+ numBuckets++;
+ numToAverage *= 2;
+ i += num;
+ }
+ if (numBuckets < 1) {
+ return -1;
+ }
+ long averageTime = buckets[numBuckets-1];
+ for (i=numBuckets-2; i>=0; i--) {
+ averageTime = (averageTime + buckets[i]) / 2;
+ }
+ return averageTime;
+ */
+ }
+
+ public long computeTimeEstimate(long modesOfInterest, long modeValues,
+ int[] outNumOfInterest) {
+ final long[] steps = mStepDurations;
+ final int count = mNumStepDurations;
+ if (count <= 0) {
+ return -1;
+ }
+ 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 -1;
+ }
+
+ if (outNumOfInterest != null) {
+ outNumOfInterest[0] = numOfInterest;
+ }
+
+ // The estimated time is the average time we spend in each level, multipled
+ // by 100 -- the total number of battery levels
+ return (total / numOfInterest) * 100;
+ }
+
+ public void addLevelSteps(int numStepLevels, long modeBits, long elapsedRealtime) {
+ int stepCount = mNumStepDurations;
+ final long lastStepTime = mLastStepTime;
+ if (lastStepTime >= 0 && numStepLevels > 0) {
+ final long[] steps = mStepDurations;
+ long duration = elapsedRealtime - lastStepTime;
+ for (int i=0; i<numStepLevels; i++) {
+ System.arraycopy(steps, 0, steps, 1, steps.length-1);
+ long thisDuration = duration / (numStepLevels-i);
+ duration -= thisDuration;
+ if (thisDuration > STEP_LEVEL_TIME_MASK) {
+ thisDuration = STEP_LEVEL_TIME_MASK;
+ }
+ steps[0] = thisDuration | modeBits;
+ }
+ stepCount += numStepLevels;
+ if (stepCount > steps.length) {
+ stepCount = steps.length;
+ }
+ }
+ mNumStepDurations = stepCount;
+ mLastStepTime = elapsedRealtime;
+ }
+
+ public void readFromParcel(Parcel in) {
+ final int N = in.readInt();
+ mNumStepDurations = N;
+ for (int i=0; i<N; i++) {
+ mStepDurations[i] = in.readLong();
+ }
+ }
+
+ public void writeToParcel(Parcel out) {
+ final int N = mNumStepDurations;
+ out.writeInt(N);
+ for (int i=0; i<N; i++) {
+ out.writeLong(mStepDurations[i]);
+ }
+ }
+ }
+
+ public static final class DailyItem {
+ public long mStartTime;
+ public long mEndTime;
+ public LevelStepTracker mDischargeSteps;
+ public LevelStepTracker mChargeSteps;
+ }
+
+ public abstract DailyItem getDailyItemLocked(int daysAgo);
+
+ public abstract long getCurrentDailyStartTime();
+
+ public abstract long getNextMinDailyDeadline();
+
+ public abstract long getNextMaxDailyDeadline();
+
public final static class HistoryTag {
public String string;
public int uid;
@@ -592,6 +886,86 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ /**
+ * Optional detailed information that can go into a history step. This is typically
+ * generated each time the battery level changes.
+ */
+ public final static class HistoryStepDetails {
+ // Time (in 1/100 second) spent in user space and the kernel since the last step.
+ public int userTime;
+ public int systemTime;
+
+ // Top three apps using CPU in the last step, with times in 1/100 second.
+ public int appCpuUid1;
+ public int appCpuUTime1;
+ public int appCpuSTime1;
+ public int appCpuUid2;
+ public int appCpuUTime2;
+ public int appCpuSTime2;
+ public int appCpuUid3;
+ public int appCpuUTime3;
+ public int appCpuSTime3;
+
+ // Information from /proc/stat
+ public int statUserTime;
+ public int statSystemTime;
+ public int statIOWaitTime;
+ public int statIrqTime;
+ public int statSoftIrqTime;
+ public int statIdlTime;
+
+ public HistoryStepDetails() {
+ clear();
+ }
+
+ public void clear() {
+ userTime = systemTime = 0;
+ appCpuUid1 = appCpuUid2 = appCpuUid3 = -1;
+ appCpuUTime1 = appCpuSTime1 = appCpuUTime2 = appCpuSTime2
+ = appCpuUTime3 = appCpuSTime3 = 0;
+ }
+
+ public void writeToParcel(Parcel out) {
+ out.writeInt(userTime);
+ out.writeInt(systemTime);
+ out.writeInt(appCpuUid1);
+ out.writeInt(appCpuUTime1);
+ out.writeInt(appCpuSTime1);
+ out.writeInt(appCpuUid2);
+ out.writeInt(appCpuUTime2);
+ out.writeInt(appCpuSTime2);
+ out.writeInt(appCpuUid3);
+ out.writeInt(appCpuUTime3);
+ out.writeInt(appCpuSTime3);
+ out.writeInt(statUserTime);
+ out.writeInt(statSystemTime);
+ out.writeInt(statIOWaitTime);
+ out.writeInt(statIrqTime);
+ out.writeInt(statSoftIrqTime);
+ out.writeInt(statIdlTime);
+ }
+
+ public void readFromParcel(Parcel in) {
+ userTime = in.readInt();
+ systemTime = in.readInt();
+ appCpuUid1 = in.readInt();
+ appCpuUTime1 = in.readInt();
+ appCpuSTime1 = in.readInt();
+ appCpuUid2 = in.readInt();
+ appCpuUTime2 = in.readInt();
+ appCpuSTime2 = in.readInt();
+ appCpuUid3 = in.readInt();
+ appCpuUTime3 = in.readInt();
+ appCpuSTime3 = in.readInt();
+ statUserTime = in.readInt();
+ statSystemTime = in.readInt();
+ statIOWaitTime = in.readInt();
+ statIrqTime = in.readInt();
+ statSoftIrqTime = in.readInt();
+ statIdlTime = in.readInt();
+ }
+ }
+
public final static class HistoryItem implements Parcelable {
public HistoryItem next;
@@ -687,6 +1061,9 @@ public abstract class BatteryStats implements Parcelable {
// Kernel wakeup reason at this point.
public HistoryTag wakeReasonTag;
+ // Non-null when there is more detailed information at this step.
+ public HistoryStepDetails stepDetails;
+
public static final int EVENT_FLAG_START = 0x8000;
public static final int EVENT_FLAG_FINISH = 0x4000;
@@ -1481,6 +1858,15 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getNetworkActivityBytes(int type, int which);
public abstract long getNetworkActivityPackets(int type, int which);
+ public static final int CONTROLLER_IDLE_TIME = 0;
+ public static final int CONTROLLER_RX_TIME = 1;
+ public static final int CONTROLLER_TX_TIME = 2;
+ public static final int CONTROLLER_ENERGY = 3;
+ public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_ENERGY + 1;
+
+ public abstract long getBluetoothControllerActivity(int type, int which);
+ public abstract long getWifiControllerActivity(int type, int which);
+
/**
* Return the wall clock time when battery stats data collection started.
*/
@@ -1641,15 +2027,15 @@ public abstract class BatteryStats implements Parcelable {
// Bits in a step duration that are the new battery level we are at.
public static final long STEP_LEVEL_LEVEL_MASK = 0x0000ff0000000000L;
- public static final long STEP_LEVEL_LEVEL_SHIFT = 40;
+ public static final int STEP_LEVEL_LEVEL_SHIFT = 40;
// Bits in a step duration that are the initial mode we were in at that step.
public static final long STEP_LEVEL_INITIAL_MODE_MASK = 0x00ff000000000000L;
- public static final long STEP_LEVEL_INITIAL_MODE_SHIFT = 48;
+ public static final int STEP_LEVEL_INITIAL_MODE_SHIFT = 48;
// Bits in a step duration that indicate which modes changed during that step.
public static final long STEP_LEVEL_MODIFIED_MODE_MASK = 0xff00000000000000L;
- public static final long STEP_LEVEL_MODIFIED_MODE_SHIFT = 56;
+ public static final int STEP_LEVEL_MODIFIED_MODE_SHIFT = 56;
// Step duration mode: the screen is on, off, dozed, etc; value is Display.STATE_* - 1.
public static final int STEP_LEVEL_MODE_SCREEN_STATE = 0x03;
@@ -1657,17 +2043,56 @@ public abstract class BatteryStats implements Parcelable {
// Step duration mode: power save is on.
public static final int STEP_LEVEL_MODE_POWER_SAVE = 0x04;
+ public static final int[] STEP_LEVEL_MODES_OF_INTEREST = new int[] {
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ STEP_LEVEL_MODE_SCREEN_STATE|STEP_LEVEL_MODE_POWER_SAVE,
+ };
+ public static final int[] STEP_LEVEL_MODE_VALUES = new int[] {
+ (Display.STATE_OFF-1),
+ (Display.STATE_OFF-1)|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_ON-1),
+ (Display.STATE_ON-1)|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_DOZE-1),
+ (Display.STATE_DOZE-1)|STEP_LEVEL_MODE_POWER_SAVE,
+ (Display.STATE_DOZE_SUSPEND-1),
+ (Display.STATE_DOZE_SUSPEND-1)|STEP_LEVEL_MODE_POWER_SAVE,
+ };
+ public static final String[] STEP_LEVEL_MODE_LABELS = new String[] {
+ "screen off",
+ "screen off power save",
+ "screen on",
+ "screen on power save",
+ "screen doze",
+ "screen doze power save",
+ "screen doze-suspend",
+ "screen doze-suspend power save",
+ };
+ public static final String[] STEP_LEVEL_MODE_TAGS = new String[] {
+ "off",
+ "off-save",
+ "on",
+ "on-save",
+ "doze",
+ "doze-save",
+ "susp",
+ "susp-save",
+ };
+
/**
- * Return the historical number of discharge steps we currently have.
+ * Return the array of discharge step durations.
*/
- public abstract int getNumDischargeStepDurations();
+ public abstract LevelStepTracker getDischargeLevelStepTracker();
/**
- * Return the array of discharge step durations; the number of valid
- * items in it is returned by {@link #getNumDischargeStepDurations()}.
- * These values are in milliseconds.
+ * Return the array of daily discharge step durations.
*/
- public abstract long[] getDischargeStepDurationsArray();
+ public abstract LevelStepTracker getDailyDischargeLevelStepTracker();
/**
* Compute an approximation for how much time (in microseconds) remains until the battery
@@ -1680,16 +2105,14 @@ public abstract class BatteryStats implements Parcelable {
public abstract long computeChargeTimeRemaining(long curTime);
/**
- * Return the historical number of charge steps we currently have.
+ * Return the array of charge step durations.
*/
- public abstract int getNumChargeStepDurations();
+ public abstract LevelStepTracker getChargeLevelStepTracker();
/**
- * Return the array of charge step durations; the number of valid
- * items in it is returned by {@link #getNumChargeStepDurations()}.
- * These values are in milliseconds.
+ * Return the array of daily charge step durations.
*/
- public abstract long[] getChargeStepDurationsArray();
+ public abstract LevelStepTracker getDailyChargeLevelStepTracker();
public abstract Map<String, ? extends Timer> getWakeupReasonStats();
@@ -1728,13 +2151,6 @@ public abstract class BatteryStats implements Parcelable {
}
}
- public final static void formatTime(StringBuilder sb, long time) {
- long sec = time / 100;
- formatTimeRaw(sb, sec);
- sb.append((time - (sec * 100)) * 10);
- sb.append("ms ");
- }
-
public final static void formatTimeMs(StringBuilder sb, long time) {
long sec = time / 1000;
formatTimeRaw(sb, sec);
@@ -2160,6 +2576,7 @@ public abstract class BatteryStats implements Parcelable {
long wifiPacketsTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+ int wifiScanCount = u.getWifiScanCount(which);
long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
if (mobileBytesRx > 0 || mobileBytesTx > 0 || wifiBytesRx > 0 || wifiBytesTx > 0
@@ -2172,10 +2589,10 @@ public abstract class BatteryStats implements Parcelable {
mobileActiveTime, mobileActiveCount);
}
- if (fullWifiLockOnTime != 0 || wifiScanTime != 0
+ if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
|| uidWifiRunningTime != 0) {
dumpLine(pw, uid, category, WIFI_DATA,
- fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime);
+ fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime, wifiScanCount);
}
if (u.hasUserActivity()) {
@@ -2293,9 +2710,9 @@ public abstract class BatteryStats implements Parcelable {
: processStats.entrySet()) {
Uid.Proc ps = ent.getValue();
- final long userMillis = ps.getUserTime(which) * 10;
- final long systemMillis = ps.getSystemTime(which) * 10;
- final long foregroundMillis = ps.getForegroundTime(which) * 10;
+ final long userMillis = ps.getUserTime(which);
+ final long systemMillis = ps.getSystemTime(which);
+ final long foregroundMillis = ps.getForegroundTime(which);
final int starts = ps.getStarts(which);
final int numCrashes = ps.getNumCrashes(which);
final int numAnrs = ps.getNumAnrs(which);
@@ -2734,6 +3151,35 @@ public abstract class BatteryStats implements Parcelable {
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
+ final long wifiIdleTimeMs = getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which);
+ final long wifiRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which);
+ final long wifiTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which);
+ final long wifiTotalTimeMs = wifiIdleTimeMs + wifiRxTimeMs + wifiTxTimeMs;
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" WiFi Idle time: "); formatTimeMs(sb, wifiIdleTimeMs);
+ sb.append(" (");
+ sb.append(formatRatioLocked(wifiIdleTimeMs, wifiTotalTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" WiFi Rx time: "); formatTimeMs(sb, wifiRxTimeMs);
+ sb.append(" (");
+ sb.append(formatRatioLocked(wifiRxTimeMs, wifiTotalTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" WiFi Tx time: "); formatTimeMs(sb, wifiTxTimeMs);
+ sb.append(" (");
+ sb.append(formatRatioLocked(wifiTxTimeMs, wifiTotalTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
+
sb.setLength(0);
sb.append(prefix);
sb.append(" Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000);
@@ -2761,9 +3207,41 @@ public abstract class BatteryStats implements Parcelable {
sb.append(getPhoneDataConnectionCount(i, which));
sb.append("x");
}
+
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
+ final long bluetoothIdleTimeMs =
+ getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which);
+ final long bluetoothRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which);
+ final long bluetoothTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which);
+ final long bluetoothTotalTimeMs = bluetoothIdleTimeMs + bluetoothRxTimeMs +
+ bluetoothTxTimeMs;
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Bluetooth Idle time: "); formatTimeMs(sb, bluetoothIdleTimeMs);
+ sb.append(" (");
+ sb.append(formatRatioLocked(bluetoothIdleTimeMs, bluetoothTotalTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Bluetooth Rx time: "); formatTimeMs(sb, bluetoothRxTimeMs);
+ sb.append(" (");
+ sb.append(formatRatioLocked(bluetoothRxTimeMs, bluetoothTotalTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Bluetooth Tx time: "); formatTimeMs(sb, bluetoothTxTimeMs);
+ sb.append(" (");
+ sb.append(formatRatioLocked(bluetoothTxTimeMs, bluetoothTotalTimeMs));
+ sb.append(")");
+ pw.println(sb.toString());
+
pw.println();
if (which == STATS_SINCE_UNPLUGGED) {
@@ -3008,6 +3486,7 @@ public abstract class BatteryStats implements Parcelable {
long wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+ int wifiScanCount = u.getWifiScanCount(which);
long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
if (mobileRxBytes > 0 || mobileTxBytes > 0
@@ -3043,7 +3522,7 @@ public abstract class BatteryStats implements Parcelable {
pw.print(" received, "); pw.print(wifiTxPackets); pw.println(" sent)");
}
- if (fullWifiLockOnTime != 0 || wifiScanTime != 0
+ if (fullWifiLockOnTime != 0 || wifiScanTime != 0 || wifiScanCount != 0
|| uidWifiRunningTime != 0) {
sb.setLength(0);
sb.append(prefix); sb.append(" Wifi Running: ");
@@ -3057,7 +3536,9 @@ public abstract class BatteryStats implements Parcelable {
sb.append(prefix); sb.append(" Wifi Scan: ");
formatTimeMs(sb, wifiScanTime / 1000);
sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
- whichBatteryRealtime)); sb.append(")");
+ whichBatteryRealtime)); sb.append(") ");
+ sb.append(wifiScanCount);
+ sb.append("x");
pw.println(sb.toString());
}
@@ -3316,9 +3797,9 @@ public abstract class BatteryStats implements Parcelable {
sb.append(prefix); sb.append(" Proc ");
sb.append(ent.getKey()); sb.append(":\n");
sb.append(prefix); sb.append(" CPU: ");
- formatTime(sb, userTime); sb.append("usr + ");
- formatTime(sb, systemTime); sb.append("krn ; ");
- formatTime(sb, foregroundTime); sb.append("fg");
+ formatTimeMs(sb, userTime); sb.append("usr + ");
+ formatTimeMs(sb, systemTime); sb.append("krn ; ");
+ formatTimeMs(sb, foregroundTime); sb.append("fg");
if (starts != 0 || numCrashes != 0 || numAnrs != 0) {
sb.append("\n"); sb.append(prefix); sb.append(" ");
boolean hasOne = false;
@@ -3692,10 +4173,115 @@ public abstract class BatteryStats implements Parcelable {
}
}
pw.println();
+ if (rec.stepDetails != null) {
+ if (!checkin) {
+ pw.print(" Details: cpu=");
+ pw.print(rec.stepDetails.userTime);
+ pw.print("u+");
+ pw.print(rec.stepDetails.systemTime);
+ pw.print("s");
+ if (rec.stepDetails.appCpuUid1 >= 0) {
+ pw.print(" (");
+ printStepCpuUidDetails(pw, rec.stepDetails.appCpuUid1,
+ rec.stepDetails.appCpuUTime1, rec.stepDetails.appCpuSTime1);
+ if (rec.stepDetails.appCpuUid2 >= 0) {
+ pw.print(", ");
+ printStepCpuUidDetails(pw, rec.stepDetails.appCpuUid2,
+ rec.stepDetails.appCpuUTime2, rec.stepDetails.appCpuSTime2);
+ }
+ if (rec.stepDetails.appCpuUid3 >= 0) {
+ pw.print(", ");
+ printStepCpuUidDetails(pw, rec.stepDetails.appCpuUid3,
+ rec.stepDetails.appCpuUTime3, rec.stepDetails.appCpuSTime3);
+ }
+ pw.print(')');
+ }
+ pw.println();
+ pw.print(" /proc/stat=");
+ pw.print(rec.stepDetails.statUserTime);
+ pw.print(" usr, ");
+ pw.print(rec.stepDetails.statSystemTime);
+ pw.print(" sys, ");
+ pw.print(rec.stepDetails.statIOWaitTime);
+ pw.print(" io, ");
+ pw.print(rec.stepDetails.statIrqTime);
+ pw.print(" irq, ");
+ pw.print(rec.stepDetails.statSoftIrqTime);
+ pw.print(" sirq, ");
+ pw.print(rec.stepDetails.statIdlTime);
+ pw.print(" idle");
+ int totalRun = rec.stepDetails.statUserTime + rec.stepDetails.statSystemTime
+ + rec.stepDetails.statIOWaitTime + rec.stepDetails.statIrqTime
+ + rec.stepDetails.statSoftIrqTime;
+ int total = totalRun + rec.stepDetails.statIdlTime;
+ if (total > 0) {
+ pw.print(" (");
+ float perc = ((float)totalRun) / ((float)total) * 100;
+ pw.print(String.format("%.1f%%", perc));
+ pw.print(" of ");
+ StringBuilder sb = new StringBuilder(64);
+ formatTimeMsNoSpace(sb, total*10);
+ pw.print(sb);
+ pw.print(")");
+ }
+ pw.println();
+ } else {
+ pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+ pw.print(HISTORY_DATA); pw.print(",0,Dcpu=");
+ pw.print(rec.stepDetails.userTime);
+ pw.print(":");
+ pw.print(rec.stepDetails.systemTime);
+ if (rec.stepDetails.appCpuUid1 >= 0) {
+ printStepCpuUidCheckinDetails(pw, rec.stepDetails.appCpuUid1,
+ rec.stepDetails.appCpuUTime1, rec.stepDetails.appCpuSTime1);
+ if (rec.stepDetails.appCpuUid2 >= 0) {
+ printStepCpuUidCheckinDetails(pw, rec.stepDetails.appCpuUid2,
+ rec.stepDetails.appCpuUTime2, rec.stepDetails.appCpuSTime2);
+ }
+ if (rec.stepDetails.appCpuUid3 >= 0) {
+ printStepCpuUidCheckinDetails(pw, rec.stepDetails.appCpuUid3,
+ rec.stepDetails.appCpuUTime3, rec.stepDetails.appCpuSTime3);
+ }
+ }
+ pw.println();
+ pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+ pw.print(HISTORY_DATA); pw.print(",0,Dpst=");
+ pw.print(rec.stepDetails.statUserTime);
+ pw.print(',');
+ pw.print(rec.stepDetails.statSystemTime);
+ pw.print(',');
+ pw.print(rec.stepDetails.statIOWaitTime);
+ pw.print(',');
+ pw.print(rec.stepDetails.statIrqTime);
+ pw.print(',');
+ pw.print(rec.stepDetails.statSoftIrqTime);
+ pw.print(',');
+ pw.print(rec.stepDetails.statIdlTime);
+ pw.println();
+ }
+ }
oldState = rec.states;
oldState2 = rec.states2;
}
}
+
+ private void printStepCpuUidDetails(PrintWriter pw, int uid, int utime, int stime) {
+ UserHandle.formatUid(pw, uid);
+ pw.print("=");
+ pw.print(utime);
+ pw.print("u+");
+ pw.print(stime);
+ pw.print("s");
+ }
+
+ private void printStepCpuUidCheckinDetails(PrintWriter pw, int uid, int utime, int stime) {
+ pw.print('/');
+ pw.print(uid);
+ pw.print(":");
+ pw.print(utime);
+ pw.print(":");
+ pw.print(stime);
+ }
}
private void printSizeValue(PrintWriter pw, long size) {
@@ -3725,47 +4311,27 @@ 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) {
+ private static boolean dumpTimeEstimate(PrintWriter pw, String label1, String label2,
+ String label3, long estimatedTime) {
+ if (estimatedTime < 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);
+ pw.print(label1);
+ pw.print(label2);
+ pw.print(label3);
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) {
+ private static boolean dumpDurationSteps(PrintWriter pw, String prefix, String header,
+ LevelStepTracker steps, boolean checkin) {
+ if (steps == null) {
+ return false;
+ }
+ int count = steps.mNumStepDurations;
if (count <= 0) {
return false;
}
@@ -3774,13 +4340,10 @@ public abstract class BatteryStats implements Parcelable {
}
String[] lineArgs = new String[4];
for (int i=0; i<count; i++) {
- long duration = steps[i] & STEP_LEVEL_TIME_MASK;
- int level = (int)((steps[i] & STEP_LEVEL_LEVEL_MASK)
- >> STEP_LEVEL_LEVEL_SHIFT);
- 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;
+ long duration = steps.getDurationAt(i);
+ int level = steps.getLevelAt(i);
+ long initMode = steps.getInitModeAt(i);
+ long modMode = steps.getModModeAt(i);
if (checkin) {
lineArgs[0] = Long.toString(duration);
lineArgs[1] = Integer.toString(level);
@@ -3802,7 +4365,8 @@ public abstract class BatteryStats implements Parcelable {
}
dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs);
} else {
- pw.print(" #"); pw.print(i); pw.print(": ");
+ pw.print(prefix);
+ pw.print("#"); pw.print(i); pw.print(": ");
TimeUtils.formatDuration(duration, pw);
pw.print(" to "); pw.print(level);
boolean haveModes = false;
@@ -3834,10 +4398,11 @@ public abstract class BatteryStats implements Parcelable {
public static final int DUMP_UNPLUGGED_ONLY = 1<<0;
public static final int DUMP_CHARGED_ONLY = 1<<1;
- public static final int DUMP_HISTORY_ONLY = 1<<2;
- public static final int DUMP_INCLUDE_HISTORY = 1<<3;
- public static final int DUMP_VERBOSE = 1<<4;
- public static final int DUMP_DEVICE_WIFI_ONLY = 1<<5;
+ public static final int DUMP_DAILY_ONLY = 1<<2;
+ public static final int DUMP_HISTORY_ONLY = 1<<3;
+ public static final int DUMP_INCLUDE_HISTORY = 1<<4;
+ public static final int DUMP_VERBOSE = 1<<5;
+ public static final int DUMP_DEVICE_WIFI_ONLY = 1<<6;
private void dumpHistoryLocked(PrintWriter pw, int flags, long histStart, boolean checkin) {
final HistoryPrinter hprinter = new HistoryPrinter();
@@ -3923,6 +4488,36 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ private void dumpDailyLevelStepSummary(PrintWriter pw, String prefix, String label,
+ LevelStepTracker steps, StringBuilder tmpSb, int[] tmpOutInt) {
+ if (steps == null) {
+ return;
+ }
+ long timeRemaining = steps.computeTimeEstimate(0, 0, tmpOutInt);
+ if (timeRemaining >= 0) {
+ pw.print(prefix); pw.print(label); pw.print(" total time: ");
+ tmpSb.setLength(0);
+ formatTimeMs(tmpSb, timeRemaining);
+ pw.print(tmpSb);
+ pw.print(" (from "); pw.print(tmpOutInt[0]);
+ pw.println(" steps)");
+ }
+ for (int i=0; i< STEP_LEVEL_MODES_OF_INTEREST.length; i++) {
+ long estimatedTime = steps.computeTimeEstimate(STEP_LEVEL_MODES_OF_INTEREST[i],
+ STEP_LEVEL_MODE_VALUES[i], tmpOutInt);
+ if (estimatedTime > 0) {
+ pw.print(prefix); pw.print(label); pw.print(" ");
+ pw.print(STEP_LEVEL_MODE_LABELS[i]);
+ pw.print(" time: ");
+ tmpSb.setLength(0);
+ formatTimeMs(tmpSb, estimatedTime);
+ pw.print(tmpSb);
+ pw.print(" (from "); pw.print(tmpOutInt[0]);
+ pw.println(" steps)");
+ }
+ }
+ }
+
/**
* Dumps a human-readable summary of the battery statistics to the given PrintWriter.
*
@@ -3932,8 +4527,8 @@ public abstract class BatteryStats implements Parcelable {
public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
prepareForDumpLocked();
- final boolean filtering =
- (flags&(DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) != 0;
+ final boolean filtering = (flags
+ & (DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0;
if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
final long historyTotalSize = getHistoryTotalSize();
@@ -3977,7 +4572,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) == 0) {
+ if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
return;
}
@@ -4011,50 +4606,24 @@ public abstract class BatteryStats implements Parcelable {
}
if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
- if (dumpDurationSteps(pw, "Discharge step durations:", getDischargeStepDurationsArray(),
- getNumDischargeStepDurations(), false)) {
+ if (dumpDurationSteps(pw, " ", "Discharge step durations:",
+ getDischargeLevelStepTracker(), false)) {
long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
if (timeRemaining >= 0) {
pw.print(" Estimated discharge time remaining: ");
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);
+ final LevelStepTracker steps = getDischargeLevelStepTracker();
+ for (int i=0; i< STEP_LEVEL_MODES_OF_INTEREST.length; i++) {
+ dumpTimeEstimate(pw, " Estimated ", STEP_LEVEL_MODE_LABELS[i], " time: ",
+ steps.computeTimeEstimate(STEP_LEVEL_MODES_OF_INTEREST[i],
+ STEP_LEVEL_MODE_VALUES[i], null));
+ }
pw.println();
}
- if (dumpDurationSteps(pw, "Charge step durations:", getChargeStepDurationsArray(),
- getNumChargeStepDurations(), false)) {
+ if (dumpDurationSteps(pw, " ", "Charge step durations:",
+ getChargeLevelStepTracker(), false)) {
long timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime());
if (timeRemaining >= 0) {
pw.print(" Estimated charge time remaining: ");
@@ -4063,6 +4632,75 @@ public abstract class BatteryStats implements Parcelable {
}
pw.println();
}
+ }
+ if (!filtering || (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0) {
+ pw.println("Daily stats:");
+ pw.print(" Current start time: ");
+ pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+ getCurrentDailyStartTime()).toString());
+ pw.print(" Next min deadline: ");
+ pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+ getNextMinDailyDeadline()).toString());
+ pw.print(" Next max deadline: ");
+ pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+ getNextMaxDailyDeadline()).toString());
+ StringBuilder sb = new StringBuilder(64);
+ int[] outInt = new int[1];
+ LevelStepTracker dsteps = getDailyDischargeLevelStepTracker();
+ LevelStepTracker csteps = getDailyChargeLevelStepTracker();
+ if (dsteps.mNumStepDurations > 0 || csteps.mNumStepDurations > 0) {
+ if ((flags&DUMP_DAILY_ONLY) != 0) {
+ if (dumpDurationSteps(pw, " ", " Current daily discharge step durations:",
+ dsteps, false)) {
+ dumpDailyLevelStepSummary(pw, " ", "Discharge", dsteps,
+ sb, outInt);
+ }
+ if (dumpDurationSteps(pw, " ", " Current daily charge step durations:",
+ csteps, false)) {
+ dumpDailyLevelStepSummary(pw, " ", "Charge", csteps,
+ sb, outInt);
+ }
+ } else {
+ pw.println(" Current daily steps:");
+ dumpDailyLevelStepSummary(pw, " ", "Discharge", dsteps,
+ sb, outInt);
+ dumpDailyLevelStepSummary(pw, " ", "Charge", csteps,
+ sb, outInt);
+ }
+ }
+ DailyItem dit;
+ int curIndex = 0;
+ while ((dit=getDailyItemLocked(curIndex)) != null) {
+ curIndex++;
+ if ((flags&DUMP_DAILY_ONLY) != 0) {
+ pw.println();
+ }
+ pw.print(" Daily from ");
+ pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", dit.mStartTime).toString());
+ pw.print(" to ");
+ pw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", dit.mEndTime).toString());
+ pw.println(":");
+ if ((flags&DUMP_DAILY_ONLY) != 0) {
+ if (dumpDurationSteps(pw, " ",
+ " Discharge step durations:", dit.mDischargeSteps, false)) {
+ dumpDailyLevelStepSummary(pw, " ", "Discharge", dit.mDischargeSteps,
+ sb, outInt);
+ }
+ if (dumpDurationSteps(pw, " ",
+ " Charge step durations:", dit.mChargeSteps, false)) {
+ dumpDailyLevelStepSummary(pw, " ", "Charge", dit.mChargeSteps,
+ sb, outInt);
+ }
+ } else {
+ dumpDailyLevelStepSummary(pw, " ", "Discharge", dit.mDischargeSteps,
+ sb, outInt);
+ dumpDailyLevelStepSummary(pw, " ", "Charge", dit.mChargeSteps,
+ sb, outInt);
+ }
+ }
+ pw.println();
+ }
+ if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
pw.println("Statistics since last charge:");
pw.println(" System starts: " + getStartCount()
+ ", currently on battery: " + getIsOnBattery());
@@ -4087,8 +4725,8 @@ public abstract class BatteryStats implements Parcelable {
long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
- final boolean filtering =
- (flags&(DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) != 0;
+ final boolean filtering = (flags &
+ (DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0;
if ((flags&DUMP_INCLUDE_HISTORY) != 0 || (flags&DUMP_HISTORY_ONLY) != 0) {
if (startIteratingHistoryLocked()) {
@@ -4114,7 +4752,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) == 0) {
+ if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
return;
}
@@ -4146,8 +4784,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
- dumpDurationSteps(pw, DISCHARGE_STEP_DATA, getDischargeStepDurationsArray(),
- getNumDischargeStepDurations(), true);
+ dumpDurationSteps(pw, "", DISCHARGE_STEP_DATA, getDischargeLevelStepTracker(), true);
String[] lineArgs = new String[1];
long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
if (timeRemaining >= 0) {
@@ -4155,8 +4792,7 @@ public abstract class BatteryStats implements Parcelable {
dumpLine(pw, 0 /* uid */, "i" /* category */, DISCHARGE_TIME_REMAIN_DATA,
(Object[])lineArgs);
}
- dumpDurationSteps(pw, CHARGE_STEP_DATA, getChargeStepDurationsArray(),
- getNumChargeStepDurations(), true);
+ dumpDurationSteps(pw, "", CHARGE_STEP_DATA, getChargeLevelStepTracker(), true);
timeRemaining = computeChargeTimeRemaining(SystemClock.elapsedRealtime());
if (timeRemaining >= 0) {
lineArgs[0] = Long.toString(timeRemaining);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index b209690..36fc4f9 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -91,7 +91,7 @@ public class Build {
/** The name of the hardware (from the kernel command line or /proc). */
public static final String HARDWARE = getString("ro.hardware");
- /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */
+ /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */
public static final String SERIAL = getString("ro.serialno");
/**
@@ -159,7 +159,7 @@ public class Build {
/**
* The user-visible SDK version of the framework in its raw String
* representation; use {@link #SDK_INT} instead.
- *
+ *
* @deprecated Use {@link #SDK_INT} to easily get this as an integer.
*/
@Deprecated
@@ -207,25 +207,25 @@ public class Build {
* not yet turned into an official release.
*/
public static final int CUR_DEVELOPMENT = 10000;
-
+
/**
* October 2008: The original, first, version of Android. Yay!
*/
public static final int BASE = 1;
-
+
/**
* February 2009: First Android update, officially called 1.1.
*/
public static final int BASE_1_1 = 2;
-
+
/**
* May 2009: Android 1.5.
*/
public static final int CUPCAKE = 3;
-
+
/**
* September 2009: Android 1.6.
- *
+ *
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
* <ul>
@@ -247,10 +247,10 @@ public class Build {
* </ul>
*/
public static final int DONUT = 4;
-
+
/**
* November 2009: Android 2.0
- *
+ *
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
* <ul>
@@ -267,22 +267,22 @@ public class Build {
* </ul>
*/
public static final int ECLAIR = 5;
-
+
/**
* December 2009: Android 2.0.1
*/
public static final int ECLAIR_0_1 = 6;
-
+
/**
* January 2010: Android 2.1
*/
public static final int ECLAIR_MR1 = 7;
-
+
/**
* June 2010: Android 2.2
*/
public static final int FROYO = 8;
-
+
/**
* November 2010: Android 2.3
*
@@ -294,7 +294,7 @@ public class Build {
* </ul>
*/
public static final int GINGERBREAD = 9;
-
+
/**
* February 2011: Android 2.3.3.
*/
@@ -339,12 +339,12 @@ public class Build {
* </ul>
*/
public static final int HONEYCOMB = 11;
-
+
/**
* May 2011: Android 3.1.
*/
public static final int HONEYCOMB_MR1 = 12;
-
+
/**
* June 2011: Android 3.2.
*
@@ -597,8 +597,13 @@ public class Build {
* Lollipop with an extra sugar coating on the outside!
*/
public static final int LOLLIPOP_MR1 = 22;
+
+ /**
+ * M comes after L.
+ */
+ public static final int MNC = CUR_DEVELOPMENT;
}
-
+
/** The type of build, like "user" or "eng". */
public static final String TYPE = getString("ro.build.type");
@@ -645,14 +650,22 @@ public class Build {
}
/**
- * Check that device fingerprint is defined and that it matches across
- * various partitions.
+ * Verifies the the current flash of the device is consistent with what
+ * was expected at build time.
+ * 1) Checks that device fingerprint is defined and that it matches across
+ * various partitions.
+ * 2) Verifies radio and bootloader partitions are those expected in the build.
*
* @hide
*/
- public static boolean isFingerprintConsistent() {
+ public static boolean isBuildConsistent() {
final String system = SystemProperties.get("ro.build.fingerprint");
final String vendor = SystemProperties.get("ro.vendor.build.fingerprint");
+ final String bootimage = SystemProperties.get("ro.bootimage.build.fingerprint");
+ final String requiredBootloader = SystemProperties.get("ro.build.expect.bootloader");
+ final String currentBootloader = SystemProperties.get("ro.bootloader");
+ final String requiredRadio = SystemProperties.get("ro.build.expect.baseband");
+ final String currentRadio = SystemProperties.get("gsm.version.baseband");
if (TextUtils.isEmpty(system)) {
Slog.e(TAG, "Required ro.build.fingerprint is empty!");
@@ -667,6 +680,32 @@ public class Build {
}
}
+ /* TODO: Figure out issue with checks failing
+ if (!TextUtils.isEmpty(bootimage)) {
+ if (!Objects.equals(system, bootimage)) {
+ Slog.e(TAG, "Mismatched fingerprints; system reported " + system
+ + " but bootimage reported " + bootimage);
+ return false;
+ }
+ }
+
+ if (!TextUtils.isEmpty(requiredBootloader)) {
+ if (!Objects.equals(currentBootloader, requiredBootloader)) {
+ Slog.e(TAG, "Mismatched bootloader version: build requires " + requiredBootloader
+ + " but runtime reports " + currentBootloader);
+ return false;
+ }
+ }
+
+ if (!TextUtils.isEmpty(requiredRadio)) {
+ if (!Objects.equals(currentRadio, requiredRadio)) {
+ Slog.e(TAG, "Mismatched radio version: build requires " + requiredRadio
+ + " but runtime reports " + currentRadio);
+ return false;
+ }
+ }
+ */
+
return true;
}
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c5c5372..5e9b8c1 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.Size;
import android.util.SizeF;
@@ -24,7 +25,6 @@ import android.util.SparseArray;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
/**
* A mapping from String values to various Parcelable types.
@@ -252,6 +252,29 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
}
/**
+ * Filter values in Bundle to only basic types.
+ * @hide
+ */
+ public void filterValues() {
+ unparcel();
+ if (mMap != null) {
+ for (int i = mMap.size() - 1; i >= 0; i--) {
+ Object value = mMap.valueAt(i);
+ if (PersistableBundle.isValidType(value)) {
+ continue;
+ }
+ if (value instanceof Bundle) {
+ ((Bundle)value).filterValues();
+ }
+ if (value.getClass().getName().startsWith("android.")) {
+ continue;
+ }
+ mMap.removeAt(i);
+ }
+ }
+ }
+
+ /**
* Inserts a byte value into the mapping of this Bundle, replacing
* any existing value for the given key.
*
@@ -259,7 +282,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a byte
*/
@Override
- public void putByte(String key, byte value) {
+ public void putByte(@Nullable String key, byte value) {
super.putByte(key, value);
}
@@ -268,10 +291,10 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* any existing value for the given key.
*
* @param key a String, or null
- * @param value a char, or null
+ * @param value a char
*/
@Override
- public void putChar(String key, char value) {
+ public void putChar(@Nullable String key, char value) {
super.putChar(key, value);
}
@@ -283,7 +306,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a short
*/
@Override
- public void putShort(String key, short value) {
+ public void putShort(@Nullable String key, short value) {
super.putShort(key, value);
}
@@ -295,7 +318,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a float
*/
@Override
- public void putFloat(String key, float value) {
+ public void putFloat(@Nullable String key, float value) {
super.putFloat(key, value);
}
@@ -307,7 +330,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a CharSequence, or null
*/
@Override
- public void putCharSequence(String key, CharSequence value) {
+ public void putCharSequence(@Nullable String key, @Nullable CharSequence value) {
super.putCharSequence(key, value);
}
@@ -318,7 +341,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @param value a Parcelable object, or null
*/
- public void putParcelable(String key, Parcelable value) {
+ public void putParcelable(@Nullable String key, @Nullable Parcelable value) {
unparcel();
mMap.put(key, value);
mFdsKnown = false;
@@ -331,7 +354,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @param value a Size object, or null
*/
- public void putSize(String key, Size value) {
+ public void putSize(@Nullable String key, @Nullable Size value) {
unparcel();
mMap.put(key, value);
}
@@ -343,7 +366,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @param value a SizeF object, or null
*/
- public void putSizeF(String key, SizeF value) {
+ public void putSizeF(@Nullable String key, @Nullable SizeF value) {
unparcel();
mMap.put(key, value);
}
@@ -356,7 +379,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @param value an array of Parcelable objects, or null
*/
- public void putParcelableArray(String key, Parcelable[] value) {
+ public void putParcelableArray(@Nullable String key, @Nullable Parcelable[] value) {
unparcel();
mMap.put(key, value);
mFdsKnown = false;
@@ -370,8 +393,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @param value an ArrayList of Parcelable objects, or null
*/
- public void putParcelableArrayList(String key,
- ArrayList<? extends Parcelable> value) {
+ public void putParcelableArrayList(@Nullable String key,
+ @Nullable ArrayList<? extends Parcelable> value) {
unparcel();
mMap.put(key, value);
mFdsKnown = false;
@@ -392,8 +415,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @param value a SparseArray of Parcelable objects, or null
*/
- public void putSparseParcelableArray(String key,
- SparseArray<? extends Parcelable> value) {
+ public void putSparseParcelableArray(@Nullable String key,
+ @Nullable SparseArray<? extends Parcelable> value) {
unparcel();
mMap.put(key, value);
mFdsKnown = false;
@@ -407,7 +430,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value an ArrayList<Integer> object, or null
*/
@Override
- public void putIntegerArrayList(String key, ArrayList<Integer> value) {
+ public void putIntegerArrayList(@Nullable String key, @Nullable ArrayList<Integer> value) {
super.putIntegerArrayList(key, value);
}
@@ -419,7 +442,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value an ArrayList<String> object, or null
*/
@Override
- public void putStringArrayList(String key, ArrayList<String> value) {
+ public void putStringArrayList(@Nullable String key, @Nullable ArrayList<String> value) {
super.putStringArrayList(key, value);
}
@@ -431,7 +454,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value an ArrayList<CharSequence> object, or null
*/
@Override
- public void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
+ public void putCharSequenceArrayList(@Nullable String key,
+ @Nullable ArrayList<CharSequence> value) {
super.putCharSequenceArrayList(key, value);
}
@@ -443,7 +467,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a Serializable object, or null
*/
@Override
- public void putSerializable(String key, Serializable value) {
+ public void putSerializable(@Nullable String key, @Nullable Serializable value) {
super.putSerializable(key, value);
}
@@ -455,7 +479,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a byte array object, or null
*/
@Override
- public void putByteArray(String key, byte[] value) {
+ public void putByteArray(@Nullable String key, @Nullable byte[] value) {
super.putByteArray(key, value);
}
@@ -467,7 +491,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a short array object, or null
*/
@Override
- public void putShortArray(String key, short[] value) {
+ public void putShortArray(@Nullable String key, @Nullable short[] value) {
super.putShortArray(key, value);
}
@@ -479,7 +503,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a char array object, or null
*/
@Override
- public void putCharArray(String key, char[] value) {
+ public void putCharArray(@Nullable String key, @Nullable char[] value) {
super.putCharArray(key, value);
}
@@ -491,7 +515,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a float array object, or null
*/
@Override
- public void putFloatArray(String key, float[] value) {
+ public void putFloatArray(@Nullable String key, @Nullable float[] value) {
super.putFloatArray(key, value);
}
@@ -503,7 +527,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param value a CharSequence array object, or null
*/
@Override
- public void putCharSequenceArray(String key, CharSequence[] value) {
+ public void putCharSequenceArray(@Nullable String key, @Nullable CharSequence[] value) {
super.putCharSequenceArray(key, value);
}
@@ -514,7 +538,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @param value a Bundle object, or null
*/
- public void putBundle(String key, Bundle value) {
+ public void putBundle(@Nullable String key, @Nullable Bundle value) {
unparcel();
mMap.put(key, value);
}
@@ -533,7 +557,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @param value an IBinder object, or null
*/
- public void putBinder(String key, IBinder value) {
+ public void putBinder(@Nullable String key, @Nullable IBinder value) {
unparcel();
mMap.put(key, value);
}
@@ -549,7 +573,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @hide This is the old name of the function.
*/
@Deprecated
- public void putIBinder(String key, IBinder value) {
+ public void putIBinder(@Nullable String key, @Nullable IBinder value) {
unparcel();
mMap.put(key, value);
}
@@ -663,7 +687,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return a CharSequence value, or null
*/
@Override
- public CharSequence getCharSequence(String key) {
+ @Nullable
+ public CharSequence getCharSequence(@Nullable String key) {
return super.getCharSequence(key);
}
@@ -679,7 +704,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* if no valid CharSequence object is currently mapped to that key.
*/
@Override
- public CharSequence getCharSequence(String key, CharSequence defaultValue) {
+ public CharSequence getCharSequence(@Nullable String key, CharSequence defaultValue) {
return super.getCharSequence(key, defaultValue);
}
@@ -691,7 +716,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @return a Size value, or null
*/
- public Size getSize(String key) {
+ @Nullable
+ public Size getSize(@Nullable String key) {
unparcel();
final Object o = mMap.get(key);
try {
@@ -710,7 +736,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @return a Size value, or null
*/
- public SizeF getSizeF(String key) {
+ @Nullable
+ public SizeF getSizeF(@Nullable String key) {
unparcel();
final Object o = mMap.get(key);
try {
@@ -729,7 +756,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @return a Bundle value, or null
*/
- public Bundle getBundle(String key) {
+ @Nullable
+ public Bundle getBundle(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -751,7 +779,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @return a Parcelable value, or null
*/
- public <T extends Parcelable> T getParcelable(String key) {
+ @Nullable
+ public <T extends Parcelable> T getParcelable(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -773,7 +802,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @return a Parcelable[] value, or null
*/
- public Parcelable[] getParcelableArray(String key) {
+ @Nullable
+ public Parcelable[] getParcelableArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -795,7 +825,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @return an ArrayList<T> value, or null
*/
- public <T extends Parcelable> ArrayList<T> getParcelableArrayList(String key) {
+ @Nullable
+ public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -818,7 +849,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
*
* @return a SparseArray of T values, or null
*/
- public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(String key) {
+ @Nullable
+ public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -841,7 +873,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return a Serializable value, or null
*/
@Override
- public Serializable getSerializable(String key) {
+ @Nullable
+ public Serializable getSerializable(@Nullable String key) {
return super.getSerializable(key);
}
@@ -854,7 +887,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return an ArrayList<String> value, or null
*/
@Override
- public ArrayList<Integer> getIntegerArrayList(String key) {
+ @Nullable
+ public ArrayList<Integer> getIntegerArrayList(@Nullable String key) {
return super.getIntegerArrayList(key);
}
@@ -867,7 +901,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return an ArrayList<String> value, or null
*/
@Override
- public ArrayList<String> getStringArrayList(String key) {
+ @Nullable
+ public ArrayList<String> getStringArrayList(@Nullable String key) {
return super.getStringArrayList(key);
}
@@ -880,7 +915,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return an ArrayList<CharSequence> value, or null
*/
@Override
- public ArrayList<CharSequence> getCharSequenceArrayList(String key) {
+ @Nullable
+ public ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) {
return super.getCharSequenceArrayList(key);
}
@@ -893,7 +929,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return a byte[] value, or null
*/
@Override
- public byte[] getByteArray(String key) {
+ @Nullable
+ public byte[] getByteArray(@Nullable String key) {
return super.getByteArray(key);
}
@@ -906,7 +943,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return a short[] value, or null
*/
@Override
- public short[] getShortArray(String key) {
+ @Nullable
+ public short[] getShortArray(@Nullable String key) {
return super.getShortArray(key);
}
@@ -919,7 +957,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return a char[] value, or null
*/
@Override
- public char[] getCharArray(String key) {
+ @Nullable
+ public char[] getCharArray(@Nullable String key) {
return super.getCharArray(key);
}
@@ -932,7 +971,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return a float[] value, or null
*/
@Override
- public float[] getFloatArray(String key) {
+ @Nullable
+ public float[] getFloatArray(@Nullable String key) {
return super.getFloatArray(key);
}
@@ -945,7 +985,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @return a CharSequence[] value, or null
*/
@Override
- public CharSequence[] getCharSequenceArray(String key) {
+ @Nullable
+ public CharSequence[] getCharSequenceArray(@Nullable String key) {
return super.getCharSequenceArray(key);
}
@@ -957,7 +998,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @param key a String, or null
* @return an IBinder value, or null
*/
- public IBinder getBinder(String key) {
+ @Nullable
+ public IBinder getBinder(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -983,7 +1025,8 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
* @hide This is the old name of the function.
*/
@Deprecated
- public IBinder getIBinder(String key) {
+ @Nullable
+ public IBinder getIBinder(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java
index f83a90b..2ecf317 100644
--- a/core/java/android/os/CommonClock.java
+++ b/core/java/android/os/CommonClock.java
@@ -23,7 +23,6 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
-import static android.system.OsConstants.*;
/**
* Used for accessing the android common time service's common clock and receiving notifications
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2d92c7b..512e212 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -63,7 +63,10 @@ public final class Debug
*
* TRACE_COUNT_ALLOCS adds the results from startAllocCounting to the
* trace key file.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static final int TRACE_COUNT_ALLOCS = VMDebug.TRACE_COUNT_ALLOCS;
/**
@@ -760,7 +763,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Stop counting the number and aggregate size of memory allocations.
*
- * @see #startAllocCounting()
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
@Deprecated
public static void stopAllocCounting() {
@@ -770,7 +773,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Returns the global count of objects allocated by the runtime between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getGlobalAllocCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
}
@@ -778,7 +784,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the global count of objects allocated.
* @see #getGlobalAllocCount()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetGlobalAllocCount() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_OBJECTS);
}
@@ -786,7 +795,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Returns the global size, in bytes, of objects allocated by the runtime between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getGlobalAllocSize() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
}
@@ -794,7 +806,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the global size of objects allocated.
* @see #getGlobalAllocSize()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetGlobalAllocSize() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_ALLOCATED_BYTES);
}
@@ -802,7 +817,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Returns the global count of objects freed by the runtime between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getGlobalFreedCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
}
@@ -810,7 +828,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the global count of objects freed.
* @see #getGlobalFreedCount()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetGlobalFreedCount() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_OBJECTS);
}
@@ -818,7 +839,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Returns the global size, in bytes, of objects freed by the runtime between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getGlobalFreedSize() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
}
@@ -826,7 +850,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the global size of objects freed.
* @see #getGlobalFreedSize()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetGlobalFreedSize() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
}
@@ -834,7 +861,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Returns the number of non-concurrent GC invocations between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getGlobalGcInvocationCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
}
@@ -842,7 +872,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the count of non-concurrent GC invocations.
* @see #getGlobalGcInvocationCount()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetGlobalGcInvocationCount() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_GC_INVOCATIONS);
}
@@ -851,7 +884,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* Returns the number of classes successfully initialized (ie those that executed without
* throwing an exception) between a {@link #startAllocCounting() start} and
* {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getGlobalClassInitCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
}
@@ -859,7 +895,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the count of classes initialized.
* @see #getGlobalClassInitCount()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetGlobalClassInitCount() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
}
@@ -867,7 +906,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Returns the time spent successfully initializing classes between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getGlobalClassInitTime() {
/* cumulative elapsed time for class initialization, in usec */
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
@@ -876,7 +918,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the count of time spent initializing classes.
* @see #getGlobalClassInitTime()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetGlobalClassInitTime() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
}
@@ -948,7 +993,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Returns the thread-local count of objects allocated by the runtime between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getThreadAllocCount() {
return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
}
@@ -956,7 +1004,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the thread-local count of objects allocated.
* @see #getThreadAllocCount()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetThreadAllocCount() {
VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_OBJECTS);
}
@@ -965,7 +1016,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* Returns the thread-local size of objects allocated by the runtime between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
* @return The allocated size in bytes.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getThreadAllocSize() {
return VMDebug.getAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
}
@@ -973,7 +1027,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the thread-local count of objects allocated.
* @see #getThreadAllocSize()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetThreadAllocSize() {
VMDebug.resetAllocCount(VMDebug.KIND_THREAD_ALLOCATED_BYTES);
}
@@ -1013,7 +1070,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Returns the number of thread-local non-concurrent GC invocations between a
* {@link #startAllocCounting() start} and {@link #stopAllocCounting() stop}.
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static int getThreadGcInvocationCount() {
return VMDebug.getAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
}
@@ -1021,7 +1081,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears the thread-local count of non-concurrent GC invocations.
* @see #getThreadGcInvocationCount()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetThreadGcInvocationCount() {
VMDebug.resetAllocCount(VMDebug.KIND_THREAD_GC_INVOCATIONS);
}
@@ -1029,7 +1092,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
/**
* Clears all the global and thread-local memory allocation counters.
* @see #startAllocCounting()
+ *
+ * @deprecated Accurate counting is a burden on the runtime and may be removed.
*/
+ @Deprecated
public static void resetAllCounts() {
VMDebug.resetAllocCount(VMDebug.KIND_ALL_COUNTS);
}
@@ -1286,7 +1352,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* + icount.globalMethodInvocations());
* }
* </pre>
+ *
+ * @deprecated Instruction counting is no longer supported.
*/
+ @Deprecated
public static class InstructionCount {
private static final int NUM_INSTR =
OpcodeInfo.MAXIMUM_PACKED_VALUE + 1;
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index f0660eb..f93550a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -178,6 +178,18 @@ interface INetworkManagementService
String[] getDnsForwarders();
/**
+ * Enables unidirectional packet forwarding from {@code fromIface} to
+ * {@code toIface}.
+ */
+ void startInterfaceForwarding(String fromIface, String toIface);
+
+ /**
+ * Disables unidirectional packet forwarding from {@code fromIface} to
+ * {@code toIface}.
+ */
+ void stopInterfaceForwarding(String fromIface, String toIface);
+
+ /**
* Enables Network Address Translation between two interfaces.
* The address and netmask of the external interface is used for
* the NAT'ed network.
diff --git a/core/java/android/net/http/RequestFeeder.java b/core/java/android/os/IProcessInfoService.aidl
index 34ca267..c98daa2 100644
--- a/core/java/android/net/http/RequestFeeder.java
+++ b/core/java/android/os/IProcessInfoService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,29 +14,16 @@
* limitations under the License.
*/
-/**
- * Supplies Requests to a Connection
- */
-
-package android.net.http;
-
-import org.apache.http.HttpHost;
-
-/**
- * {@hide}
- */
-interface RequestFeeder {
-
- Request getRequest();
- Request getRequest(HttpHost host);
+package android.os;
+/** {@hide} */
+interface IProcessInfoService
+{
/**
- * @return true if a request for this host is available
+ * For each PID in the given input array, write the current process state
+ * for that process into the output array, or ActivityManager.PROCESS_STATE_NONEXISTENT
+ * to indicate that no process with the given PID exists.
*/
- boolean haveRequest(HttpHost host);
-
- /**
- * Put request back on head of queue
- */
- void requeueRequest(Request request);
+ void getProcessStatesFromPids(in int[] pids, out int[] states);
}
+
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b5295fb..236003b 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -21,6 +21,7 @@ import android.os.Bundle;
import android.content.pm.UserInfo;
import android.content.RestrictionEntry;
import android.graphics.Bitmap;
+import android.os.ParcelFileDescriptor;
/**
* {@hide}
@@ -32,7 +33,7 @@ interface IUserManager {
boolean removeUser(int userHandle);
void setUserName(int userHandle, String name);
void setUserIcon(int userHandle, in Bitmap icon);
- Bitmap getUserIcon(int userHandle);
+ ParcelFileDescriptor getUserIcon(int userHandle);
List<UserInfo> getUsers(boolean excludeDying);
List<UserInfo> getProfiles(int userHandle, boolean enabledOnly);
UserInfo getProfileParent(int userHandle);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 6d7c9cf..34c880f 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Log;
import android.util.Printer;
@@ -24,10 +26,10 @@ import android.util.Printer;
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
- *
+ *
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
- *
+ *
* <p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
@@ -50,6 +52,16 @@ import android.util.Printer;
* }</pre>
*/
public final class Looper {
+ /*
+ * API Implementation Note:
+ *
+ * This class contains the code required to set up and manage an event loop
+ * based on MessageQueue. APIs that affect the state of the queue should be
+ * defined on MessageQueue or Handler rather than on Looper itself. For example,
+ * idle handlers and sync barriers are defined on the queue whereas preparing the
+ * thread, looping, and quitting are defined on the looper.
+ */
+
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
@@ -94,7 +106,8 @@ public final class Looper {
}
}
- /** Returns the application's main looper, which lives in the main thread of the application.
+ /**
+ * Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
@@ -157,29 +170,16 @@ public final class Looper {
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
- public static Looper myLooper() {
+ public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/**
- * Control logging of messages as they are processed by this Looper. If
- * enabled, a log message will be written to <var>printer</var>
- * at the beginning and ending of each message dispatch, identifying the
- * target Handler and message contents.
- *
- * @param printer A Printer object that will receive log messages, or
- * null to disable message logging.
- */
- public void setMessageLogging(Printer printer) {
- mLogging = printer;
- }
-
- /**
* Return the {@link MessageQueue} object associated with the current
* thread. This must be called from a thread running a Looper, or a
* NullPointerException will be thrown.
*/
- public static MessageQueue myQueue() {
+ public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
@@ -190,13 +190,25 @@ public final class Looper {
/**
* Returns true if the current thread is this looper's thread.
- * @hide
*/
public boolean isCurrentThread() {
return Thread.currentThread() == mThread;
}
/**
+ * Control logging of messages as they are processed by this Looper. If
+ * enabled, a log message will be written to <var>printer</var>
+ * at the beginning and ending of each message dispatch, identifying the
+ * target Handler and message contents.
+ *
+ * @param printer A Printer object that will receive log messages, or
+ * null to disable message logging.
+ */
+ public void setMessageLogging(@Nullable Printer printer) {
+ mLogging = printer;
+ }
+
+ /**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
@@ -233,74 +245,35 @@ public final class Looper {
}
/**
- * Posts a synchronization barrier to the Looper's message queue.
- *
- * Message processing occurs as usual until the message queue encounters the
- * synchronization barrier that has been posted. When the barrier is encountered,
- * later synchronous messages in the queue are stalled (prevented from being executed)
- * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
- * the token that identifies the synchronization barrier.
- *
- * This method is used to immediately postpone execution of all subsequently posted
- * synchronous messages until a condition is met that releases the barrier.
- * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
- * and continue to be processed as usual.
+ * Gets the Thread associated with this Looper.
*
- * This call must be always matched by a call to {@link #removeSyncBarrier} with
- * the same token to ensure that the message queue resumes normal operation.
- * Otherwise the application will probably hang!
- *
- * @return A token that uniquely identifies the barrier. This token must be
- * passed to {@link #removeSyncBarrier} to release the barrier.
- *
- * @hide
+ * @return The looper's thread.
*/
- public int postSyncBarrier() {
- return mQueue.enqueueSyncBarrier(SystemClock.uptimeMillis());
+ public @NonNull Thread getThread() {
+ return mThread;
}
-
/**
- * Removes a synchronization barrier.
- *
- * @param token The synchronization barrier token that was returned by
- * {@link #postSyncBarrier}.
+ * Gets this looper's message queue.
*
- * @throws IllegalStateException if the barrier was not found.
- *
- * @hide
- */
- public void removeSyncBarrier(int token) {
- mQueue.removeSyncBarrier(token);
- }
-
- /**
- * Return the Thread associated with this Looper.
+ * @return The looper's message queue.
*/
- public Thread getThread() {
- return mThread;
- }
-
- /** @hide */
- public MessageQueue getQueue() {
+ public @NonNull MessageQueue getQueue() {
return mQueue;
}
/**
- * Return whether this looper's thread is currently idle, waiting for new work
- * to do. This is intrinsically racy, since its state can change before you get
- * the result back.
- * @hide
+ * Dumps the state of the looper for debugging purposes.
+ *
+ * @param pw A printer to receive the contents of the dump.
+ * @param prefix A prefix to prepend to each line which is printed.
*/
- public boolean isIdling() {
- return mQueue.isIdling();
- }
-
- public void dump(Printer pw, String prefix) {
+ public void dump(@NonNull Printer pw, @NonNull String prefix) {
pw.println(prefix + toString());
mQueue.dump(pw, prefix + " ");
}
+ @Override
public String toString() {
return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
+ ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 01a23ce..7dd4f94 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -16,9 +16,15 @@
package android.os;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.util.Log;
import android.util.Printer;
+import android.util.SparseArray;
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
@@ -30,6 +36,9 @@ import java.util.ArrayList;
* {@link Looper#myQueue() Looper.myQueue()}.
*/
public final class MessageQueue {
+ private static final String TAG = "MessageQueue";
+ private static final boolean DEBUG = false;
+
// True if the message queue can be quit.
private final boolean mQuitAllowed;
@@ -38,6 +47,7 @@ public final class MessageQueue {
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
+ private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
@@ -50,23 +60,46 @@ public final class MessageQueue {
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
- private native static void nativePollOnce(long ptr, int timeoutMillis);
+ private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
- private native static boolean nativeIsIdling(long ptr);
+ private native static boolean nativeIsPolling(long ptr);
+ private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
+
+ MessageQueue(boolean quitAllowed) {
+ mQuitAllowed = quitAllowed;
+ mPtr = nativeInit();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ // Disposes of the underlying message queue.
+ // Must only be called on the looper thread or the finalizer.
+ private void dispose() {
+ if (mPtr != 0) {
+ nativeDestroy(mPtr);
+ mPtr = 0;
+ }
+ }
/**
- * Callback interface for discovering when a thread is going to block
- * waiting for more messages.
+ * Returns true if the looper has no pending messages which are due to be processed.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is idle.
*/
- public static interface IdleHandler {
- /**
- * Called when the message queue has run out of messages and will now
- * wait for more. Return true to keep your idle handler active, false
- * to have it removed. This may be called if there are still messages
- * pending in the queue, but they are all scheduled to be dispatched
- * after the current time.
- */
- boolean queueIdle();
+ public boolean isIdle() {
+ synchronized (this) {
+ final long now = SystemClock.uptimeMillis();
+ return mMessages == null || now < mMessages.when;
+ }
}
/**
@@ -74,12 +107,12 @@ public final class MessageQueue {
* removed automatically for you by returning false from
* {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
* invoked, or explicitly removing it with {@link #removeIdleHandler}.
- *
+ *
* <p>This method is safe to call from any thread.
- *
+ *
* @param handler The IdleHandler to be added.
*/
- public void addIdleHandler(IdleHandler handler) {
+ public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
@@ -92,38 +125,185 @@ public final class MessageQueue {
* Remove an {@link IdleHandler} from the queue that was previously added
* with {@link #addIdleHandler}. If the given object is not currently
* in the idle list, nothing is done.
- *
+ *
+ * <p>This method is safe to call from any thread.
+ *
* @param handler The IdleHandler to be removed.
*/
- public void removeIdleHandler(IdleHandler handler) {
+ public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
- MessageQueue(boolean quitAllowed) {
- mQuitAllowed = quitAllowed;
- mPtr = nativeInit();
+ /**
+ * Returns whether this looper's thread is currently polling for more work to do.
+ * This is a good signal that the loop is still alive rather than being stuck
+ * handling a callback. Note that this method is intrinsically racy, since the
+ * state of the loop can change before you get the result back.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is currently polling for events.
+ * @hide
+ */
+ public boolean isPolling() {
+ synchronized (this) {
+ return isPollingLocked();
+ }
}
- @Override
- protected void finalize() throws Throwable {
- try {
- dispose();
- } finally {
- super.finalize();
+ private boolean isPollingLocked() {
+ // If the loop is quitting then it must not be idling.
+ // We can assume mPtr != 0 when mQuitting is false.
+ return !mQuitting && nativeIsPolling(mPtr);
+ }
+
+ /**
+ * Registers a file descriptor callback to receive notification when file descriptor
+ * related events occur.
+ * <p>
+ * If the file descriptor has already been registered, the specified events
+ * and callback will replace any that were previously associated with it.
+ * It is not possible to set more than one callback per file descriptor.
+ * </p><p>
+ * It is important to always unregister the callback when the file descriptor
+ * is no longer of use.
+ * </p>
+ *
+ * @param fd The file descriptor for which a callback will be registered.
+ * @param events The set of events to receive: a combination of the
+ * {@link FileDescriptorCallback#EVENT_INPUT},
+ * {@link FileDescriptorCallback#EVENT_OUTPUT}, and
+ * {@link FileDescriptorCallback#EVENT_ERROR} event masks. If the requested
+ * set of events is zero, then the callback is unregistered.
+ * @param callback The callback to invoke when file descriptor events occur.
+ *
+ * @see FileDescriptorCallback
+ * @see #unregisterFileDescriptorCallback
+ */
+ public void registerFileDescriptorCallback(@NonNull FileDescriptor fd,
+ @FileDescriptorCallback.Events int events,
+ @NonNull FileDescriptorCallback callback) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
+
+ synchronized (this) {
+ setFileDescriptorCallbackLocked(fd, events, callback);
}
}
- // Disposes of the underlying message queue.
- // Must only be called on the looper thread or the finalizer.
- private void dispose() {
- if (mPtr != 0) {
- nativeDestroy(mPtr);
- mPtr = 0;
+ /**
+ * Unregisters a file descriptor callback.
+ * <p>
+ * This method does nothing if no callback has been registered for the
+ * specified file descriptor.
+ * </p>
+ *
+ * @param fd The file descriptor whose callback will be unregistered.
+ *
+ * @see FileDescriptorCallback
+ * @see #registerFileDescriptorCallback
+ */
+ public void unregisterFileDescriptorCallback(@NonNull FileDescriptor fd) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+
+ synchronized (this) {
+ setFileDescriptorCallbackLocked(fd, 0, null);
}
}
+ private void setFileDescriptorCallbackLocked(FileDescriptor fd, int events,
+ FileDescriptorCallback callback) {
+ final int fdNum = fd.getInt$();
+
+ int index = -1;
+ FileDescriptorRecord record = null;
+ if (mFileDescriptorRecords != null) {
+ index = mFileDescriptorRecords.indexOfKey(fdNum);
+ if (index >= 0) {
+ record = mFileDescriptorRecords.valueAt(index);
+ if (record != null && record.mEvents == events) {
+ return;
+ }
+ }
+ }
+
+ if (events != 0) {
+ events |= FileDescriptorCallback.EVENT_ERROR;
+ if (record == null) {
+ if (mFileDescriptorRecords == null) {
+ mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
+ }
+ record = new FileDescriptorRecord(fd, events, callback);
+ mFileDescriptorRecords.put(fdNum, record);
+ } else {
+ record.mCallback = callback;
+ record.mEvents = events;
+ record.mSeq += 1;
+ }
+ nativeSetFileDescriptorEvents(mPtr, fdNum, events);
+ } else if (record != null) {
+ record.mEvents = 0;
+ mFileDescriptorRecords.removeAt(index);
+ }
+ }
+
+ // Called from native code.
+ private int dispatchEvents(int fd, int events) {
+ // Get the file descriptor record and any state that might change.
+ final FileDescriptorRecord record;
+ final int oldWatchedEvents;
+ final FileDescriptorCallback callback;
+ final int seq;
+ synchronized (this) {
+ record = mFileDescriptorRecords.get(fd);
+ if (record == null) {
+ return 0; // spurious, no callback registered
+ }
+
+ oldWatchedEvents = record.mEvents;
+ events &= oldWatchedEvents; // filter events based on current watched set
+ if (events == 0) {
+ return oldWatchedEvents; // spurious, watched events changed
+ }
+
+ callback = record.mCallback;
+ seq = record.mSeq;
+ }
+
+ // Invoke the callback outside of the lock.
+ int newWatchedEvents = callback.onFileDescriptorEvents(
+ record.mDescriptor, events);
+ if (newWatchedEvents != 0) {
+ newWatchedEvents |= FileDescriptorCallback.EVENT_ERROR;
+ }
+
+ // Update the file descriptor record if the callback changed the set of
+ // events to watch and the callback itself hasn't been updated since.
+ if (newWatchedEvents != oldWatchedEvents) {
+ synchronized (this) {
+ int index = mFileDescriptorRecords.indexOfKey(fd);
+ if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+ && record.mSeq == seq) {
+ record.mEvents = newWatchedEvents;
+ if (newWatchedEvents == 0) {
+ mFileDescriptorRecords.removeAt(index);
+ }
+ }
+ }
+ }
+
+ // Return the new set of events to watch for native code to take care of.
+ return newWatchedEvents;
+ }
+
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
@@ -167,7 +347,8 @@ public final class MessageQueue {
mMessages = msg.next;
}
msg.next = null;
- if (false) Log.v("MessageQueue", "Returning message: " + msg);
+ if (DEBUG) Log.v(TAG, "Returning message: " + msg);
+ msg.markInUse();
return msg;
}
} else {
@@ -210,7 +391,7 @@ public final class MessageQueue {
try {
keep = idler.queueIdle();
} catch (Throwable t) {
- Log.wtf("MessageQueue", "IdleHandler threw exception", t);
+ Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
@@ -251,7 +432,34 @@ public final class MessageQueue {
}
}
- int enqueueSyncBarrier(long when) {
+ /**
+ * Posts a synchronization barrier to the Looper's message queue.
+ *
+ * Message processing occurs as usual until the message queue encounters the
+ * synchronization barrier that has been posted. When the barrier is encountered,
+ * later synchronous messages in the queue are stalled (prevented from being executed)
+ * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+ * the token that identifies the synchronization barrier.
+ *
+ * This method is used to immediately postpone execution of all subsequently posted
+ * synchronous messages until a condition is met that releases the barrier.
+ * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+ * and continue to be processed as usual.
+ *
+ * This call must be always matched by a call to {@link #removeSyncBarrier} with
+ * the same token to ensure that the message queue resumes normal operation.
+ * Otherwise the application will probably hang!
+ *
+ * @return A token that uniquely identifies the barrier. This token must be
+ * passed to {@link #removeSyncBarrier} to release the barrier.
+ *
+ * @hide
+ */
+ public int postSyncBarrier() {
+ return postSyncBarrier(SystemClock.uptimeMillis());
+ }
+
+ private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
@@ -280,7 +488,17 @@ public final class MessageQueue {
}
}
- void removeSyncBarrier(int token) {
+ /**
+ * Removes a synchronization barrier.
+ *
+ * @param token The synchronization barrier token that was returned by
+ * {@link #postSyncBarrier}.
+ *
+ * @throws IllegalStateException if the barrier was not found.
+ *
+ * @hide
+ */
+ public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
@@ -324,7 +542,7 @@ public final class MessageQueue {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
- Log.w("MessageQueue", e.getMessage(), e);
+ Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
@@ -400,18 +618,6 @@ public final class MessageQueue {
}
}
- boolean isIdling() {
- synchronized (this) {
- return isIdlingLocked();
- }
- }
-
- private boolean isIdlingLocked() {
- // If the loop is quitting then it must not be idling.
- // We can assume mPtr != 0 when mQuitting is false.
- return !mQuitting && nativeIsIdling(mPtr);
- }
-
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
@@ -559,8 +765,113 @@ public final class MessageQueue {
pw.println(prefix + "Message " + n + ": " + msg.toString(now));
n++;
}
- pw.println(prefix + "(Total messages: " + n + ", idling=" + isIdlingLocked()
+ pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
+ ", quitting=" + mQuitting + ")");
}
}
+
+ /**
+ * Callback interface for discovering when a thread is going to block
+ * waiting for more messages.
+ */
+ public static interface IdleHandler {
+ /**
+ * Called when the message queue has run out of messages and will now
+ * wait for more. Return true to keep your idle handler active, false
+ * to have it removed. This may be called if there are still messages
+ * pending in the queue, but they are all scheduled to be dispatched
+ * after the current time.
+ */
+ boolean queueIdle();
+ }
+
+ /**
+ * A callback which is invoked when file descriptor related events occur.
+ */
+ public static abstract class FileDescriptorCallback {
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for input
+ * operations, such as reading.
+ * <p>
+ * The callback should read all available data from the file descriptor
+ * then return <code>true</code> to keep the callback active or <code>false</code>
+ * to remove the callback.
+ * </p><p>
+ * In the case of a socket, this event may be generated to indicate
+ * that there is at least one incoming connection that the callback
+ * should accept.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_INPUT} event mask was
+ * specified when the callback was added.
+ * </p>
+ */
+ public static final int EVENT_INPUT = 1 << 0;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for output
+ * operations, such as writing.
+ * <p>
+ * The callback should write as much data as it needs. If it could not
+ * write everything at once, then it should return <code>true</code> to
+ * keep the callback active. Otherwise, it should return <code>false</code>
+ * to remove the callback then re-register it later when it needs to write
+ * something else.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
+ * specified when the callback was added.
+ * </p>
+ */
+ public static final int EVENT_OUTPUT = 1 << 1;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor encountered a
+ * fatal error.
+ * <p>
+ * File descriptor errors can occur for various reasons. One common error
+ * is when the remote peer of a socket or pipe closes its end of the connection.
+ * </p><p>
+ * This event may be generated at any time regardless of whether the
+ * {@link #EVENT_ERROR} event mask was specified when the callback was added.
+ * </p>
+ */
+ public static final int EVENT_ERROR = 1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag=true, value={EVENT_INPUT, EVENT_OUTPUT, EVENT_ERROR})
+ public @interface Events {}
+
+ /**
+ * Called when a file descriptor receives events.
+ * <p>
+ * The default implementation does nothing and returns 0 to unregister the callback.
+ * </p>
+ *
+ * @param fd The file descriptor.
+ * @param events The set of events that occurred: a combination of the
+ * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
+ * @return The new set of events to watch, or 0 to unregister the callback.
+ *
+ * @see #EVENT_INPUT
+ * @see #EVENT_OUTPUT
+ * @see #EVENT_ERROR
+ */
+ public @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events) {
+ return 0;
+ }
+ }
+
+ private static final class FileDescriptorRecord {
+ public final FileDescriptor mDescriptor;
+ public int mEvents;
+ public FileDescriptorCallback mCallback;
+ public int mSeq;
+
+ public FileDescriptorRecord(FileDescriptor descriptor,
+ int events, FileDescriptorCallback callback) {
+ mDescriptor = descriptor;
+ mEvents = events;
+ mCallback = callback;
+ }
+ }
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3d5215b..9d8a1ba 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1059,6 +1059,21 @@ public final class Parcel {
}
}
+ /**
+ * @hide
+ */
+ public final void writeCharSequenceList(ArrayList<CharSequence> val) {
+ if (val != null) {
+ int N = val.size();
+ writeInt(N);
+ for (int i=0; i<N; i++) {
+ writeCharSequence(val.get(i));
+ }
+ } else {
+ writeInt(-1);
+ }
+ }
+
public final IBinder[] createBinderArray() {
int N = readInt();
if (N >= 0) {
@@ -1828,6 +1843,25 @@ public final class Parcel {
}
/**
+ * Read and return an ArrayList&lt;CharSequence&gt; object from the parcel.
+ * {@hide}
+ */
+ public final ArrayList<CharSequence> readCharSequenceList() {
+ ArrayList<CharSequence> array = null;
+
+ int length = readInt();
+ if (length >= 0) {
+ array = new ArrayList<CharSequence>(length);
+
+ for (int i = 0 ; i < length ; i++) {
+ array.add(readCharSequence());
+ }
+ }
+
+ return array;
+ }
+
+ /**
* Read and return a new ArrayList object from the parcel at the current
* dataPosition(). Returns null if the previously written list object was
* null. The given class loader will be used to load any enclosed
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index c6b2151..5b26304 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -19,11 +19,13 @@ package android.os;
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.SEEK_SET;
import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.SOCK_SEQPACKET;
import static android.system.OsConstants.S_ISLNK;
import static android.system.OsConstants.S_ISREG;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
+import android.os.MessageQueue.FileDescriptorCallback;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -31,7 +33,6 @@ import android.system.StructStat;
import android.util.Log;
import dalvik.system.CloseGuard;
-
import libcore.io.IoUtils;
import libcore.io.Memory;
@@ -220,8 +221,8 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
* be opened with the requested mode.
* @see #parseMode(String)
*/
- public static ParcelFileDescriptor open(
- File file, int mode, Handler handler, OnCloseListener listener) throws IOException {
+ public static ParcelFileDescriptor open(File file, int mode, Handler handler,
+ final OnCloseListener listener) throws IOException {
if (handler == null) {
throw new IllegalArgumentException("Handler must not be null");
}
@@ -234,11 +235,27 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
final FileDescriptor[] comm = createCommSocketPair();
final ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd, comm[0]);
-
- // Kick off thread to watch for status updates
- IoUtils.setBlocking(comm[1], true);
- final ListenerBridge bridge = new ListenerBridge(comm[1], handler.getLooper(), listener);
- bridge.start();
+ final MessageQueue queue = handler.getLooper().getQueue();
+ queue.registerFileDescriptorCallback(comm[1],
+ FileDescriptorCallback.EVENT_INPUT, new FileDescriptorCallback() {
+ @Override
+ public int onFileDescriptorEvents(FileDescriptor fd, int events) {
+ Status status = null;
+ if ((events & FileDescriptorCallback.EVENT_INPUT) != 0) {
+ final byte[] buf = new byte[MAX_STATUS];
+ status = readCommStatus(fd, buf);
+ } else if ((events & FileDescriptorCallback.EVENT_ERROR) != 0) {
+ status = new Status(Status.DEAD);
+ }
+ if (status != null) {
+ queue.unregisterFileDescriptorCallback(fd);
+ IoUtils.closeQuietly(fd);
+ listener.onClose(status.asIOException());
+ return 0;
+ }
+ return EVENT_INPUT;
+ }
+ });
return pfd;
}
@@ -395,10 +412,17 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
* connected to each other. The two sockets are indistinguishable.
*/
public static ParcelFileDescriptor[] createSocketPair() throws IOException {
+ return createSocketPair(SOCK_STREAM);
+ }
+
+ /**
+ * @hide
+ */
+ public static ParcelFileDescriptor[] createSocketPair(int type) throws IOException {
try {
final FileDescriptor fd0 = new FileDescriptor();
final FileDescriptor fd1 = new FileDescriptor();
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1);
+ Os.socketpair(AF_UNIX, type, 0, fd0, fd1);
return new ParcelFileDescriptor[] {
new ParcelFileDescriptor(fd0),
new ParcelFileDescriptor(fd1) };
@@ -417,11 +441,18 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
* This can also be used to detect remote crashes.
*/
public static ParcelFileDescriptor[] createReliableSocketPair() throws IOException {
+ return createReliableSocketPair(SOCK_STREAM);
+ }
+
+ /**
+ * @hide
+ */
+ public static ParcelFileDescriptor[] createReliableSocketPair(int type) throws IOException {
try {
final FileDescriptor[] comm = createCommSocketPair();
final FileDescriptor fd0 = new FileDescriptor();
final FileDescriptor fd1 = new FileDescriptor();
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1);
+ Os.socketpair(AF_UNIX, type, 0, fd0, fd1);
return new ParcelFileDescriptor[] {
new ParcelFileDescriptor(fd0, comm[0]),
new ParcelFileDescriptor(fd1, comm[1]) };
@@ -432,9 +463,12 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
private static FileDescriptor[] createCommSocketPair() throws IOException {
try {
+ // Use SOCK_SEQPACKET so that we have a guarantee that the status
+ // is written and read atomically as one unit and is not split
+ // across multiple IO operations.
final FileDescriptor comm1 = new FileDescriptor();
final FileDescriptor comm2 = new FileDescriptor();
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, comm1, comm2);
+ Os.socketpair(AF_UNIX, SOCK_SEQPACKET, 0, comm1, comm2);
IoUtils.setBlocking(comm1, false);
IoUtils.setBlocking(comm2, false);
return new FileDescriptor[] { comm1, comm2 };
@@ -695,6 +729,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
writePtr += len;
}
+ // Must write the entire status as a single operation.
Os.write(mCommFd, buf, 0, writePtr);
} catch (ErrnoException e) {
// Reporting status is best-effort
@@ -712,6 +747,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
private static Status readCommStatus(FileDescriptor comm, byte[] buf) {
try {
+ // Must read the entire status as a single operation.
final int n = Os.read(comm, buf, 0, buf.length);
if (n == 0) {
// EOF means they're dead
@@ -1000,39 +1036,10 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
return new IOException("Unknown status: " + status);
}
}
- }
-
- /**
- * Bridge to watch for remote status, and deliver to listener. Currently
- * requires that communication socket is <em>blocking</em>.
- */
- private static final class ListenerBridge extends Thread {
- // TODO: switch to using Looper to avoid burning a thread
-
- private FileDescriptor mCommFd;
- private final Handler mHandler;
-
- public ListenerBridge(FileDescriptor comm, Looper looper, final OnCloseListener listener) {
- mCommFd = comm;
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- final Status s = (Status) msg.obj;
- listener.onClose(s != null ? s.asIOException() : null);
- }
- };
- }
@Override
- public void run() {
- try {
- final byte[] buf = new byte[MAX_STATUS];
- final Status status = readCommStatus(mCommFd, buf);
- mHandler.obtainMessage(0, status).sendToTarget();
- } finally {
- IoUtils.closeQuietly(mCommFd);
- mCommFd = null;
- }
+ public String toString() {
+ return "{" + status + ": " + msg + "}";
}
}
}
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index 3a44428..ea180b2 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.Nullable;
import android.util.ArrayMap;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -44,6 +45,16 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL;
}
+ /** @hide */
+ public static boolean isValidType(Object value) {
+ return (value instanceof Integer) || (value instanceof Long) ||
+ (value instanceof Double) || (value instanceof String) ||
+ (value instanceof int[]) || (value instanceof long[]) ||
+ (value instanceof double[]) || (value instanceof String[]) ||
+ (value instanceof PersistableBundle) || (value == null) ||
+ (value instanceof Boolean) || (value instanceof boolean[]);
+ }
+
/**
* Constructs a new, empty PersistableBundle.
*/
@@ -77,29 +88,22 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
* @param map a Map containing only those items that can be persisted.
* @throws IllegalArgumentException if any element of #map cannot be persisted.
*/
- private PersistableBundle(Map<String, Object> map) {
+ private PersistableBundle(ArrayMap<String, Object> map) {
super();
// First stuff everything in.
putAll(map);
// Now verify each item throwing an exception if there is a violation.
- Set<String> keys = map.keySet();
- Iterator<String> iterator = keys.iterator();
- while (iterator.hasNext()) {
- String key = iterator.next();
- Object value = map.get(key);
- if (value instanceof Map) {
+ final int N = mMap.size();
+ for (int i=0; i<N; i++) {
+ Object value = mMap.valueAt(i);
+ if (value instanceof ArrayMap) {
// Fix up any Maps by replacing them with PersistableBundles.
- putPersistableBundle(key, new PersistableBundle((Map<String, Object>) value));
- } else if (!(value instanceof Integer) && !(value instanceof Long) &&
- !(value instanceof Double) && !(value instanceof String) &&
- !(value instanceof int[]) && !(value instanceof long[]) &&
- !(value instanceof double[]) && !(value instanceof String[]) &&
- !(value instanceof PersistableBundle) && (value != null) &&
- !(value instanceof Boolean) && !(value instanceof boolean[])) {
- throw new IllegalArgumentException("Bad value in PersistableBundle key=" + key +
- " value=" + value);
+ mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value));
+ } else if (!isValidType(value)) {
+ throw new IllegalArgumentException("Bad value in PersistableBundle key="
+ + mMap.keyAt(i) + " value=" + value);
}
}
}
@@ -135,7 +139,7 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
* @param key a String, or null
* @param value a Bundle object, or null
*/
- public void putPersistableBundle(String key, PersistableBundle value) {
+ public void putPersistableBundle(@Nullable String key, @Nullable PersistableBundle value) {
unparcel();
mMap.put(key, value);
}
@@ -148,7 +152,8 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
* @param key a String, or null
* @return a Bundle value, or null
*/
- public PersistableBundle getPersistableBundle(String key) {
+ @Nullable
+ public PersistableBundle getPersistableBundle(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -240,8 +245,9 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
(event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
if (event == XmlPullParser.START_TAG) {
- return new PersistableBundle((Map<String, Object>)
- XmlUtils.readThisMapXml(in, startTag, tagName, new MyReadMapCallback()));
+ return new PersistableBundle((ArrayMap<String, Object>)
+ XmlUtils.readThisArrayMapXml(in, startTag, tagName,
+ new MyReadMapCallback()));
}
}
return EMPTY;
diff --git a/core/java/android/os/PooledStringReader.java b/core/java/android/os/PooledStringReader.java
new file mode 100644
index 0000000..7795957
--- /dev/null
+++ b/core/java/android/os/PooledStringReader.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Helper class for reading pooling strings from a Parcel. It must be used
+ * in conjunction with {@link android.os.PooledStringWriter}. This really needs
+ * to be pushed in to Parcel itself, but doing that is... complicated.
+ * @hide
+ */
+public class PooledStringReader {
+ private final Parcel mIn;
+
+ /**
+ * The pool of strings we have collected so far.
+ */
+ private final String[] mPool;
+
+ public PooledStringReader(Parcel in) {
+ mIn = in;
+ final int size = in.readInt();
+ mPool = new String[size];
+ }
+
+ public String readString() {
+ int idx = mIn.readInt();
+ if (idx >= 0) {
+ return mPool[idx];
+ } else {
+ idx = (-idx) - 1;
+ String str = mIn.readString();
+ mPool[idx] = str;
+ return str;
+ }
+ }
+}
diff --git a/core/java/android/os/PooledStringWriter.java b/core/java/android/os/PooledStringWriter.java
new file mode 100644
index 0000000..eac297d
--- /dev/null
+++ b/core/java/android/os/PooledStringWriter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import java.util.HashMap;
+
+/**
+ * Helper class for writing pooled strings into a Parcel. It must be used
+ * in conjunction with {@link android.os.PooledStringReader}. This really needs
+ * to be pushed in to Parcel itself, but doing that is... complicated.
+ * @hide
+ */
+public class PooledStringWriter {
+ private final Parcel mOut;
+
+ /**
+ * Book-keeping for writing pooled string objects, mapping strings we have
+ * written so far to their index in the pool. We deliberately use HashMap
+ * here since performance is critical, we expect to be doing lots of adds to
+ * it, and it is only a temporary object so its overall memory footprint is
+ * not a signifciant issue.
+ */
+ private final HashMap<String, Integer> mPool;
+
+ /**
+ * Book-keeping for writing pooling string objects, indicating where we
+ * started writing the pool, which is where we need to ultimately store
+ * how many strings are in the pool.
+ */
+ private int mStart;
+
+ /**
+ * Next available index in the pool.
+ */
+ private int mNext;
+
+ public PooledStringWriter(Parcel out) {
+ mOut = out;
+ mPool = new HashMap<>();
+ mStart = out.dataPosition();
+ out.writeInt(0); // reserve space for final pool size.
+ }
+
+ public void writeString(String str) {
+ final Integer cur = mPool.get(str);
+ if (cur != null) {
+ mOut.writeInt(cur);
+ } else {
+ mPool.put(str, mNext);
+ mOut.writeInt(-(mNext+1));
+ mOut.writeString(str);
+ mNext++;
+ }
+ }
+
+ public void finish() {
+ final int pos = mOut.dataPosition();
+ mOut.setDataPosition(mStart);
+ mOut.writeInt(mNext);
+ mOut.setDataPosition(pos);
+ }
+}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 5018711..0b55998 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -145,7 +145,7 @@ public final class StrictMode {
* in {@link VmPolicy.Builder#detectAll()}. Apps can still always opt-into
* detection using {@link VmPolicy.Builder#detectCleartextNetwork()}.
*/
- private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.nonssl";
+ private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
// Only log a duplicate stack trace to the logs every second.
private static final long MIN_LOG_INTERVAL_MS = 1000;
@@ -184,8 +184,16 @@ public final class StrictMode {
*/
public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
+ /**
+ * For StrictMode.noteResourceMismatch()
+ *
+ * @hide
+ */
+ public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
+
private static final int ALL_THREAD_DETECT_BITS =
- DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM;
+ DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM |
+ DETECT_RESOURCE_MISMATCH;
// Byte 2: Process-policy
@@ -460,6 +468,22 @@ public final class StrictMode {
}
/**
+ * Disable detection of mismatches between defined resource types
+ * and getter calls.
+ */
+ public Builder permitResourceMismatches() {
+ return disable(DETECT_RESOURCE_MISMATCH);
+ }
+
+ /**
+ * Enable detection of mismatches between defined resource types
+ * and getter calls.
+ */
+ public Builder detectResourceMismatches() {
+ return enable(DETECT_RESOURCE_MISMATCH);
+ }
+
+ /**
* Enable detection of disk writes.
*/
public Builder detectDiskWrites() {
@@ -739,8 +763,6 @@ public final class StrictMode {
* This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it
* may be subject to false positives, such as when STARTTLS
* protocols or HTTP proxies are used.
- *
- * @hide
*/
public Builder detectCleartextNetwork() {
return enable(DETECT_VM_CLEARTEXT_NETWORK);
@@ -760,7 +782,6 @@ public final class StrictMode {
* detected.
*
* @see #detectCleartextNetwork()
- * @hide
*/
public Builder penaltyDeathOnCleartextNetwork() {
return enable(PENALTY_DEATH_ON_CLEARTEXT_NETWORK);
@@ -923,6 +944,15 @@ public final class StrictMode {
}
/**
+ * @hide
+ */
+ private static class StrictModeResourceMismatchViolation extends StrictModeViolation {
+ public StrictModeResourceMismatchViolation(int policyMask, Object tag) {
+ super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null);
+ }
+ }
+
+ /**
* Returns the bitmask of the current thread's policy.
*
* @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled
@@ -1197,6 +1227,20 @@ public final class StrictMode {
startHandlingViolationException(e);
}
+ // Not part of BlockGuard.Policy; just part of StrictMode:
+ void onResourceMismatch(Object tag) {
+ if ((mPolicyMask & DETECT_RESOURCE_MISMATCH) == 0) {
+ return;
+ }
+ if (tooManyViolationsThisLoop()) {
+ return;
+ }
+ BlockGuard.BlockGuardPolicyException e =
+ new StrictModeResourceMismatchViolation(mPolicyMask, tag);
+ e.fillInStackTrace();
+ startHandlingViolationException(e);
+ }
+
// Part of BlockGuard.Policy interface:
public void onReadFromDisk() {
if ((mPolicyMask & DETECT_DISK_READ) == 0) {
@@ -2083,6 +2127,26 @@ public final class StrictMode {
}
/**
+ * For code to note that a resource was obtained using a type other than
+ * its defined type. This is a no-op unless the current thread's
+ * {@link android.os.StrictMode.ThreadPolicy} has
+ * {@link android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()}
+ * enabled.
+ *
+ * @param tag an object for the exception stack trace that's
+ * built if when this fires.
+ * @hide
+ */
+ public static void noteResourceMismatch(Object tag) {
+ BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+ if (!(policy instanceof AndroidBlockGuardPolicy)) {
+ // StrictMode not enabled.
+ return;
+ }
+ ((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag);
+ }
+
+ /**
* @hide
*/
public static void noteDiskRead() {
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 74e064e..6874e77 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -20,7 +20,6 @@ import android.annotation.SystemApi;
import android.util.SparseArray;
import java.io.PrintWriter;
-import java.util.HashMap;
/**
* Representation of a user on the device.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d124a49..3601a1c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.provider.Settings;
@@ -30,6 +31,7 @@ import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -114,6 +116,7 @@ public class UserManager {
/**
* Specifies if a user is disallowed from configuring bluetooth.
+ * This does <em>not</em> restrict the user from turning bluetooth on or off.
* The default value is <code>false</code>.
* <p/>This restriction has no effect in a managed profile.
*
@@ -388,6 +391,27 @@ public class UserManager {
public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
/**
+ * Hidden user restriction to disallow access to wallpaper manager APIs. This user restriction
+ * is always set for managed profiles.
+ * @hide
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_WALLPAPER = "no_wallpaper";
+
+ /**
+ * Specifies if the user is not allowed to reboot the device into safe boot mode.
+ * This can only be set by device owners and profile owners on the primary user.
+ * The default value is <code>false</code>.
+ *
+ * <p/>Key for user restrictions.
+ * <p/>Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_SAFE_BOOT = "no_safe_boot";
+
+ /**
* Application restriction key that is used to indicate the pending arrival
* of real restrictions for the app.
*
@@ -1083,11 +1107,21 @@ public class UserManager {
*/
public Bitmap getUserIcon(int userHandle) {
try {
- return mService.getUserIcon(userHandle);
+ ParcelFileDescriptor fd = mService.getUserIcon(userHandle);
+ if (fd != null) {
+ try {
+ return BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor());
+ } finally {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ }
+ }
} catch (RemoteException re) {
Log.w(TAG, "Could not get the user icon ", re);
- return null;
}
+ return null;
}
/**
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
index b65eac7..3d57b4d 100644
--- a/core/java/android/preference/DialogPreference.java
+++ b/core/java/android/preference/DialogPreference.java
@@ -17,6 +17,9 @@
package android.preference;
+import android.annotation.CallSuper;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
@@ -168,7 +171,7 @@ public abstract class DialogPreference extends Preference implements
*
* @param dialogIconRes The icon, as a resource ID.
*/
- public void setDialogIcon(int dialogIconRes) {
+ public void setDialogIcon(@DrawableRes int dialogIconRes) {
mDialogIcon = getContext().getDrawable(dialogIconRes);
}
@@ -194,7 +197,7 @@ public abstract class DialogPreference extends Preference implements
* @see #setPositiveButtonText(CharSequence)
* @param positiveButtonTextResId The positive button text as a resource.
*/
- public void setPositiveButtonText(int positiveButtonTextResId) {
+ public void setPositiveButtonText(@StringRes int positiveButtonTextResId) {
setPositiveButtonText(getContext().getString(positiveButtonTextResId));
}
@@ -222,7 +225,7 @@ public abstract class DialogPreference extends Preference implements
* @see #setNegativeButtonText(CharSequence)
* @param negativeButtonTextResId The negative button text as a resource.
*/
- public void setNegativeButtonText(int negativeButtonTextResId) {
+ public void setNegativeButtonText(@StringRes int negativeButtonTextResId) {
setNegativeButtonText(getContext().getString(negativeButtonTextResId));
}
@@ -358,6 +361,7 @@ public abstract class DialogPreference extends Preference implements
*
* @param view The content View of the dialog, if it is custom.
*/
+ @CallSuper
protected void onBindDialogView(View view) {
View dialogMessageView = view.findViewById(com.android.internal.R.id.message);
diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java
index 7de7d1c..918933b 100644
--- a/core/java/android/preference/GenericInflater.java
+++ b/core/java/android/preference/GenericInflater.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.XmlRes;
import android.content.Context;
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
@@ -216,7 +217,7 @@ abstract class GenericInflater<T, P extends GenericInflater.Parent> {
* this is the root item; otherwise it is the root of the inflated
* XML file.
*/
- public T inflate(int resource, P root) {
+ public T inflate(@XmlRes int resource, P root) {
return inflate(resource, root, root != null);
}
@@ -256,7 +257,7 @@ abstract class GenericInflater<T, P extends GenericInflater.Parent> {
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
- public T inflate(int resource, P root, boolean attachToRoot) {
+ public T inflate(@XmlRes int resource, P root, boolean attachToRoot) {
if (DEBUG) System.out.println("INFLATING from resource: " + resource);
XmlResourceParser parser = getContext().getResources().getXml(resource);
try {
diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java
index 9482a72..2700373 100644
--- a/core/java/android/preference/ListPreference.java
+++ b/core/java/android/preference/ListPreference.java
@@ -16,6 +16,7 @@
package android.preference;
+import android.annotation.ArrayRes;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
@@ -91,7 +92,7 @@ public class ListPreference extends DialogPreference {
* @see #setEntries(CharSequence[])
* @param entriesResId The entries array as a resource.
*/
- public void setEntries(int entriesResId) {
+ public void setEntries(@ArrayRes int entriesResId) {
setEntries(getContext().getResources().getTextArray(entriesResId));
}
@@ -119,7 +120,7 @@ public class ListPreference extends DialogPreference {
* @see #setEntryValues(CharSequence[])
* @param entryValuesResId The entry values array as a resource.
*/
- public void setEntryValues(int entryValuesResId) {
+ public void setEntryValues(@ArrayRes int entryValuesResId) {
setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
}
diff --git a/core/java/android/preference/MultiCheckPreference.java b/core/java/android/preference/MultiCheckPreference.java
index 57c906d..c1260a4 100644
--- a/core/java/android/preference/MultiCheckPreference.java
+++ b/core/java/android/preference/MultiCheckPreference.java
@@ -18,6 +18,7 @@ package android.preference;
import java.util.Arrays;
+import android.annotation.ArrayRes;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
@@ -96,7 +97,7 @@ public class MultiCheckPreference extends DialogPreference {
* @see #setEntries(CharSequence[])
* @param entriesResId The entries array as a resource.
*/
- public void setEntries(int entriesResId) {
+ public void setEntries(@ArrayRes int entriesResId) {
setEntries(getContext().getResources().getTextArray(entriesResId));
}
@@ -126,7 +127,7 @@ public class MultiCheckPreference extends DialogPreference {
* @see #setEntryValues(CharSequence[])
* @param entryValuesResId The entry values array as a resource.
*/
- public void setEntryValues(int entryValuesResId) {
+ public void setEntryValues(@ArrayRes int entryValuesResId) {
setEntryValuesCS(getContext().getResources().getTextArray(entryValuesResId));
}
diff --git a/core/java/android/preference/MultiSelectListPreference.java b/core/java/android/preference/MultiSelectListPreference.java
index 6c4c20f..138bd87 100644
--- a/core/java/android/preference/MultiSelectListPreference.java
+++ b/core/java/android/preference/MultiSelectListPreference.java
@@ -16,6 +16,7 @@
package android.preference;
+import android.annotation.ArrayRes;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
@@ -87,7 +88,7 @@ public class MultiSelectListPreference extends DialogPreference {
* @see #setEntries(CharSequence[])
* @param entriesResId The entries array as a resource.
*/
- public void setEntries(int entriesResId) {
+ public void setEntries(@ArrayRes int entriesResId) {
setEntries(getContext().getResources().getTextArray(entriesResId));
}
@@ -115,7 +116,7 @@ public class MultiSelectListPreference extends DialogPreference {
* @see #setEntryValues(CharSequence[])
* @param entryValuesResId The entry values array as a resource.
*/
- public void setEntryValues(int entryValuesResId) {
+ public void setEntryValues(@ArrayRes int entryValuesResId) {
setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 0224c73..ccf2cfa 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -16,8 +16,12 @@
package android.preference;
+import android.annotation.CallSuper;
import com.android.internal.util.CharSequences;
+import android.annotation.DrawableRes;
+import android.annotation.LayoutRes;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -424,7 +428,7 @@ public class Preference implements Comparable<Preference> {
* a {@link View}.
* @see #setWidgetLayoutResource(int)
*/
- public void setLayoutResource(int layoutResId) {
+ public void setLayoutResource(@LayoutRes int layoutResId) {
if (layoutResId != mLayoutResId) {
// Layout changed
mCanRecycleLayout = false;
@@ -438,6 +442,7 @@ public class Preference implements Comparable<Preference> {
*
* @return The layout resource ID.
*/
+ @LayoutRes
public int getLayoutResource() {
return mLayoutResId;
}
@@ -452,7 +457,7 @@ public class Preference implements Comparable<Preference> {
* main layout.
* @see #setLayoutResource(int)
*/
- public void setWidgetLayoutResource(int widgetLayoutResId) {
+ public void setWidgetLayoutResource(@LayoutRes int widgetLayoutResId) {
if (widgetLayoutResId != mWidgetLayoutResId) {
// Layout changed
mCanRecycleLayout = false;
@@ -465,6 +470,7 @@ public class Preference implements Comparable<Preference> {
*
* @return The layout resource ID.
*/
+ @LayoutRes
public int getWidgetLayoutResource() {
return mWidgetLayoutResId;
}
@@ -503,6 +509,7 @@ public class Preference implements Comparable<Preference> {
* @return The View that displays this Preference.
* @see #onBindView(View)
*/
+ @CallSuper
protected View onCreateView(ViewGroup parent) {
final LayoutInflater layoutInflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -532,6 +539,7 @@ public class Preference implements Comparable<Preference> {
* @param view The View that shows this Preference.
* @see #onCreateView(ViewGroup)
*/
+ @CallSuper
protected void onBindView(View view) {
final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title);
if (titleView != null) {
@@ -648,7 +656,7 @@ public class Preference implements Comparable<Preference> {
* @see #setTitle(CharSequence)
* @param titleResId The title as a resource ID.
*/
- public void setTitle(int titleResId) {
+ public void setTitle(@StringRes int titleResId) {
setTitle(mContext.getString(titleResId));
mTitleRes = titleResId;
}
@@ -660,6 +668,7 @@ public class Preference implements Comparable<Preference> {
* @return The title resource.
* @see #setTitle(int)
*/
+ @StringRes
public int getTitleRes() {
return mTitleRes;
}
@@ -696,7 +705,7 @@ public class Preference implements Comparable<Preference> {
* @see #setIcon(Drawable)
* @param iconResId The icon as a resource ID.
*/
- public void setIcon(int iconResId) {
+ public void setIcon(@DrawableRes int iconResId) {
mIconResId = iconResId;
setIcon(mContext.getDrawable(iconResId));
}
@@ -739,7 +748,7 @@ public class Preference implements Comparable<Preference> {
* @see #setSummary(CharSequence)
* @param summaryResId The summary as a resource.
*/
- public void setSummary(int summaryResId) {
+ public void setSummary(@StringRes int summaryResId) {
setSummary(mContext.getString(summaryResId));
}
@@ -1350,6 +1359,7 @@ public class Preference implements Comparable<Preference> {
* should remove any references to this Preference that you know about. Make
* sure to call through to the superclass implementation.
*/
+ @CallSuper
protected void onPrepareForRemoval() {
unregisterDependency();
}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 04cd7d5..06666f4 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -16,6 +16,9 @@
package android.preference;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.XmlRes;
import android.app.Fragment;
import android.app.FragmentBreadCrumbs;
import android.app.FragmentManager;
@@ -337,6 +340,7 @@ public abstract class PreferenceActivity extends ListActivity implements
* Resource ID of title of the header that is shown to the user.
* @attr ref android.R.styleable#PreferenceHeader_title
*/
+ @StringRes
public int titleRes;
/**
@@ -349,6 +353,7 @@ public abstract class PreferenceActivity extends ListActivity implements
* Resource ID of optional summary describing what this header controls.
* @attr ref android.R.styleable#PreferenceHeader_summary
*/
+ @StringRes
public int summaryRes;
/**
@@ -361,6 +366,7 @@ public abstract class PreferenceActivity extends ListActivity implements
* Resource ID of optional text to show as the title in the bread crumb.
* @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
*/
+ @StringRes
public int breadCrumbTitleRes;
/**
@@ -373,6 +379,7 @@ public abstract class PreferenceActivity extends ListActivity implements
* Resource ID of optional text to show as the short title in the bread crumb.
* @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
*/
+ @StringRes
public int breadCrumbShortTitleRes;
/**
@@ -525,7 +532,7 @@ public abstract class PreferenceActivity extends ListActivity implements
}
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Theming for the PreferenceActivity layout and for the Preference Header(s) layout
@@ -797,7 +804,7 @@ public abstract class PreferenceActivity extends ListActivity implements
* @param resid The XML resource to load and parse.
* @param target The list in which the parsed headers should be placed.
*/
- public void loadHeadersFromResource(int resid, List<Header> target) {
+ public void loadHeadersFromResource(@XmlRes int resid, List<Header> target) {
XmlResourceParser parser = null;
try {
parser = getResources().getXml(resid);
@@ -1086,7 +1093,7 @@ public abstract class PreferenceActivity extends ListActivity implements
* fragment.
*/
public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
- int titleRes, int shortTitleRes) {
+ @StringRes int titleRes, int shortTitleRes) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClass(this, getClass());
intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
@@ -1124,7 +1131,8 @@ public abstract class PreferenceActivity extends ListActivity implements
* this set of preferences.
*/
public void startWithFragment(String fragmentName, Bundle args,
- Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
+ Fragment resultTo, int resultRequestCode, @StringRes int titleRes,
+ @StringRes int shortTitleRes) {
Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
if (resultTo == null) {
startActivity(intent);
@@ -1343,9 +1351,9 @@ public abstract class PreferenceActivity extends ListActivity implements
* preference panel is done. The launched panel must use
* {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
* @param resultRequestCode If resultTo is non-null, this is the caller's
- * request code to be received with the resut.
+ * request code to be received with the result.
*/
- public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
+ public void startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes,
CharSequence titleText, Fragment resultTo, int resultRequestCode) {
if (mSinglePane) {
startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index e95e6e2..66642de 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -16,6 +16,8 @@
package android.preference;
+import android.annotation.Nullable;
+import android.annotation.XmlRes;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
@@ -153,15 +155,15 @@ public abstract class PreferenceFragment extends Fragment implements
}
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
mPreferenceManager.setFragment(this);
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
TypedArray a = getActivity().obtainStyledAttributes(null,
com.android.internal.R.styleable.PreferenceFragment,
@@ -177,7 +179,7 @@ public abstract class PreferenceFragment extends Fragment implements
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mHavePrefs) {
@@ -293,7 +295,7 @@ public abstract class PreferenceFragment extends Fragment implements
*
* @param preferencesResId The XML resource ID to inflate.
*/
- public void addPreferencesFromResource(int preferencesResId) {
+ public void addPreferencesFromResource(@XmlRes int preferencesResId) {
requirePreferenceManager();
setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index 2d35b1b..5e84086 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -19,12 +19,9 @@ package android.preference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
-import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index 0a0e625..55ee77a 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -16,6 +16,7 @@
package android.preference;
+import android.annotation.XmlRes;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
@@ -263,7 +264,7 @@ public class PreferenceManager {
* root).
* @hide
*/
- public PreferenceScreen inflateFromResource(Context context, int resId,
+ public PreferenceScreen inflateFromResource(Context context, @XmlRes int resId,
PreferenceScreen rootPreferences) {
// Block commits
setNoCommit(true);
@@ -438,7 +439,7 @@ public class PreferenceManager {
* and clear it followed by a call to this method with this
* parameter set to true.
*/
- public static void setDefaultValues(Context context, int resId, boolean readAgain) {
+ public static void setDefaultValues(Context context, @XmlRes int resId, boolean readAgain) {
// Use the default shared preferences name and mode
setDefaultValues(context, getDefaultSharedPreferencesName(context),
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index c3dd4ce..30da0e7 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -174,6 +175,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
if (mRingtone != null) {
try {
+ mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
+ .getAudioAttributes())
+ .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
+ AudioAttributes.FLAG_BYPASS_MUTE)
+ .build());
mRingtone.play();
} catch (Throwable e) {
Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 53b5aad..9c3cefc 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -16,6 +16,7 @@
package android.preference;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@@ -169,7 +170,7 @@ public class SwitchPreference extends TwoStatePreference {
*
* @param resId The text as a string resource ID
*/
- public void setSwitchTextOn(int resId) {
+ public void setSwitchTextOn(@StringRes int resId) {
setSwitchTextOn(getContext().getString(resId));
}
@@ -179,7 +180,7 @@ public class SwitchPreference extends TwoStatePreference {
*
* @param resId The text as a string resource ID
*/
- public void setSwitchTextOff(int resId) {
+ public void setSwitchTextOff(@StringRes int resId) {
setSwitchTextOff(getContext().getString(resId));
}
diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java
index 3823b27..7037aca 100644
--- a/core/java/android/preference/TwoStatePreference.java
+++ b/core/java/android/preference/TwoStatePreference.java
@@ -16,6 +16,7 @@
package android.preference;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
@@ -116,7 +117,7 @@ public abstract class TwoStatePreference extends Preference {
* @see #setSummaryOn(CharSequence)
* @param summaryResId The summary as a resource.
*/
- public void setSummaryOn(int summaryResId) {
+ public void setSummaryOn(@StringRes int summaryResId) {
setSummaryOn(getContext().getString(summaryResId));
}
@@ -144,7 +145,7 @@ public abstract class TwoStatePreference extends Preference {
* @see #setSummaryOff(CharSequence)
* @param summaryResId The summary as a resource.
*/
- public void setSummaryOff(int summaryResId) {
+ public void setSummaryOff(@StringRes int summaryResId) {
setSummaryOff(getContext().getString(summaryResId));
}
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 30f0c6a..90d30d6 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -45,11 +45,22 @@ public final class PrintAttributes implements Parcelable {
private static final int VALID_COLOR_MODES =
COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR;
+ /** Duplex mode: No duplexing. */
+ public static final int DUPLEX_MODE_NONE = 1 << 0;
+ /** Duplex mode: Pages are turned sideways along the long edge - like a book. */
+ public static final int DUPLEX_MODE_LONG_EDGE = 1 << 1;
+ /** Duplex mode: Pages are turned upwards along the short edge - like a notpad. */
+ public static final int DUPLEX_MODE_SHORT_EDGE = 1 << 2;
+
+ private static final int VALID_DUPLEX_MODES =
+ DUPLEX_MODE_NONE | DUPLEX_MODE_LONG_EDGE | DUPLEX_MODE_SHORT_EDGE;
+
private MediaSize mMediaSize;
private Resolution mResolution;
private Margins mMinMargins;
private int mColorMode;
+ private int mDuplexMode = DUPLEX_MODE_NONE;
PrintAttributes() {
/* hide constructor */
@@ -60,6 +71,7 @@ public final class PrintAttributes implements Parcelable {
mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null;
mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
mColorMode = parcel.readInt();
+ mDuplexMode = parcel.readInt();
}
/**
@@ -74,7 +86,7 @@ public final class PrintAttributes implements Parcelable {
/**
* Sets the media size.
*
- * @param The media size.
+ * @param mediaSize The media size.
*
* @hide
*/
@@ -94,7 +106,7 @@ public final class PrintAttributes implements Parcelable {
/**
* Sets the resolution.
*
- * @param The resolution.
+ * @param resolution The resolution.
*
* @hide
*/
@@ -130,7 +142,7 @@ public final class PrintAttributes implements Parcelable {
* </strong>
* </p>
*
- * @param The margins.
+ * @param margins The margins.
*
* @hide
*/
@@ -153,7 +165,7 @@ public final class PrintAttributes implements Parcelable {
/**
* Sets the color mode.
*
- * @param The color mode.
+ * @param colorMode The color mode.
*
* @see #COLOR_MODE_MONOCHROME
* @see #COLOR_MODE_COLOR
@@ -179,6 +191,35 @@ public final class PrintAttributes implements Parcelable {
}
/**
+ * Gets the duplex mode.
+ *
+ * @return The duplex mode.
+ *
+ * @see #DUPLEX_MODE_NONE
+ * @see #DUPLEX_MODE_LONG_EDGE
+ * @see #DUPLEX_MODE_SHORT_EDGE
+ */
+ public int getDuplexMode() {
+ return mDuplexMode;
+ }
+
+ /**
+ * Sets the duplex mode.
+ *
+ * @param duplexMode The duplex mode.
+ *
+ * @see #DUPLEX_MODE_NONE
+ * @see #DUPLEX_MODE_LONG_EDGE
+ * @see #DUPLEX_MODE_SHORT_EDGE
+ *
+ * @hide
+ */
+ public void setDuplexMode(int duplexMode) {
+ enforceValidDuplexMode(duplexMode);
+ mDuplexMode = duplexMode;
+ }
+
+ /**
* Gets a new print attributes instance which is in portrait orientation,
* which is the media size is in portrait and all orientation dependent
* attributes such as resolution and margins are properly adjusted.
@@ -211,6 +252,7 @@ public final class PrintAttributes implements Parcelable {
attributes.setMinMargins(getMinMargins());
attributes.setColorMode(getColorMode());
+ attributes.setDuplexMode(getDuplexMode());
return attributes;
}
@@ -248,6 +290,7 @@ public final class PrintAttributes implements Parcelable {
attributes.setMinMargins(getMinMargins());
attributes.setColorMode(getColorMode());
+ attributes.setDuplexMode(getDuplexMode());
return attributes;
}
@@ -273,6 +316,7 @@ public final class PrintAttributes implements Parcelable {
parcel.writeInt(0);
}
parcel.writeInt(mColorMode);
+ parcel.writeInt(mDuplexMode);
}
@Override
@@ -285,6 +329,7 @@ public final class PrintAttributes implements Parcelable {
final int prime = 31;
int result = 1;
result = prime * result + mColorMode;
+ result = prime * result + mDuplexMode;
result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode());
result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode());
@@ -306,6 +351,9 @@ public final class PrintAttributes implements Parcelable {
if (mColorMode != other.mColorMode) {
return false;
}
+ if (mDuplexMode != other.mDuplexMode) {
+ return false;
+ }
if (mMinMargins == null) {
if (other.mMinMargins != null) {
return false;
@@ -344,6 +392,7 @@ public final class PrintAttributes implements Parcelable {
builder.append(", resolution: ").append(mResolution);
builder.append(", minMargins: ").append(mMinMargins);
builder.append(", colorMode: ").append(colorModeToString(mColorMode));
+ builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
builder.append("}");
return builder.toString();
}
@@ -354,6 +403,7 @@ public final class PrintAttributes implements Parcelable {
mResolution = null;
mMinMargins = null;
mColorMode = 0;
+ mDuplexMode = DUPLEX_MODE_NONE;
}
/**
@@ -364,6 +414,7 @@ public final class PrintAttributes implements Parcelable {
mResolution = other.mResolution;
mMinMargins = other.mMinMargins;
mColorMode = other.mColorMode;
+ mDuplexMode = other.mDuplexMode;
}
/**
@@ -1270,17 +1321,41 @@ public final class PrintAttributes implements Parcelable {
case COLOR_MODE_COLOR: {
return "COLOR_MODE_COLOR";
}
- default:
+ default: {
return "COLOR_MODE_UNKNOWN";
+ }
+ }
+ }
+
+ static String duplexModeToString(int duplexMode) {
+ switch (duplexMode) {
+ case DUPLEX_MODE_NONE: {
+ return "DUPLEX_MODE_NONE";
+ }
+ case DUPLEX_MODE_LONG_EDGE: {
+ return "DUPLEX_MODE_LONG_EDGE";
+ }
+ case DUPLEX_MODE_SHORT_EDGE: {
+ return "DUPLEX_MODE_SHORT_EDGE";
+ }
+ default: {
+ return "DUPLEX_MODE_UNKNOWN";
+ }
}
}
static void enforceValidColorMode(int colorMode) {
- if ((colorMode & VALID_COLOR_MODES) == 0 && Integer.bitCount(colorMode) == 1) {
+ if ((colorMode & VALID_COLOR_MODES) == 0 || Integer.bitCount(colorMode) != 1) {
throw new IllegalArgumentException("invalid color mode: " + colorMode);
}
}
+ static void enforceValidDuplexMode(int duplexMode) {
+ if ((duplexMode & VALID_DUPLEX_MODES) == 0 || Integer.bitCount(duplexMode) != 1) {
+ throw new IllegalArgumentException("invalid duplex mode: " + duplexMode);
+ }
+ }
+
/**
* Builder for creating {@link PrintAttributes}.
*/
@@ -1331,15 +1406,31 @@ public final class PrintAttributes implements Parcelable {
* @see PrintAttributes#COLOR_MODE_COLOR
*/
public Builder setColorMode(int colorMode) {
- if (Integer.bitCount(colorMode) > 1) {
- throw new IllegalArgumentException("can specify at most one colorMode bit.");
- }
mAttributes.setColorMode(colorMode);
return this;
}
/**
+ * Sets the duplex mode.
+ *
+ * @param duplexMode A valid duplex mode or zero.
+ * @return This builder.
+ *
+ * @see PrintAttributes#DUPLEX_MODE_NONE
+ * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
+ * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
+ */
+ public Builder setDuplexMode(int duplexMode) {
+ mAttributes.setDuplexMode(duplexMode);
+ return this;
+ }
+
+ /**
* Creates a new {@link PrintAttributes} instance.
+ * <p>
+ * If you do not specify a duplex mode, the default
+ * {@link #DUPLEX_MODE_NONE} will be used.
+ * </p>
*
* @return The new instance.
*/
diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java
index 806a89d..96f3185 100644
--- a/core/java/android/print/PrinterCapabilitiesInfo.java
+++ b/core/java/android/print/PrinterCapabilitiesInfo.java
@@ -47,7 +47,8 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
private static final int PROPERTY_MEDIA_SIZE = 0;
private static final int PROPERTY_RESOLUTION = 1;
private static final int PROPERTY_COLOR_MODE = 2;
- private static final int PROPERTY_COUNT = 3;
+ private static final int PROPERTY_DUPLEX_MODE = 3;
+ private static final int PROPERTY_COUNT = 4;
private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0);
@@ -56,6 +57,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
private List<Resolution> mResolutions;
private int mColorModes;
+ private int mDuplexModes;
private final int[] mDefaults = new int[PROPERTY_COUNT];
@@ -106,6 +108,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
}
mColorModes = other.mColorModes;
+ mDuplexModes = other.mDuplexModes;
final int defaultCount = other.mDefaults.length;
for (int i = 0; i < defaultCount; i++) {
@@ -154,6 +157,19 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
}
/**
+ * Gets the bit mask of supported duplex modes.
+ *
+ * @return The bit mask of supported duplex modes.
+ *
+ * @see PrintAttributes#DUPLEX_MODE_NONE
+ * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
+ * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
+ */
+ public int getDuplexModes() {
+ return mDuplexModes;
+ }
+
+ /**
* Gets the default print attributes.
*
* @return The default attributes.
@@ -178,6 +194,11 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
builder.setColorMode(colorMode);
}
+ final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE];
+ if (duplexMode > 0) {
+ builder.setDuplexMode(duplexMode);
+ }
+
return builder.build();
}
@@ -187,6 +208,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
readResolutions(parcel);
mColorModes = parcel.readInt();
+ mDuplexModes = parcel.readInt();
readDefaults(parcel);
}
@@ -203,6 +225,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
writeResolutions(parcel);
parcel.writeInt(mColorModes);
+ parcel.writeInt(mDuplexModes);
writeDefaults(parcel);
}
@@ -215,6 +238,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode());
result = prime * result + mColorModes;
+ result = prime * result + mDuplexModes;
result = prime * result + Arrays.hashCode(mDefaults);
return result;
}
@@ -255,6 +279,9 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
if (mColorModes != other.mColorModes) {
return false;
}
+ if (mDuplexModes != other.mDuplexModes) {
+ return false;
+ }
if (!Arrays.equals(mDefaults, other.mDefaults)) {
return false;
}
@@ -269,6 +296,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
builder.append(", mediaSizes=").append(mMediaSizes);
builder.append(", resolutions=").append(mResolutions);
builder.append(", colorModes=").append(colorModesToString());
+ builder.append(", duplexModes=").append(duplexModesToString());
builder.append("\"}");
return builder.toString();
}
@@ -289,6 +317,22 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
return builder.toString();
}
+ private String duplexModesToString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append('[');
+ int duplexModes = mDuplexModes;
+ while (duplexModes != 0) {
+ final int duplexMode = 1 << Integer.numberOfTrailingZeros(duplexModes);
+ duplexModes &= ~duplexMode;
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append(PrintAttributes.duplexModeToString(duplexMode));
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+
private void writeMediaSizes(Parcel parcel) {
if (mMediaSizes == null) {
parcel.writeInt(0);
@@ -495,19 +539,51 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
currentModes &= ~currentMode;
PrintAttributes.enforceValidColorMode(currentMode);
}
- if ((colorModes & defaultColorMode) == 0) {
- throw new IllegalArgumentException("Default color mode not in color modes.");
- }
- PrintAttributes.enforceValidColorMode(colorModes);
+ PrintAttributes.enforceValidColorMode(defaultColorMode);
mPrototype.mColorModes = colorModes;
mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
return this;
}
/**
+ * Sets the duplex modes.
+ * <p>
+ * <strong>Required:</strong> No
+ * </p>
+ *
+ * @param duplexModes The duplex mode bit mask.
+ * @param defaultDuplexMode The default duplex mode.
+ * @return This builder.
+ *
+ * @throws IllegalArgumentException If duplex modes contains an invalid
+ * mode bit or if the default duplex mode is invalid.
+ *
+ * @see PrintAttributes#DUPLEX_MODE_NONE
+ * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
+ * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
+ */
+ public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) {
+ int currentModes = duplexModes;
+ while (currentModes > 0) {
+ final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
+ currentModes &= ~currentMode;
+ PrintAttributes.enforceValidDuplexMode(currentMode);
+ }
+ PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
+ mPrototype.mDuplexModes = duplexModes;
+ mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
+ return this;
+ }
+
+ /**
* Crates a new {@link PrinterCapabilitiesInfo} enforcing that all
* required properties have been specified. See individual methods
* in this class for reference about required attributes.
+ * <p>
+ * <strong>Note:</strong> If you do not add supported duplex modes,
+ * {@link android.print.PrintAttributes#DUPLEX_MODE_NONE} will set
+ * as the only supported mode and also as the default duplex mode.
+ * </p>
*
* @return A new {@link PrinterCapabilitiesInfo}.
*
@@ -532,6 +608,10 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
if (mPrototype.mDefaults[PROPERTY_COLOR_MODE] == DEFAULT_UNDEFINED) {
throw new IllegalStateException("No default color mode specified.");
}
+ if (mPrototype.mDuplexModes == 0) {
+ setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE,
+ PrintAttributes.DUPLEX_MODE_NONE);
+ }
if (mPrototype.mMinMargins == null) {
throw new IllegalArgumentException("margins cannot be null");
}
@@ -558,4 +638,3 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
}
};
}
-
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index ae95854..527c8ae 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -16,7 +16,6 @@
package android.printservice;
-import android.R;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -192,7 +191,7 @@ public abstract class PrintService extends Service {
/**
* If you declared an optional activity with advanced print options via the
- * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
+ * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
* attribute, this extra is used to pass in the currently constructed {@link
* PrintJobInfo} to your activity allowing you to modify it. After you are
* done, you must return the modified {@link PrintJobInfo} via the same extra.
@@ -224,7 +223,7 @@ public abstract class PrintService extends Service {
/**
* If you declared an optional activity with advanced print options via the
- * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
+ * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
* attribute, this extra is used to pass in the currently selected printer's
* {@link android.print.PrinterInfo} to your activity allowing you to inspect it.
*
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index 3853003..69a05c4 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -25,7 +25,6 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
import android.graphics.BitmapFactory;
import android.net.Uri;
-import android.os.Build;
import android.provider.BrowserContract.Bookmarks;
import android.provider.BrowserContract.Combined;
import android.provider.BrowserContract.History;
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index f023df7..6517f35 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -33,9 +33,12 @@ import android.provider.ContactsContract.CommonDataKinds.Callable;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.DataUsageFeedback;
+import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.PhoneConstants;
@@ -46,6 +49,8 @@ import java.util.List;
* The CallLog provider contains information about placed and received calls.
*/
public class CallLog {
+ private static final String LOG_TAG = "CallLog";
+
public static final String AUTHORITY = "call_log";
/**
@@ -323,6 +328,14 @@ public class CallLog {
public static final String CACHED_PHOTO_ID = "photo_id";
/**
+ * The cached photo URI of the picture associated with the phone number, if it exists.
+ * This value may not be current if the contact information associated with this number
+ * has changed.
+ * <P>Type: TEXT (URI)</P>
+ */
+ public static final String CACHED_PHOTO_URI = "photo_uri";
+
+ /**
* The cached phone number, formatted with formatting rules based on the country the
* user was in when the call was made or received.
* This value is not guaranteed to be present, and may not be current if the contact
@@ -336,24 +349,44 @@ public class CallLog {
// that was encoded into call log databases.
/**
- * The component name of the account in string form.
+ * The component name of the account used to place or receive the call; in string form.
* <P>Type: TEXT</P>
*/
public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
/**
- * The identifier of a account that is unique to a specified component.
+ * The identifier for the account used to place or receive the call.
* <P>Type: TEXT</P>
*/
public static final String PHONE_ACCOUNT_ID = "subscription_id";
/**
- * The identifier of a account that is unique to a specified component. Equivalent value
- * to {@link #PHONE_ACCOUNT_ID}. For ContactsProvider internal use only.
+ * The address associated with the account used to place or receive the call; in string
+ * form. For SIM-based calls, this is the user's own phone number.
+ * <P>Type: TEXT</P>
+ *
+ * @hide
+ */
+ public static final String PHONE_ACCOUNT_ADDRESS = "phone_account_address";
+
+ /**
+ * Indicates that the entry will be hidden from all queries until the associated
+ * {@link android.telecom.PhoneAccount} is registered with the system.
* <P>Type: INTEGER</P>
*
* @hide
*/
+ public static final String PHONE_ACCOUNT_HIDDEN = "phone_account_hidden";
+
+ /**
+ * The subscription ID used to place this call. This is no longer used and has been
+ * replaced with PHONE_ACCOUNT_COMPONENT_NAME/PHONE_ACCOUNT_ID.
+ * For ContactsProvider internal use only.
+ * <P>Type: INTEGER</P>
+ *
+ * @Deprecated
+ * @hide
+ */
public static final String SUB_ID = "sub_id";
/**
@@ -421,6 +454,29 @@ public class CallLog {
long start, int duration, Long dataUsage, boolean addForAllUsers) {
final ContentResolver resolver = context.getContentResolver();
int numberPresentation = PRESENTATION_ALLOWED;
+ boolean isHidden = false;
+
+ TelecomManager tm = null;
+ try {
+ tm = TelecomManager.from(context);
+ } catch (UnsupportedOperationException e) {}
+
+ String accountAddress = null;
+ if (tm != null && accountHandle != null) {
+ PhoneAccount account = tm.getPhoneAccount(accountHandle);
+ if (account != null) {
+ Uri address = account.getSubscriptionAddress();
+ if (address != null) {
+ accountAddress = address.getSchemeSpecificPart();
+ }
+ } else {
+ // We could not find the account through telecom. For call log entries that
+ // are added with a phone account which is not registered, we automatically
+ // mark them as hidden. They are unhidden once the account is registered.
+ Log.i(LOG_TAG, "Marking call log entry as hidden.");
+ isHidden = true;
+ }
+ }
// Remap network specified number presentation types
// PhoneConstants.PRESENTATION_xxx to calllog number presentation types
@@ -463,6 +519,8 @@ public class CallLog {
}
values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
values.put(PHONE_ACCOUNT_ID, accountId);
+ values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
+ values.put(PHONE_ACCOUNT_HIDDEN, Integer.valueOf(isHidden ? 1 : 0));
values.put(NEW, Integer.valueOf(1));
if (callType == MISSED_TYPE) {
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index d4c5cfb..3aa526d 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -2077,12 +2077,12 @@ public class Contacts {
/**
* Intents related to the Contacts app UI.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final class UI {
/**
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public UI() {
@@ -2090,76 +2090,77 @@ public class Contacts {
/**
* The action for the default contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
- public static final String LIST_DEFAULT = ContactsContract.Intents.UI.LIST_DEFAULT;
+ public static final String LIST_DEFAULT
+ = "com.android.contacts.action.LIST_DEFAULT";
/**
* The action for the contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_GROUP_ACTION =
- ContactsContract.Intents.UI.LIST_GROUP_ACTION;
+ "com.android.contacts.action.LIST_GROUP";
/**
* When in LIST_GROUP_ACTION mode, this is the group to display.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String GROUP_NAME_EXTRA_KEY =
- ContactsContract.Intents.UI.GROUP_NAME_EXTRA_KEY;
+ "com.android.contacts.extra.GROUP";
/**
* The action for the all contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_ALL_CONTACTS_ACTION =
- ContactsContract.Intents.UI.LIST_ALL_CONTACTS_ACTION;
+ "com.android.contacts.action.LIST_ALL_CONTACTS";
/**
* The action for the contacts with phone numbers list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_CONTACTS_WITH_PHONES_ACTION =
- ContactsContract.Intents.UI.LIST_CONTACTS_WITH_PHONES_ACTION;
+ "com.android.contacts.action.LIST_CONTACTS_WITH_PHONES";
/**
* The action for the starred contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_STARRED_ACTION =
- ContactsContract.Intents.UI.LIST_STARRED_ACTION;
+ "com.android.contacts.action.LIST_STARRED";
/**
* The action for the frequent contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_FREQUENT_ACTION =
- ContactsContract.Intents.UI.LIST_FREQUENT_ACTION;
+ "com.android.contacts.action.LIST_FREQUENT";
/**
* The action for the "strequent" contacts list tab. It first lists the starred
* contacts in alphabetical order and then the frequent contacts in descending
* order of the number of times they have been contacted.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_STREQUENT_ACTION =
- ContactsContract.Intents.UI.LIST_STREQUENT_ACTION;
+ "com.android.contacts.action.LIST_STREQUENT";
/**
* A key for to be used as an intent extra to set the activity
* title to a custom String value.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String TITLE_EXTRA_KEY =
- ContactsContract.Intents.UI.TITLE_EXTRA_KEY;
+ "com.android.contacts.extra.TITLE_EXTRA";
/**
* Activity Action: Display a filtered list of contacts
@@ -2168,20 +2169,20 @@ public class Contacts {
* filtering
* <p>
* Output: Nothing.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String FILTER_CONTACTS_ACTION =
- ContactsContract.Intents.UI.FILTER_CONTACTS_ACTION;
+ "com.android.contacts.action.FILTER_CONTACTS";
/**
* Used as an int extra field in {@link #FILTER_CONTACTS_ACTION}
* intents to supply the text on which to filter.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String FILTER_TEXT_EXTRA_KEY =
- ContactsContract.Intents.UI.FILTER_TEXT_EXTRA_KEY;
+ "com.android.contacts.extra.FILTER_TEXT";
}
/**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 18a9eb1..06862d7 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -197,12 +197,15 @@ public final class ContactsContract {
* API for obtaining a pre-authorized version of a URI that normally requires special
* permission (beyond READ_CONTACTS) to read. The caller obtaining the pre-authorized URI
* must already have the necessary permissions to access the URI; otherwise a
- * {@link SecurityException} will be thrown.
+ * {@link SecurityException} will be thrown. Unlike {@link Context#grantUriPermission},
+ * this can be used to grant permissions that aren't explicitly required for the URI inside
+ * AndroidManifest.xml. For example, permissions that are only required when reading URIs
+ * that refer to the user's profile.
* </p>
* <p>
* The authorized URI returned in the bundle contains an expiring token that allows the
* caller to execute the query without having the special permissions that would normally
- * be required.
+ * be required. The token expires in five minutes.
* </p>
* <p>
* This API does not access disk, and should be safe to invoke from the UI thread.
@@ -226,7 +229,6 @@ public final class ContactsContract {
* }
* </pre>
* </p>
- * @hide
*/
public static final class Authorization {
/**
@@ -1008,7 +1010,8 @@ public final class ContactsContract {
/**
* Types of data used to produce the display name for a contact. In the order
* of increasing priority: {@link #EMAIL}, {@link #PHONE},
- * {@link #ORGANIZATION}, {@link #NICKNAME}, {@link #STRUCTURED_NAME}.
+ * {@link #ORGANIZATION}, {@link #NICKNAME}, {@link #STRUCTURED_PHONETIC_NAME},
+ * {@link #STRUCTURED_NAME}.
*/
public interface DisplayNameSources {
public static final int UNDEFINED = 0;
@@ -1016,6 +1019,8 @@ public final class ContactsContract {
public static final int PHONE = 20;
public static final int ORGANIZATION = 30;
public static final int NICKNAME = 35;
+ /** Display name comes from a structured name that only has phonetic components. */
+ public static final int STRUCTURED_PHONETIC_NAME = 37;
public static final int STRUCTURED_NAME = 40;
}
@@ -1432,9 +1437,9 @@ public final class ContactsContract {
* and {@link #CONTENT_MULTI_VCARD_URI} to indicate that the returned
* vcard should not contain a photo.
*
- * @hide
+ * This is useful for obtaining a space efficient vcard.
*/
- public static final String QUERY_PARAMETER_VCARD_NO_PHOTO = "nophoto";
+ public static final String QUERY_PARAMETER_VCARD_NO_PHOTO = "no_photo";
/**
* Base {@link Uri} for referencing multiple {@link Contacts} entry,
@@ -1790,52 +1795,26 @@ public final class ContactsContract {
public static final String CONTENT_DIRECTORY = "suggestions";
/**
- * Used with {@link Builder#addParameter} to specify what kind of data is
- * supplied for the suggestion query.
+ * Used to specify what kind of data is supplied for the suggestion query.
*
* @hide
*/
public static final String PARAMETER_MATCH_NAME = "name";
/**
- * Used with {@link Builder#addParameter} to specify what kind of data is
- * supplied for the suggestion query.
- *
- * @hide
- */
- public static final String PARAMETER_MATCH_EMAIL = "email";
-
- /**
- * Used with {@link Builder#addParameter} to specify what kind of data is
- * supplied for the suggestion query.
- *
- * @hide
- */
- public static final String PARAMETER_MATCH_PHONE = "phone";
-
- /**
- * Used with {@link Builder#addParameter} to specify what kind of data is
- * supplied for the suggestion query.
- *
- * @hide
- */
- public static final String PARAMETER_MATCH_NICKNAME = "nickname";
-
- /**
* A convenience builder for aggregation suggestion content URIs.
- *
- * TODO: change documentation for this class to use the builder.
- * @hide
*/
public static final class Builder {
private long mContactId;
- private ArrayList<String> mKinds = new ArrayList<String>();
- private ArrayList<String> mValues = new ArrayList<String>();
+ private final ArrayList<String> mValues = new ArrayList<String>();
private int mLimit;
/**
* Optional existing contact ID. If it is not provided, the search
- * will be based exclusively on the values supplied with {@link #addParameter}.
+ * will be based exclusively on the values supplied with {@link #addNameParameter}.
+ *
+ * @param contactId contact to find aggregation suggestions for
+ * @return This Builder object to allow for chaining of calls to builder methods
*/
public Builder setContactId(long contactId) {
this.mContactId = contactId;
@@ -1843,28 +1822,31 @@ public final class ContactsContract {
}
/**
- * A value that can be used when searching for an aggregation
- * suggestion.
+ * Add a name to be used when searching for aggregation suggestions.
*
- * @param kind can be one of
- * {@link AggregationSuggestions#PARAMETER_MATCH_NAME},
- * {@link AggregationSuggestions#PARAMETER_MATCH_EMAIL},
- * {@link AggregationSuggestions#PARAMETER_MATCH_NICKNAME},
- * {@link AggregationSuggestions#PARAMETER_MATCH_PHONE}
+ * @param name name to find aggregation suggestions for
+ * @return This Builder object to allow for chaining of calls to builder methods
*/
- public Builder addParameter(String kind, String value) {
- if (!TextUtils.isEmpty(value)) {
- mKinds.add(kind);
- mValues.add(value);
- }
+ public Builder addNameParameter(String name) {
+ mValues.add(name);
return this;
}
+ /**
+ * Sets the Maximum number of suggested aggregations that should be returned.
+ * @param limit The maximum number of suggested aggregations
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods
+ */
public Builder setLimit(int limit) {
mLimit = limit;
return this;
}
+ /**
+ * Combine all of the options that have been set and return a new {@link Uri}
+ * object for fetching aggregation suggestions.
+ */
public Uri build() {
android.net.Uri.Builder builder = Contacts.CONTENT_URI.buildUpon();
builder.appendEncodedPath(String.valueOf(mContactId));
@@ -1873,9 +1855,10 @@ public final class ContactsContract {
builder.appendQueryParameter("limit", String.valueOf(mLimit));
}
- int count = mKinds.size();
+ int count = mValues.size();
for (int i = 0; i < count; i++) {
- builder.appendQueryParameter("query", mKinds.get(i) + ":" + mValues.get(i));
+ builder.appendQueryParameter("query", PARAMETER_MATCH_NAME
+ + ":" + mValues.get(i));
}
return builder.build();
@@ -2214,6 +2197,16 @@ public final class ContactsContract {
public static final String CONTACT_ID = "contact_id";
/**
+ * Persistent unique id for each raw_contact within its account.
+ * This id is provided by its own data source, and can be used to backup metadata
+ * to the server.
+ * This should be unique within each set of account_name/account_type/data_set
+ *
+ * @hide
+ */
+ public static final String BACKUP_ID = "backup_id";
+
+ /**
* The data set within the account that this row belongs to. This allows
* multiple sync adapters for the same account type to distinguish between
* each others' data.
@@ -2258,33 +2251,6 @@ public final class ContactsContract {
public static final String DELETED = "deleted";
/**
- * The "name_verified" flag: "1" means that the name fields on this raw
- * contact can be trusted and therefore should be used for the entire
- * aggregated contact.
- * <p>
- * If an aggregated contact contains more than one raw contact with a
- * verified name, one of those verified names is chosen at random.
- * If an aggregated contact contains no verified names, the
- * name is chosen randomly from the constituent raw contacts.
- * </p>
- * <p>
- * Updating this flag from "0" to "1" automatically resets it to "0" on
- * all other raw contacts in the same aggregated contact.
- * </p>
- * <p>
- * Sync adapters should only specify a value for this column when
- * inserting a raw contact and leave it out when doing an update.
- * </p>
- * <p>
- * The default value is "0"
- * </p>
- * <p>Type: INTEGER</p>
- *
- * @hide
- */
- public static final String NAME_VERIFIED = "name_verified";
-
- /**
* The "read-only" flag: "0" by default, "1" if the row cannot be modified or
* deleted except by a sync adapter. See {@link ContactsContract#CALLER_IS_SYNCADAPTER}.
* <P>Type: INTEGER</P>
@@ -2980,7 +2946,6 @@ public final class ContactsContract {
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DELETED);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, CONTACT_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, STARRED);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, NAME_VERIFIED);
android.content.Entity contact = new android.content.Entity(cv);
// read data rows until the contact id changes
@@ -4006,6 +3971,13 @@ public final class ContactsContract {
public static final String MIMETYPE = "mimetype";
/**
+ * Hash id on the data fields, used for backup and restore.
+ *
+ * @hide
+ */
+ public static final String HASH_ID = "hash_id";
+
+ /**
* A reference to the {@link RawContacts#_ID}
* that this data belongs to.
*/
@@ -4628,6 +4600,15 @@ public final class ContactsContract {
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
/**
+ * The content:// style URI for this table in managed profile, which requests a directory
+ * of data rows matching the selection criteria.
+ *
+ * @hide
+ */
+ static final Uri ENTERPRISE_CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "data_enterprise");
+
+ /**
* A boolean parameter for {@link Data#CONTENT_URI}.
* This specifies whether or not the returned data items should be filtered to show
* data items belonging to visible contacts only.
@@ -5013,14 +4994,16 @@ public final class ContactsContract {
"phone_lookup");
/**
- * URI used for the "enterprise caller-id".
+ * <p>URI used for the "enterprise caller-id".</p>
*
+ * <p>
* It supports the same semantics as {@link #CONTENT_FILTER_URI} and returns the same
* columns. If the device has no corp profile that is linked to the current profile, it
* behaves in the exact same way as {@link #CONTENT_FILTER_URI}. If there is a corp profile
* linked to the current profile, it first queries against the personal contact database,
* and if no matching contacts are found there, then queries against the
* corp contacts database.
+ * </p>
* <p>
* If a result is from the corp profile, it makes the following changes to the data:
* <ul>
@@ -5779,6 +5762,20 @@ public final class ContactsContract {
"phones");
/**
+ * URI used for getting all contacts from primary and managed profile.
+ *
+ * It supports the same semantics as {@link #CONTENT_URI} and returns the same
+ * columns. If the device has no corp profile that is linked to the current profile, it
+ * behaves in the exact same way as {@link #CONTENT_URI}. If there is a corp profile
+ * linked to the current profile, it will merge corp profile and current profile's
+ * results and return
+ *
+ * @hide
+ */
+ public static final Uri ENTERPRISE_CONTENT_URI =
+ Uri.withAppendedPath(Data.ENTERPRISE_CONTENT_URI, "phones");
+
+ /**
* The content:// style URL for phone lookup using a filter. The filter returns
* records of MIME type {@link #CONTENT_ITEM_TYPE}. The filter is applied
* to display names as well as phone numbers. The filter argument should be passed
@@ -5989,6 +5986,45 @@ public final class ContactsContract {
"lookup");
/**
+ * <p>URI used for enterprise email lookup.</p>
+ *
+ * <p>
+ * It supports the same semantics as {@link #CONTENT_LOOKUP_URI} and returns the same
+ * columns. If the device has no corp profile that is linked to the current profile, it
+ * behaves in the exact same way as {@link #CONTENT_LOOKUP_URI}. If there is a
+ * corp profile linked to the current profile, it first queries against the personal contact database,
+ * and if no matching contacts are found there, then queries against the
+ * corp contacts database.
+ * </p>
+ * <p>
+ * If a result is from the corp profile, it makes the following changes to the data:
+ * <ul>
+ * <li>
+ * {@link #PHOTO_THUMBNAIL_URI} and {@link #PHOTO_URI} will be rewritten to special
+ * URIs. Use {@link ContentResolver#openAssetFileDescriptor} or its siblings to
+ * load pictures from them.
+ * {@link #PHOTO_ID} and {@link #PHOTO_FILE_ID} will be set to null. Do not
+ * use them.
+ * </li>
+ * <li>
+ * Corp contacts will get artificial {@link #CONTACT_ID}s. In order to tell whether
+ * a contact
+ * is from the corp profile, use
+ * {@link ContactsContract.Contacts#isEnterpriseContactId(long)}.
+ * </li>
+ * </ul>
+ * <p>
+ * This URI does NOT support selection nor order-by.
+ *
+ * <pre>
+ * Uri lookupUri = Uri.withAppendedPath(Email.ENTERPRISE_CONTENT_LOOKUP_URI,
+ * Uri.encode(email));
+ * </pre>
+ */
+ public static final Uri ENTERPRISE_CONTENT_LOOKUP_URI =
+ Uri.withAppendedPath(CONTENT_URI, "lookup_enterprise");
+
+ /**
* <p>
* The content:// style URL for email lookup using a filter. The filter returns
* records of MIME type {@link #CONTENT_ITEM_TYPE}. The filter is applied
@@ -7839,9 +7875,7 @@ public final class ContactsContract {
}
/**
- * Private API for inquiring about the general status of the provider.
- *
- * @hide
+ * API for inquiring about the general status of the provider.
*/
public static final class ProviderStatus {
@@ -7854,8 +7888,6 @@ public final class ContactsContract {
/**
* The content:// style URI for this table. Requests to this URI can be
* performed on the UI thread because they are always unblocking.
- *
- * @hide
*/
public static final Uri CONTENT_URI =
Uri.withAppendedPath(AUTHORITY_URI, "provider_status");
@@ -7863,64 +7895,35 @@ public final class ContactsContract {
/**
* The MIME-type of {@link #CONTENT_URI} providing a directory of
* settings.
- *
- * @hide
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/provider_status";
/**
* An integer representing the current status of the provider.
- *
- * @hide
*/
public static final String STATUS = "status";
/**
* Default status of the provider.
- *
- * @hide
*/
public static final int STATUS_NORMAL = 0;
/**
* The status used when the provider is in the process of upgrading. Contacts
* are temporarily unaccessible.
- *
- * @hide
*/
public static final int STATUS_UPGRADING = 1;
/**
- * The status used if the provider was in the process of upgrading but ran
- * out of storage. The DATA1 column will contain the estimated amount of
- * storage required (in bytes). Update status to STATUS_NORMAL to force
- * the provider to retry the upgrade.
- *
- * @hide
- */
- public static final int STATUS_UPGRADE_OUT_OF_MEMORY = 2;
-
- /**
* The status used during a locale change.
- *
- * @hide
*/
public static final int STATUS_CHANGING_LOCALE = 3;
/**
* The status that indicates that there are no accounts and no contacts
* on the device.
- *
- * @hide
*/
public static final int STATUS_NO_ACCOUNTS_NO_CONTACTS = 4;
-
- /**
- * Additional data associated with the status.
- *
- * @hide
- */
- public static final String DATA1 = "data1";
}
/**
@@ -8125,12 +8128,22 @@ public final class ContactsContract {
public static final String EXTRA_TARGET_RECT = "android.provider.extra.TARGET_RECT";
/**
- * Extra used to specify size of pivot dialog.
- * @hide
+ * Extra used to specify size of QuickContacts. Not all implementations of QuickContacts
+ * will respect this extra's value.
+ *
+ * One of {@link #MODE_SMALL}, {@link #MODE_MEDIUM}, or {@link #MODE_LARGE}.
*/
public static final String EXTRA_MODE = "android.provider.extra.MODE";
/**
+ * Extra used to specify which mimetype should be prioritized in the QuickContacts UI.
+ * For example, passing the value {@link CommonDataKinds.Phone#CONTENT_ITEM_TYPE} can
+ * cause phone numbers to be displayed more prominently in QuickContacts.
+ */
+ public static final String EXTRA_PRIORITIZED_MIMETYPE
+ = "android.provider.extra.PRIORITIZED_MIMETYPE";
+
+ /**
* Extra used to indicate a list of specific MIME-types to exclude and not display in the
* QuickContacts dialog. Stored as a {@link String} array.
*/
@@ -8268,6 +8281,80 @@ public final class ContactsContract {
startActivityWithErrorToast(context, intent);
}
+ /**
+ * Trigger a dialog that lists the various methods of interacting with
+ * the requested {@link Contacts} entry. This may be based on available
+ * {@link ContactsContract.Data} rows under that contact, and may also
+ * include social status and presence details.
+ *
+ * @param context The parent {@link Context} that may be used as the
+ * parent for this dialog.
+ * @param target Specific {@link View} from your layout that this dialog
+ * should be centered around. In particular, if the dialog
+ * has a "callout" arrow, it will be pointed and centered
+ * around this {@link View}.
+ * @param lookupUri A
+ * {@link ContactsContract.Contacts#CONTENT_LOOKUP_URI} style
+ * {@link Uri} that describes a specific contact to feature
+ * in this dialog.
+ * @param excludeMimes Optional list of {@link Data#MIMETYPE} MIME-types
+ * to exclude when showing this dialog. For example, when
+ * already viewing the contact details card, this can be used
+ * to omit the details entry from the dialog.
+ * @param prioritizedMimeType This mimetype should be prioritized in the QuickContacts UI.
+ * For example, passing the value
+ * {@link CommonDataKinds.Phone#CONTENT_ITEM_TYPE} can cause phone numbers to be
+ * displayed more prominently in QuickContacts.
+ */
+ public static void showQuickContact(Context context, View target, Uri lookupUri,
+ String[] excludeMimes, String prioritizedMimeType) {
+ // Use MODE_LARGE instead of accepting mode as a parameter. The different mode
+ // values defined in ContactsContract only affect very old implementations
+ // of QuickContacts.
+ Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_LARGE,
+ excludeMimes);
+ intent.putExtra(EXTRA_PRIORITIZED_MIMETYPE, prioritizedMimeType);
+ startActivityWithErrorToast(context, intent);
+ }
+
+ /**
+ * Trigger a dialog that lists the various methods of interacting with
+ * the requested {@link Contacts} entry. This may be based on available
+ * {@link ContactsContract.Data} rows under that contact, and may also
+ * include social status and presence details.
+ *
+ * @param context The parent {@link Context} that may be used as the
+ * parent for this dialog.
+ * @param target Specific {@link Rect} that this dialog should be
+ * centered around, in screen coordinates. In particular, if
+ * the dialog has a "callout" arrow, it will be pointed and
+ * centered around this {@link Rect}. If you are running at a
+ * non-native density, you need to manually adjust using
+ * {@link DisplayMetrics#density} before calling.
+ * @param lookupUri A
+ * {@link ContactsContract.Contacts#CONTENT_LOOKUP_URI} style
+ * {@link Uri} that describes a specific contact to feature
+ * in this dialog.
+ * @param excludeMimes Optional list of {@link Data#MIMETYPE} MIME-types
+ * to exclude when showing this dialog. For example, when
+ * already viewing the contact details card, this can be used
+ * to omit the details entry from the dialog.
+ * @param prioritizedMimeType This mimetype should be prioritized in the QuickContacts UI.
+ * For example, passing the value
+ * {@link CommonDataKinds.Phone#CONTENT_ITEM_TYPE} can cause phone numbers to be
+ * displayed more prominently in QuickContacts.
+ */
+ public static void showQuickContact(Context context, Rect target, Uri lookupUri,
+ String[] excludeMimes, String prioritizedMimeType) {
+ // Use MODE_LARGE instead of accepting mode as a parameter. The different mode
+ // values defined in ContactsContract only affect very old implementations
+ // of QuickContacts.
+ Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_LARGE,
+ excludeMimes);
+ intent.putExtra(EXTRA_PRIORITIZED_MIMETYPE, prioritizedMimeType);
+ startActivityWithErrorToast(context, intent);
+ }
+
private static void startActivityWithErrorToast(Context context, Intent intent) {
try {
context.startActivity(intent);
@@ -8538,102 +8625,6 @@ public final class ContactsContract {
public static final String EXTRA_EXCLUDE_MIMES = "exclude_mimes";
/**
- * Intents related to the Contacts app UI.
- *
- * @hide
- */
- public static final class UI {
- /**
- * The action for the default contacts list tab.
- */
- public static final String LIST_DEFAULT =
- "com.android.contacts.action.LIST_DEFAULT";
-
- /**
- * The action for the contacts list tab.
- */
- public static final String LIST_GROUP_ACTION =
- "com.android.contacts.action.LIST_GROUP";
-
- /**
- * When in LIST_GROUP_ACTION mode, this is the group to display.
- */
- public static final String GROUP_NAME_EXTRA_KEY = "com.android.contacts.extra.GROUP";
-
- /**
- * The action for the all contacts list tab.
- */
- public static final String LIST_ALL_CONTACTS_ACTION =
- "com.android.contacts.action.LIST_ALL_CONTACTS";
-
- /**
- * The action for the contacts with phone numbers list tab.
- */
- public static final String LIST_CONTACTS_WITH_PHONES_ACTION =
- "com.android.contacts.action.LIST_CONTACTS_WITH_PHONES";
-
- /**
- * The action for the starred contacts list tab.
- */
- public static final String LIST_STARRED_ACTION =
- "com.android.contacts.action.LIST_STARRED";
-
- /**
- * The action for the frequent contacts list tab.
- */
- public static final String LIST_FREQUENT_ACTION =
- "com.android.contacts.action.LIST_FREQUENT";
-
- /**
- * The action for the "Join Contact" picker.
- */
- public static final String PICK_JOIN_CONTACT_ACTION =
- "com.android.contacts.action.JOIN_CONTACT";
-
- /**
- * The action for the "strequent" contacts list tab. It first lists the starred
- * contacts in alphabetical order and then the frequent contacts in descending
- * order of the number of times they have been contacted.
- */
- public static final String LIST_STREQUENT_ACTION =
- "com.android.contacts.action.LIST_STREQUENT";
-
- /**
- * A key for to be used as an intent extra to set the activity
- * title to a custom String value.
- */
- public static final String TITLE_EXTRA_KEY =
- "com.android.contacts.extra.TITLE_EXTRA";
-
- /**
- * Activity Action: Display a filtered list of contacts
- * <p>
- * Input: Extra field {@link #FILTER_TEXT_EXTRA_KEY} is the text to use for
- * filtering
- * <p>
- * Output: Nothing.
- */
- public static final String FILTER_CONTACTS_ACTION =
- "com.android.contacts.action.FILTER_CONTACTS";
-
- /**
- * Used as an int extra field in {@link #FILTER_CONTACTS_ACTION}
- * intents to supply the text on which to filter.
- */
- public static final String FILTER_TEXT_EXTRA_KEY =
- "com.android.contacts.extra.FILTER_TEXT";
-
- /**
- * Used with JOIN_CONTACT action to set the target for aggregation. This action type
- * uses contact ids instead of contact uris for the sake of backwards compatibility.
- * <p>
- * Type: LONG
- */
- public static final String TARGET_CONTACT_ID_EXTRA_KEY
- = "com.android.contacts.action.CONTACT_ID";
- }
-
- /**
* Convenience class that contains string constants used
* to create contact {@link android.content.Intent Intents}.
*/
@@ -8858,10 +8849,8 @@ public final class ContactsContract {
* dialog to chose an account
* <p>
* Type: {@link Account}
- *
- * @hide
*/
- public static final String ACCOUNT = "com.android.contacts.extra.ACCOUNT";
+ public static final String EXTRA_ACCOUNT = "android.provider.extra.ACCOUNT";
/**
* Used to specify the data set within the account in which to create the
@@ -8871,10 +8860,8 @@ public final class ContactsContract {
* created in the base account, with no data set.
* <p>
* Type: String
- *
- * @hide
*/
- public static final String DATA_SET = "com.android.contacts.extra.DATA_SET";
+ public static final String EXTRA_DATA_SET = "android.provider.extra.DATA_SET";
}
}
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 1316471..6979bee 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -28,6 +28,7 @@ import static android.provider.DocumentsContract.getSearchDocumentsQuery;
import static android.provider.DocumentsContract.getTreeDocumentId;
import static android.provider.DocumentsContract.isTreeUri;
+import android.annotation.CallSuper;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -541,6 +542,7 @@ public abstract class DocumentsProvider extends ContentProvider {
*
* @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
*/
+ @CallSuper
@Override
public Uri canonicalize(Uri uri) {
final Context context = getContext();
@@ -616,6 +618,7 @@ public abstract class DocumentsProvider extends ContentProvider {
* call the superclass. If the superclass returns {@code null}, the subclass
* may implement custom behavior.
*/
+ @CallSuper
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (!method.startsWith("android:")) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef0f72f..3813277 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -51,14 +51,20 @@ import android.os.Build.VERSION_CODES;
import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.util.AndroidException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings;
import java.net.URISyntaxException;
+import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
/**
* The Settings provider contains global system-level device preferences.
@@ -132,7 +138,6 @@ public final class Settings {
"android.settings.AIRPLANE_MODE_SETTINGS";
/**
- * @hide
* Activity Action: Modify Airplane mode settings using the users voice.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard against this.
@@ -154,7 +159,6 @@ public final class Settings {
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- @SystemApi
public static final String ACTION_VOICE_CONTROL_AIRPLANE_MODE =
"android.settings.VOICE_CONTROL_AIRPLANE_MODE";
@@ -308,7 +312,7 @@ public final class Settings {
/**
* Activity Action: Show settings to allow configuration of
- * cast endpoints.
+ * {@link android.media.routing.MediaRouteService media route providers}.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
@@ -991,13 +995,11 @@ public final class Settings {
public static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier";
/**
- * @hide
* Activity Extra: Enable or disable Airplane Mode.
* <p>
* This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_AIRPLANE_MODE}
* intent as a boolean.
*/
- @SystemApi
public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
private static final String JID_RESOURCE_PREFIX = "android";
@@ -1196,6 +1198,11 @@ public final class Settings {
public static final class System extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
+ /** @hide */
+ public static interface Validator {
+ public boolean validate(String value);
+ }
+
/**
* The content:// style URL for this table
*/
@@ -1298,13 +1305,53 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.CERT_PIN_UPDATE_METADATA_URL);
}
+ private static final Validator sBooleanValidator =
+ new DiscreteValueValidator(new String[] {"0", "1"});
+
+ private static final Validator sNonNegativeIntegerValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ return Integer.parseInt(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
+ private static final Validator sUriValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ Uri.decode(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
+ private static final Validator sLenientIpAddressValidator = new Validator() {
+ private static final int MAX_IPV6_LENGTH = 45;
+
+ @Override
+ public boolean validate(String value) {
+ return value.length() <= MAX_IPV6_LENGTH;
+ }
+ };
+
/** @hide */
- public static void getMovedKeys(HashSet<String> outKeySet) {
+ public static void getMovedToGlobalSettings(Set<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
outKeySet.addAll(MOVED_TO_SECURE_THEN_GLOBAL);
}
/** @hide */
+ public static void getMovedToSecureSettings(Set<String> outKeySet) {
+ outKeySet.addAll(MOVED_TO_SECURE);
+ }
+
+ /** @hide */
public static void getNonLegacyMovedKeys(HashSet<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
}
@@ -1727,6 +1774,59 @@ public final class Settings {
putIntForUser(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0, userHandle);
}
+ private static final class DiscreteValueValidator implements Validator {
+ private final String[] mValues;
+
+ public DiscreteValueValidator(String[] values) {
+ mValues = values;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ return ArrayUtils.contains(mValues, value);
+ }
+ }
+
+ private static final class InclusiveIntegerRangeValidator implements Validator {
+ private final int mMin;
+ private final int mMax;
+
+ public InclusiveIntegerRangeValidator(int min, int max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ try {
+ final int intValue = Integer.parseInt(value);
+ return intValue >= mMin && intValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
+ private static final class InclusiveFloatRangeValidator implements Validator {
+ private final float mMin;
+ private final float mMax;
+
+ public InclusiveFloatRangeValidator(float min, float max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ @Override
+ public boolean validate(String value) {
+ try {
+ final float floatValue = Float.parseFloat(value);
+ return floatValue >= mMin && floatValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
/**
* @deprecated Use {@link android.provider.Settings.Global#STAY_ON_WHILE_PLUGGED_IN} instead
*/
@@ -1745,6 +1845,9 @@ public final class Settings {
*/
public static final String END_BUTTON_BEHAVIOR = "end_button_behavior";
+ private static final Validator END_BUTTON_BEHAVIOR_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 3);
+
/**
* END_BUTTON_BEHAVIOR value for "go home".
* @hide
@@ -1769,6 +1872,8 @@ public final class Settings {
*/
public static final String ADVANCED_SETTINGS = "advanced_settings";
+ private static final Validator ADVANCED_SETTINGS_VALIDATOR = sBooleanValidator;
+
/**
* ADVANCED_SETTINGS default value.
* @hide
@@ -1868,6 +1973,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_USE_STATIC_IP = "wifi_use_static_ip";
+ private static final Validator WIFI_USE_STATIC_IP_VALIDATOR = sBooleanValidator;
+
/**
* The static IP address.
* <p>
@@ -1878,6 +1985,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_IP = "wifi_static_ip";
+ private static final Validator WIFI_STATIC_IP_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the gateway's IP address.
* <p>
@@ -1888,6 +1997,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_GATEWAY = "wifi_static_gateway";
+ private static final Validator WIFI_STATIC_GATEWAY_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the net mask.
* <p>
@@ -1898,6 +2009,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_NETMASK = "wifi_static_netmask";
+ private static final Validator WIFI_STATIC_NETMASK_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the primary DNS's IP address.
* <p>
@@ -1908,6 +2021,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_DNS1 = "wifi_static_dns1";
+ private static final Validator WIFI_STATIC_DNS1_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the secondary DNS's IP address.
* <p>
@@ -1918,6 +2033,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2";
+ private static final Validator WIFI_STATIC_DNS2_VALIDATOR = sLenientIpAddressValidator;
/**
* Determines whether remote devices may discover and/or connect to
@@ -1930,6 +2046,9 @@ public final class Settings {
public static final String BLUETOOTH_DISCOVERABILITY =
"bluetooth_discoverability";
+ private static final Validator BLUETOOTH_DISCOVERABILITY_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 2);
+
/**
* Bluetooth discoverability timeout. If this value is nonzero, then
* Bluetooth becomes discoverable for a certain number of seconds,
@@ -1938,6 +2057,9 @@ public final class Settings {
public static final String BLUETOOTH_DISCOVERABILITY_TIMEOUT =
"bluetooth_discoverability_timeout";
+ private static final Validator BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Secure#LOCK_PATTERN_ENABLED}
* instead
@@ -1961,7 +2083,6 @@ public final class Settings {
public static final String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED =
"lock_pattern_tactile_feedback_enabled";
-
/**
* A formatted string of the next alarm that is set, or the empty string
* if there is no alarm set.
@@ -1971,11 +2092,32 @@ public final class Settings {
@Deprecated
public static final String NEXT_ALARM_FORMATTED = "next_alarm_formatted";
+ private static final Validator NEXT_ALARM_FORMATTED_VALIDATOR = new Validator() {
+ private static final int MAX_LENGTH = 1000;
+
+ @Override
+ public boolean validate(String value) {
+ // TODO: No idea what the correct format is.
+ return value == null || value.length() < MAX_LENGTH;
+ }
+ };
+
/**
* Scaling factor for fonts, float.
*/
public static final String FONT_SCALE = "font_scale";
+ private static final Validator FONT_SCALE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ return Float.parseFloat(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Name of an application package to be debugged.
*
@@ -2000,6 +2142,8 @@ public final class Settings {
@Deprecated
public static final String DIM_SCREEN = "dim_screen";
+ private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator;
+
/**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
@@ -2008,16 +2152,23 @@ public final class Settings {
*/
public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
+ private static final Validator SCREEN_OFF_TIMEOUT_VALIDATOR = sNonNegativeIntegerValidator;
+
/**
* The screen backlight brightness between 0 and 255.
*/
public static final String SCREEN_BRIGHTNESS = "screen_brightness";
+ private static final Validator SCREEN_BRIGHTNESS_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 255);
+
/**
* Control whether to enable automatic brightness mode.
*/
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
+ private static final Validator SCREEN_BRIGHTNESS_MODE_VALIDATOR = sBooleanValidator;
+
/**
* Adjustment to auto-brightness to make it generally more (>0.0 <1.0)
* or less (<0.0 >-1.0) bright.
@@ -2025,6 +2176,9 @@ public final class Settings {
*/
public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj";
+ private static final Validator SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR =
+ new InclusiveFloatRangeValidator(-1, 1);
+
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
*/
@@ -2060,12 +2214,18 @@ public final class Settings {
*/
public static final String MODE_RINGER_STREAMS_AFFECTED = "mode_ringer_streams_affected";
- /**
+ private static final Validator MODE_RINGER_STREAMS_AFFECTED_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
* Determines which streams are affected by mute. The
* stream type's bit should be set to 1 if it should be muted when a mute request
* is received.
*/
- public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
+ public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
+
+ private static final Validator MUTE_STREAMS_AFFECTED_VALIDATOR =
+ sNonNegativeIntegerValidator;
/**
* Whether vibrate is on for different events. This is used internally,
@@ -2073,6 +2233,8 @@ public final class Settings {
*/
public static final String VIBRATE_ON = "vibrate_on";
+ private static final Validator VIBRATE_ON_VALIDATOR = sBooleanValidator;
+
/**
* If 1, redirects the system vibrator to all currently attached input devices
* that support vibration. If there are no such input devices, then the system
@@ -2087,50 +2249,67 @@ public final class Settings {
*/
public static final String VIBRATE_INPUT_DEVICES = "vibrate_input_devices";
+ private static final Validator VIBRATE_INPUT_DEVICES_VALIDATOR = sBooleanValidator;
+
/**
* Ringer volume. This is used internally, changing this value will not
* change the volume. See AudioManager.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String VOLUME_RING = "volume_ring";
/**
* System/notifications volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String VOLUME_SYSTEM = "volume_system";
/**
* Voice call volume. This is used internally, changing this value will
* not change the volume. See AudioManager.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String VOLUME_VOICE = "volume_voice";
/**
* Music/media/gaming volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String VOLUME_MUSIC = "volume_music";
/**
* Alarm volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String VOLUME_ALARM = "volume_alarm";
/**
* Notification volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String VOLUME_NOTIFICATION = "volume_notification";
/**
* Bluetooth Headset volume. This is used internally, changing this value will
* not change the volume. See AudioManager.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String VOLUME_BLUETOOTH_SCO = "volume_bluetooth_sco";
/**
* Master volume (float in the range 0.0f to 1.0f).
+ *
* @hide
*/
public static final String VOLUME_MASTER = "volume_master";
@@ -2142,6 +2321,8 @@ public final class Settings {
*/
public static final String VOLUME_MASTER_MUTE = "volume_master_mute";
+ private static final Validator VOLUME_MASTER_MUTE_VALIDATOR = sBooleanValidator;
+
/**
* Microphone mute (int 1 = mute, 0 = not muted).
*
@@ -2149,6 +2330,8 @@ public final class Settings {
*/
public static final String MICROPHONE_MUTE = "microphone_mute";
+ private static final Validator MICROPHONE_MUTE_VALIDATOR = sBooleanValidator;
+
/**
* Whether the notifications should use the ring volume (value of 1) or
* a separate notification volume (value of 0). In most cases, users
@@ -2167,6 +2350,8 @@ public final class Settings {
public static final String NOTIFICATIONS_USE_RING_VOLUME =
"notifications_use_ring_volume";
+ private static final Validator NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR = sBooleanValidator;
+
/**
* Whether silent mode should allow vibration feedback. This is used
* internally in AudioService and the Sound settings activity to
@@ -2181,8 +2366,12 @@ public final class Settings {
*/
public static final String VIBRATE_IN_SILENT = "vibrate_in_silent";
+ private static final Validator VIBRATE_IN_SILENT_VALIDATOR = sBooleanValidator;
+
/**
* The mapping of stream type (integer) to its setting.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String[] VOLUME_SETTINGS = {
VOLUME_VOICE, VOLUME_SYSTEM, VOLUME_RING, VOLUME_MUSIC,
@@ -2193,6 +2382,8 @@ public final class Settings {
* Appended to various volume related settings to record the previous
* values before they the settings were affected by a silent/vibrate
* ringer mode change.
+ *
+ * @removed Not used by anything since API 2.
*/
public static final String APPEND_FOR_LAST_AUDIBLE = "_last_audible";
@@ -2207,6 +2398,8 @@ public final class Settings {
*/
public static final String RINGTONE = "ringtone";
+ private static final Validator RINGTONE_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default ringtone at any
* given time.
@@ -2225,6 +2418,8 @@ public final class Settings {
*/
public static final String NOTIFICATION_SOUND = "notification_sound";
+ private static final Validator NOTIFICATION_SOUND_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default notification
* sound at any given time.
@@ -2241,6 +2436,8 @@ public final class Settings {
*/
public static final String ALARM_ALERT = "alarm_alert";
+ private static final Validator ALARM_ALERT_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default alarm alert at
* any given time.
@@ -2256,30 +2453,52 @@ public final class Settings {
*/
public static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
+ private static final Validator MEDIA_BUTTON_RECEIVER_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ ComponentName.unflattenFromString(value);
+ return true;
+ } catch (NullPointerException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Setting to enable Auto Replace (AutoText) in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_REPLACE = "auto_replace";
+ private static final Validator TEXT_AUTO_REPLACE_VALIDATOR = sBooleanValidator;
+
/**
* Setting to enable Auto Caps in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_CAPS = "auto_caps";
+ private static final Validator TEXT_AUTO_CAPS_VALIDATOR = sBooleanValidator;
+
/**
* Setting to enable Auto Punctuate in text editors. 1 = On, 0 = Off. This
* feature converts two spaces to a "." and space.
*/
public static final String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
+ private static final Validator TEXT_AUTO_PUNCTUATE_VALIDATOR = sBooleanValidator;
+
/**
* Setting to showing password characters in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_SHOW_PASSWORD = "show_password";
+ private static final Validator TEXT_SHOW_PASSWORD_VALIDATOR = sBooleanValidator;
+
public static final String SHOW_GTALK_SERVICE_STATUS =
"SHOW_GTALK_SERVICE_STATUS";
+ private static final Validator SHOW_GTALK_SERVICE_STATUS_VALIDATOR = sBooleanValidator;
+
/**
* Name of activity to use for wallpaper on the home screen.
*
@@ -2288,6 +2507,18 @@ public final class Settings {
@Deprecated
public static final String WALLPAPER_ACTIVITY = "wallpaper_activity";
+ private static final Validator WALLPAPER_ACTIVITY_VALIDATOR = new Validator() {
+ private static final int MAX_LENGTH = 1000;
+
+ @Override
+ public boolean validate(String value) {
+ if (value != null && value.length() > MAX_LENGTH) {
+ return false;
+ }
+ return ComponentName.unflattenFromString(value) != null;
+ }
+ };
+
/**
* @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME}
* instead
@@ -2309,6 +2540,10 @@ public final class Settings {
*/
public static final String TIME_12_24 = "time_12_24";
+ /** @hide */
+ public static final Validator TIME_12_24_VALIDATOR =
+ new DiscreteValueValidator(new String[] {"12", "24"});
+
/**
* Date format string
* mm/dd/yyyy
@@ -2317,6 +2552,19 @@ public final class Settings {
*/
public static final String DATE_FORMAT = "date_format";
+ /** @hide */
+ public static final Validator DATE_FORMAT_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ new SimpleDateFormat(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Whether the setup wizard has been run before (on first boot), or if
* it still needs to be run.
@@ -2326,6 +2574,9 @@ public final class Settings {
*/
public static final String SETUP_WIZARD_HAS_RUN = "setup_wizard_has_run";
+ /** @hide */
+ public static final Validator SETUP_WIZARD_HAS_RUN_VALIDATOR = sBooleanValidator;
+
/**
* Scaling factor for normal window animations. Setting to 0 will disable window
* animations.
@@ -2362,6 +2613,9 @@ public final class Settings {
*/
public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
+ /** @hide */
+ public static final Validator ACCELEROMETER_ROTATION_VALIDATOR = sBooleanValidator;
+
/**
* Default screen rotation when no other policy applies.
* When {@link #ACCELEROMETER_ROTATION} is zero and no on-screen Activity expresses a
@@ -2372,6 +2626,10 @@ public final class Settings {
*/
public static final String USER_ROTATION = "user_rotation";
+ /** @hide */
+ public static final Validator USER_ROTATION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 3);
+
/**
* Control whether the rotation lock toggle in the System UI should be hidden.
* Typically this is done for accessibility purposes to make it harder for
@@ -2386,6 +2644,10 @@ public final class Settings {
public static final String HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY =
"hide_rotation_lock_toggle_for_accessibility";
+ /** @hide */
+ public static final Validator HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR =
+ sBooleanValidator;
+
/**
* Whether the phone vibrates when it is ringing due to an incoming call. This will
* be used by Phone and Setting apps; it shouldn't affect other apps.
@@ -2400,12 +2662,18 @@ public final class Settings {
*/
public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing";
+ /** @hide */
+ public static final Validator VIBRATE_WHEN_RINGING_VALIDATOR = sBooleanValidator;
+
/**
* Whether the audible DTMF tones are played by the dialer when dialing. The value is
* boolean (1 or 0).
*/
public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
+ /** @hide */
+ public static final Validator DTMF_TONE_WHEN_DIALING_VALIDATOR = sBooleanValidator;
+
/**
* CDMA only settings
* DTMF tone type played by the dialer when dialing.
@@ -2415,6 +2683,9 @@ public final class Settings {
*/
public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type";
+ /** @hide */
+ public static final Validator DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR = sBooleanValidator;
+
/**
* Whether the hearing aid is enabled. The value is
* boolean (1 or 0).
@@ -2422,6 +2693,9 @@ public final class Settings {
*/
public static final String HEARING_AID = "hearing_aid";
+ /** @hide */
+ public static final Validator HEARING_AID_VALIDATOR = sBooleanValidator;
+
/**
* CDMA only settings
* TTY Mode
@@ -2433,18 +2707,27 @@ public final class Settings {
*/
public static final String TTY_MODE = "tty_mode";
+ /** @hide */
+ public static final Validator TTY_MODE_VALIDATOR = new InclusiveIntegerRangeValidator(0, 3);
+
/**
* Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
* boolean (1 or 0).
*/
public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
+ /** @hide */
+ public static final Validator SOUND_EFFECTS_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* Whether the haptic feedback (long presses, ...) are enabled. The value is
* boolean (1 or 0).
*/
public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
+ /** @hide */
+ public static final Validator HAPTIC_FEEDBACK_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Each application that shows web suggestions should have its own
* setting for this.
@@ -2452,6 +2735,9 @@ public final class Settings {
@Deprecated
public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
+ /** @hide */
+ public static final Validator SHOW_WEB_SUGGESTIONS_VALIDATOR = sBooleanValidator;
+
/**
* Whether the notification LED should repeatedly flash when a notification is
* pending. The value is boolean (1 or 0).
@@ -2459,6 +2745,9 @@ public final class Settings {
*/
public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_VALIDATOR = sBooleanValidator;
+
/**
* Show pointer location on screen?
* 0 = no
@@ -2467,6 +2756,9 @@ public final class Settings {
*/
public static final String POINTER_LOCATION = "pointer_location";
+ /** @hide */
+ public static final Validator POINTER_LOCATION_VALIDATOR = sBooleanValidator;
+
/**
* Show touch positions on screen?
* 0 = no
@@ -2475,9 +2767,12 @@ public final class Settings {
*/
public static final String SHOW_TOUCHES = "show_touches";
+ /** @hide */
+ public static final Validator SHOW_TOUCHES_VALIDATOR = sBooleanValidator;
+
/**
* Log raw orientation data from
- * {@link com.android.internal.policy.impl.WindowOrientationListener} for use with the
+ * {@link com.android.server.policy.WindowOrientationListener} for use with the
* orientationplot.py tool.
* 0 = no
* 1 = yes
@@ -2486,6 +2781,9 @@ public final class Settings {
public static final String WINDOW_ORIENTATION_LISTENER_LOG =
"window_orientation_listener_log";
+ /** @hide */
+ public static final Validator WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#POWER_SOUNDS_ENABLED}
* instead
@@ -2508,12 +2806,18 @@ public final class Settings {
*/
public static final String LOCKSCREEN_SOUNDS_ENABLED = "lockscreen_sounds_enabled";
+ /** @hide */
+ public static final Validator LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* Whether the lockscreen should be completely disabled.
* @hide
*/
public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled";
+ /** @hide */
+ public static final Validator LOCKSCREEN_DISABLED_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#LOW_BATTERY_SOUND}
* instead
@@ -2578,6 +2882,9 @@ public final class Settings {
*/
public static final String SIP_RECEIVE_CALLS = "sip_receive_calls";
+ /** @hide */
+ public static final Validator SIP_RECEIVE_CALLS_VALIDATOR = sBooleanValidator;
+
/**
* Call Preference String.
* "SIP_ALWAYS" : Always use SIP with network access
@@ -2586,18 +2893,28 @@ public final class Settings {
*/
public static final String SIP_CALL_OPTIONS = "sip_call_options";
+ /** @hide */
+ public static final Validator SIP_CALL_OPTIONS_VALIDATOR = new DiscreteValueValidator(
+ new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"});
+
/**
* One of the sip call options: Always use SIP with network access.
* @hide
*/
public static final String SIP_ALWAYS = "SIP_ALWAYS";
+ /** @hide */
+ public static final Validator SIP_ALWAYS_VALIDATOR = sBooleanValidator;
+
/**
* One of the sip call options: Only if destination is a SIP address.
* @hide
*/
public static final String SIP_ADDRESS_ONLY = "SIP_ADDRESS_ONLY";
+ /** @hide */
+ public static final Validator SIP_ADDRESS_ONLY_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use SIP_ALWAYS or SIP_ADDRESS_ONLY instead. Formerly used to indicate that
* the user should be prompted each time a call is made whether it should be placed using
@@ -2608,6 +2925,9 @@ public final class Settings {
@Deprecated
public static final String SIP_ASK_ME_EACH_TIME = "SIP_ASK_ME_EACH_TIME";
+ /** @hide */
+ public static final Validator SIP_ASK_ME_EACH_TIME_VALIDATOR = sBooleanValidator;
+
/**
* Pointer speed setting.
* This is an integer value in a range between -7 and +7, so there are 15 possible values.
@@ -2618,12 +2938,19 @@ public final class Settings {
*/
public static final String POINTER_SPEED = "pointer_speed";
+ /** @hide */
+ public static final Validator POINTER_SPEED_VALIDATOR =
+ new InclusiveFloatRangeValidator(-7, 7);
+
/**
* Whether lock-to-app will be triggered by long-press on recents.
* @hide
*/
public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
+ /** @hide */
+ public static final Validator LOCK_TO_APP_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* I am the lolrus.
* <p>
@@ -2633,6 +2960,16 @@ public final class Settings {
*/
public static final String EGG_MODE = "egg_mode";
+ /** @hide */
+ public static final Validator EGG_MODE_VALIDATOR = sBooleanValidator;
+
+ /**
+ * IMPORTANT: If you add a new public settings you also have to add it to
+ * PUBLIC_SETTINGS below. If the new setting is hidden you have to add
+ * it to PRIVATE_SETTINGS below. Also add a validator that can validate
+ * the setting value. See an example above.
+ */
+
/**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
@@ -2660,20 +2997,6 @@ public final class Settings {
SCREEN_AUTO_BRIGHTNESS_ADJ,
VIBRATE_INPUT_DEVICES,
MODE_RINGER_STREAMS_AFFECTED,
- VOLUME_VOICE,
- VOLUME_SYSTEM,
- VOLUME_RING,
- VOLUME_MUSIC,
- VOLUME_ALARM,
- VOLUME_NOTIFICATION,
- VOLUME_BLUETOOTH_SCO,
- VOLUME_VOICE + APPEND_FOR_LAST_AUDIBLE,
- VOLUME_SYSTEM + APPEND_FOR_LAST_AUDIBLE,
- VOLUME_RING + APPEND_FOR_LAST_AUDIBLE,
- VOLUME_MUSIC + APPEND_FOR_LAST_AUDIBLE,
- VOLUME_ALARM + APPEND_FOR_LAST_AUDIBLE,
- VOLUME_NOTIFICATION + APPEND_FOR_LAST_AUDIBLE,
- VOLUME_BLUETOOTH_SCO + APPEND_FOR_LAST_AUDIBLE,
TEXT_AUTO_REPLACE,
TEXT_AUTO_CAPS,
TEXT_AUTO_PUNCTUATE,
@@ -2703,17 +3026,199 @@ public final class Settings {
};
/**
+ * These are all pulbic system settings
+ *
+ * @hide
+ */
+ public static final Set<String> PUBLIC_SETTINGS = new ArraySet<>();
+ static {
+ PUBLIC_SETTINGS.add(END_BUTTON_BEHAVIOR);
+ PUBLIC_SETTINGS.add(WIFI_USE_STATIC_IP);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_IP);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_GATEWAY);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_NETMASK);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_DNS1);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_DNS2);
+ PUBLIC_SETTINGS.add(BLUETOOTH_DISCOVERABILITY);
+ PUBLIC_SETTINGS.add(BLUETOOTH_DISCOVERABILITY_TIMEOUT);
+ PUBLIC_SETTINGS.add(NEXT_ALARM_FORMATTED);
+ PUBLIC_SETTINGS.add(FONT_SCALE);
+ PUBLIC_SETTINGS.add(DIM_SCREEN);
+ PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT);
+ PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
+ PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
+ PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
+ PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
+ PUBLIC_SETTINGS.add(VIBRATE_ON);
+ PUBLIC_SETTINGS.add(VOLUME_RING);
+ PUBLIC_SETTINGS.add(VOLUME_SYSTEM);
+ PUBLIC_SETTINGS.add(VOLUME_VOICE);
+ PUBLIC_SETTINGS.add(VOLUME_MUSIC);
+ PUBLIC_SETTINGS.add(VOLUME_ALARM);
+ PUBLIC_SETTINGS.add(VOLUME_NOTIFICATION);
+ PUBLIC_SETTINGS.add(VOLUME_BLUETOOTH_SCO);
+ PUBLIC_SETTINGS.add(RINGTONE);
+ PUBLIC_SETTINGS.add(NOTIFICATION_SOUND);
+ PUBLIC_SETTINGS.add(ALARM_ALERT);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_REPLACE);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_CAPS);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_PUNCTUATE);
+ PUBLIC_SETTINGS.add(TEXT_SHOW_PASSWORD);
+ PUBLIC_SETTINGS.add(SHOW_GTALK_SERVICE_STATUS);
+ PUBLIC_SETTINGS.add(WALLPAPER_ACTIVITY);
+ PUBLIC_SETTINGS.add(TIME_12_24);
+ PUBLIC_SETTINGS.add(DATE_FORMAT);
+ PUBLIC_SETTINGS.add(SETUP_WIZARD_HAS_RUN);
+ PUBLIC_SETTINGS.add(ACCELEROMETER_ROTATION);
+ PUBLIC_SETTINGS.add(USER_ROTATION);
+ PUBLIC_SETTINGS.add(DTMF_TONE_WHEN_DIALING);
+ PUBLIC_SETTINGS.add(SOUND_EFFECTS_ENABLED);
+ PUBLIC_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
+ PUBLIC_SETTINGS.add(SHOW_WEB_SUGGESTIONS);
+ }
+
+ /**
+ * These are all hidden system settings.
+ *
+ * @hide
+ */
+ public static final Set<String> PRIVATE_SETTINGS = new ArraySet<>();
+ static {
+ PRIVATE_SETTINGS.add(WIFI_USE_STATIC_IP);
+ PRIVATE_SETTINGS.add(END_BUTTON_BEHAVIOR);
+ PRIVATE_SETTINGS.add(ADVANCED_SETTINGS);
+ PRIVATE_SETTINGS.add(SCREEN_AUTO_BRIGHTNESS_ADJ);
+ PRIVATE_SETTINGS.add(VIBRATE_INPUT_DEVICES);
+ PRIVATE_SETTINGS.add(VOLUME_MASTER);
+ PRIVATE_SETTINGS.add(VOLUME_MASTER_MUTE);
+ PRIVATE_SETTINGS.add(MICROPHONE_MUTE);
+ PRIVATE_SETTINGS.add(NOTIFICATIONS_USE_RING_VOLUME);
+ PRIVATE_SETTINGS.add(VIBRATE_IN_SILENT);
+ PRIVATE_SETTINGS.add(MEDIA_BUTTON_RECEIVER);
+ PRIVATE_SETTINGS.add(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY);
+ PRIVATE_SETTINGS.add(VIBRATE_WHEN_RINGING);
+ PRIVATE_SETTINGS.add(DTMF_TONE_TYPE_WHEN_DIALING);
+ PRIVATE_SETTINGS.add(HEARING_AID);
+ PRIVATE_SETTINGS.add(TTY_MODE);
+ PRIVATE_SETTINGS.add(NOTIFICATION_LIGHT_PULSE);
+ PRIVATE_SETTINGS.add(POINTER_LOCATION);
+ PRIVATE_SETTINGS.add(SHOW_TOUCHES);
+ PRIVATE_SETTINGS.add(WINDOW_ORIENTATION_LISTENER_LOG);
+ PRIVATE_SETTINGS.add(POWER_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(DOCK_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(LOCKSCREEN_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(LOCKSCREEN_DISABLED);
+ PRIVATE_SETTINGS.add(LOW_BATTERY_SOUND);
+ PRIVATE_SETTINGS.add(DESK_DOCK_SOUND);
+ PRIVATE_SETTINGS.add(DESK_UNDOCK_SOUND);
+ PRIVATE_SETTINGS.add(CAR_DOCK_SOUND);
+ PRIVATE_SETTINGS.add(CAR_UNDOCK_SOUND);
+ PRIVATE_SETTINGS.add(LOCK_SOUND);
+ PRIVATE_SETTINGS.add(UNLOCK_SOUND);
+ PRIVATE_SETTINGS.add(SIP_RECEIVE_CALLS);
+ PRIVATE_SETTINGS.add(SIP_CALL_OPTIONS);
+ PRIVATE_SETTINGS.add(SIP_ALWAYS);
+ PRIVATE_SETTINGS.add(SIP_ADDRESS_ONLY);
+ PRIVATE_SETTINGS.add(SIP_ASK_ME_EACH_TIME);
+ PRIVATE_SETTINGS.add(POINTER_SPEED);
+ PRIVATE_SETTINGS.add(LOCK_TO_APP_ENABLED);
+ PRIVATE_SETTINGS.add(EGG_MODE);
+ }
+
+ /**
+ * These are all pulbic system settings
+ *
+ * @hide
+ */
+ public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
+ static {
+ VALIDATORS.put(END_BUTTON_BEHAVIOR,END_BUTTON_BEHAVIOR_VALIDATOR);
+ VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(BLUETOOTH_DISCOVERABILITY, BLUETOOTH_DISCOVERABILITY_VALIDATOR);
+ VALIDATORS.put(BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR);
+ VALIDATORS.put(NEXT_ALARM_FORMATTED, NEXT_ALARM_FORMATTED_VALIDATOR);
+ VALIDATORS.put(FONT_SCALE, FONT_SCALE_VALIDATOR);
+ VALIDATORS.put(DIM_SCREEN, DIM_SCREEN_VALIDATOR);
+ VALIDATORS.put(SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT_VALIDATOR);
+ VALIDATORS.put(SCREEN_BRIGHTNESS, SCREEN_BRIGHTNESS_VALIDATOR);
+ VALIDATORS.put(SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_VALIDATOR);
+ VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
+ VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
+ VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
+ VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR);
+ VALIDATORS.put(ALARM_ALERT, ALARM_ALERT_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_REPLACE, TEXT_AUTO_REPLACE_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_CAPS, TEXT_AUTO_CAPS_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_PUNCTUATE, TEXT_AUTO_PUNCTUATE_VALIDATOR);
+ VALIDATORS.put(TEXT_SHOW_PASSWORD, TEXT_SHOW_PASSWORD_VALIDATOR);
+ VALIDATORS.put(SHOW_GTALK_SERVICE_STATUS, SHOW_GTALK_SERVICE_STATUS_VALIDATOR);
+ VALIDATORS.put(WALLPAPER_ACTIVITY, WALLPAPER_ACTIVITY_VALIDATOR);
+ VALIDATORS.put(TIME_12_24, TIME_12_24_VALIDATOR);
+ VALIDATORS.put(DATE_FORMAT, DATE_FORMAT_VALIDATOR);
+ VALIDATORS.put(SETUP_WIZARD_HAS_RUN, SETUP_WIZARD_HAS_RUN_VALIDATOR);
+ VALIDATORS.put(ACCELEROMETER_ROTATION, ACCELEROMETER_ROTATION_VALIDATOR);
+ VALIDATORS.put(USER_ROTATION, USER_ROTATION_VALIDATOR);
+ VALIDATORS.put(DTMF_TONE_WHEN_DIALING, DTMF_TONE_WHEN_DIALING_VALIDATOR);
+ VALIDATORS.put(SOUND_EFFECTS_ENABLED, SOUND_EFFECTS_ENABLED_VALIDATOR);
+ VALIDATORS.put(HAPTIC_FEEDBACK_ENABLED, HAPTIC_FEEDBACK_ENABLED_VALIDATOR);
+ VALIDATORS.put(SHOW_WEB_SUGGESTIONS, SHOW_WEB_SUGGESTIONS_VALIDATOR);
+ VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(END_BUTTON_BEHAVIOR, END_BUTTON_BEHAVIOR_VALIDATOR);
+ VALIDATORS.put(ADVANCED_SETTINGS, ADVANCED_SETTINGS_VALIDATOR);
+ VALIDATORS.put(SCREEN_AUTO_BRIGHTNESS_ADJ, SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR);
+ VALIDATORS.put(VIBRATE_INPUT_DEVICES, VIBRATE_INPUT_DEVICES_VALIDATOR);
+ VALIDATORS.put(VOLUME_MASTER_MUTE, VOLUME_MASTER_MUTE_VALIDATOR);
+ VALIDATORS.put(MICROPHONE_MUTE, MICROPHONE_MUTE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATIONS_USE_RING_VOLUME, NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR);
+ VALIDATORS.put(VIBRATE_IN_SILENT, VIBRATE_IN_SILENT_VALIDATOR);
+ VALIDATORS.put(MEDIA_BUTTON_RECEIVER, MEDIA_BUTTON_RECEIVER_VALIDATOR);
+ VALIDATORS.put(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
+ HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR);
+ VALIDATORS.put(VIBRATE_WHEN_RINGING, VIBRATE_WHEN_RINGING_VALIDATOR);
+ VALIDATORS.put(DTMF_TONE_TYPE_WHEN_DIALING, DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR);
+ VALIDATORS.put(HEARING_AID, HEARING_AID_VALIDATOR);
+ VALIDATORS.put(TTY_MODE, TTY_MODE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, NOTIFICATION_LIGHT_PULSE_VALIDATOR);
+ VALIDATORS.put(POINTER_LOCATION, POINTER_LOCATION_VALIDATOR);
+ VALIDATORS.put(SHOW_TOUCHES, SHOW_TOUCHES_VALIDATOR);
+ VALIDATORS.put(WINDOW_ORIENTATION_LISTENER_LOG,
+ WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR);
+ VALIDATORS.put(LOCKSCREEN_SOUNDS_ENABLED, LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR);
+ VALIDATORS.put(LOCKSCREEN_DISABLED, LOCKSCREEN_DISABLED_VALIDATOR);
+ VALIDATORS.put(SIP_RECEIVE_CALLS, SIP_RECEIVE_CALLS_VALIDATOR);
+ VALIDATORS.put(SIP_CALL_OPTIONS, SIP_CALL_OPTIONS_VALIDATOR);
+ VALIDATORS.put(SIP_ALWAYS, SIP_ALWAYS_VALIDATOR);
+ VALIDATORS.put(SIP_ADDRESS_ONLY, SIP_ADDRESS_ONLY_VALIDATOR);
+ VALIDATORS.put(SIP_ASK_ME_EACH_TIME, SIP_ASK_ME_EACH_TIME_VALIDATOR);
+ VALIDATORS.put(POINTER_SPEED, POINTER_SPEED_VALIDATOR);
+ VALIDATORS.put(LOCK_TO_APP_ENABLED, LOCK_TO_APP_ENABLED_VALIDATOR);
+ VALIDATORS.put(EGG_MODE, EGG_MODE_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_IP, WIFI_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_GATEWAY, WIFI_STATIC_GATEWAY_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_NETMASK, WIFI_STATIC_NETMASK_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_DNS1, WIFI_STATIC_DNS1_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_DNS2, WIFI_STATIC_DNS2_VALIDATOR);
+ }
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
- * @hide
*/
- public static final String[] CLONE_TO_MANAGED_PROFILE = {
- DATE_FORMAT,
- HAPTIC_FEEDBACK_ENABLED,
- SOUND_EFFECTS_ENABLED,
- TEXT_SHOW_PASSWORD,
- TIME_12_24
- };
+ private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ CLONE_TO_MANAGED_PROFILE.add(DATE_FORMAT);
+ CLONE_TO_MANAGED_PROFILE.add(HAPTIC_FEEDBACK_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(SOUND_EFFECTS_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(TEXT_SHOW_PASSWORD);
+ CLONE_TO_MANAGED_PROFILE.add(TIME_12_24);
+ }
+
+ /** @hide */
+ public static void getCloneToManagedProfileSettings(Set<String> outKeySet) {
+ outKeySet.addAll(CLONE_TO_MANAGED_PROFILE);
+ }
/**
* When to use Wi-Fi calling
@@ -3103,7 +3608,7 @@ public final class Settings {
}
/** @hide */
- public static void getMovedKeys(HashSet<String> outKeySet) {
+ public static void getMovedToGlobalSettings(Set<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
}
@@ -3657,6 +4162,7 @@ public final class Settings {
* A flag containing settings used for biometric weak
* @hide
*/
+ @Deprecated
public static final String LOCK_BIOMETRIC_WEAK_FLAGS =
"lock_biometric_weak_flags";
@@ -3668,7 +4174,11 @@ public final class Settings {
/**
* Whether autolock is enabled (0 = false, 1 = true)
+ *
+ * @deprecated Use {@link android.app.KeyguardManager} to determine the state and security
+ * level of the keyguard.
*/
+ @Deprecated
public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
/**
@@ -3707,6 +4217,7 @@ public final class Settings {
* Ids of the user-selected appwidgets on the lockscreen (comma-delimited).
* @hide
*/
+ @Deprecated
public static final String LOCK_SCREEN_APPWIDGET_IDS =
"lock_screen_appwidget_ids";
@@ -3720,6 +4231,7 @@ public final class Settings {
* Id of the appwidget shown on the lock screen when appwidgets are disabled.
* @hide
*/
+ @Deprecated
public static final String LOCK_SCREEN_FALLBACK_APPWIDGET_ID =
"lock_screen_fallback_appwidget_id";
@@ -3727,6 +4239,7 @@ public final class Settings {
* Index of the lockscreen appwidget to restore, -1 if none.
* @hide
*/
+ @Deprecated
public static final String LOCK_SCREEN_STICKY_APPWIDGET =
"lock_screen_sticky_appwidget";
@@ -4758,6 +5271,10 @@ public final class Settings {
public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
/** @hide */
+ public static final String VOLUME_CONTROLLER_SERVICE_COMPONENT
+ = "volume_controller_service_component";
+
+ /** @hide */
public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations";
/**
@@ -4857,6 +5374,7 @@ public final class Settings {
ACCESSIBILITY_SCRIPT_INJECTION,
BACKUP_AUTO_RESTORE,
ENABLED_ACCESSIBILITY_SERVICES,
+ ENABLED_NOTIFICATION_LISTENERS,
TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
TOUCH_EXPLORATION_ENABLED,
ACCESSIBILITY_ENABLED,
@@ -4881,6 +5399,9 @@ public final class Settings {
WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, // moved to global
WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, // moved to global
WIFI_NUM_OPEN_NETWORKS_KEPT, // moved to global
+ SELECTED_SPELL_CHECKER,
+ SELECTED_SPELL_CHECKER_SUBTYPE,
+ SPELL_CHECKER_ENABLED,
MOUNT_PLAY_NOTIFICATION_SND,
MOUNT_UMS_AUTOSTART,
MOUNT_UMS_PROMPT,
@@ -4892,22 +5413,27 @@ public final class Settings {
/**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
- * @hide
*/
- public static final String[] CLONE_TO_MANAGED_PROFILE = {
- ACCESSIBILITY_ENABLED,
- ALLOW_MOCK_LOCATION,
- ALLOWED_GEOLOCATION_ORIGINS,
- DEFAULT_INPUT_METHOD,
- ENABLED_ACCESSIBILITY_SERVICES,
- ENABLED_INPUT_METHODS,
- LOCATION_MODE,
- LOCATION_PROVIDERS_ALLOWED,
- LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- SELECTED_INPUT_METHOD_SUBTYPE,
- SELECTED_SPELL_CHECKER,
- SELECTED_SPELL_CHECKER_SUBTYPE
- };
+ private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION);
+ CLONE_TO_MANAGED_PROFILE.add(ALLOWED_GEOLOCATION_ORIGINS);
+ CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
+ CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
+ CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
+ CLONE_TO_MANAGED_PROFILE.add(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER_SUBTYPE);
+ }
+
+ /** @hide */
+ public static void getCloneToManagedProfileSettings(Set<String> outKeySet) {
+ outKeySet.addAll(CLONE_TO_MANAGED_PROFILE);
+ }
/**
* Helper method for determining if a location provider is enabled.
@@ -6541,7 +7067,7 @@ public final class Settings {
/**
* Defines global runtime overrides to window policy.
*
- * See {@link com.android.internal.policy.impl.PolicyControl} for value format.
+ * See {@link com.android.server.policy.PolicyControl} for value format.
*
* @hide
*/
@@ -6624,6 +7150,33 @@ public final class Settings {
public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
/**
+ * Whether WFC is enabled
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+
+ /**
+ * WFC Mode.
+ * <p>
+ * Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
+ *
+ * @hide
+ */
+ public static final String WFC_IMS_MODE = "wfc_ims_mode";
+
+ /**
+ * Whether WFC roaming is enabled
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+
+ /**
* Global override to disable VoLTE (independent of user setting)
* <p>
* Type: int (1 for disable VoLTE, 0 to use user configuration)
@@ -6689,6 +7242,11 @@ public final class Settings {
MOVED_TO_SECURE.add(Settings.Global.INSTALL_NON_MARKET_APPS);
}
+ /** @hide */
+ public static void getMovedToSecureSettings(Set<String> outKeySet) {
+ outKeySet.addAll(MOVED_TO_SECURE);
+ }
+
/**
* Look up a name in the database.
* @param resolver to access the database with
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index d71ad03..0da4fd5 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -19,10 +19,18 @@ package android.provider;
import android.Manifest;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
+import android.database.Cursor;
import android.net.Uri;
import android.provider.CallLog.Calls;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.Voicemail;
+
+import java.util.List;
/**
* The contract between the voicemail provider and applications. Contains
@@ -199,13 +207,100 @@ public class VoicemailContract {
*/
public static final String _DATA = "_data";
+ // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming
+ // that was encoded into call log databases.
+
+ /**
+ * The component name of the account in string form.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
+
+ /**
+ * The identifier of a account that is unique to a specified component.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PHONE_ACCOUNT_ID = "subscription_id";
+
+ /**
+ * Flag used to indicate that local, unsynced changes are present.
+ * Currently, this is used to indicate that the voicemail was read or deleted.
+ * The value will be 1 if dirty is true, 0 if false.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DIRTY = "dirty";
+
+ /**
+ * Flag used to indicate that the voicemail was deleted but not synced to the server.
+ * A deleted row should be ignored.
+ * The value will be 1 if deleted is true, 0 if false.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String DELETED = "deleted";
+
/**
* A convenience method to build voicemail URI specific to a source package by appending
* {@link VoicemailContract#PARAM_KEY_SOURCE_PACKAGE} param to the base URI.
*/
public static Uri buildSourceUri(String packageName) {
return Voicemails.CONTENT_URI.buildUpon()
- .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build();
+ .appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName)
+ .build();
+ }
+
+ /**
+ * Inserts a new voicemail into the voicemail content provider.
+ *
+ * @param context The context of the app doing the inserting
+ * @param voicemail Data to be inserted
+ * @return {@link Uri} of the newly inserted {@link Voicemail}
+ */
+ public static Uri insert(Context context, Voicemail voicemail) {
+ ContentResolver contentResolver = context.getContentResolver();
+ ContentValues contentValues = getContentValues(voicemail);
+ return contentResolver.insert(Voicemails.CONTENT_URI, contentValues);
+ }
+
+ /**
+ * Inserts a list of voicemails into the voicemail content provider.
+ *
+ * @param context The context of the app doing the inserting
+ * @param voicemails Data to be inserted
+ * @return the number of voicemails inserted
+ */
+ public static int insert(Context context, List<Voicemail> voicemails) {
+ ContentResolver contentResolver = context.getContentResolver();
+ int count = voicemails.size();
+ for (int i = 0; i < count; i++) {
+ ContentValues contentValues = getContentValues(voicemails.get(i));
+ contentResolver.insert(Voicemails.CONTENT_URI, contentValues);
+ }
+ return count;
+ }
+
+ /**
+ * Clears all voicemails accessible to this voicemail content provider for the calling
+ * package. By default, a package only has permission to delete voicemails it inserted.
+ *
+ * @return the number of voicemails deleted
+ */
+ public static int deleteAll(Context context) {
+ return context.getContentResolver().delete(
+ buildSourceUri(context.getPackageName()), "", new String[0]);
+ }
+
+ /**
+ * Maps structured {@link Voicemail} to {@link ContentValues} in content provider.
+ */
+ private static ContentValues getContentValues(Voicemail voicemail) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Voicemails.DATE, String.valueOf(voicemail.getTimestampMillis()));
+ contentValues.put(Voicemails.NUMBER, voicemail.getNumber());
+ contentValues.put(Voicemails.DURATION, String.valueOf(voicemail.getDuration()));
+ contentValues.put(Voicemails.SOURCE_PACKAGE, voicemail.getSourcePackage());
+ contentValues.put(Voicemails.SOURCE_DATA, voicemail.getSourceData());
+ contentValues.put(Voicemails.IS_READ, voicemail.isRead() ? 1 : 0);
+ return contentValues;
}
}
@@ -222,10 +317,27 @@ public class VoicemailContract {
private Status() {
}
/**
- * The package name of the voicemail source. There can only be a one entry per source.
+ * The package name of the voicemail source. There can only be a one entry per account
+ * per source.
* <P>Type: TEXT</P>
*/
public static final String SOURCE_PACKAGE = SOURCE_PACKAGE_FIELD;
+
+ // Note: Multiple entries may exist for a single source if they are differentiated by the
+ // PHONE_ACCOUNT_* fields.
+
+ /**
+ * The component name of the account in string form.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name";
+
+ /**
+ * The identifier of a account that is unique to a specified component.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PHONE_ACCOUNT_ID = "phone_account_id";
+
/**
* The URI to call to invoke source specific voicemail settings screen. On a user request
* to setup voicemail an intent with action VIEW with this URI will be fired by the system.
@@ -318,5 +430,50 @@ public class VoicemailContract {
return Status.CONTENT_URI.buildUpon()
.appendQueryParameter(PARAM_KEY_SOURCE_PACKAGE, packageName).build();
}
+
+ /**
+ * A helper method to set the status of a voicemail source.
+ *
+ * @param context The context from the package calling the method. This will be the source.
+ * @param accountHandle The handle for the account the source is associated with.
+ * @param configurationState See {@link Status#CONFIGURATION_STATE}
+ * @param dataChannelState See {@link Status#DATA_CHANNEL_STATE}
+ * @param notificationChannelState See {@link Status#NOTIFICATION_CHANNEL_STATE}
+ */
+ public static void setStatus(Context context, PhoneAccountHandle accountHandle,
+ int configurationState, int dataChannelState, int notificationChannelState) {
+ ContentResolver contentResolver = context.getContentResolver();
+ Uri statusUri = buildSourceUri(context.getPackageName());
+ ContentValues values = new ContentValues();
+ values.put(Status.PHONE_ACCOUNT_COMPONENT_NAME,
+ accountHandle.getComponentName().toString());
+ values.put(Status.PHONE_ACCOUNT_ID, accountHandle.getId());
+ values.put(Status.CONFIGURATION_STATE, configurationState);
+ values.put(Status.DATA_CHANNEL_STATE, dataChannelState);
+ values.put(Status.NOTIFICATION_CHANNEL_STATE, notificationChannelState);
+
+ if (isStatusPresent(contentResolver, statusUri)) {
+ contentResolver.update(statusUri, values, null, null);
+ } else {
+ contentResolver.insert(statusUri, values);
+ }
+ }
+
+ /**
+ * Determines if a voicemail source exists in the status table.
+ *
+ * @param contentResolver A content resolver constructed from the appropriate context.
+ * @param statusUri The content uri for the source.
+ * @return {@code true} if a status entry for this source exists
+ */
+ private static boolean isStatusPresent(ContentResolver contentResolver, Uri statusUri) {
+ Cursor cursor = null;
+ try {
+ cursor = contentResolver.query(statusUri, null, null, null, null);
+ return cursor != null && cursor.getCount() != 0;
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
}
}
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java
index b803a1b..0f1d422 100644
--- a/core/java/android/security/keymaster/KeyCharacteristics.java
+++ b/core/java/android/security/keymaster/KeyCharacteristics.java
@@ -19,8 +19,6 @@ package android.security.keymaster;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.List;
-
/**
* @hide
*/
diff --git a/core/java/android/security/keymaster/KeymasterBlobArgument.java b/core/java/android/security/keymaster/KeymasterBlobArgument.java
index 27f1153..9af4445 100644
--- a/core/java/android/security/keymaster/KeymasterBlobArgument.java
+++ b/core/java/android/security/keymaster/KeymasterBlobArgument.java
@@ -17,7 +17,6 @@
package android.security.keymaster;
import android.os.Parcel;
-import android.os.Parcelable;
/**
* @hide
diff --git a/core/java/android/security/keymaster/KeymasterBooleanArgument.java b/core/java/android/security/keymaster/KeymasterBooleanArgument.java
index 8e17db4..5481e8f 100644
--- a/core/java/android/security/keymaster/KeymasterBooleanArgument.java
+++ b/core/java/android/security/keymaster/KeymasterBooleanArgument.java
@@ -17,7 +17,6 @@
package android.security.keymaster;
import android.os.Parcel;
-import android.os.Parcelable;
/**
* @hide
diff --git a/core/java/android/security/keymaster/KeymasterDateArgument.java b/core/java/android/security/keymaster/KeymasterDateArgument.java
index e8f4055..310f546 100644
--- a/core/java/android/security/keymaster/KeymasterDateArgument.java
+++ b/core/java/android/security/keymaster/KeymasterDateArgument.java
@@ -17,8 +17,6 @@
package android.security.keymaster;
import android.os.Parcel;
-import android.os.Parcelable;
-
import java.util.Date;
/**
diff --git a/core/java/android/security/keymaster/KeymasterIntArgument.java b/core/java/android/security/keymaster/KeymasterIntArgument.java
index 71797ae..c3738d7 100644
--- a/core/java/android/security/keymaster/KeymasterIntArgument.java
+++ b/core/java/android/security/keymaster/KeymasterIntArgument.java
@@ -17,7 +17,6 @@
package android.security.keymaster;
import android.os.Parcel;
-import android.os.Parcelable;
/**
* @hide
diff --git a/core/java/android/security/keymaster/KeymasterLongArgument.java b/core/java/android/security/keymaster/KeymasterLongArgument.java
index 781b1ab..3c565b8 100644
--- a/core/java/android/security/keymaster/KeymasterLongArgument.java
+++ b/core/java/android/security/keymaster/KeymasterLongArgument.java
@@ -17,7 +17,6 @@
package android.security.keymaster;
import android.os.Parcel;
-import android.os.Parcelable;
/**
* @hide
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
index ad54c96..4fc9d24 100644
--- a/core/java/android/security/keymaster/OperationResult.java
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -20,8 +20,6 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.List;
-
/**
* Class for handling the parceling of return values from keymaster crypto operations
* (begin/update/finish).
diff --git a/core/java/android/service/carrier/CarrierMessagingService.java b/core/java/android/service/carrier/CarrierMessagingService.java
index 3d6ebca..0592a84 100644
--- a/core/java/android/service/carrier/CarrierMessagingService.java
+++ b/core/java/android/service/carrier/CarrierMessagingService.java
@@ -23,11 +23,8 @@ 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;
/**
diff --git a/core/java/android/service/chooser/ChooserTarget.aidl b/core/java/android/service/chooser/ChooserTarget.aidl
new file mode 100644
index 0000000..ca91bc8
--- /dev/null
+++ b/core/java/android/service/chooser/ChooserTarget.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.chooser;
+
+parcelable ChooserTarget;
diff --git a/core/java/android/service/chooser/ChooserTarget.java b/core/java/android/service/chooser/ChooserTarget.java
new file mode 100644
index 0000000..7fd1d10
--- /dev/null
+++ b/core/java/android/service/chooser/ChooserTarget.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.chooser;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A ChooserTarget represents a deep-link into an application as returned by a
+ * {@link android.service.chooser.ChooserTargetService}.
+ */
+public final class ChooserTarget implements Parcelable {
+ private static final String TAG = "ChooserTarget";
+
+ /**
+ * The title of this target that will be shown to the user. The title may be truncated
+ * if it is too long to display in the space provided.
+ */
+ private CharSequence mTitle;
+
+ /**
+ * The icon that will be shown to the user to represent this target.
+ * The system may resize this icon as appropriate.
+ */
+ private Bitmap mIcon;
+
+ /**
+ * The IntentSender that will be used to deliver the intent to the target.
+ * It will be {@link android.content.Intent#fillIn(android.content.Intent, int)} filled in}
+ * by the real intent sent by the application.
+ */
+ private IntentSender mIntentSender;
+
+ /**
+ * The score given to this item. It can be normalized.
+ */
+ private float mScore;
+
+ /**
+ * Construct a deep link target for presentation by a chooser UI.
+ *
+ * <p>A target is composed of a title and an icon for presentation to the user.
+ * The UI presenting this target may truncate the title if it is too long to be presented
+ * in the available space, as well as crop, resize or overlay the supplied icon.</p>
+ *
+ * <p>The creator of a target may supply a ranking score. This score is assumed to be relative
+ * to the other targets supplied by the same
+ * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}.
+ * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).</p>
+ *
+ * <p>Before being sent, the PendingIntent supplied will be
+ * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied
+ * to the chooser. When constructing a PendingIntent for use in a ChooserTarget, make sure
+ * that you permit the relevant fields to be filled in using the appropriate flags such as
+ * {@link Intent#FILL_IN_ACTION}, {@link Intent#FILL_IN_CATEGORIES},
+ * {@link Intent#FILL_IN_DATA} and {@link Intent#FILL_IN_CLIP_DATA}. Note that
+ * {@link Intent#FILL_IN_CLIP_DATA} is required to appropriately receive URI permission grants
+ * for {@link Intent#ACTION_SEND} intents.</p>
+ *
+ * <p>Take care not to place custom {@link android.os.Parcelable} types into
+ * the PendingIntent as extras, as the system will not be able to unparcel it to merge
+ * additional extras.</p>
+ *
+ * @param title title of this target that will be shown to a user
+ * @param icon icon to represent this target
+ * @param score ranking score for this target between 0.0f and 1.0f, inclusive
+ * @param pendingIntent PendingIntent to fill in and send if the user chooses this target
+ */
+ public ChooserTarget(CharSequence title, Bitmap icon, float score,
+ PendingIntent pendingIntent) {
+ this(title, icon, score, pendingIntent.getIntentSender());
+ }
+
+ /**
+ * Construct a deep link target for presentation by a chooser UI.
+ *
+ * <p>A target is composed of a title and an icon for presentation to the user.
+ * The UI presenting this target may truncate the title if it is too long to be presented
+ * in the available space, as well as crop, resize or overlay the supplied icon.</p>
+ *
+ * <p>The creator of a target may supply a ranking score. This score is assumed to be relative
+ * to the other targets supplied by the same
+ * {@link ChooserTargetService#onGetChooserTargets(ComponentName, IntentFilter) query}.
+ * Scores should be in the range from 0.0f (unlikely match) to 1.0f (very relevant match).</p>
+ *
+ * <p>Before being sent, the IntentSender supplied will be
+ * {@link Intent#fillIn(Intent, int) filled in} by the Intent originally supplied
+ * to the chooser. When constructing an IntentSender for use in a ChooserTarget, make sure
+ * that you permit the relevant fields to be filled in using the appropriate flags such as
+ * {@link Intent#FILL_IN_ACTION}, {@link Intent#FILL_IN_CATEGORIES},
+ * {@link Intent#FILL_IN_DATA} and {@link Intent#FILL_IN_CLIP_DATA}. Note that
+ * {@link Intent#FILL_IN_CLIP_DATA} is required to appropriately receive URI permission grants
+ * for {@link Intent#ACTION_SEND} intents.</p>
+ *
+ * <p>Take care not to place custom {@link android.os.Parcelable} types into
+ * the IntentSender as extras, as the system will not be able to unparcel it to merge
+ * additional extras.</p>
+ *
+ * @param title title of this target that will be shown to a user
+ * @param icon icon to represent this target
+ * @param score ranking score for this target between 0.0f and 1.0f, inclusive
+ * @param intentSender IntentSender to fill in and send if the user chooses this target
+ */
+ public ChooserTarget(CharSequence title, Bitmap icon, float score, IntentSender intentSender) {
+ mTitle = title;
+ mIcon = icon;
+ if (score > 1.f || score < 0.f) {
+ throw new IllegalArgumentException("Score " + score + " out of range; "
+ + "must be between 0.0f and 1.0f");
+ }
+ mScore = score;
+ mIntentSender = intentSender;
+ }
+
+ ChooserTarget(Parcel in) {
+ mTitle = in.readCharSequence();
+ if (in.readInt() != 0) {
+ mIcon = Bitmap.CREATOR.createFromParcel(in);
+ } else {
+ mIcon = null;
+ }
+ mScore = in.readFloat();
+ mIntentSender = IntentSender.readIntentSenderOrNullFromParcel(in);
+ }
+
+ /**
+ * Returns the title of this target for display to a user. The UI displaying the title
+ * may truncate this title if it is too long to be displayed in full.
+ *
+ * @return the title of this target, intended to be shown to a user
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the icon representing this target for display to a user. The UI displaying the icon
+ * may crop, resize or overlay this icon.
+ *
+ * @return the icon representing this target, intended to be shown to a user
+ */
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Returns the ranking score supplied by the creator of this ChooserTarget.
+ * Values are between 0.0f and 1.0f. The UI displaying the target may
+ * take this score into account when sorting and merging targets from multiple sources.
+ *
+ * @return the ranking score for this target between 0.0f and 1.0f, inclusive
+ */
+ public float getScore() {
+ return mScore;
+ }
+
+ /**
+ * Returns the raw IntentSender supplied by the ChooserTarget's creator.
+ *
+ * <p>To fill in and send the intent, see {@link #sendIntent(Context, Intent)}.</p>
+ *
+ * @return the IntentSender supplied by the ChooserTarget's creator
+ */
+ public IntentSender getIntentSender() {
+ return mIntentSender;
+ }
+
+ /**
+ * Fill in the IntentSender supplied by the ChooserTarget's creator and send it.
+ *
+ * @param context the sending Context; generally the Activity presenting the chooser UI
+ * @param fillInIntent the Intent provided to the Chooser to be sent to a selected target
+ * @return true if sending the Intent was successful
+ */
+ public boolean sendIntent(Context context, Intent fillInIntent) {
+ if (fillInIntent != null) {
+ fillInIntent.migrateExtraStreamToClipData();
+ fillInIntent.prepareToLeaveProcess();
+ }
+ try {
+ mIntentSender.sendIntent(context, 0, fillInIntent, null, null);
+ return true;
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "sendIntent " + this + " failed", e);
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ChooserTarget{" + mIntentSender.getCreatorPackage() + "'" + mTitle
+ + "', " + mScore + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeCharSequence(mTitle);
+ if (mIcon != null) {
+ dest.writeInt(1);
+ mIcon.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeFloat(mScore);
+ IntentSender.writeIntentSenderOrNullToParcel(mIntentSender, dest);
+ }
+
+ public static final Creator<ChooserTarget> CREATOR
+ = new Creator<ChooserTarget>() {
+ @Override
+ public ChooserTarget createFromParcel(Parcel source) {
+ return new ChooserTarget(source);
+ }
+
+ @Override
+ public ChooserTarget[] newArray(int size) {
+ return new ChooserTarget[size];
+ }
+ };
+}
diff --git a/core/java/android/service/chooser/ChooserTargetService.java b/core/java/android/service/chooser/ChooserTargetService.java
new file mode 100644
index 0000000..9188806
--- /dev/null
+++ b/core/java/android/service/chooser/ChooserTargetService.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.chooser;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * A service that receives calls from the system when the user is asked to choose
+ * a target for an intent explicitly by another app. The calling app must have invoked
+ * {@link android.content.Intent#ACTION_CHOOSER ACTION_CHOOSER} as handled by the system;
+ * applications do not have the ability to query a ChooserTargetService directly.
+ *
+ * <p>Which ChooserTargetServices are queried depends on a system-level policy decision
+ * made at the moment the chooser is invoked, including but not limited to user time
+ * spent with the app package or associated components in the foreground, recency of usage
+ * or frequency of usage. These will generally correlate with the order that app targets
+ * are shown in the list of intent handlers shown in the system chooser or resolver.</p>
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_CHOOSER_TARGET_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * &lt;service android:name=".ChooserTargetService"
+ * android:label="&#64;string/service_name"
+ * android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.service.chooser.ChooserTargetService" />
+ * &lt;/intent-filter>
+ * &lt;/service>
+ * </pre>
+ *
+ * <p>For the system to query your service, you must add a &lt;meta-data> element to the
+ * Activity in your manifest that can handle Intents that you would also like to provide
+ * optional deep links for. For example, a chat app might offer deep links to recent active
+ * conversations instead of invoking a generic picker after the app itself is chosen as a target.
+ * </p>
+ *
+ * <p>The meta-data element should have the name
+ * <code>android.service.chooser.chooser_target_service</code> and a value corresponding to
+ * the component name of your service. Example:</p>
+ * <pre>
+ * &lt;activity android:name=".MyShareActivity"
+ * android:label="&#64;string/share_activity_label">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.intent.action.SEND" />
+ * &lt;/intent-filter>
+ * &lt;meta-data android:name="android.service.chooser.chooser_target_service"
+ * android:value=".ChooserTargetService" />
+ * &lt;/activity>
+ * </pre>
+ */
+public abstract class ChooserTargetService extends Service {
+ // TAG = "ChooserTargetService[MySubclass]";
+ private final String TAG = ChooserTargetService.class.getSimpleName()
+ + '[' + getClass().getSimpleName() + ']';
+
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.service.chooser.ChooserTargetService";
+
+ private IChooserTargetServiceWrapper mWrapper = null;
+
+ /**
+ * Called by the system to retrieve a set of deep-link {@link ChooserTarget targets} that
+ * can handle an intent.
+ *
+ * <p>The returned list should be sorted such that the most relevant targets appear first.
+ * Any PendingIntents used to construct the resulting ChooserTargets should always be prepared
+ * to have the relevant data fields filled in by the sender. See
+ * {@link ChooserTarget#ChooserTarget(CharSequence, android.graphics.Bitmap, float, android.app.PendingIntent) ChooserTarget}.</p>
+ *
+ * <p><em>Important:</em> Calls to this method from other applications will occur on
+ * a binder thread, not on your app's main thread. Make sure that access to relevant data
+ * within your app is thread-safe.</p>
+ *
+ * @param targetActivityName the ComponentName of the matched activity that referred the system
+ * to this ChooserTargetService
+ * @param matchedFilter the specific IntentFilter on the component that was matched
+ * @return a list of deep-link targets to fulfill the intent match, sorted by relevance
+ */
+ public abstract List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName,
+ IntentFilter matchedFilter);
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (!SERVICE_INTERFACE.equals(intent.getAction())) {
+ return null;
+ }
+
+ if (mWrapper == null) {
+ mWrapper = new IChooserTargetServiceWrapper();
+ }
+ return mWrapper;
+ }
+
+ private class IChooserTargetServiceWrapper extends IChooserTargetService.Stub {
+ @Override
+ public void getChooserTargets(ComponentName targetComponentName,
+ IntentFilter matchedFilter, IChooserTargetResult result) throws RemoteException {
+ List<ChooserTarget> targets = null;
+ try {
+ targets = onGetChooserTargets(targetComponentName, matchedFilter);
+ } finally {
+ result.sendResult(targets);
+ }
+ }
+ }
+}
diff --git a/core/java/android/net/http/HttpLog.java b/core/java/android/service/chooser/IChooserTargetResult.aidl
index 0934664..dbd7cbd 100644
--- a/core/java/android/net/http/HttpLog.java
+++ b/core/java/android/service/chooser/IChooserTargetResult.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,30 +14,14 @@
* limitations under the License.
*/
-/**
- * package-level logging flag
- */
-
-package android.net.http;
-
-import android.os.SystemClock;
+package android.service.chooser;
-import android.util.Log;
+import android.service.chooser.ChooserTarget;
/**
- * {@hide}
+ * @hide
*/
-class HttpLog {
- private final static String LOGTAG = "http";
-
- private static final boolean DEBUG = false;
- static final boolean LOGV = false;
-
- static void v(String logMe) {
- Log.v(LOGTAG, SystemClock.uptimeMillis() + " " + Thread.currentThread().getName() + " " + logMe);
- }
-
- static void e(String logMe) {
- Log.e(LOGTAG, logMe);
- }
+interface IChooserTargetResult
+{
+ void sendResult(in List<ChooserTarget> targets);
}
diff --git a/core/java/android/service/chooser/IChooserTargetService.aidl b/core/java/android/service/chooser/IChooserTargetService.aidl
new file mode 100644
index 0000000..6cfa9a2
--- /dev/null
+++ b/core/java/android/service/chooser/IChooserTargetService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.chooser;
+
+import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.service.chooser.IChooserTargetResult;
+
+/**
+ * @hide
+ */
+oneway interface IChooserTargetService
+{
+ void getChooserTargets(in ComponentName targetComponentName,
+ in IntentFilter matchedFilter, IChooserTargetResult result);
+} \ No newline at end of file
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 38b0439..822bfcc 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -18,6 +18,9 @@ package android.service.dreams;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.AlarmManager;
@@ -37,6 +40,7 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.PhoneWindow;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@@ -46,7 +50,6 @@ import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.util.MathUtils;
-import com.android.internal.policy.PolicyManager;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils.Dump;
@@ -341,6 +344,13 @@ public class DreamService extends Service implements Window.Callback {
/** {@inheritDoc} */
@Override
+ public ActionMode onWindowStartingActionMode(
+ android.view.ActionMode.Callback callback, int type) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void onActionModeStarted(ActionMode mode) {
}
@@ -382,7 +392,7 @@ public class DreamService extends Service implements Window.Callback {
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
- public void setContentView(int layoutResID) {
+ public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
@@ -442,7 +452,8 @@ public class DreamService extends Service implements Window.Callback {
*
* @return The view if found or null otherwise.
*/
- public View findViewById(int id) {
+ @Nullable
+ public View findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
@@ -945,7 +956,7 @@ public class DreamService extends Service implements Window.Callback {
throw new IllegalStateException("Only doze dreams can be windowless");
}
if (!mWindowless) {
- mWindow = PolicyManager.makeNewWindow(this);
+ mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
@@ -1037,10 +1048,10 @@ public class DreamService extends Service implements Window.Callback {
protected void dump(final FileDescriptor fd, PrintWriter pw, final String[] args) {
DumpUtils.dumpAsync(mHandler, new Dump() {
@Override
- public void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw, String prefix) {
dumpOnHandler(fd, pw, args);
}
- }, pw, 1000);
+ }, pw, "", 1000);
}
/** @hide */
diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java
index 178cc8b..6375668 100644
--- a/core/java/android/service/fingerprint/FingerprintManager.java
+++ b/core/java/android/service/fingerprint/FingerprintManager.java
@@ -17,11 +17,8 @@
package android.service.fingerprint;
import android.app.ActivityManagerNative;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -31,6 +28,9 @@ import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A class that coordinates access to the fingerprint hardware.
* @hide
@@ -97,6 +97,15 @@ public class FingerprintManager {
}
};
+ public static final class FingerprintItem {
+ public CharSequence name;
+ public int id;
+ FingerprintItem(CharSequence name, int id) {
+ this.name = name;
+ this.id = id;
+ }
+ }
+
/**
* @hide
*/
@@ -248,4 +257,57 @@ public class FingerprintManager {
private void sendError(int msg, int arg1, int arg2) {
mHandler.obtainMessage(msg, arg1, arg2);
}
+
+ /**
+ * @return list of current fingerprint items
+ * @hide
+ */
+ public List<FingerprintItem> getEnrolledFingerprints() {
+ int[] ids = FingerprintUtils.getFingerprintIdsForUser(mContext.getContentResolver(),
+ getCurrentUserId());
+ List<FingerprintItem> result = new ArrayList<FingerprintItem>();
+ for (int i = 0; i < ids.length; i++) {
+ // TODO: persist names in Settings
+ FingerprintItem item = new FingerprintItem("Finger" + ids[i], ids[i]);
+ result.add(item);
+ }
+ return result;
+ }
+
+ /**
+ * Determine if fingerprint hardware is present and functional.
+ * @return true if hardware is present and functional, false otherwise.
+ * @hide
+ */
+ public boolean isHardwareDetected() {
+ if (mService != null) {
+ try {
+ return mService.isHardwareDetected();
+ } catch (RemoteException e) {
+ Log.v(TAG, "Remote exception in isFingerprintHardwareDetected(): ", e);
+ }
+ } else {
+ Log.w(TAG, "isFingerprintHardwareDetected(): Service not connected!");
+ }
+ return false;
+ }
+
+ /**
+ * Renames the given fingerprint template
+ * @param fpId the fingerprint id
+ * @param newName the new name
+ * @hide
+ */
+ public void rename(int fpId, String newName) {
+ // Renames the given fpId
+ if (mService != null) {
+ try {
+ mService.rename(fpId, newName);
+ } catch (RemoteException e) {
+ Log.v(TAG, "Remote exception in rename(): ", e);
+ }
+ } else {
+ Log.w(TAG, "rename(): Service not connected!");
+ }
+ }
} \ No newline at end of file
diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/service/fingerprint/FingerprintUtils.java
index a4caf8e..cc17b99 100644
--- a/core/java/android/service/fingerprint/FingerprintUtils.java
+++ b/core/java/android/service/fingerprint/FingerprintUtils.java
@@ -21,7 +21,11 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* Utility class for dealing with fingerprints and fingerprint settings.
@@ -32,34 +36,50 @@ class FingerprintUtils {
private static final boolean DEBUG = true;
private static final String TAG = "FingerprintUtils";
+ private static int[] toIntArray(List<Integer> list) {
+ if (list == null) {
+ return null;
+ }
+ int[] arr = new int[list.size()];
+ int i = 0;
+ for (int elem : list) {
+ arr[i] = elem;
+ i++;
+ }
+ return arr;
+ }
+
public static int[] getFingerprintIdsForUser(ContentResolver res, int userId) {
String fingerIdsRaw = Settings.Secure.getStringForUser(res,
Settings.Secure.USER_FINGERPRINT_IDS, userId);
-
- int result[] = {};
+ ArrayList<Integer> tmp = new ArrayList<Integer>();
if (!TextUtils.isEmpty(fingerIdsRaw)) {
String[] fingerStringIds = fingerIdsRaw.replace("[","").replace("]","").split(", ");
- result = new int[fingerStringIds.length];
- for (int i = 0; i < result.length; i++) {
+ int length = fingerStringIds.length;
+ for (int i = 0; i < length; i++) {
try {
- result[i] = Integer.decode(fingerStringIds[i]);
+ tmp.add(Integer.decode(fingerStringIds[i]));
} catch (NumberFormatException e) {
- if (DEBUG) Log.d(TAG, "Error when parsing finger id " + fingerStringIds[i]);
+ if (DEBUG) Log.w(TAG, "Error parsing finger id: '" + fingerStringIds[i] + "'");
}
}
}
- return result;
+ return toIntArray(tmp);
}
public static void addFingerprintIdForUser(int fingerId, ContentResolver res, int userId) {
- int[] fingerIds = getFingerprintIdsForUser(res, userId);
-
// FingerId 0 has special meaning.
- if (fingerId == 0) return;
+ if (fingerId == 0) {
+ Log.w(TAG, "Tried to add fingerId 0");
+ return;
+ }
+
+ int[] fingerIds = getFingerprintIdsForUser(res, userId);
// Don't allow dups
- for (int i = 0; i < fingerIds.length; i++) {
- if (fingerIds[i] == fingerId) return;
+ if (ArrayUtils.contains(fingerIds, fingerId)) {
+ Log.w(TAG, "finger already added " + fingerId);
+ return;
}
int[] newList = Arrays.copyOf(fingerIds, fingerIds.length + 1);
newList[fingerIds.length] = fingerId;
@@ -72,19 +92,13 @@ class FingerprintUtils {
// FingerId 0 has special meaning. The HAL layer is supposed to remove each finger one
// at a time and invoke notify() for each fingerId. If we get called with 0 here, it means
// something bad has happened.
- if (fingerId == 0) throw new IllegalStateException("Bad fingerId");
+ if (fingerId == 0) throw new IllegalArgumentException("fingerId can't be 0");
- int[] fingerIds = getFingerprintIdsForUser(res, userId);
- int[] resultIds = Arrays.copyOf(fingerIds, fingerIds.length);
- int resultCount = 0;
- for (int i = 0; i < fingerIds.length; i++) {
- if (fingerId != fingerIds[i]) {
- resultIds[resultCount++] = fingerIds[i];
- }
- }
- if (resultCount > 0) {
+ final int[] fingerIds = getFingerprintIdsForUser(res, userId);
+ if (ArrayUtils.contains(fingerIds, fingerId)) {
+ final int[] result = ArrayUtils.removeInt(fingerIds, fingerId);
Settings.Secure.putStringForUser(res, Settings.Secure.USER_FINGERPRINT_IDS,
- Arrays.toString(Arrays.copyOf(resultIds, resultCount)), userId);
+ Arrays.toString(result), userId);
return true;
}
return false;
diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/service/fingerprint/IFingerprintService.aidl
index 43d5e9a..9b4750b 100644
--- a/core/java/android/service/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/service/fingerprint/IFingerprintService.aidl
@@ -22,10 +22,10 @@ import android.service.fingerprint.IFingerprintServiceReceiver;
* Communication channel from client to the fingerprint service.
* @hide
*/
-oneway interface IFingerprintService {
+interface IFingerprintService {
// Any errors resulting from this call will be returned to the listener
void enroll(IBinder token, long timeout, int userId);
-
+
// Any errors resulting from this call will be returned to the listener
void enrollCancel(IBinder token, int userId);
@@ -38,4 +38,10 @@ oneway interface IFingerprintService {
// Stops listening for fingerprints
void stopListening(IBinder token, int userId);
+
+ // Determine if HAL is loaded and ready
+ boolean isHardwareDetected();
+
+ // Rename the given fingerprint id
+ void rename(int fpId, String name);
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 3d39b18..0860153 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -77,6 +77,14 @@ public abstract class NotificationListenerService extends Service {
*/
public static final int INTERRUPTION_FILTER_NONE = 3;
+ /** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
+ * the value is unavailable for any reason. For example, before the notification listener
+ * is connected.
+ *
+ * {@see #onListenerConnected()}
+ */
+ public static final int INTERRUPTION_FILTER_UNKNOWN = 0;
+
/** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
* should disable notification sound, vibrating and other visual or aural effects.
* This does not change the interruption filter, only the effects. **/
@@ -473,15 +481,16 @@ public abstract class NotificationListenerService extends Service {
* <p>
* Listen for updates using {@link #onInterruptionFilterChanged(int)}.
*
- * @return One of the INTERRUPTION_FILTER_ constants, or 0 on errors.
+ * @return One of the INTERRUPTION_FILTER_ constants, or INTERRUPTION_FILTER_UNKNOWN when
+ * unavailable.
*/
public final int getCurrentInterruptionFilter() {
- if (!isBound()) return 0;
+ if (!isBound()) return INTERRUPTION_FILTER_UNKNOWN;
try {
return getNotificationInterface().getInterruptionFilterFromListener(mWrapper);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
- return 0;
+ return INTERRUPTION_FILTER_UNKNOWN;
}
}
diff --git a/core/java/android/service/restrictions/RestrictionsReceiver.java b/core/java/android/service/restrictions/RestrictionsReceiver.java
index 7c6e1f6..b830cb1 100644
--- a/core/java/android/service/restrictions/RestrictionsReceiver.java
+++ b/core/java/android/service/restrictions/RestrictionsReceiver.java
@@ -21,7 +21,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.RestrictionsManager;
-import android.os.IBinder;
import android.os.PersistableBundle;
/**
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
index 62fa978..a3178e2 100644
--- a/core/java/android/service/trust/TrustAgentService.java
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -25,13 +25,10 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-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;
@@ -125,12 +122,14 @@ public class TrustAgentService extends Service {
case MSG_CONFIGURE:
ConfigurationData data = (ConfigurationData) msg.obj;
boolean result = onConfigure(data.options);
- try {
- synchronized (mLock) {
- mCallback.onConfigureCompleted(result, data.token);
+ if (data.token != null) {
+ try {
+ synchronized (mLock) {
+ mCallback.onConfigureCompleted(result, data.token);
+ }
+ } catch (RemoteException e) {
+ onError("calling onSetTrustAgentFeaturesEnabledCompleted()");
}
- } catch (RemoteException e) {
- onError("calling onSetTrustAgentFeaturesEnabledCompleted()");
}
break;
case MSG_TRUST_TIMEOUT:
@@ -206,7 +205,7 @@ public class TrustAgentService extends Service {
* PersistableBundle)}.
* <p>Agents that support configuration options should overload this method and return 'true'.
*
- * @param options bundle containing all options or null if none.
+ * @param options The aggregated list of options or an empty list if no restrictions apply.
* @return true if the {@link TrustAgentService} supports configuration options.
*/
public boolean onConfigure(List<PersistableBundle> options) {
diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl
index 9f9c312..4f4b2d5 100644
--- a/core/java/android/service/voice/IVoiceInteractionSession.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl
@@ -17,11 +17,17 @@
package android.service.voice;
import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
/**
* @hide
*/
oneway interface IVoiceInteractionSession {
+ void show(in Bundle sessionArgs, int flags);
+ void hide();
+ void handleAssist(in Bundle assistData);
+ void handleScreenshot(in Bitmap screenshot);
void taskStarted(in Intent intent, int taskId);
void taskFinished(in Intent intent, int taskId);
void closeSystemDialogs();
diff --git a/core/java/android/service/voice/IVoiceInteractionSessionService.aidl b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
index 2519442..7f8158f 100644
--- a/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
@@ -24,5 +24,5 @@ import android.service.voice.IVoiceInteractionSession;
* @hide
*/
oneway interface IVoiceInteractionSessionService {
- void newSession(IBinder token, in Bundle args);
+ void newSession(IBinder token, in Bundle args, int startFlags);
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 0cde4f2..419b92b 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -40,15 +40,16 @@ import java.util.Locale;
/**
* Top-level service of the current global voice interactor, which is providing
- * support for hotwording etc.
+ * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
* The current VoiceInteractionService that has been selected by the user is kept
* always running by the system, to allow it to do things like listen for hotwords
- * in the background.
+ * in the background to instigate voice interactions.
*
* <p>Because this service is always running, it should be kept as lightweight as
* possible. Heavy-weight operations (including showing UI) should be implemented
- * in the associated {@link android.service.voice.VoiceInteractionSessionService}
- * that only runs while the operation is active.
+ * in the associated {@link android.service.voice.VoiceInteractionSessionService} when
+ * an actual voice interaction is taking place, and that service should run in a
+ * separate process from this one.
*/
public class VoiceInteractionService extends Service {
/**
@@ -69,6 +70,18 @@ public class VoiceInteractionService extends Service {
*/
public static final String SERVICE_META_DATA = "android.voice_interaction";
+ /**
+ * Flag for use with {@link #showSession}: request that the session be started with
+ * assist data from the currently focused activity.
+ */
+ public static final int START_WITH_ASSIST = 1<<0;
+
+ /**
+ * Flag for use with {@link #showSession}: request that the session be started with
+ * a screen shot of the currently focused activity.
+ */
+ public static final int START_WITH_SCREENSHOT = 1<<1;
+
IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
@Override public void ready() {
mHandler.sendEmptyMessage(MSG_READY);
@@ -132,19 +145,29 @@ public class VoiceInteractionService extends Service {
}
/**
- * Initiate the execution of a new {@link android.service.voice.VoiceInteractionSession}.
+ * Request that the associated {@link android.service.voice.VoiceInteractionSession} be
+ * shown to the user, starting it if necessary.
* @param args Arbitrary arguments that will be propagated to the session.
*/
- public void startSession(Bundle args) {
+ public void showSession(Bundle args, int flags) {
if (mSystemService == null) {
throw new IllegalStateException("Not available until onReady() is called");
}
try {
- mSystemService.startSession(mInterface, args);
+ mSystemService.showSession(mInterface, args, flags);
} catch (RemoteException e) {
}
}
+ /** @hide */
+ public void startSession(Bundle args, int flags) {
+ showSession(args, flags);
+ }
+ /** @hide */
+ public void startSession(Bundle args) {
+ startSession(args, 0);
+ }
+
@Override
public void onCreate() {
super.onCreate();
@@ -162,8 +185,8 @@ public class VoiceInteractionService extends Service {
/**
* Called during service initialization to tell you when the system is ready
* to receive interaction from it. You should generally do initialization here
- * rather than in {@link #onCreate()}. Methods such as {@link #startSession(Bundle)} and
- * {@link #createAlwaysOnHotwordDetector(String, Locale, android.service.voice.AlwaysOnHotwordDetector.Callback)}
+ * rather than in {@link #onCreate}. Methods such as {@link #showSession} and
+ * {@link #createAlwaysOnHotwordDetector}
* will not be operational until this point.
*/
public void onReady() {
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index e6e9413..ebc7507 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -99,6 +99,10 @@ public class VoiceInteractionServiceInfo {
mParseError = "No sessionService specified";
return;
}
+ if (mRecognitionService == null) {
+ mParseError = "No recognitionService specified";
+ return;
+ }
/* Not yet time
if (mRecognitionService == null) {
mParseError = "No recogitionService specified";
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 749f813..7a5bb90 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -16,12 +16,13 @@
package android.service.voice;
-import android.annotation.SystemApi;
import android.app.Dialog;
import android.app.Instrumentation;
+import android.app.VoiceInteractor;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.Region;
import android.inputmethodservice.SoftInputWindow;
@@ -51,10 +52,16 @@ import com.android.internal.os.SomeArgs;
import java.lang.ref.WeakReference;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
- * An active interaction session, started by a {@link VoiceInteractionService}.
+ * An active voice interaction session, providing a facility for the implementation
+ * to interact with the user in the voice interaction layer. The user interface is
+ * initially shown by default, and can be created be overriding {@link #onCreateContentView()}
+ * in which the UI can be built.
+ *
+ * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
+ * when done. It can also initiate voice interactions with applications by calling
+ * {@link #startVoiceActivity}</p>.
*/
public abstract class VoiceInteractionSession implements KeyEvent.Callback {
static final String TAG = "VoiceInteractionSession";
@@ -84,7 +91,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
final Insets mTmpInsets = new Insets();
- final int[] mTmpLocation = new int[2];
final WeakReference<VoiceInteractionSession> mWeakRef
= new WeakReference<VoiceInteractionSession>(this);
@@ -101,6 +107,17 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
@Override
+ public IVoiceInteractorRequest startPickOption(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence prompt,
+ VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) {
+ Request request = newRequest(callback);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOOO(MSG_START_PICK_OPTION,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ prompt, options, extras));
+ return request.mInterface;
+ }
+
+ @Override
public IVoiceInteractorRequest startCompleteVoice(String callingPackage,
IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
Request request = newRequest(callback);
@@ -146,6 +163,29 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
@Override
+ public void show(Bundle sessionArgs, int flags) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_SHOW,
+ flags, sessionArgs));
+ }
+
+ @Override
+ public void hide() {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessage(MSG_HIDE));
+ }
+
+ @Override
+ public void handleAssist(Bundle assistBundle) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_ASSIST,
+ assistBundle));
+ }
+
+ @Override
+ public void handleScreenshot(Bitmap screenshot) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_SCREENSHOT,
+ screenshot));
+ }
+
+ @Override
public void taskStarted(Intent intent, int taskId) {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_TASK_STARTED,
taskId, intent));
@@ -168,10 +208,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
};
- /**
- * @hide
- */
- @SystemApi
public static class Request {
final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
@Override
@@ -215,6 +251,20 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
+ public void sendPickOptionResult(boolean finished,
+ VoiceInteractor.PickOptionRequest.Option[] selections, Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendPickOptionResult: req=" + mInterface
+ + " finished=" + finished + " selections=" + selections
+ + " result=" + result);
+ if (finished) {
+ finishRequest();
+ }
+ mCallback.deliverPickOptionResult(mInterface, finished, selections, result);
+ } catch (RemoteException e) {
+ }
+ }
+
public void sendCompleteVoiceResult(Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface
@@ -235,12 +285,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
- public void sendCommandResult(boolean complete, Bundle result) {
+ public void sendCommandResult(boolean finished, Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+ " result=" + result);
- finishRequest();
- mCallback.deliverCommandResult(mInterface, complete, result);
+ if (finished) {
+ finishRequest();
+ }
+ mCallback.deliverCommandResult(mInterface, finished, result);
} catch (RemoteException e) {
}
}
@@ -255,10 +307,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
- /**
- * @hide
- */
- @SystemApi
public static class Caller {
final String packageName;
final int uid;
@@ -270,16 +318,21 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
static final int MSG_START_CONFIRMATION = 1;
- static final int MSG_START_COMPLETE_VOICE = 2;
- static final int MSG_START_ABORT_VOICE = 3;
- static final int MSG_START_COMMAND = 4;
- static final int MSG_SUPPORTS_COMMANDS = 5;
- static final int MSG_CANCEL = 6;
+ static final int MSG_START_PICK_OPTION = 2;
+ static final int MSG_START_COMPLETE_VOICE = 3;
+ static final int MSG_START_ABORT_VOICE = 4;
+ static final int MSG_START_COMMAND = 5;
+ static final int MSG_SUPPORTS_COMMANDS = 6;
+ static final int MSG_CANCEL = 7;
static final int MSG_TASK_STARTED = 100;
static final int MSG_TASK_FINISHED = 101;
static final int MSG_CLOSE_SYSTEM_DIALOGS = 102;
static final int MSG_DESTROY = 103;
+ static final int MSG_HANDLE_ASSIST = 104;
+ static final int MSG_HANDLE_SCREENSHOT = 105;
+ static final int MSG_SHOW = 106;
+ static final int MSG_HIDE = 107;
class MyCallbacks implements HandlerCaller.Callback, SoftInputWindow.Callback {
@Override
@@ -293,6 +346,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
(Bundle)args.arg4);
break;
+ case MSG_START_PICK_OPTION:
+ args = (SomeArgs)msg.obj;
+ if (DEBUG) Log.d(TAG, "onPickOption: req=" + ((Request) args.arg2).mInterface
+ + " prompt=" + args.arg3 + " options=" + args.arg4
+ + " extras=" + args.arg5);
+ onPickOption((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
+ (VoiceInteractor.PickOptionRequest.Option[])args.arg4,
+ (Bundle)args.arg5);
+ break;
case MSG_START_COMPLETE_VOICE:
args = (SomeArgs)msg.obj;
if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((Request) args.arg2).mInterface
@@ -320,9 +382,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
args.arg1 = onGetSupportedCommands((Caller) args.arg1, (String[]) args.arg2);
break;
case MSG_CANCEL:
- args = (SomeArgs)msg.obj;
- if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request) args.arg1).mInterface);
- onCancel((Request)args.arg1);
+ if (DEBUG) Log.d(TAG, "onCancel: req=" + ((Request)msg.obj));
+ onCancel((Request)msg.obj);
break;
case MSG_TASK_STARTED:
if (DEBUG) Log.d(TAG, "onTaskStarted: intent=" + msg.obj
@@ -342,6 +403,23 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
if (DEBUG) Log.d(TAG, "doDestroy");
doDestroy();
break;
+ case MSG_HANDLE_ASSIST:
+ if (DEBUG) Log.d(TAG, "onHandleAssist: " + msg.obj);
+ onHandleAssist((Bundle) msg.obj);
+ break;
+ case MSG_HANDLE_SCREENSHOT:
+ if (DEBUG) Log.d(TAG, "onHandleScreenshot: " + msg.obj);
+ onHandleScreenshot((Bitmap) msg.obj);
+ break;
+ case MSG_SHOW:
+ if (DEBUG) Log.d(TAG, "doShow: args=" + msg.obj
+ + " flags=" + msg.arg1);
+ doShow((Bundle) msg.obj, msg.arg1);
+ break;
+ case MSG_HIDE:
+ if (DEBUG) Log.d(TAG, "doHide");
+ doHide();
+ break;
}
}
@@ -354,10 +432,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final MyCallbacks mCallbacks = new MyCallbacks();
/**
- * @hide
* Information about where interesting parts of the input method UI appear.
*/
- @SystemApi
public static final class Insets {
/**
* This is the part of the UI that is the main content. It is
@@ -444,10 +520,50 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
- void doCreate(IVoiceInteractionManagerService service, IBinder token, Bundle args) {
+ void doCreate(IVoiceInteractionManagerService service, IBinder token, Bundle args,
+ int startFlags) {
mSystemService = service;
mToken = token;
- onCreate(args);
+ onCreate(args, startFlags);
+ }
+
+ void doShow(Bundle args, int flags) {
+ if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded
+ + " mWindowVisible=" + mWindowVisible);
+
+ if (mInShowWindow) {
+ Log.w(TAG, "Re-entrance in to showWindow");
+ return;
+ }
+
+ try {
+ mInShowWindow = true;
+ if (!mWindowVisible) {
+ if (!mWindowAdded) {
+ mWindowAdded = true;
+ View v = onCreateContentView();
+ if (v != null) {
+ setContentView(v);
+ }
+ }
+ }
+ onShow(args, flags);
+ if (!mWindowVisible) {
+ mWindowVisible = true;
+ mWindow.show();
+ }
+ } finally {
+ mWindowWasVisible = true;
+ mInShowWindow = false;
+ }
+ }
+
+ void doHide() {
+ if (mWindowVisible) {
+ mWindow.hide();
+ mWindowVisible = false;
+ onHide();
+ }
}
void doDestroy() {
@@ -477,57 +593,34 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content);
}
- /**
- * @hide
- */
- @SystemApi
- public void showWindow() {
- if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded
- + " mWindowVisible=" + mWindowVisible);
-
- if (mInShowWindow) {
- Log.w(TAG, "Re-entrance in to showWindow");
- return;
+ public void show() {
+ try {
+ mSystemService.showSessionFromSession(mToken, null, 0);
+ } catch (RemoteException e) {
}
+ }
+ public void hide() {
try {
- mInShowWindow = true;
- if (!mWindowVisible) {
- mWindowVisible = true;
- if (!mWindowAdded) {
- mWindowAdded = true;
- View v = onCreateContentView();
- if (v != null) {
- setContentView(v);
- }
- }
- mWindow.show();
- }
- } finally {
- mWindowWasVisible = true;
- mInShowWindow = false;
+ mSystemService.hideSessionFromSession(mToken);
+ } catch (RemoteException e) {
}
}
- /**
- * @hide
- */
- @SystemApi
+ /** TODO: remove */
+ public void showWindow() {
+ }
+
+ /** TODO: remove */
public void hideWindow() {
- if (mWindowVisible) {
- mWindow.hide();
- mWindowVisible = false;
- }
}
/**
- * @hide
* You can call this to customize the theme used by your IME's window.
* This must be set before {@link #onCreate}, so you
* will typically call it in your constructor with the resource ID
* of your custom theme.
*/
- @SystemApi
public void setTheme(int theme) {
if (mWindow != null) {
throw new IllegalStateException("Must be called before onCreate()");
@@ -536,7 +629,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
* Ask that a new activity be started for voice interaction. This will create a
* new dedicated task in the activity manager for this voice interaction session;
* this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
@@ -557,7 +649,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
* always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
* this is part of a voice interaction.
*/
- @SystemApi
public void startVoiceActivity(Intent intent) {
if (mToken == null) {
throw new IllegalStateException("Can't call before onCreate()");
@@ -573,19 +664,35 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
+ * Set whether this session will keep the device awake while it is running a voice
+ * activity. By default, the system holds a wake lock for it while in this state,
+ * so that it can work even if the screen is off. Setting this to false removes that
+ * wake lock, allowing the CPU to go to sleep. This is typically used if the
+ * session decides it has been waiting too long for a response from the user and
+ * doesn't want to let this continue to drain the battery.
+ *
+ * <p>Passing false here will release the wake lock, and you can call later with
+ * true to re-acquire it. It will also be automatically re-acquired for you each
+ * time you start a new voice activity task -- that is when you call
+ * {@link #startVoiceActivity}.</p>
+ */
+ public void setKeepAwake(boolean keepAwake) {
+ try {
+ mSystemService.setKeepAwake(mToken, keepAwake);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Convenience for inflating views.
*/
- @SystemApi
public LayoutInflater getLayoutInflater() {
return mInflater;
}
/**
- * @hide
* Retrieve the window being used to show the session's UI.
*/
- @SystemApi
public Dialog getWindow() {
return mWindow;
}
@@ -604,12 +711,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
- /**
- * Initiatize a new session.
- *
- * @param args The arguments that were supplied to
- * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}.
- */
+ /** @hide */
public void onCreate(Bundle args) {
mTheme = mTheme != 0 ? mTheme
: com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession;
@@ -617,24 +719,52 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
mCallbacks, this, mDispatcherState,
- WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.TOP, true);
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true);
mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
initViews();
- mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
+ mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT);
mWindow.setToken(mToken);
}
/**
+ * Initiatize a new session. The given args and showFlags are the initial values
+ * passed to {@link VoiceInteractionService#showSession VoiceInteractionService.showSession},
+ * if possible. Normally you should handle these in {@link #onShow}.
+ */
+ public void onCreate(Bundle args, int showFlags) {
+ onCreate(args);
+ }
+
+ /**
+ * Called when the session UI is going to be shown. This is called after
+ * {@link #onCreateContentView} (if the session's content UI needed to be created) and
+ * immediately prior to the window being shown. This may be called while the window
+ * is already shown, if a show request has come in while it is shown, to allow you to
+ * update the UI to match the new show arguments.
+ *
+ * @param args The arguments that were supplied to
+ * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
+ * @param showFlags The show flags originally provided to
+ * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
+ */
+ public void onShow(Bundle args, int showFlags) {
+ }
+
+ /**
+ * Called immediately after stopping to show the session UI.
+ */
+ public void onHide() {
+ }
+
+ /**
* Last callback to the session as it is being finished.
*/
public void onDestroy() {
}
/**
- * @hide
* Hook in which to create the session's UI.
*/
- @SystemApi
public View onCreateContentView() {
return null;
}
@@ -643,48 +773,34 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mContentFrame.removeAllViews();
mContentFrame.addView(view, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
+ ViewGroup.LayoutParams.MATCH_PARENT));
}
- /**
- * @hide
- */
- @SystemApi
+ public void onHandleAssist(Bundle assistBundle) {
+ }
+
+ public void onHandleScreenshot(Bitmap screenshot) {
+ }
+
public boolean onKeyDown(int keyCode, KeyEvent event) {
return false;
}
- /**
- * @hide
- */
- @SystemApi
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return false;
}
- /**
- * @hide
- */
- @SystemApi
public boolean onKeyUp(int keyCode, KeyEvent event) {
return false;
}
- /**
- * @hide
- */
- @SystemApi
public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
return false;
}
- /**
- * @hide
- */
- @SystemApi
public void onBackPressed() {
- finish();
+ hide();
}
/**
@@ -693,33 +809,29 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
* calls {@link #finish}.
*/
public void onCloseSystemDialogs() {
- finish();
+ hide();
}
/**
- * @hide
* Compute the interesting insets into your UI. The default implementation
- * uses the entire window frame as the insets. The default touchable
- * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}.
+ * sets {@link Insets#contentInsets outInsets.contentInsets.top} to the height
+ * of the window, meaning it should not adjust content underneath. The default touchable
+ * insets are {@link Insets#TOUCHABLE_INSETS_FRAME}, meaning it consumes all touch
+ * events within its window frame.
*
* @param outInsets Fill in with the current UI insets.
*/
- @SystemApi
public void onComputeInsets(Insets outInsets) {
- int[] loc = mTmpLocation;
- View decor = getWindow().getWindow().getDecorView();
- decor.getLocationInWindow(loc);
- outInsets.contentInsets.top = 0;
outInsets.contentInsets.left = 0;
- outInsets.contentInsets.right = 0;
outInsets.contentInsets.bottom = 0;
+ outInsets.contentInsets.right = 0;
+ View decor = getWindow().getWindow().getDecorView();
+ outInsets.contentInsets.top = decor.getHeight();
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME;
outInsets.touchableRegion.setEmpty();
}
/**
- * @hide
- * @SystemApi
* Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
* has actually started.
*
@@ -731,8 +843,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Called when the last activity of a task initiated by
* {@link #startVoiceActivity(android.content.Intent)} has finished. The default
* implementation calls {@link #finish()} on the assumption that this represents
@@ -744,12 +854,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
* @param taskId Unique ID of the finished task.
*/
public void onTaskFinished(Intent intent, int taskId) {
- finish();
+ hide();
}
/**
- * @hide
- * @SystemApi
* Request to query for what extended commands the session supports.
*
* @param caller Who is making the request.
@@ -764,8 +872,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Request to confirm with the user before proceeding with an unrecoverable operation,
* corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
* VoiceInteractor.ConfirmationRequest}.
@@ -781,8 +887,22 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
Bundle extras);
/**
- * @hide
- * @SystemApi
+ * Request for the user to pick one of N options, corresponding to a
+ * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param prompt The prompt informing the user of what they are picking, as per
+ * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
+ * @param options The set of options the user is picking from, as per
+ * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.PickOptionRequest VoiceInteractor.PickOptionRequest}.
+ */
+ public abstract void onPickOption(Caller caller, Request request, CharSequence prompt,
+ VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras);
+
+ /**
* Request to complete the voice interaction session because the voice activity successfully
* completed its interaction using voice. Corresponds to
* {@link android.app.VoiceInteractor.CompleteVoiceRequest
@@ -804,8 +924,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Request to abort the voice interaction session because the voice activity can not
* complete its interaction using voice. Corresponds to
* {@link android.app.VoiceInteractor.AbortVoiceRequest
@@ -824,8 +942,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Process an arbitrary extended command from the caller,
* corresponding to a {@link android.app.VoiceInteractor.CommandRequest
* VoiceInteractor.CommandRequest}.
@@ -840,8 +956,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
/**
- * @hide
- * @SystemApi
* Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
* that was previously delivered to {@link #onConfirm} or {@link #onCommand}.
*
diff --git a/core/java/android/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java
index e793849..008d55f 100644
--- a/core/java/android/service/voice/VoiceInteractionSessionService.java
+++ b/core/java/android/service/voice/VoiceInteractionSessionService.java
@@ -40,9 +40,9 @@ public abstract class VoiceInteractionSessionService extends Service {
VoiceInteractionSession mSession;
IVoiceInteractionSessionService mInterface = new IVoiceInteractionSessionService.Stub() {
- public void newSession(IBinder token, Bundle args) {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(MSG_NEW_SESSION,
- token, args));
+ public void newSession(IBinder token, Bundle args, int startFlags) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_NEW_SESSION,
+ startFlags, token, args));
}
};
@@ -54,7 +54,7 @@ public abstract class VoiceInteractionSessionService extends Service {
SomeArgs args = (SomeArgs)msg.obj;
switch (msg.what) {
case MSG_NEW_SESSION:
- doNewSession((IBinder)args.arg1, (Bundle)args.arg2);
+ doNewSession((IBinder)args.arg1, (Bundle)args.arg2, args.argi1);
break;
}
}
@@ -76,7 +76,7 @@ public abstract class VoiceInteractionSessionService extends Service {
return mInterface.asBinder();
}
- void doNewSession(IBinder token, Bundle args) {
+ void doNewSession(IBinder token, Bundle args, int startFlags) {
if (mSession != null) {
mSession.doDestroy();
mSession = null;
@@ -84,7 +84,7 @@ public abstract class VoiceInteractionSessionService extends Service {
mSession = onNewSession(args);
try {
mSystemService.deliverNewSession(token, mSession.mSession, mSession.mInteractor);
- mSession.doCreate(mSystemService, token, args);
+ mSession.doCreate(mSystemService, token, args, startFlags);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 9496b53..4902a71 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -64,8 +64,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
-
/**
* A wallpaper service is responsible for showing a live wallpaper behind
* applications that would like to sit on top of it. This service object
diff --git a/core/java/android/speech/tts/AudioPlaybackQueueItem.java b/core/java/android/speech/tts/AudioPlaybackQueueItem.java
index b4ac429..d4fea53 100644
--- a/core/java/android/speech/tts/AudioPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/AudioPlaybackQueueItem.java
@@ -17,7 +17,6 @@ package android.speech.tts;
import android.content.Context;
import android.media.AudioSystem;
-import android.media.AudioTrack;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.ConditionVariable;
diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java
index dc4e9d3..9920ea1 100644
--- a/core/java/android/speech/tts/BlockingAudioTrack.java
+++ b/core/java/android/speech/tts/BlockingAudioTrack.java
@@ -2,7 +2,6 @@
package android.speech.tts;
-import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioTrack;
import android.speech.tts.TextToSpeechService.AudioOutputParams;
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 899515f..d785c3f 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -40,7 +40,7 @@ oneway interface ITextToSpeechCallback {
*
* @param utteranceId Unique id identifying synthesis request.
*/
- void onStop(String utteranceId);
+ void onStop(String utteranceId, boolean isStarted);
/**
* Tells the client that the synthesis has failed.
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index c59ca8a..13fb657 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -15,6 +15,7 @@
*/
package android.speech.tts;
+import android.annotation.RawRes;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
@@ -37,7 +38,6 @@ import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -884,7 +884,7 @@ public class TextToSpeech {
*
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
- public int addSpeech(String text, String packagename, int resourceId) {
+ public int addSpeech(String text, String packagename, @RawRes int resourceId) {
synchronized (mStartLock) {
mUtterances.put(text, makeResourceUri(packagename, resourceId));
return SUCCESS;
@@ -918,7 +918,7 @@ public class TextToSpeech {
*
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
- public int addSpeech(CharSequence text, String packagename, int resourceId) {
+ public int addSpeech(CharSequence text, String packagename, @RawRes int resourceId) {
synchronized (mStartLock) {
mUtterances.put(text, makeResourceUri(packagename, resourceId));
return SUCCESS;
@@ -993,7 +993,7 @@ public class TextToSpeech {
*
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
- public int addEarcon(String earcon, String packagename, int resourceId) {
+ public int addEarcon(String earcon, String packagename, @RawRes int resourceId) {
synchronized(mStartLock) {
mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
return SUCCESS;
@@ -2066,10 +2066,10 @@ public class TextToSpeech {
private boolean mEstablished;
private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
- public void onStop(String utteranceId) throws RemoteException {
+ public void onStop(String utteranceId, boolean isStarted) throws RemoteException {
UtteranceProgressListener listener = mUtteranceProgressListener;
if (listener != null) {
- listener.onDone(utteranceId);
+ listener.onStop(utteranceId, isStarted);
}
};
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 9bb7f02..ba98f27 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -39,11 +39,9 @@ import android.util.Log;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
@@ -455,10 +453,37 @@ public abstract class TextToSpeechService extends Service {
private class SynthHandler extends Handler {
private SpeechItem mCurrentSpeechItem = null;
+ private ArrayList<Object> mFlushedObjects = new ArrayList<Object>();
+ private boolean mFlushAll;
+
public SynthHandler(Looper looper) {
super(looper);
}
+ private void startFlushingSpeechItems(Object callerIdentity) {
+ synchronized (mFlushedObjects) {
+ if (callerIdentity == null) {
+ mFlushAll = true;
+ } else {
+ mFlushedObjects.add(callerIdentity);
+ }
+ }
+ }
+ private void endFlushingSpeechItems(Object callerIdentity) {
+ synchronized (mFlushedObjects) {
+ if (callerIdentity == null) {
+ mFlushAll = false;
+ } else {
+ mFlushedObjects.remove(callerIdentity);
+ }
+ }
+ }
+ private boolean isFlushed(SpeechItem speechItem) {
+ synchronized (mFlushedObjects) {
+ return mFlushAll || mFlushedObjects.contains(speechItem.getCallerIdentity());
+ }
+ }
+
private synchronized SpeechItem getCurrentSpeechItem() {
return mCurrentSpeechItem;
}
@@ -522,9 +547,13 @@ public abstract class TextToSpeechService extends Service {
Runnable runnable = new Runnable() {
@Override
public void run() {
- setCurrentSpeechItem(speechItem);
- speechItem.play();
- setCurrentSpeechItem(null);
+ if (isFlushed(speechItem)) {
+ speechItem.stop();
+ } else {
+ setCurrentSpeechItem(speechItem);
+ speechItem.play();
+ setCurrentSpeechItem(null);
+ }
}
};
Message msg = Message.obtain(this, runnable);
@@ -552,12 +581,14 @@ public abstract class TextToSpeechService extends Service {
*
* Called on a service binder thread.
*/
- public int stopForApp(Object callerIdentity) {
+ public int stopForApp(final Object callerIdentity) {
if (callerIdentity == null) {
return TextToSpeech.ERROR;
}
- removeCallbacksAndMessages(callerIdentity);
+ // Flush pending messages from callerIdentity
+ startFlushingSpeechItems(callerIdentity);
+
// This stops writing data to the file / or publishing
// items to the audio playback handler.
//
@@ -573,20 +604,39 @@ public abstract class TextToSpeechService extends Service {
// Remove any enqueued audio too.
mAudioPlaybackHandler.stopForApp(callerIdentity);
+ // Stop flushing pending messages
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ endFlushingSpeechItems(callerIdentity);
+ }
+ };
+ sendMessage(Message.obtain(this, runnable));
return TextToSpeech.SUCCESS;
}
public int stopAll() {
+ // Order to flush pending messages
+ startFlushingSpeechItems(null);
+
// Stop the current speech item unconditionally .
SpeechItem current = setCurrentSpeechItem(null);
if (current != null) {
current.stop();
}
- // Remove all other items from the queue.
- removeCallbacksAndMessages(null);
// Remove all pending playback as well.
mAudioPlaybackHandler.stop();
+ // Message to stop flushing pending messages
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ endFlushingSpeechItems(null);
+ }
+ };
+ sendMessage(Message.obtain(this, runnable));
+
+
return TextToSpeech.SUCCESS;
}
}
@@ -698,7 +748,6 @@ public abstract class TextToSpeechService extends Service {
return mCallerIdentity;
}
-
public int getCallerUid() {
return mCallerUid;
}
@@ -752,6 +801,10 @@ public abstract class TextToSpeechService extends Service {
protected synchronized boolean isStopped() {
return mStopped;
}
+
+ protected synchronized boolean isStarted() {
+ return mStarted;
+ }
}
/**
@@ -777,7 +830,7 @@ public abstract class TextToSpeechService extends Service {
public void dispatchOnStop() {
final String utteranceId = getUtteranceId();
if (utteranceId != null) {
- mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId);
+ mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted());
}
}
@@ -940,6 +993,8 @@ public abstract class TextToSpeechService extends Service {
// turn implies that synthesis would not have started.
synthesisCallback.stop();
TextToSpeechService.this.onStop();
+ } else {
+ dispatchOnStop();
}
}
@@ -1345,11 +1400,11 @@ public abstract class TextToSpeechService extends Service {
}
}
- public void dispatchOnStop(Object callerIdentity, String utteranceId) {
+ public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) {
ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
if (cb == null) return;
try {
- cb.onStop(utteranceId);
+ cb.onStop(utteranceId, started);
} catch (RemoteException e) {
Log.e(TAG, "Callback onStop failed: " + e);
}
diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
index df6c010..412eba3 100644
--- a/core/java/android/speech/tts/TtsEngines.java
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -17,7 +17,6 @@ package android.speech.tts;
import org.xmlpull.v1.XmlPullParserException;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index 6769794..9eb22ef 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -60,6 +60,20 @@ public abstract class UtteranceProgressListener {
}
/**
+ * Called when an utterance has been stopped while in progress or flushed from the
+ * synthesis queue. This can happen if client calls {@link TextToSpeech#stop()}
+ * or use {@link TextToSpeech#QUEUE_FLUSH} as an argument in
+ * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} methods.
+ *
+ * @param utteranceId the utterance ID of the utterance.
+ * @param isStarted If true, then utterance was interrupted while being synthesized
+ * and it's output is incomplete. If it's false, then utterance was flushed
+ * before the synthesis started.
+ */
+ public void onStop(String utteranceId, boolean isStarted) {
+ }
+
+ /**
* Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
* progress listener.
*
@@ -83,6 +97,11 @@ public abstract class UtteranceProgressListener {
// Left unimplemented, has no equivalent in the old
// API.
}
+
+ @Override
+ public void onStop(String utteranceId, boolean isStarted) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
};
}
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 77ef1da..1bdaef0 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -270,22 +270,30 @@ public class DynamicLayout extends Layout
// generate new layout for affected text
StaticLayout reflowed;
+ StaticLayout.Builder b;
synchronized (sLock) {
reflowed = sStaticLayout;
+ b = sBuilder;
sStaticLayout = null;
+ sBuilder = null;
}
+ // TODO: make sure reflowed is properly initialized
if (reflowed == null) {
reflowed = new StaticLayout(null);
- } else {
- reflowed.prepare();
- }
-
- reflowed.generate(text, where, where + after,
- getPaint(), getWidth(), getTextDirectionHeuristic(), getSpacingMultiplier(),
- getSpacingAdd(), false,
- true, mEllipsizedWidth, mEllipsizeAt);
+ b = StaticLayout.Builder.obtain();
+ }
+
+ b.setText(text, where, where + after)
+ .setPaint(getPaint())
+ .setWidth(getWidth())
+ .setTextDir(getTextDirectionHeuristic())
+ .setSpacingMult(getSpacingMultiplier())
+ .setSpacingAdd(getSpacingAdd())
+ .setEllipsizedWidth(mEllipsizedWidth)
+ .setEllipsize(mEllipsizeAt);
+ reflowed.generate(b, false, true);
int n = reflowed.getLineCount();
// If the new layout has a blank line at the end, but it is not
@@ -359,9 +367,10 @@ public class DynamicLayout extends Layout
updateBlocks(startline, endline - 1, n);
+ b.finish();
synchronized (sLock) {
sStaticLayout = reflowed;
- reflowed.finish();
+ sBuilder = b;
}
}
@@ -720,7 +729,8 @@ public class DynamicLayout extends Layout
private int mTopPadding, mBottomPadding;
- private static StaticLayout sStaticLayout = new StaticLayout(null);
+ private static StaticLayout sStaticLayout = null;
+ private static StaticLayout.Builder sBuilder = null;
private static final Object[] sLock = new Object[0];
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index dc93bc2..8cf1b4b 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -672,7 +672,7 @@ class HtmlToSpannedConverter implements ContentHandler {
String name = f.mColor.substring(1);
int colorRes = res.getIdentifier(name, "color", "android");
if (colorRes != 0) {
- ColorStateList colors = res.getColorStateList(colorRes);
+ ColorStateList colors = res.getColorStateList(colorRes, null);
text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null),
where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index b84c3aa..fcf1828 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1564,7 +1564,7 @@ public abstract class Layout {
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
try {
- mt.setPara(text, start, end, TextDirectionHeuristics.LTR);
+ mt.setPara(text, start, end, TextDirectionHeuristics.LTR, null);
Directions directions;
int dir;
if (mt.mEasy) {
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 2415b11..55df206 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -16,7 +16,6 @@
package android.text;
-import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
@@ -40,6 +39,7 @@ class MeasuredText {
private int mPos;
private TextPaint mWorkPaint;
+ private StaticLayout.Builder mBuilder;
private MeasuredText() {
mWorkPaint = new TextPaint();
@@ -67,21 +67,29 @@ class MeasuredText {
}
static MeasuredText recycle(MeasuredText mt) {
- mt.mText = null;
- if (mt.mLen < 1000) {
- synchronized(sLock) {
- for (int i = 0; i < sCached.length; ++i) {
- if (sCached[i] == null) {
- sCached[i] = mt;
- mt.mText = null;
- break;
- }
+ mt.finish();
+ synchronized(sLock) {
+ for (int i = 0; i < sCached.length; ++i) {
+ if (sCached[i] == null) {
+ sCached[i] = mt;
+ mt.mText = null;
+ break;
}
}
}
return null;
}
+ void finish() {
+ mText = null;
+ mBuilder = null;
+ if (mLen > 1000) {
+ mWidths = null;
+ mChars = null;
+ mLevels = null;
+ }
+ }
+
void setPos(int pos) {
mPos = pos - mTextStart;
}
@@ -89,7 +97,9 @@ class MeasuredText {
/**
* Analyzes text for bidirectional runs. Allocates working buffers.
*/
- void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
+ void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
+ StaticLayout.Builder builder) {
+ mBuilder = builder;
mText = text;
mTextStart = start;
@@ -158,9 +168,24 @@ class MeasuredText {
int p = mPos;
mPos = p + len;
+ // try to do widths measurement in native code, but use Java if paint has been subclassed
+ // FIXME: may want to eliminate special case for subclass
+ float[] widths = null;
+ if (mBuilder == null || paint.getClass() != TextPaint.class) {
+ widths = mWidths;
+ }
if (mEasy) {
boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
- return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
+ float width = 0;
+ if (widths != null) {
+ width = paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, widths, p);
+ if (mBuilder != null) {
+ mBuilder.addMeasuredRun(p, p + len, widths);
+ }
+ } else {
+ width = mBuilder.addStyleRun(paint, p, p + len, isRtl);
+ }
+ return width;
}
float totalAdvance = 0;
@@ -168,8 +193,15 @@ class MeasuredText {
for (int q = p, i = p + 1, e = p + len;; ++i) {
if (i == e || mLevels[i] != level) {
boolean isRtl = (level & 0x1) != 0;
- totalAdvance +=
- paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
+ if (widths != null) {
+ totalAdvance +=
+ paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, widths, q);
+ if (mBuilder != null) {
+ mBuilder.addMeasuredRun(q, i, widths);
+ }
+ } else {
+ totalAdvance += mBuilder.addStyleRun(paint, q, i, isRtl);
+ }
if (i == e) {
break;
}
@@ -205,10 +237,14 @@ class MeasuredText {
// Use original text. Shouldn't matter.
wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
mTextStart + mPos + len, fm);
- float[] w = mWidths;
- w[mPos] = wid;
- for (int i = mPos + 1, e = mPos + len; i < e; i++)
- w[i] = 0;
+ if (mBuilder == null) {
+ float[] w = mWidths;
+ w[mPos] = wid;
+ for (int i = mPos + 1, e = mPos + len; i < e; i++)
+ w[i] = 0;
+ } else {
+ mBuilder.addReplacementRun(mPos, mPos + len, wid);
+ }
mPos += len;
}
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 06df683..992dc4d 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -26,6 +26,7 @@ import com.android.internal.util.GrowingArrayUtils;
import libcore.util.EmptyArray;
import java.lang.reflect.Array;
+import java.util.IdentityHashMap;
/**
* This is the class for text whose content and markup can both be changed.
@@ -68,6 +69,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
mSpanStarts = EmptyArray.INT;
mSpanEnds = EmptyArray.INT;
mSpanFlags = EmptyArray.INT;
+ mSpanMax = EmptyArray.INT;
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
@@ -94,6 +96,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
setSpan(false, spans[i], st, en, fl);
}
+ restoreInvariants();
}
}
@@ -147,9 +150,12 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
- for (int i = 0; i < mSpanCount; i++) {
- if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
- if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
+ if (mSpanCount != 0) {
+ for (int i = 0; i < mSpanCount; i++) {
+ if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
+ if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
+ }
+ calcMax(treeRoot());
}
}
@@ -167,35 +173,38 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
}
- // XXX be more clever
- for (int i = 0; i < mSpanCount; i++) {
- int start = mSpanStarts[i];
- int end = mSpanEnds[i];
-
- if (start > mGapStart)
- start -= mGapLength;
- if (start > where)
- start += mGapLength;
- else if (start == where) {
- int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
+ // TODO: be more clever (although the win really isn't that big)
+ if (mSpanCount != 0) {
+ for (int i = 0; i < mSpanCount; i++) {
+ int start = mSpanStarts[i];
+ int end = mSpanEnds[i];
- if (flag == POINT || (atEnd && flag == PARAGRAPH))
+ if (start > mGapStart)
+ start -= mGapLength;
+ if (start > where)
start += mGapLength;
- }
+ else if (start == where) {
+ int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
- if (end > mGapStart)
- end -= mGapLength;
- if (end > where)
- end += mGapLength;
- else if (end == where) {
- int flag = (mSpanFlags[i] & END_MASK);
+ if (flag == POINT || (atEnd && flag == PARAGRAPH))
+ start += mGapLength;
+ }
- if (flag == POINT || (atEnd && flag == PARAGRAPH))
+ if (end > mGapStart)
+ end -= mGapLength;
+ if (end > where)
end += mGapLength;
- }
+ else if (end == where) {
+ int flag = (mSpanFlags[i] & END_MASK);
+
+ if (flag == POINT || (atEnd && flag == PARAGRAPH))
+ end += mGapLength;
+ }
- mSpanStarts[i] = start;
- mSpanEnds[i] = end;
+ mSpanStarts[i] = start;
+ mSpanEnds[i] = end;
+ }
+ calcMax(treeRoot());
}
mGapStart = where;
@@ -243,6 +252,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
sendSpanRemoved(what, ostart, oend);
}
+ if (mIndexOfSpan != null) {
+ mIndexOfSpan.clear();
+ }
}
// Documentation from interface
@@ -277,12 +289,39 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
return append(String.valueOf(text));
}
+ // Returns true if a node was removed (so we can restart search from root)
+ private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i) {
+ if ((i & 1) != 0) {
+ // internal tree node
+ if (resolveGap(mSpanMax[i]) >= start &&
+ removeSpansForChange(start, end, textIsRemoved, leftChild(i))) {
+ return true;
+ }
+ }
+ if (i < mSpanCount) {
+ if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
+ mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
+ mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
+ // The following condition indicates that the span would become empty
+ (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
+ mIndexOfSpan.remove(mSpans[i]);
+ removeSpan(i);
+ return true;
+ }
+ return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 &&
+ removeSpansForChange(start, end, textIsRemoved, rightChild(i));
+ }
+ return false;
+ }
+
private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
// Can be negative
final int replacedLength = end - start;
final int replacementLength = csEnd - csStart;
final int nbNewChars = replacementLength - replacedLength;
+ boolean changed = false;
for (int i = mSpanCount - 1; i >= 0; i--) {
int spanStart = mSpanStarts[i];
if (spanStart > mGapStart)
@@ -309,8 +348,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
break;
}
- if (spanStart != ost || spanEnd != oen)
+ if (spanStart != ost || spanEnd != oen) {
setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
+ changed = true;
+ }
}
int flags = 0;
@@ -320,6 +361,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
mSpanFlags[i] |= flags;
}
+ if (changed) {
+ restoreInvariants();
+ }
moveGapTo(end);
@@ -331,23 +375,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
// The removal pass needs to be done before the gap is updated in order to broadcast the
// correct previous positions to the correct intersecting SpanWatchers
if (replacedLength > 0) { // no need for span fixup on pure insertion
- // A for loop will not work because the array is being modified
- // Do not iterate in reverse to keep the SpanWatchers notified in ordering
- // Also, a removed SpanWatcher should not get notified of removed spans located
- // further in the span array.
- int i = 0;
- while (i < mSpanCount) {
- if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
- mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
- mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
- // This condition indicates that the span would become empty
- (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
- removeSpan(i);
- continue; // do not increment i, spans will be shifted left in the array
- }
-
- i++;
+ while (mSpanCount > 0 &&
+ removeSpansForChange(start, end, textIsRemoved, treeRoot())) {
+ // keep deleting spans as needed, and restart from root after every deletion
+ // because deletion can invalidate an index.
}
}
@@ -360,6 +391,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
TextUtils.getChars(cs, csStart, csEnd, mText, start);
if (replacedLength > 0) { // no need for span fixup on pure insertion
+ // TODO potential optimization: only update bounds on intersecting spans
final boolean atEnd = (mGapStart + mGapLength == mText.length);
for (int i = 0; i < mSpanCount; i++) {
@@ -371,10 +403,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
atEnd, textIsRemoved);
}
+ // TODO potential optimization: only fix up invariants when bounds actually changed
+ restoreInvariants();
}
- mSpanCountBeforeAdd = mSpanCount;
-
if (cs instanceof Spanned) {
Spanned sp = (Spanned) cs;
Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
@@ -389,9 +421,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
// Add span only if this object is not yet used as a span in this string
if (getSpanStart(spans[i]) < 0) {
setSpan(false, spans[i], st - csStart + start, en - csStart + start,
- sp.getSpanFlags(spans[i]));
+ sp.getSpanFlags(spans[i]) | SPAN_ADDED);
}
}
+ restoreInvariants();
}
}
@@ -427,6 +460,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
return offset;
}
+ // Note: caller is responsible for removing the mIndexOfSpan entry.
private void removeSpan(int i) {
Object object = mSpans[i];
@@ -444,8 +478,12 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
mSpanCount--;
+ invalidateIndex(i);
mSpans[mSpanCount] = null;
+ // Invariants must be restored before sending span removed notifications.
+ restoreInvariants();
+
sendSpanRemoved(object, start, end);
}
@@ -496,10 +534,12 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
change(start, end, tb, tbstart, tbend);
if (adjustSelection) {
+ boolean changed = false;
if (selectionStart > start && selectionStart < end) {
final int offset = (selectionStart - start) * newLen / origLen;
selectionStart = start + offset;
+ changed = true;
setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
Spanned.SPAN_POINT_POINT);
}
@@ -507,9 +547,13 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
final int offset = (selectionEnd - start) * newLen / origLen;
selectionEnd = start + offset;
+ changed = true;
setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
Spanned.SPAN_POINT_POINT);
}
+ if (changed) {
+ restoreInvariants();
+ }
}
sendTextChanged(textWatchers, start, origLen, newLen);
@@ -536,12 +580,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
- for (int i = 0; i < mSpanCountBeforeAdd; i++) {
+ for (int i = 0; i < mSpanCount; i++) {
+ int spanFlags = mSpanFlags[i];
+
+ // This loop handles only modified (not added) spans.
+ if ((spanFlags & SPAN_ADDED) != 0) continue;
int spanStart = mSpanStarts[i];
int spanEnd = mSpanEnds[i];
if (spanStart > mGapStart) spanStart -= mGapLength;
if (spanEnd > mGapStart) spanEnd -= mGapLength;
- int spanFlags = mSpanFlags[i];
int newReplaceEnd = replaceEnd + nbNewChars;
boolean spanChanged = false;
@@ -588,13 +635,17 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
mSpanFlags[i] &= ~SPAN_START_END_MASK;
}
- // The spans starting at mIntermediateSpanCount were added from the replacement text
- for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
- int spanStart = mSpanStarts[i];
- int spanEnd = mSpanEnds[i];
- if (spanStart > mGapStart) spanStart -= mGapLength;
- if (spanEnd > mGapStart) spanEnd -= mGapLength;
- sendSpanAdded(mSpans[i], spanStart, spanEnd);
+ // Handle added spans
+ for (int i = 0; i < mSpanCount; i++) {
+ int spanFlags = mSpanFlags[i];
+ if ((spanFlags & SPAN_ADDED) != 0) {
+ mSpanFlags[i] &= ~SPAN_ADDED;
+ int spanStart = mSpanStarts[i];
+ int spanEnd = mSpanEnds[i];
+ if (spanStart > mGapStart) spanStart -= mGapLength;
+ if (spanEnd > mGapStart) spanEnd -= mGapLength;
+ sendSpanAdded(mSpans[i], spanStart, spanEnd);
+ }
}
}
@@ -607,6 +658,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
setSpan(true, what, start, end, flags);
}
+ // Note: if send is false, then it is the caller's responsibility to restore
+ // invariants. If send is false and the span already exists, then this method
+ // will not change the index of any spans.
private void setSpan(boolean send, Object what, int start, int end, int flags) {
checkRange("setSpan", start, end);
@@ -661,8 +715,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
int count = mSpanCount;
Object[] spans = mSpans;
- for (int i = 0; i < count; i++) {
- if (spans[i] == what) {
+ if (mIndexOfSpan != null) {
+ Integer index = mIndexOfSpan.get(what);
+ if (index != null) {
+ int i = index;
int ostart = mSpanStarts[i];
int oend = mSpanEnds[i];
@@ -675,7 +731,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
mSpanEnds[i] = end;
mSpanFlags[i] = flags;
- if (send) sendSpanChanged(what, ostart, oend, nstart, nend);
+ if (send) {
+ restoreInvariants();
+ sendSpanChanged(what, ostart, oend, nstart, nend);
+ }
return;
}
@@ -685,43 +744,48 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
+ invalidateIndex(mSpanCount);
mSpanCount++;
+ // Make sure there is enough room for empty interior nodes.
+ // This magic formula computes the size of the smallest perfect binary
+ // tree no smaller than mSpanCount.
+ int sizeOfMax = 2 * treeRoot() + 1;
+ if (mSpanMax.length < sizeOfMax) {
+ mSpanMax = new int[sizeOfMax];
+ }
- if (send) sendSpanAdded(what, nstart, nend);
+ if (send) {
+ restoreInvariants();
+ sendSpanAdded(what, nstart, nend);
+ }
}
/**
* Remove the specified markup object from the buffer.
*/
public void removeSpan(Object what) {
- for (int i = mSpanCount - 1; i >= 0; i--) {
- if (mSpans[i] == what) {
- removeSpan(i);
- return;
- }
+ if (mIndexOfSpan == null) return;
+ Integer i = mIndexOfSpan.remove(what);
+ if (i != null) {
+ removeSpan(i.intValue());
}
}
/**
+ * Return externally visible offset given offset into gapped buffer.
+ */
+ private int resolveGap(int i) {
+ return i > mGapStart ? i - mGapLength : i;
+ }
+
+ /**
* Return the buffer offset of the beginning of the specified
* markup object, or -1 if it is not attached to this buffer.
*/
public int getSpanStart(Object what) {
- int count = mSpanCount;
- Object[] spans = mSpans;
-
- for (int i = count - 1; i >= 0; i--) {
- if (spans[i] == what) {
- int where = mSpanStarts[i];
-
- if (where > mGapStart)
- where -= mGapLength;
-
- return where;
- }
- }
-
- return -1;
+ if (mIndexOfSpan == null) return -1;
+ Integer i = mIndexOfSpan.get(what);
+ return i == null ? -1 : resolveGap(mSpanStarts[i]);
}
/**
@@ -729,21 +793,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
* markup object, or -1 if it is not attached to this buffer.
*/
public int getSpanEnd(Object what) {
- int count = mSpanCount;
- Object[] spans = mSpans;
-
- for (int i = count - 1; i >= 0; i--) {
- if (spans[i] == what) {
- int where = mSpanEnds[i];
-
- if (where > mGapStart)
- where -= mGapLength;
-
- return where;
- }
- }
-
- return -1;
+ if (mIndexOfSpan == null) return -1;
+ Integer i = mIndexOfSpan.get(what);
+ return i == null ? -1 : resolveGap(mSpanEnds[i]);
}
/**
@@ -751,16 +803,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
* markup object, or 0 if it is not attached to this buffer.
*/
public int getSpanFlags(Object what) {
- int count = mSpanCount;
- Object[] spans = mSpans;
-
- for (int i = count - 1; i >= 0; i--) {
- if (spans[i] == what) {
- return mSpanFlags[i];
- }
- }
-
- return 0;
+ if (mIndexOfSpan == null) return 0;
+ Integer i = mIndexOfSpan.get(what);
+ return i == null ? 0 : mSpanFlags[i];
}
/**
@@ -770,59 +815,84 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
*/
@SuppressWarnings("unchecked")
public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
- if (kind == null) return ArrayUtils.emptyArray(kind);
+ if (kind == null || mSpanCount == 0) return ArrayUtils.emptyArray(kind);
+ int count = countSpans(queryStart, queryEnd, kind, treeRoot());
+ if (count == 0) {
+ return ArrayUtils.emptyArray(kind);
+ }
- int spanCount = mSpanCount;
- Object[] spans = mSpans;
- int[] starts = mSpanStarts;
- int[] ends = mSpanEnds;
- int[] flags = mSpanFlags;
- int gapstart = mGapStart;
- int gaplen = mGapLength;
+ // Safe conversion, but requires a suppressWarning
+ T[] ret = (T[]) Array.newInstance(kind, count);
+ getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, 0);
+ return ret;
+ }
+ private int countSpans(int queryStart, int queryEnd, Class kind, int i) {
int count = 0;
- T[] ret = null;
- T ret1 = null;
-
- for (int i = 0; i < spanCount; i++) {
- int spanStart = starts[i];
- if (spanStart > gapstart) {
- spanStart -= gaplen;
+ if ((i & 1) != 0) {
+ // internal tree node
+ int left = leftChild(i);
+ int spanMax = mSpanMax[left];
+ if (spanMax > mGapStart) {
+ spanMax -= mGapLength;
}
- if (spanStart > queryEnd) {
- continue;
+ if (spanMax >= queryStart) {
+ count = countSpans(queryStart, queryEnd, kind, left);
}
-
- int spanEnd = ends[i];
- if (spanEnd > gapstart) {
- spanEnd -= gaplen;
+ }
+ if (i < mSpanCount) {
+ int spanStart = mSpanStarts[i];
+ if (spanStart > mGapStart) {
+ spanStart -= mGapLength;
}
- if (spanEnd < queryStart) {
- continue;
+ if (spanStart <= queryEnd) {
+ int spanEnd = mSpanEnds[i];
+ if (spanEnd > mGapStart) {
+ spanEnd -= mGapLength;
+ }
+ if (spanEnd >= queryStart &&
+ (spanStart == spanEnd || queryStart == queryEnd ||
+ (spanStart != queryEnd && spanEnd != queryStart)) &&
+ kind.isInstance(mSpans[i])) {
+ count++;
+ }
+ if ((i & 1) != 0) {
+ count += countSpans(queryStart, queryEnd, kind, rightChild(i));
+ }
}
+ }
+ return count;
+ }
- if (spanStart != spanEnd && queryStart != queryEnd) {
- if (spanStart == queryEnd)
- continue;
- if (spanEnd == queryStart)
- continue;
+ @SuppressWarnings("unchecked")
+ private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind,
+ int i, T[] ret, int count) {
+ if ((i & 1) != 0) {
+ // internal tree node
+ int left = leftChild(i);
+ int spanMax = mSpanMax[left];
+ if (spanMax > mGapStart) {
+ spanMax -= mGapLength;
}
-
- // Expensive test, should be performed after the previous tests
- if (!kind.isInstance(spans[i])) continue;
-
- if (count == 0) {
- // Safe conversion thanks to the isInstance test above
- ret1 = (T) spans[i];
- count++;
- } else {
- if (count == 1) {
- // Safe conversion, but requires a suppressWarning
- ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
- ret[0] = ret1;
- }
-
- int prio = flags[i] & SPAN_PRIORITY;
+ if (spanMax >= queryStart) {
+ count = getSpansRec(queryStart, queryEnd, kind, left, ret, count);
+ }
+ }
+ if (i >= mSpanCount) return count;
+ int spanStart = mSpanStarts[i];
+ if (spanStart > mGapStart) {
+ spanStart -= mGapLength;
+ }
+ if (spanStart <= queryEnd) {
+ int spanEnd = mSpanEnds[i];
+ if (spanEnd > mGapStart) {
+ spanEnd -= mGapLength;
+ }
+ if (spanEnd >= queryStart &&
+ (spanStart == spanEnd || queryStart == queryEnd ||
+ (spanStart != queryEnd && spanEnd != queryStart)) &&
+ kind.isInstance(mSpans[i])) {
+ int prio = mSpanFlags[i] & SPAN_PRIORITY;
if (prio != 0) {
int j;
@@ -836,32 +906,18 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
System.arraycopy(ret, j, ret, j + 1, count - j);
// Safe conversion thanks to the isInstance test above
- ret[j] = (T) spans[i];
- count++;
+ ret[j] = (T) mSpans[i];
} else {
// Safe conversion thanks to the isInstance test above
- ret[count++] = (T) spans[i];
+ ret[count] = (T) mSpans[i];
}
+ count++;
+ }
+ if (count < ret.length && (i & 1) != 0) {
+ count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, count);
}
}
-
- if (count == 0) {
- return ArrayUtils.emptyArray(kind);
- }
- if (count == 1) {
- // Safe conversion, but requires a suppressWarning
- ret = (T[]) Array.newInstance(kind, 1);
- ret[0] = ret1;
- return ret;
- }
- if (count == ret.length) {
- return ret;
- }
-
- // Safe conversion, but requires a suppressWarning
- T[] nret = (T[]) Array.newInstance(kind, count);
- System.arraycopy(ret, 0, nret, 0, count);
- return nret;
+ return count;
}
/**
@@ -870,30 +926,31 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
* begins or ends.
*/
public int nextSpanTransition(int start, int limit, Class kind) {
- int count = mSpanCount;
- Object[] spans = mSpans;
- int[] starts = mSpanStarts;
- int[] ends = mSpanEnds;
- int gapstart = mGapStart;
- int gaplen = mGapLength;
-
+ if (mSpanCount == 0) return limit;
if (kind == null) {
kind = Object.class;
}
+ return nextSpanTransitionRec(start, limit, kind, treeRoot());
+ }
- for (int i = 0; i < count; i++) {
- int st = starts[i];
- int en = ends[i];
-
- if (st > gapstart)
- st -= gaplen;
- if (en > gapstart)
- en -= gaplen;
-
- if (st > start && st < limit && kind.isInstance(spans[i]))
+ private int nextSpanTransitionRec(int start, int limit, Class kind, int i) {
+ if ((i & 1) != 0) {
+ // internal tree node
+ int left = leftChild(i);
+ if (resolveGap(mSpanMax[left]) > start) {
+ limit = nextSpanTransitionRec(start, limit, kind, left);
+ }
+ }
+ if (i < mSpanCount) {
+ int st = resolveGap(mSpanStarts[i]);
+ int en = resolveGap(mSpanEnds[i]);
+ if (st > start && st < limit && kind.isInstance(mSpans[i]))
limit = st;
- if (en > start && en < limit && kind.isInstance(spans[i]))
+ if (en > start && en < limit && kind.isInstance(mSpans[i]))
limit = en;
+ if (st < limit && (i & 1) != 0) {
+ limit = nextSpanTransitionRec(start, limit, kind, rightChild(i));
+ }
}
return limit;
@@ -949,28 +1006,43 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
return new String(buf);
}
+ /**
+ * Returns the depth of TextWatcher callbacks. Returns 0 when the object is not handling
+ * TextWatchers. A return value greater than 1 implies that a TextWatcher caused a change that
+ * recursively triggered a TextWatcher.
+ */
+ public int getTextWatcherDepth() {
+ return mTextWatcherDepth;
+ }
+
private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
int n = watchers.length;
+ mTextWatcherDepth++;
for (int i = 0; i < n; i++) {
watchers[i].beforeTextChanged(this, start, before, after);
}
+ mTextWatcherDepth--;
}
private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
int n = watchers.length;
+ mTextWatcherDepth++;
for (int i = 0; i < n; i++) {
watchers[i].onTextChanged(this, start, before, after);
}
+ mTextWatcherDepth--;
}
private void sendAfterTextChanged(TextWatcher[] watchers) {
int n = watchers.length;
+ mTextWatcherDepth++;
for (int i = 0; i < n; i++) {
watchers[i].afterTextChanged(this);
}
+ mTextWatcherDepth--;
}
private void sendSpanAdded(Object what, int start, int end) {
@@ -1339,6 +1411,118 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
return hash;
}
+ // Primitives for treating span list as binary tree
+
+ // The spans (along with start and end offsets and flags) are stored in linear arrays sorted
+ // by start offset. For fast searching, there is a binary search structure imposed over these
+ // arrays. This structure is inorder traversal of a perfect binary tree, a slightly unusual
+ // but advantageous approach.
+
+ // The value-containing nodes are indexed 0 <= i < n (where n = mSpanCount), thus preserving
+ // logic that accesses the values as a contiguous array. Other balanced binary tree approaches
+ // (such as a complete binary tree) would require some shuffling of node indices.
+
+ // Basic properties of this structure: For a perfect binary tree of height m:
+ // The tree has 2^(m+1) - 1 total nodes.
+ // The root of the tree has index 2^m - 1.
+ // All leaf nodes have even index, all interior nodes odd.
+ // The height of a node of index i is the number of trailing ones in i's binary representation.
+ // The left child of a node i of height h is i - 2^(h - 1).
+ // The right child of a node i of height h is i + 2^(h - 1).
+
+ // Note that for arbitrary n, interior nodes of this tree may be >= n. Thus, the general
+ // structure of a recursive traversal of node i is:
+ // * traverse left child if i is an interior node
+ // * process i if i < n
+ // * traverse right child if i is an interior node and i < n
+
+ private int treeRoot() {
+ return Integer.highestOneBit(mSpanCount) - 1;
+ }
+
+ // (i+1) & ~i is equal to 2^(the number of trailing ones in i)
+ private static int leftChild(int i) {
+ return i - (((i + 1) & ~i) >> 1);
+ }
+
+ private static int rightChild(int i) {
+ return i + (((i + 1) & ~i) >> 1);
+ }
+
+ // The span arrays are also augmented by an mSpanMax[] array that represents an interval tree
+ // over the binary tree structure described above. For each node, the mSpanMax[] array contains
+ // the maximum value of mSpanEnds of that node and its descendants. Thus, traversals can
+ // easily reject subtrees that contain no spans overlapping the area of interest.
+
+ // Note that mSpanMax[] also has a valid valuefor interior nodes of index >= n, but which have
+ // descendants of index < n. In these cases, it simply represents the maximum span end of its
+ // descendants. This is a consequence of the perfect binary tree structure.
+ private int calcMax(int i) {
+ int max = 0;
+ if ((i & 1) != 0) {
+ // internal tree node
+ max = calcMax(leftChild(i));
+ }
+ if (i < mSpanCount) {
+ max = Math.max(max, mSpanEnds[i]);
+ if ((i & 1) != 0) {
+ max = Math.max(max, calcMax(rightChild(i)));
+ }
+ }
+ mSpanMax[i] = max;
+ return max;
+ }
+
+ // restores binary interval tree invariants after any mutation of span structure
+ private void restoreInvariants() {
+ if (mSpanCount == 0) return;
+
+ // invariant 1: span starts are nondecreasing
+
+ // This is a simple insertion sort because we expect it to be mostly sorted.
+ for (int i = 1; i < mSpanCount; i++) {
+ if (mSpanStarts[i] < mSpanStarts[i - 1]) {
+ Object span = mSpans[i];
+ int start = mSpanStarts[i];
+ int end = mSpanEnds[i];
+ int flags = mSpanFlags[i];
+ int j = i;
+ do {
+ mSpans[j] = mSpans[j - 1];
+ mSpanStarts[j] = mSpanStarts[j - 1];
+ mSpanEnds[j] = mSpanEnds[j - 1];
+ mSpanFlags[j] = mSpanFlags[j - 1];
+ j--;
+ } while (j > 0 && start < mSpanStarts[j - 1]);
+ mSpans[j] = span;
+ mSpanStarts[j] = start;
+ mSpanEnds[j] = end;
+ mSpanFlags[j] = flags;
+ invalidateIndex(j);
+ }
+ }
+
+ // invariant 2: max is max span end for each node and its descendants
+ calcMax(treeRoot());
+
+ // invariant 3: mIndexOfSpan maps spans back to indices
+ if (mIndexOfSpan == null) {
+ mIndexOfSpan = new IdentityHashMap<Object, Integer>();
+ }
+ for (int i = mLowWaterMark; i < mSpanCount; i++) {
+ Integer existing = mIndexOfSpan.get(mSpans[i]);
+ if (existing == null || existing != i) {
+ mIndexOfSpan.put(mSpans[i], i);
+ }
+ }
+ mLowWaterMark = Integer.MAX_VALUE;
+ }
+
+ // Call this on any update to mSpans[], so that mIndexOfSpan can be updated
+ private void invalidateIndex(int i) {
+ mLowWaterMark = Math.min(i, mLowWaterMark);
+ }
+
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
private InputFilter[] mFilters = NO_FILTERS;
@@ -1349,9 +1533,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private Object[] mSpans;
private int[] mSpanStarts;
private int[] mSpanEnds;
+ private int[] mSpanMax; // see calcMax() for an explanation of what this array stores
private int[] mSpanFlags;
private int mSpanCount;
- private int mSpanCountBeforeAdd;
+ private IdentityHashMap<Object, Integer> mIndexOfSpan;
+ private int mLowWaterMark; // indices below this have not been touched
+
+ // TextWatcher callbacks may trigger changes that trigger more callbacks. This keeps track of
+ // how deep the callbacks go.
+ private int mTextWatcherDepth;
// TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
private static final int MARK = 1;
@@ -1363,6 +1553,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private static final int START_SHIFT = 4;
// These bits are not (currently) used by SPANNED flags
+ private static final int SPAN_ADDED = 0x800;
private static final int SPAN_START_AT_START = 0x1000;
private static final int SPAN_START_AT_END = 0x2000;
private static final int SPAN_END_AT_START = 0x4000;
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 07505a9..ee39e27 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,7 +16,6 @@
package android.text;
-import android.graphics.Bitmap;
import android.graphics.Paint;
import android.text.style.LeadingMarginSpan;
import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
@@ -28,6 +27,9 @@ import android.util.Log;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import java.util.Arrays;
+import java.util.Locale;
+
/**
* StaticLayout is a Layout for text that will not be edited after it
* is laid out. Use {@link DynamicLayout} for text that may change.
@@ -42,6 +44,209 @@ public class StaticLayout extends Layout {
static final String TAG = "StaticLayout";
+ /**
+ * Builder for static layouts. It would be better if this were a public
+ * API (as it would offer much greater flexibility for adding new options)
+ * but for the time being it's just internal.
+ *
+ * @hide
+ */
+ public final static class Builder {
+ private Builder() {
+ mNativePtr = nNewBuilder();
+ }
+
+ static Builder obtain() {
+ Builder b = null;
+ synchronized (sLock) {
+ for (int i = 0; i < sCached.length; i++) {
+ if (sCached[i] != null) {
+ b = sCached[i];
+ sCached[i] = null;
+ break;
+ }
+ }
+ }
+ if (b == null) {
+ b = new Builder();
+ }
+
+ // set default initial values
+ b.mWidth = 0;
+ b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
+ b.mSpacingMult = 1.0f;
+ b.mSpacingAdd = 0.0f;
+ b.mIncludePad = true;
+ b.mEllipsizedWidth = 0;
+ b.mEllipsize = null;
+ b.mMaxLines = Integer.MAX_VALUE;
+
+ b.mMeasuredText = MeasuredText.obtain();
+ return b;
+ }
+
+ static void recycle(Builder b) {
+ b.mPaint = null;
+ b.mText = null;
+ MeasuredText.recycle(b.mMeasuredText);
+ synchronized (sLock) {
+ for (int i = 0; i < sCached.length; i++) {
+ if (sCached[i] == null) {
+ sCached[i] = b;
+ break;
+ }
+ }
+ }
+ }
+
+ // release any expensive state
+ /* package */ void finish() {
+ nFinishBuilder(mNativePtr);
+ mMeasuredText.finish();
+ }
+
+ public Builder setText(CharSequence source) {
+ return setText(source, 0, source.length());
+ }
+
+ public Builder setText(CharSequence source, int start, int end) {
+ mText = source;
+ mStart = start;
+ mEnd = end;
+ return this;
+ }
+
+ public Builder setPaint(TextPaint paint) {
+ mPaint = paint;
+ return this;
+ }
+
+ public Builder setWidth(int width) {
+ mWidth = width;
+ if (mEllipsize == null) {
+ mEllipsizedWidth = width;
+ }
+ return this;
+ }
+
+ public Builder setTextDir(TextDirectionHeuristic textDir) {
+ mTextDir = textDir;
+ return this;
+ }
+
+ // TODO: combine the following, as they're almost always set together?
+ public Builder setSpacingMult(float spacingMult) {
+ mSpacingMult = spacingMult;
+ return this;
+ }
+
+ public Builder setSpacingAdd(float spacingAdd) {
+ mSpacingAdd = spacingAdd;
+ return this;
+ }
+
+ public Builder setIncludePad(boolean includePad) {
+ mIncludePad = includePad;
+ return this;
+ }
+
+ // TODO: combine the following?
+ public Builder setEllipsizedWidth(int ellipsizedWidth) {
+ mEllipsizedWidth = ellipsizedWidth;
+ return this;
+ }
+
+ public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
+ mEllipsize = ellipsize;
+ return this;
+ }
+
+ public Builder setMaxLines(int maxLines) {
+ mMaxLines = maxLines;
+ return this;
+ }
+
+ /**
+ * Measurement and break iteration is done in native code. The protocol for using
+ * the native code is as follows.
+ *
+ * For each paragraph, do a nSetText of the paragraph text. Then, for each run within the
+ * paragraph:
+ * - setLocale (this must be done at least for the first run, optional afterwards)
+ * - one of the following, depending on the type of run:
+ * + addStyleRun (a text run, to be measured in native code)
+ * + addMeasuredRun (a run already measured in Java, passed into native code)
+ * + addReplacementRun (a replacement run, width is given)
+ *
+ * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
+ * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+ *
+ * After all paragraphs, call finish() to release expensive buffers.
+ */
+
+ private void setLocale(Locale locale) {
+ if (!locale.equals(mLocale)) {
+ nSetLocale(mNativePtr, locale.toLanguageTag());
+ mLocale = locale;
+ }
+ }
+
+ /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
+ return nAddStyleRun(mNativePtr, paint.getNativeInstance(), paint.mNativeTypeface,
+ start, end, isRtl);
+ }
+
+ /* package */ void addMeasuredRun(int start, int end, float[] widths) {
+ nAddMeasuredRun(mNativePtr, start, end, widths);
+ }
+
+ /* package */ void addReplacementRun(int start, int end, float width) {
+ nAddReplacementRun(mNativePtr, start, end, width);
+ }
+
+ public StaticLayout build() {
+ // TODO: can optimize based on whether ellipsis is needed
+ StaticLayout result = new StaticLayout(mText);
+ result.generate(this, this.mIncludePad, this.mIncludePad);
+ recycle(this);
+ return result;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nFreeBuilder(mNativePtr);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /* package */ long mNativePtr;
+
+ CharSequence mText;
+ int mStart;
+ int mEnd;
+ TextPaint mPaint;
+ int mWidth;
+ TextDirectionHeuristic mTextDir;
+ float mSpacingMult;
+ float mSpacingAdd;
+ boolean mIncludePad;
+ int mEllipsizedWidth;
+ TextUtils.TruncateAt mEllipsize;
+ int mMaxLines;
+
+ Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
+
+ // This will go away and be subsumed by native builder code
+ MeasuredText mMeasuredText;
+
+ Locale mLocale;
+
+ private static final Object sLock = new Object();
+ private static final Builder[] sCached = new Builder[3];
+ }
+
public StaticLayout(CharSequence source, TextPaint paint,
int width,
Alignment align, float spacingmult, float spacingadd,
@@ -109,6 +314,17 @@ public class StaticLayout extends Layout {
: new Ellipsizer(source),
paint, outerwidth, align, textDir, spacingmult, spacingadd);
+ Builder b = Builder.obtain();
+ b.setText(source, bufstart, bufend)
+ .setPaint(paint)
+ .setWidth(outerwidth)
+ .setTextDir(textDir)
+ .setSpacingMult(spacingmult)
+ .setSpacingAdd(spacingadd)
+ .setIncludePad(includepad)
+ .setEllipsizedWidth(ellipsizedWidth)
+ .setEllipsize(ellipsize)
+ .setMaxLines(maxLines);
/*
* This is annoying, but we can't refer to the layout until
* superclass construction is finished, and the superclass
@@ -135,14 +351,9 @@ public class StaticLayout extends Layout {
mLines = new int[mLineDirections.length];
mMaximumVisibleLineCount = maxLines;
- mMeasured = MeasuredText.obtain();
-
- generate(source, bufstart, bufend, paint, outerwidth, textDir, spacingmult,
- spacingadd, includepad, includepad, ellipsizedWidth,
- ellipsize);
+ generate(b, b.mIncludePad, b.mIncludePad);
- mMeasured = MeasuredText.recycle(mMeasured);
- mFontMetricsInt = null;
+ Builder.recycle(b);
}
/* package */ StaticLayout(CharSequence text) {
@@ -151,28 +362,36 @@ public class StaticLayout extends Layout {
mColumns = COLUMNS_ELLIPSIZE;
mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
mLines = new int[mLineDirections.length];
- // FIXME This is never recycled
- mMeasured = MeasuredText.obtain();
}
- /* package */ void generate(CharSequence source, int bufStart, int bufEnd,
- TextPaint paint, int outerWidth,
- TextDirectionHeuristic textDir, float spacingmult,
- float spacingadd, boolean includepad,
- boolean trackpad, float ellipsizedWidth,
- TextUtils.TruncateAt ellipsize) {
- int[] breakOpp = null;
- final String localeLanguageTag = paint.getTextLocale().toLanguageTag();
+ /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
+ CharSequence source = b.mText;
+ int bufStart = b.mStart;
+ int bufEnd = b.mEnd;
+ TextPaint paint = b.mPaint;
+ int outerWidth = b.mWidth;
+ TextDirectionHeuristic textDir = b.mTextDir;
+ float spacingmult = b.mSpacingMult;
+ float spacingadd = b.mSpacingAdd;
+ float ellipsizedWidth = b.mEllipsizedWidth;
+ TextUtils.TruncateAt ellipsize = b.mEllipsize;
+ LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs
+ // store span end locations
+ int[] spanEndCache = new int[4];
+ // store fontMetrics per span range
+ // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
+ int[] fmCache = new int[4 * 4];
+ b.setLocale(paint.getTextLocale()); // TODO: also respect LocaleSpan within the text
mLineCount = 0;
int v = 0;
boolean needMultiply = (spacingmult != 1 || spacingadd != 0);
- Paint.FontMetricsInt fm = mFontMetricsInt;
+ Paint.FontMetricsInt fm = b.mFontMetricsInt;
int[] chooseHtv = null;
- MeasuredText measured = mMeasured;
+ MeasuredText measured = b.mMeasuredText;
Spanned spanned = null;
if (source instanceof Spanned)
@@ -186,7 +405,7 @@ public class StaticLayout extends Layout {
else
paraEnd++;
- int firstWidthLineLimit = mLineCount + 1;
+ int firstWidthLineCount = 1;
int firstWidth = outerWidth;
int restWidth = outerWidth;
@@ -204,9 +423,8 @@ public class StaticLayout extends Layout {
// leading margin spans, not just this particular one
if (lms instanceof LeadingMarginSpan2) {
LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
- int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
- firstWidthLineLimit = Math.max(firstWidthLineLimit,
- lmsFirstLine + lms2.getLeadingMarginLineCount());
+ firstWidthLineCount = Math.max(firstWidthLineCount,
+ lms2.getLeadingMarginLineCount());
}
}
@@ -235,41 +453,31 @@ public class StaticLayout extends Layout {
}
}
- measured.setPara(source, paraStart, paraEnd, textDir);
+ measured.setPara(source, paraStart, paraEnd, textDir, b);
char[] chs = measured.mChars;
float[] widths = measured.mWidths;
byte[] chdirs = measured.mLevels;
int dir = measured.mDir;
boolean easy = measured.mEasy;
+ nSetText(b.mNativePtr, chs, paraEnd - paraStart);
- breakOpp = nLineBreakOpportunities(localeLanguageTag, chs, paraEnd - paraStart, breakOpp);
- int breakOppIndex = 0;
-
- int width = firstWidth;
-
- float w = 0;
- // here is the offset of the starting character of the line we are currently measuring
- int here = paraStart;
-
- // ok is a character offset located after a word separator (space, tab, number...) where
- // we would prefer to cut the current line. Equals to here when no such break was found.
- int ok = paraStart;
- float okWidth = w;
- int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
-
- // fit is a character offset such that the [here, fit[ range fits in the allowed width.
- // We will cut the line there if no ok position is found.
- int fit = paraStart;
- float fitWidth = w;
- int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
- // same as fitWidth but not including any trailing whitespace
- float fitWidthGraphing = w;
-
- boolean hasTabOrEmoji = false;
- boolean hasTab = false;
- TabStops tabStops = null;
-
+ // measurement has to be done before performing line breaking
+ // but we don't want to recompute fontmetrics or span ranges the
+ // second time, so we cache those and then use those stored values
+ int fmCacheCount = 0;
+ int spanEndCacheCount = 0;
for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ if (fmCacheCount * 4 >= fmCache.length) {
+ int[] grow = new int[fmCacheCount * 4 * 2];
+ System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+ fmCache = grow;
+ }
+
+ if (spanEndCacheCount >= spanEndCache.length) {
+ int[] grow = new int[spanEndCacheCount * 2];
+ System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+ spanEndCache = grow;
+ }
if (spanned == null) {
spanEnd = paraEnd;
@@ -285,200 +493,108 @@ public class StaticLayout extends Layout {
measured.addStyleRun(paint, spans, spanLen, fm);
}
- int fmTop = fm.top;
- int fmBottom = fm.bottom;
- int fmAscent = fm.ascent;
- int fmDescent = fm.descent;
-
- for (int j = spanStart; j < spanEnd; j++) {
- char c = chs[j - paraStart];
-
- if (c == CHAR_NEW_LINE) {
- // intentionally left empty
- } else if (c == CHAR_TAB) {
- if (hasTab == false) {
- hasTab = true;
- hasTabOrEmoji = true;
- if (spanned != null) {
- // First tab this para, check for tabstops
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- tabStops = new TabStops(TAB_INCREMENT, spans);
- }
- }
- }
- if (tabStops != null) {
- w = tabStops.nextTab(w);
- } else {
- w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
- }
- } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE
- && j + 1 < spanEnd) {
- int emoji = Character.codePointAt(chs, j - paraStart);
-
- if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
- Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji);
-
- if (bm != null) {
- Paint whichPaint;
-
- if (spanned == null) {
- whichPaint = paint;
- } else {
- whichPaint = mWorkPaint;
- }
-
- float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight();
-
- w += wid;
- hasTabOrEmoji = true;
- j++;
- } else {
- w += widths[j - paraStart];
- }
- } else {
- w += widths[j - paraStart];
- }
- } else {
- w += widths[j - paraStart];
+ // the order of storage here (top, bottom, ascent, descent) has to match the code below
+ // where these values are retrieved
+ fmCache[fmCacheCount * 4 + 0] = fm.top;
+ fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+ fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+ fmCache[fmCacheCount * 4 + 3] = fm.descent;
+ fmCacheCount++;
+
+ spanEndCache[spanEndCacheCount] = spanEnd;
+ spanEndCacheCount++;
+ }
+
+ // tab stop locations
+ int[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ int[] stops = new int[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = spans[i].getTabStop();
}
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
+ }
+ }
- boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP;
+ nGetWidths(b.mNativePtr, widths);
+ int breakCount = nComputeLineBreaks(b.mNativePtr, paraEnd - paraStart, firstWidth,
+ firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
+ lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
- if (w <= width || isSpaceOrTab) {
- fitWidth = w;
- if (!isSpaceOrTab) {
- fitWidthGraphing = w;
- }
- fit = j + 1;
-
- if (fmTop < fitTop)
- fitTop = fmTop;
- if (fmAscent < fitAscent)
- fitAscent = fmAscent;
- if (fmDescent > fitDescent)
- fitDescent = fmDescent;
- if (fmBottom > fitBottom)
- fitBottom = fmBottom;
-
- while (breakOpp[breakOppIndex] != -1
- && breakOpp[breakOppIndex] < j - paraStart + 1) {
- breakOppIndex++;
- }
- boolean isLineBreak = breakOppIndex < breakOpp.length &&
- breakOpp[breakOppIndex] == j - paraStart + 1;
-
- if (isLineBreak) {
- okWidth = fitWidthGraphing;
- ok = j + 1;
-
- if (fitTop < okTop)
- okTop = fitTop;
- if (fitAscent < okAscent)
- okAscent = fitAscent;
- if (fitDescent > okDescent)
- okDescent = fitDescent;
- if (fitBottom > okBottom)
- okBottom = fitBottom;
- }
- } else {
- int endPos;
- int above, below, top, bottom;
- float currentTextWidth;
-
- if (ok != here) {
- endPos = ok;
- above = okAscent;
- below = okDescent;
- top = okTop;
- bottom = okBottom;
- currentTextWidth = okWidth;
- } else if (fit != here) {
- endPos = fit;
- above = fitAscent;
- below = fitDescent;
- top = fitTop;
- bottom = fitBottom;
- currentTextWidth = fitWidth;
- } else {
- // must make progress, so take next character
- endPos = here + 1;
- // but to deal properly with clusters
- // take all zero width characters following that
- while (endPos < spanEnd && widths[endPos - paraStart] == 0) {
- endPos++;
- }
- above = fmAscent;
- below = fmDescent;
- top = fmTop;
- bottom = fmBottom;
- currentTextWidth = widths[here - paraStart];
- }
+ int[] breaks = lineBreaks.breaks;
+ float[] lineWidths = lineBreaks.widths;
+ boolean[] flags = lineBreaks.flags;
- 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, true);
-
- here = endPos;
- j = here - 1; // restart j-span loop from here, compensating for the j++
- ok = fit = here;
- w = 0;
- fitWidthGraphing = w;
- fitAscent = fitDescent = fitTop = fitBottom = 0;
- okAscent = okDescent = okTop = okBottom = 0;
-
- if (--firstWidthLineLimit <= 0) {
- width = restWidth;
- }
+ // here is the offset of the starting character of the line we are currently measuring
+ int here = paraStart;
- if (here < spanStart) {
- // The text was cut before the beginning of the current span range.
- // Exit the span loop, and get spanStart to start over from here.
- measured.setPos(here);
- spanEnd = here;
- break;
- }
+ int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmCacheIndex = 0;
+ int spanEndCacheIndex = 0;
+ int breakIndex = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ // retrieve end of span
+ spanEnd = spanEndCache[spanEndCacheIndex++];
+
+ // retrieve cached metrics, order matches above
+ fm.top = fmCache[fmCacheIndex * 4 + 0];
+ fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+ fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+ fm.descent = fmCache[fmCacheIndex * 4 + 3];
+ fmCacheIndex++;
+
+ if (fm.top < fmTop) {
+ fmTop = fm.top;
+ }
+ if (fm.ascent < fmAscent) {
+ fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
+ fmDescent = fm.descent;
+ }
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
+ }
- if (mLineCount >= mMaximumVisibleLineCount) {
- return;
- }
- }
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
}
- }
- if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) {
- if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
- paint.getFontMetricsInt(fm);
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+ int endPos = paraStart + breaks[breakIndex];
- fitTop = fm.top;
- fitBottom = fm.bottom;
- fitAscent = fm.ascent;
- fitDescent = fm.descent;
- }
+ boolean moreChars = (endPos < bufEnd);
- // Log.e("text", "output rest " + here + " to " + end);
-
- v = out(source,
- here, paraEnd, fitAscent, fitDescent,
- fitTop, fitBottom,
- v,
- spacingmult, spacingadd, chooseHt,
- chooseHtv, fm, hasTabOrEmoji,
- needMultiply, chdirs, dir, easy, bufEnd,
- includepad, trackpad, chs,
- widths, paraStart, ellipsize,
- ellipsizedWidth, w, paint, paraEnd != bufEnd);
- }
+ v = out(source, here, endPos,
+ fmAscent, fmDescent, fmTop, fmBottom,
+ v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
+ needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
+ chs, widths, paraStart, ellipsize, ellipsizedWidth,
+ lineWidths[breakIndex], paint, moreChars);
+
+ if (endPos < spanEnd) {
+ // preserve metrics for current span
+ fmTop = fm.top;
+ fmBottom = fm.bottom;
+ fmAscent = fm.ascent;
+ fmDescent = fm.descent;
+ } else {
+ fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
- paraStart = paraEnd;
+ here = endPos;
+ breakIndex++;
+
+ if (mLineCount >= mMaximumVisibleLineCount) {
+ return;
+ }
+ }
+ }
if (paraEnd == bufEnd)
break;
@@ -488,7 +604,7 @@ public class StaticLayout extends Layout {
mLineCount < mMaximumVisibleLineCount) {
// Log.e("text", "output last " + bufEnd);
- measured.setPara(source, bufStart, bufEnd, textDir);
+ measured.setPara(source, bufEnd, bufEnd, textDir, b);
paint.getFontMetricsInt(fm);
@@ -845,18 +961,32 @@ public class StaticLayout extends Layout {
return mEllipsizedWidth;
}
- void prepare() {
- mMeasured = MeasuredText.obtain();
- }
+ private static native long nNewBuilder();
+ private static native void nFreeBuilder(long nativePtr);
+ private static native void nFinishBuilder(long nativePtr);
+ private static native void nSetLocale(long nativePtr, String locale);
- void finish() {
- mMeasured = MeasuredText.recycle(mMeasured);
- }
+ private static native void nSetText(long nativePtr, char[] text, int length);
+
+ private static native float nAddStyleRun(long nativePtr, long nativePaint,
+ long nativeTypeface, int start, int end, boolean isRtl);
+
+ private static native void nAddMeasuredRun(long nativePtr,
+ int start, int end, float[] widths);
- // returns an array with terminal sentinel value -1 to indicate end
- // this is so that arrays can be recycled instead of allocating new arrays
- // every time
- private static native int[] nLineBreakOpportunities(String locale, char[] text, int length, int[] recycle);
+ private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
+
+ private static native void nGetWidths(long nativePtr, float[] widths);
+
+ // populates LineBreaks and returns the number of breaks found
+ //
+ // the arrays inside the LineBreaks objects are passed in as well
+ // to reduce the number of JNI calls in the common case where the
+ // arrays do not have to be resized
+ private static native int nComputeLineBreaks(long nativePtr,
+ int length, float firstWidth, int firstWidthLineCount, float restWidth,
+ int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
+ int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
private int mLineCount;
private int mTopPadding, mBottomPadding;
@@ -884,18 +1014,17 @@ public class StaticLayout extends Layout {
private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
private static final char CHAR_NEW_LINE = '\n';
- private static final char CHAR_TAB = '\t';
- private static final char CHAR_SPACE = ' ';
- private static final char CHAR_ZWSP = '\u200B';
private static final double EXTRA_ROUNDING = 0.5;
- private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800;
- private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF;
+ // This is used to return three arrays from a single JNI call when
+ // performing line breaking
+ /*package*/ static class LineBreaks {
+ private static final int INITIAL_SIZE = 16;
+ public int[] breaks = new int[INITIAL_SIZE];
+ public float[] widths = new float[INITIAL_SIZE];
+ public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji
+ // breaks, widths, and flags should all have the same length
+ }
- /*
- * This is reused across calls to generate()
- */
- private MeasuredText mMeasured;
- private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
}
diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java
index 0447117..4f8cff0 100644
--- a/core/java/android/text/TextPaint.java
+++ b/core/java/android/text/TextPaint.java
@@ -16,6 +16,7 @@
package android.text;
+import android.annotation.ColorInt;
import android.graphics.Paint;
/**
@@ -25,8 +26,10 @@ import android.graphics.Paint;
public class TextPaint extends Paint {
// Special value 0 means no background paint
+ @ColorInt
public int bgColor;
public int baselineShift;
+ @ColorInt
public int linkColor;
public int[] drawableState;
public float density = 1.0f;
@@ -34,6 +37,7 @@ public class TextPaint extends Paint {
* Special value 0 means no custom underline
* @hide
*/
+ @ColorInt
public int underlineColor = 0;
/**
* Defined as a multiplier of the default underline thickness. Use 1.0f for default thickness.
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 48bb5dd..676986d 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -16,6 +16,7 @@
package android.text;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
@@ -457,7 +458,7 @@ public class TextUtils {
* @param str the string to be examined
* @return true if str is null or zero length
*/
- public static boolean isEmpty(CharSequence str) {
+ public static boolean isEmpty(@Nullable CharSequence str) {
if (str == null || str.length() == 0)
return true;
else
@@ -1258,7 +1259,7 @@ public class TextUtils {
}
// XXX this is probably ok, but need to look at it more
- tempMt.setPara(format, 0, format.length(), textDir);
+ tempMt.setPara(format, 0, format.length(), textDir, null);
float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
if (w + moreWid <= avail) {
@@ -1280,7 +1281,7 @@ public class TextUtils {
private static float setPara(MeasuredText mt, TextPaint paint,
CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
- mt.setPara(text, start, end, textDir);
+ mt.setPara(text, start, end, textDir, null);
float width;
Spanned sp = text instanceof Spanned ? (Spanned) text : null;
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index c03f7a6..3ed37b3 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -23,8 +23,6 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
-import com.android.internal.R;
-
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java
index c9e09bd..f167aab 100644
--- a/core/java/android/text/style/ForegroundColorSpan.java
+++ b/core/java/android/text/style/ForegroundColorSpan.java
@@ -16,6 +16,7 @@
package android.text.style;
+import android.annotation.ColorInt;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextPaint;
@@ -26,7 +27,7 @@ public class ForegroundColorSpan extends CharacterStyle
private final int mColor;
- public ForegroundColorSpan(int color) {
+ public ForegroundColorSpan(@ColorInt int color) {
mColor = color;
}
@@ -46,6 +47,7 @@ public class ForegroundColorSpan extends CharacterStyle
dest.writeInt(mColor);
}
+ @ColorInt
public int getForegroundColor() {
return mColor;
}
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index 3d6f8e6..856dd0b 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -16,6 +16,7 @@
package android.text.style;
+import android.annotation.DrawableRes;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -110,7 +111,7 @@ public class ImageSpan extends DynamicDrawableSpan {
mSource = uri.toString();
}
- public ImageSpan(Context context, int resourceId) {
+ public ImageSpan(Context context, @DrawableRes int resourceId) {
this(context, resourceId, ALIGN_BOTTOM);
}
@@ -118,7 +119,7 @@ public class ImageSpan extends DynamicDrawableSpan {
* @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
* {@link DynamicDrawableSpan#ALIGN_BASELINE}.
*/
- public ImageSpan(Context context, int resourceId, int verticalAlignment) {
+ public ImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) {
super(verticalAlignment);
mContext = context;
mResourceId = resourceId;
diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java
index 29dd273..17748ca 100644
--- a/core/java/android/text/style/QuoteSpan.java
+++ b/core/java/android/text/style/QuoteSpan.java
@@ -16,6 +16,7 @@
package android.text.style;
+import android.annotation.ColorInt;
import android.graphics.Paint;
import android.graphics.Canvas;
import android.os.Parcel;
@@ -34,7 +35,7 @@ public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan {
mColor = 0xff0000ff;
}
- public QuoteSpan(int color) {
+ public QuoteSpan(@ColorInt int color) {
super();
mColor = color;
}
@@ -55,6 +56,7 @@ public class QuoteSpan implements LeadingMarginSpan, ParcelableSpan {
dest.writeInt(mColor);
}
+ @ColorInt
public int getColor() {
return mColor;
}
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 0f401a4..c119277 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -78,7 +78,10 @@ public class Linkify {
/**
* Bit field indicating that street addresses should be matched in methods that
- * take an options mask
+ * take an options mask. Note that this uses the
+ * {@link android.webkit.WebView#findAddress(String) findAddress()} method in
+ * {@link android.webkit.WebView} for finding addresses, which has various
+ * limitations.
*/
public static final int MAP_ADDRESSES = 0x08;
diff --git a/core/java/android/text/util/Rfc822Token.java b/core/java/android/text/util/Rfc822Token.java
index 0edeeb5..058757a 100644
--- a/core/java/android/text/util/Rfc822Token.java
+++ b/core/java/android/text/util/Rfc822Token.java
@@ -16,18 +16,21 @@
package android.text.util;
+import android.annotation.Nullable;
+
/**
* This class stores an RFC 822-like name, address, and comment,
* and provides methods to convert them to quoted strings.
*/
public class Rfc822Token {
+ @Nullable
private String mName, mAddress, mComment;
/**
* Creates a new Rfc822Token with the specified name, address,
* and comment.
*/
- public Rfc822Token(String name, String address, String comment) {
+ public Rfc822Token(@Nullable String name, @Nullable String address, @Nullable String comment) {
mName = name;
mAddress = address;
mComment = comment;
@@ -36,6 +39,7 @@ public class Rfc822Token {
/**
* Returns the name part.
*/
+ @Nullable
public String getName() {
return mName;
}
@@ -43,6 +47,7 @@ public class Rfc822Token {
/**
* Returns the address part.
*/
+ @Nullable
public String getAddress() {
return mAddress;
}
@@ -50,6 +55,7 @@ public class Rfc822Token {
/**
* Returns the comment part.
*/
+ @Nullable
public String getComment() {
return mComment;
}
@@ -57,21 +63,21 @@ public class Rfc822Token {
/**
* Changes the name to the specified name.
*/
- public void setName(String name) {
+ public void setName(@Nullable String name) {
mName = name;
}
/**
* Changes the address to the specified address.
*/
- public void setAddress(String address) {
+ public void setAddress(@Nullable String address) {
mAddress = address;
}
/**
* Changes the comment to the specified comment.
*/
- public void setComment(String comment) {
+ public void setComment(@Nullable String comment) {
mComment = comment;
}
diff --git a/core/java/android/transition/ChangeScroll.java b/core/java/android/transition/ChangeScroll.java
index 39291bf..5a78b94 100644
--- a/core/java/android/transition/ChangeScroll.java
+++ b/core/java/android/transition/ChangeScroll.java
@@ -28,8 +28,6 @@ import android.view.ViewGroup;
/**
* This transition captures the scroll properties of targets before and after
* the scene change and animates any changes.
- *
- * @hide
*/
public class ChangeScroll extends Transition {
diff --git a/core/java/android/transition/CircularPropagation.java b/core/java/android/transition/CircularPropagation.java
index 1e44cfa..c9faa0f 100644
--- a/core/java/android/transition/CircularPropagation.java
+++ b/core/java/android/transition/CircularPropagation.java
@@ -16,7 +16,6 @@
package android.transition;
import android.graphics.Rect;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
diff --git a/core/java/android/transition/SidePropagation.java b/core/java/android/transition/SidePropagation.java
index 5dd1fff..b10f6a0 100644
--- a/core/java/android/transition/SidePropagation.java
+++ b/core/java/android/transition/SidePropagation.java
@@ -16,7 +16,6 @@
package android.transition;
import android.graphics.Rect;
-import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 2705bcf..c942042 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -1762,7 +1762,17 @@ public abstract class Transition implements Cloneable {
runAnimators();
}
- boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) {
+ /**
+ * Returns whether transition values have changed between the start scene and the end scene
+ * (thus determining whether animation is required). The default implementation compares the
+ * property values returned from {@link #getTransitionProperties()}, or all property values if
+ * {@code getTransitionProperties()} returns null. Subclasses may override this method to
+ * provide logic more specific to their transition implementation.
+ *
+ * @param oldValues the first set of values, may be {@code null}
+ * @param newValues the second set of values, may be {@code null}
+ */
+ protected boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) {
boolean valuesChanged = false;
// if oldValues null, then transition didn't care to stash values,
// and won't get canceled
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 9009d6a..a7d9503 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -16,6 +16,7 @@
package android.transition;
+import android.annotation.TransitionRes;
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
@@ -71,7 +72,8 @@ public class TransitionInflater {
* @throws android.content.res.Resources.NotFoundException when the
* transition cannot be loaded
*/
- public Transition inflateTransition(int resource) {
+ public Transition inflateTransition(@TransitionRes int resource) {
+ //noinspection ResourceType
XmlResourceParser parser = mContext.getResources().getXml(resource);
try {
return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null);
@@ -98,7 +100,9 @@ public class TransitionInflater {
* @throws android.content.res.Resources.NotFoundException when the
* transition manager cannot be loaded
*/
- public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) {
+ public TransitionManager inflateTransitionManager(@TransitionRes int resource,
+ ViewGroup sceneRoot) {
+ //noinspection ResourceType
XmlResourceParser parser = mContext.getResources().getXml(resource);
try {
return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot);
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 7bd6287..0b70fdb 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -181,24 +181,27 @@ public class TransitionManager {
private static void changeScene(Scene scene, Transition transition) {
final ViewGroup sceneRoot = scene.getSceneRoot();
+ if (!sPendingTransitions.contains(sceneRoot)) {
+ sPendingTransitions.add(sceneRoot);
- Transition transitionClone = null;
- if (transition != null) {
- transitionClone = transition.clone();
- transitionClone.setSceneRoot(sceneRoot);
- }
+ Transition transitionClone = null;
+ if (transition != null) {
+ transitionClone = transition.clone();
+ transitionClone.setSceneRoot(sceneRoot);
+ }
- Scene oldScene = Scene.getCurrentScene(sceneRoot);
- if (oldScene != null && transitionClone != null &&
- oldScene.isCreatedFromLayoutResource()) {
- transitionClone.setCanRemoveViews(true);
- }
+ Scene oldScene = Scene.getCurrentScene(sceneRoot);
+ if (oldScene != null && transitionClone != null &&
+ oldScene.isCreatedFromLayoutResource()) {
+ transitionClone.setCanRemoveViews(true);
+ }
- sceneChangeSetup(sceneRoot, transitionClone);
+ sceneChangeSetup(sceneRoot, transitionClone);
- scene.enter();
+ scene.enter();
- sceneChangeRunTransition(sceneRoot, transitionClone);
+ sceneChangeRunTransition(sceneRoot, transitionClone);
+ }
}
private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
@@ -268,7 +271,12 @@ public class TransitionManager {
@Override
public boolean onPreDraw() {
removeListeners();
- sPendingTransitions.remove(mSceneRoot);
+
+ // Don't start the transition if it's no longer pending.
+ if (!sPendingTransitions.remove(mSceneRoot)) {
+ return true;
+ }
+
// Add to running list, handle end to remove it
final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
getRunningTransitions();
@@ -417,4 +425,24 @@ public class TransitionManager {
sceneChangeRunTransition(sceneRoot, transitionClone);
}
}
+
+ /**
+ * Ends all pending and ongoing transitions on the specified scene root.
+ *
+ * @param sceneRoot The root of the View hierarchy to end transitions on.
+ * @hide
+ */
+ public static void endTransitions(final ViewGroup sceneRoot) {
+ sPendingTransitions.remove(sceneRoot);
+
+ final ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
+ if (runningTransitions != null) {
+ final int count = runningTransitions.size();
+ for (int i = 0; i < count; i++) {
+ final Transition transition = runningTransitions.get(i);
+ transition.end();
+ }
+ }
+
+ }
}
diff --git a/core/java/android/transition/TransitionPropagation.java b/core/java/android/transition/TransitionPropagation.java
index 9a481c2..b831038 100644
--- a/core/java/android/transition/TransitionPropagation.java
+++ b/core/java/android/transition/TransitionPropagation.java
@@ -15,7 +15,6 @@
*/
package android.transition;
-import android.graphics.Rect;
import android.view.ViewGroup;
/**
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 8779229..cd68fd1 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -22,9 +22,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -182,7 +179,7 @@ public abstract class Visibility extends Transition {
return visibility == View.VISIBLE && parent != null;
}
- private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
+ private static VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
TransitionValues endValues) {
final VisibilityInfo visInfo = new VisibilityInfo();
visInfo.visibilityChange = false;
@@ -484,7 +481,7 @@ public abstract class Visibility extends Transition {
}
@Override
- boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) {
+ protected boolean areValuesChanged(TransitionValues oldValues, TransitionValues newValues) {
if (oldValues == null && newValues == null) {
return false;
}
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index a6466fc..3aa3447 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -102,7 +102,7 @@ public class AtomicFile {
str = new FileOutputStream(mBaseName);
} catch (FileNotFoundException e) {
File parent = mBaseName.getParentFile();
- if (!parent.mkdir()) {
+ if (!parent.mkdirs()) {
throw new IOException("Couldn't create directory " + mBaseName);
}
FileUtils.setPermissions(
diff --git a/core/java/android/util/AttributeSet.java b/core/java/android/util/AttributeSet.java
index 74942ba..eb8c168 100644
--- a/core/java/android/util/AttributeSet.java
+++ b/core/java/android/util/AttributeSet.java
@@ -39,7 +39,7 @@ package android.util;
* is more useful in conjunction with compiled XML resources:
*
* <pre>
- * XmlPullParser parser = resources.getXml(myResouce);
+ * XmlPullParser parser = resources.getXml(myResource);
* AttributeSet attributes = Xml.asAttributeSet(parser);</pre>
*
* <p>The implementation returned here, unlike using
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index f607207..84d9ce8 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -16,6 +16,7 @@
package android.util;
+import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
@@ -123,4 +124,83 @@ public class DebugUtils {
}
}
+ /** @hide */
+ public static void printSizeValue(PrintWriter pw, long number) {
+ float result = number;
+ String suffix = "";
+ if (result > 900) {
+ suffix = "KB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "MB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "GB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "TB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "PB";
+ result = result / 1024;
+ }
+ String value;
+ if (result < 1) {
+ value = String.format("%.2f", result);
+ } else if (result < 10) {
+ value = String.format("%.1f", result);
+ } else if (result < 100) {
+ value = String.format("%.0f", result);
+ } else {
+ value = String.format("%.0f", result);
+ }
+ pw.print(value);
+ pw.print(suffix);
+ }
+
+ /** @hide */
+ public static String sizeValueToString(long number, StringBuilder outBuilder) {
+ if (outBuilder == null) {
+ outBuilder = new StringBuilder(32);
+ }
+ float result = number;
+ String suffix = "";
+ if (result > 900) {
+ suffix = "KB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "MB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "GB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "TB";
+ result = result / 1024;
+ }
+ if (result > 900) {
+ suffix = "PB";
+ result = result / 1024;
+ }
+ String value;
+ if (result < 1) {
+ value = String.format("%.2f", result);
+ } else if (result < 10) {
+ value = String.format("%.1f", result);
+ } else if (result < 100) {
+ value = String.format("%.0f", result);
+ } else {
+ value = String.format("%.0f", result);
+ }
+ outBuilder.append(value);
+ outBuilder.append(suffix);
+ return outBuilder.toString();
+ }
}
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index e49b8c3..cab5d19 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -16,8 +16,6 @@
package android.util;
-import android.text.format.Time;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Calendar;
diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java
index 2623638..83dfc47 100644
--- a/core/java/android/util/StateSet.java
+++ b/core/java/android/util/StateSet.java
@@ -36,7 +36,88 @@ import com.android.internal.R;
*/
public class StateSet {
- /** @hide */ public StateSet() {}
+ /**
+ * The order here is very important to
+ * {@link android.view.View#getDrawableState()}
+ */
+ private static final int[][] VIEW_STATE_SETS;
+
+ /** @hide */
+ public static final int VIEW_STATE_WINDOW_FOCUSED = 1;
+ /** @hide */
+ public static final int VIEW_STATE_SELECTED = 1 << 1;
+ /** @hide */
+ public static final int VIEW_STATE_FOCUSED = 1 << 2;
+ /** @hide */
+ public static final int VIEW_STATE_ENABLED = 1 << 3;
+ /** @hide */
+ public static final int VIEW_STATE_PRESSED = 1 << 4;
+ /** @hide */
+ public static final int VIEW_STATE_ACTIVATED = 1 << 5;
+ /** @hide */
+ public static final int VIEW_STATE_ACCELERATED = 1 << 6;
+ /** @hide */
+ public static final int VIEW_STATE_HOVERED = 1 << 7;
+ /** @hide */
+ public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
+ /** @hide */
+ public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
+
+ static final int[] VIEW_STATE_IDS = new int[] {
+ R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED,
+ R.attr.state_selected, VIEW_STATE_SELECTED,
+ R.attr.state_focused, VIEW_STATE_FOCUSED,
+ R.attr.state_enabled, VIEW_STATE_ENABLED,
+ R.attr.state_pressed, VIEW_STATE_PRESSED,
+ R.attr.state_activated, VIEW_STATE_ACTIVATED,
+ R.attr.state_accelerated, VIEW_STATE_ACCELERATED,
+ R.attr.state_hovered, VIEW_STATE_HOVERED,
+ R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT,
+ R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED
+ };
+
+ static {
+ if ((VIEW_STATE_IDS.length / 2) != R.styleable.ViewDrawableStates.length) {
+ throw new IllegalStateException(
+ "VIEW_STATE_IDs array length does not match ViewDrawableStates style array");
+ }
+
+ final int[] orderedIds = new int[VIEW_STATE_IDS.length];
+ for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) {
+ final int viewState = R.styleable.ViewDrawableStates[i];
+ for (int j = 0; j < VIEW_STATE_IDS.length; j += 2) {
+ if (VIEW_STATE_IDS[j] == viewState) {
+ orderedIds[i * 2] = viewState;
+ orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1];
+ }
+ }
+ }
+
+ final int NUM_BITS = VIEW_STATE_IDS.length / 2;
+ VIEW_STATE_SETS = new int[1 << NUM_BITS][];
+ for (int i = 0; i < VIEW_STATE_SETS.length; i++) {
+ final int numBits = Integer.bitCount(i);
+ final int[] set = new int[numBits];
+ int pos = 0;
+ for (int j = 0; j < orderedIds.length; j += 2) {
+ if ((i & orderedIds[j + 1]) != 0) {
+ set[pos++] = orderedIds[j];
+ }
+ }
+ VIEW_STATE_SETS[i] = set;
+ }
+ }
+
+ /** @hide */
+ public static int[] get(int mask) {
+ if (mask >= VIEW_STATE_SETS.length) {
+ throw new IllegalArgumentException("Invalid state set mask");
+ }
+ return VIEW_STATE_SETS[mask];
+ }
+
+ /** @hide */
+ public StateSet() {}
public static final int[] WILD_CARD = new int[0];
public static final int[] NOTHING = new int[] { 0 };
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 74d4245..98aaa81 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -16,6 +16,8 @@
package android.util;
+import android.annotation.AnyRes;
+
/**
* Container for a dynamically typed data value. Primarily used with
* {@link android.content.res.Resources} for holding resource values.
@@ -178,6 +180,7 @@ public class TypedValue {
public int assetCookie;
/** If Value came from a resource, this holds the corresponding resource id. */
+ @AnyRes
public int resourceId;
/** If Value came from a resource, these are the configurations for which
diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java
index a359952..9f202a9 100644
--- a/core/java/android/view/ActionMode.java
+++ b/core/java/android/view/ActionMode.java
@@ -17,6 +17,9 @@
package android.view;
+import android.annotation.StringRes;
+import android.graphics.Rect;
+
/**
* Represents a contextual mode of the user interface. Action modes can be used to provide
* alternative interaction modes and replace parts of the normal UI until finished.
@@ -29,8 +32,21 @@ package android.view;
* </div>
*/
public abstract class ActionMode {
+
+ /**
+ * The action mode is treated as a Primary mode. This is the default.
+ * Use with {@link #setType}.
+ */
+ public static final int TYPE_PRIMARY = 0;
+ /**
+ * The action mode is treated as a Floating Toolbar.
+ * Use with {@link #setType}.
+ */
+ public static final int TYPE_FLOATING = 1;
+
private Object mTag;
private boolean mTitleOptionalHint;
+ private int mType = TYPE_PRIMARY;
/**
* Set a tag object associated with this ActionMode.
@@ -80,7 +96,7 @@ public abstract class ActionMode {
* @see #setTitle(CharSequence)
* @see #setCustomView(View)
*/
- public abstract void setTitle(int resId);
+ public abstract void setTitle(@StringRes int resId);
/**
* Set the subtitle of the action mode. This method will have no visible effect if
@@ -102,7 +118,7 @@ public abstract class ActionMode {
* @see #setSubtitle(CharSequence)
* @see #setCustomView(View)
*/
- public abstract void setSubtitle(int resId);
+ public abstract void setSubtitle(@StringRes int resId);
/**
* Set whether or not the title/subtitle display for this action mode
@@ -154,6 +170,25 @@ public abstract class ActionMode {
public abstract void setCustomView(View view);
/**
+ * Set a type for this action mode. This will affect how the system renders the action mode if
+ * it has to.
+ *
+ * @param type One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}.
+ */
+ public void setType(int type) {
+ mType = type;
+ }
+
+ /**
+ * Returns the type for this action mode.
+ *
+ * @return One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
* Invalidate the action mode and refresh menu content. The mode's
* {@link ActionMode.Callback} will have its
* {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called.
@@ -163,6 +198,15 @@ public abstract class ActionMode {
public abstract void invalidate();
/**
+ * Invalidate the content rect associated to this ActionMode. This only makes sense for
+ * action modes that support dynamic positioning on the screen, and provides a more efficient
+ * way to reposition it without invalidating the whole action mode.
+ *
+ * @see Callback2#onGetContentRect(ActionMode, View, Rect) .
+ */
+ public void invalidateContentRect() {}
+
+ /**
* Finish and close this action mode. The action mode's {@link ActionMode.Callback} will
* have its {@link Callback#onDestroyActionMode(ActionMode)} method called.
*/
@@ -264,4 +308,31 @@ public abstract class ActionMode {
*/
public void onDestroyActionMode(ActionMode mode);
}
-} \ No newline at end of file
+
+ /**
+ * Extension of {@link ActionMode.Callback} to provide content rect information. This is
+ * required for ActionModes with dynamic positioning such as the ones with type
+ * {@link ActionMode#TYPE_FLOATING} to ensure the positioning doesn't obscure app content. If
+ * an app fails to provide a subclass of this class, a default implementation will be used.
+ */
+ public static abstract class Callback2 implements ActionMode.Callback {
+
+ /**
+ * Called when an ActionMode needs to be positioned on screen, potentially occluding view
+ * content. Note this may be called on a per-frame basis.
+ *
+ * @param mode The ActionMode that requires positioning.
+ * @param view The View that originated the ActionMode, in whose coordinates the Rect should
+ * be provided.
+ * @param outRect The Rect to be populated with the content position.
+ */
+ public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+ if (view != null) {
+ outRect.set(0, 0, view.getWidth(), view.getHeight());
+ } else {
+ outRect.set(0, 0, 0, 0);
+ }
+ }
+
+ }
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index f41afcf..c8149d9 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -141,6 +141,19 @@ public final class Choreographer {
private long mFrameIntervalNanos;
/**
+ * Contains information about the current frame for jank-tracking,
+ * mainly timings of key events along with a bit of metadata about
+ * view tree state
+ *
+ * TODO: Is there a better home for this? Currently Choreographer
+ * is the only one with CALLBACK_ANIMATION start time, hence why this
+ * resides here.
+ *
+ * @hide
+ */
+ FrameInfo mFrameInfo = new FrameInfo();
+
+ /**
* Callback type: Input callback. Runs first.
* @hide
*/
@@ -513,6 +526,7 @@ public final class Choreographer {
return; // no work to do
}
+ long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
@@ -541,12 +555,18 @@ public final class Choreographer {
return;
}
+ mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
+ mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+ mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+ mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
if (DEBUG) {
diff --git a/core/java/android/view/ContextMenu.java b/core/java/android/view/ContextMenu.java
index decabcb..85fe421 100644
--- a/core/java/android/view/ContextMenu.java
+++ b/core/java/android/view/ContextMenu.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.widget.AdapterView;
@@ -44,7 +46,7 @@ public interface ContextMenu extends Menu {
* @param titleRes The string resource identifier used for the title.
* @return This ContextMenu so additional setters can be called.
*/
- public ContextMenu setHeaderTitle(int titleRes);
+ public ContextMenu setHeaderTitle(@StringRes int titleRes);
/**
* Sets the context menu header's title to the title given in <var>title</var>.
@@ -61,7 +63,7 @@ public interface ContextMenu extends Menu {
* @param iconRes The resource identifier used for the icon.
* @return This ContextMenu so additional setters can be called.
*/
- public ContextMenu setHeaderIcon(int iconRes);
+ public ContextMenu setHeaderIcon(@DrawableRes int iconRes);
/**
* Sets the context menu header's icon to the icon given in <var>icon</var>
diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java
index 0afbde9..9047b1d 100644
--- a/core/java/android/view/ContextThemeWrapper.java
+++ b/core/java/android/view/ContextThemeWrapper.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.StyleRes;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
@@ -35,13 +36,19 @@ public class ContextThemeWrapper extends ContextWrapper {
public ContextThemeWrapper() {
super(null);
}
-
- public ContextThemeWrapper(Context base, int themeres) {
+
+ public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
+ super(base);
+ mThemeResource = themeResId;
+ }
+
+ public ContextThemeWrapper(Context base, Resources.Theme theme) {
super(base);
- mThemeResource = themeres;
+ mTheme = theme;
}
- @Override protected void attachBaseContext(Context newBase) {
+ @Override
+ protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index cfb0297..71863b7 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -716,7 +716,7 @@ public final class Display {
updateDisplayInfoLocked();
mDisplayInfo.getLogicalMetrics(outMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,
- mDisplayAdjustments.getActivityToken());
+ mDisplayAdjustments.getConfiguration());
}
}
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index 35fb504..272740f 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -17,7 +17,7 @@
package android.view;
import android.content.res.CompatibilityInfo;
-import android.os.IBinder;
+import android.content.res.Configuration;
import java.util.Objects;
@@ -28,22 +28,18 @@ public class DisplayAdjustments {
public static final DisplayAdjustments DEFAULT_DISPLAY_ADJUSTMENTS = new DisplayAdjustments();
private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- private volatile IBinder mActivityToken;
+ private Configuration mConfiguration = Configuration.EMPTY;
public DisplayAdjustments() {
}
- public DisplayAdjustments(IBinder token) {
- mActivityToken = token;
+ public DisplayAdjustments(Configuration configuration) {
+ mConfiguration = configuration;
}
public DisplayAdjustments(DisplayAdjustments daj) {
- this (daj.getCompatibilityInfo(), daj.getActivityToken());
- }
-
- public DisplayAdjustments(CompatibilityInfo compatInfo, IBinder token) {
- setCompatibilityInfo(compatInfo);
- mActivityToken = token;
+ setCompatibilityInfo(daj.mCompatInfo);
+ mConfiguration = daj.mConfiguration;
}
public void setCompatibilityInfo(CompatibilityInfo compatInfo) {
@@ -63,16 +59,16 @@ public class DisplayAdjustments {
return mCompatInfo;
}
- public void setActivityToken(IBinder token) {
+ public void setConfiguration(Configuration configuration) {
if (this == DEFAULT_DISPLAY_ADJUSTMENTS) {
throw new IllegalArgumentException(
- "setActivityToken: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
+ "setConfiguration: Cannot modify DEFAULT_DISPLAY_ADJUSTMENTS");
}
- mActivityToken = token;
+ mConfiguration = configuration;
}
- public IBinder getActivityToken() {
- return mActivityToken;
+ public Configuration getConfiguration() {
+ return mConfiguration;
}
@Override
@@ -80,7 +76,7 @@ public class DisplayAdjustments {
int hash = 17;
hash = hash * 31 + mCompatInfo.hashCode();
if (DEVELOPMENT_RESOURCES_DEPEND_ON_ACTIVITY_TOKEN) {
- hash = hash * 31 + (mActivityToken == null ? 0 : mActivityToken.hashCode());
+ hash = hash * 31 + (mConfiguration == null ? 0 : mConfiguration.hashCode());
}
return hash;
}
@@ -92,6 +88,6 @@ public class DisplayAdjustments {
}
DisplayAdjustments daj = (DisplayAdjustments)o;
return Objects.equals(daj.mCompatInfo, mCompatInfo) &&
- Objects.equals(daj.mActivityToken, mActivityToken);
+ Objects.equals(daj.mConfiguration, mConfiguration);
}
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 9feb681..ecf45b4 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -17,7 +17,7 @@
package android.view;
import android.content.res.CompatibilityInfo;
-import android.os.IBinder;
+import android.content.res.Configuration;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
@@ -401,16 +401,17 @@ public final class DisplayInfo implements Parcelable {
public void getAppMetrics(DisplayMetrics outMetrics, DisplayAdjustments displayAdjustments) {
getMetricsWithSize(outMetrics, displayAdjustments.getCompatibilityInfo(),
- displayAdjustments.getActivityToken(), appWidth, appHeight);
+ displayAdjustments.getConfiguration(), appWidth, appHeight);
}
- public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci, IBinder token) {
- getMetricsWithSize(outMetrics, ci, token, appWidth, appHeight);
+ public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfo ci,
+ Configuration configuration) {
+ getMetricsWithSize(outMetrics, ci, configuration, appWidth, appHeight);
}
public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
- IBinder token) {
- getMetricsWithSize(outMetrics, compatInfo, token, logicalWidth, logicalHeight);
+ Configuration configuration) {
+ getMetricsWithSize(outMetrics, compatInfo, configuration, logicalWidth, logicalHeight);
}
public int getNaturalWidth() {
@@ -431,17 +432,24 @@ public final class DisplayInfo implements Parcelable {
}
private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfo compatInfo,
- IBinder token, int width, int height) {
+ Configuration configuration, int width, int height) {
outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
- outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
- outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
-
outMetrics.density = outMetrics.noncompatDensity =
logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density;
outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
+ width = (configuration != null
+ && configuration.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED)
+ ? (int)((configuration.screenWidthDp * outMetrics.density) + 0.5f) : width;
+ height = (configuration != null
+ && configuration.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED)
+ ? (int)((configuration.screenHeightDp * outMetrics.density) + 0.5f) : height;
+
+ outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
+ outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
+
if (!compatInfo.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) {
compatInfo.applyToDisplayMetrics(outMetrics);
}
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
new file mode 100644
index 0000000..3caf6f0
--- /dev/null
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
+import android.graphics.NinePatch;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Pools.SynchronizedPool;
+
+/**
+ * An implementation of a GL canvas that records drawing operations.
+ * This is intended for use with a DisplayList. This class keeps a list of all the Paint and
+ * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
+ * the DisplayList is still holding a native reference to the memory.
+ *
+ * @hide
+ */
+public class DisplayListCanvas extends Canvas {
+ // The recording canvas pool should be large enough to handle a deeply nested
+ // view hierarchy because display lists are generated recursively.
+ private static final int POOL_LIMIT = 25;
+
+ private static final SynchronizedPool<DisplayListCanvas> sPool =
+ new SynchronizedPool<DisplayListCanvas>(POOL_LIMIT);
+
+ RenderNode mNode;
+ private int mWidth;
+ private int mHeight;
+
+
+ static DisplayListCanvas obtain(@NonNull RenderNode node) {
+ if (node == null) throw new IllegalArgumentException("node cannot be null");
+ DisplayListCanvas canvas = sPool.acquire();
+ if (canvas == null) {
+ canvas = new DisplayListCanvas();
+ }
+ canvas.mNode = node;
+ return canvas;
+ }
+
+ void recycle() {
+ mNode = null;
+ sPool.release(this);
+ }
+
+ long finishRecording() {
+ return nFinishRecording(mNativeCanvasWrapper);
+ }
+
+ @Override
+ public boolean isRecordingFor(Object o) {
+ return o == mNode;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // JNI
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static native boolean nIsAvailable();
+ private static boolean sIsAvailable = nIsAvailable();
+
+ static boolean isAvailable() {
+ return sIsAvailable;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Constructors
+ ///////////////////////////////////////////////////////////////////////////
+
+ private DisplayListCanvas() {
+ super(nCreateDisplayListRenderer());
+ }
+
+ private static native long nCreateDisplayListRenderer();
+
+ public static void setProperty(String name, String value) {
+ nSetProperty(name, value);
+ }
+
+ private static native void nSetProperty(String name, String value);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Canvas management
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public boolean isHardwareAccelerated() {
+ return true;
+ }
+
+ @Override
+ public void setBitmap(Bitmap bitmap) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return false;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public int getMaximumBitmapWidth() {
+ return nGetMaximumTextureWidth();
+ }
+
+ @Override
+ public int getMaximumBitmapHeight() {
+ return nGetMaximumTextureHeight();
+ }
+
+ private static native int nGetMaximumTextureWidth();
+ private static native int nGetMaximumTextureHeight();
+
+ /**
+ * Returns the native OpenGLRenderer object.
+ */
+ long getRenderer() {
+ return mNativeCanvasWrapper;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Setup
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void setViewport(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+
+ nSetViewport(mNativeCanvasWrapper, width, height);
+ }
+
+ private static native void nSetViewport(long renderer,
+ int width, int height);
+
+ @Override
+ public void setHighContrastText(boolean highContrastText) {
+ nSetHighContrastText(mNativeCanvasWrapper, highContrastText);
+ }
+
+ private static native void nSetHighContrastText(long renderer, boolean highContrastText);
+
+ @Override
+ public void insertReorderBarrier() {
+ nInsertReorderBarrier(mNativeCanvasWrapper, true);
+ }
+
+ @Override
+ public void insertInorderBarrier() {
+ nInsertReorderBarrier(mNativeCanvasWrapper, false);
+ }
+
+ private static native void nInsertReorderBarrier(long renderer, boolean enableReorder);
+
+ /**
+ * Invoked before any drawing operation is performed in this canvas.
+ *
+ * @param dirty The dirty rectangle to update, can be null.
+ */
+ public void onPreDraw(Rect dirty) {
+ if (dirty != null) {
+ nPrepareDirty(mNativeCanvasWrapper, dirty.left, dirty.top, dirty.right, dirty.bottom);
+ } else {
+ nPrepare(mNativeCanvasWrapper);
+ }
+ }
+
+ private static native void nPrepare(long renderer);
+ private static native void nPrepareDirty(long renderer, int left, int top, int right, int bottom);
+
+ /**
+ * Invoked after all drawing operation have been performed.
+ */
+ public void onPostDraw() {
+ nFinish(mNativeCanvasWrapper);
+ }
+
+ private static native void nFinish(long renderer);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Functor
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Calls the function specified with the drawGLFunction function pointer. This is
+ * functionality used by webkit for calling into their renderer from our display lists.
+ * This function may return true if an invalidation is needed after the call.
+ *
+ * @param drawGLFunction A native function pointer
+ */
+ public void callDrawGLFunction2(long drawGLFunction) {
+ nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction);
+ }
+
+ private static native void nCallDrawGLFunction(long renderer, long drawGLFunction);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Display list
+ ///////////////////////////////////////////////////////////////////////////
+
+ protected static native long nFinishRecording(long renderer);
+
+ /**
+ * Draws the specified display list onto this canvas. The display list can only
+ * be drawn if {@link android.view.RenderNode#isValid()} returns true.
+ *
+ * @param renderNode The RenderNode to replay.
+ */
+ public void drawRenderNode(RenderNode renderNode) {
+ drawRenderNode(renderNode, RenderNode.FLAG_CLIP_CHILDREN);
+ }
+
+ /**
+ * Draws the specified display list onto this canvas.
+ *
+ * @param renderNode The RenderNode to replay.
+ * @param flags Optional flags about drawing, see {@link RenderNode} for
+ * the possible flags.
+ */
+ public void drawRenderNode(RenderNode renderNode, int flags) {
+ nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList(), flags);
+ }
+
+ private static native void nDrawRenderNode(long renderer, long renderNode,
+ int flags);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Hardware layer
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Draws the specified layer onto this canvas.
+ *
+ * @param layer The layer to composite on this canvas
+ * @param x The left coordinate of the layer
+ * @param y The top coordinate of the layer
+ * @param paint The paint used to draw the layer
+ */
+ void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
+ layer.setLayerPaint(paint);
+ nDrawLayer(mNativeCanvasWrapper, layer.getLayerHandle(), x, y);
+ }
+
+ private static native void nDrawLayer(long renderer, long layer, float x, float y);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Drawing
+ ///////////////////////////////////////////////////////////////////////////
+
+ // TODO: move to Canvas.java
+ @Override
+ public void drawPatch(NinePatch patch, Rect dst, Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawPatch(mNativeCanvasWrapper, bitmap.getSkBitmap(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+ }
+
+ // TODO: move to Canvas.java
+ @Override
+ public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
+ Bitmap bitmap = patch.getBitmap();
+ throwIfCannotDraw(bitmap);
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawPatch(mNativeCanvasWrapper, bitmap.getSkBitmap(), patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+ }
+
+ private static native void nDrawPatch(long renderer, long bitmap, long chunk,
+ float left, float top, float right, float bottom, long paint);
+
+ public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
+ CanvasProperty<Float> radius, CanvasProperty<Paint> paint) {
+ nDrawCircle(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
+ radius.getNativeContainer(), paint.getNativeContainer());
+ }
+
+ private static native void nDrawCircle(long renderer, long propCx,
+ long propCy, long propRadius, long propPaint);
+
+ public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
+ CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
+ CanvasProperty<Float> ry, CanvasProperty<Paint> paint) {
+ nDrawRoundRect(mNativeCanvasWrapper, left.getNativeContainer(), top.getNativeContainer(),
+ right.getNativeContainer(), bottom.getNativeContainer(),
+ rx.getNativeContainer(), ry.getNativeContainer(),
+ paint.getNativeContainer());
+ }
+
+ private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
+ long propRight, long propBottom, long propRx, long propRy, long propPaint);
+
+ // TODO: move this optimization to Canvas.java
+ @Override
+ public void drawPath(Path path, Paint paint) {
+ if (path.isSimplePath) {
+ if (path.rects != null) {
+ nDrawRects(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
+ }
+ } else {
+ super.drawPath(path, paint);
+ }
+ }
+
+ private static native void nDrawRects(long renderer, long region, long paint);
+
+ @Override
+ public void drawPicture(Picture picture) {
+ picture.endRecording();
+ // TODO: Implement rendering
+ }
+}
diff --git a/core/java/android/view/FrameInfo.java b/core/java/android/view/FrameInfo.java
new file mode 100644
index 0000000..c79547c
--- /dev/null
+++ b/core/java/android/view/FrameInfo.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that contains all the timing information for the current frame. This
+ * is used in conjunction with the hardware renderer to provide
+ * continous-monitoring jank events
+ *
+ * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime()
+ *
+ * To minimize overhead from System.nanoTime() calls we infer durations of
+ * things by knowing the ordering of the events. For example, to know how
+ * long layout & measure took it's displayListRecordStart - performTraversalsStart.
+ *
+ * These constants must be kept in sync with FrameInfo.h in libhwui and are
+ * used for indexing into AttachInfo's mFrameInfo long[], which is intended
+ * to be quick to pass down to native via JNI, hence a pre-packed format
+ *
+ * @hide
+ */
+final class FrameInfo {
+
+ long[] mFrameInfo = new long[9];
+
+ // Various flags set to provide extra metadata about the current frame
+ private static final int FLAGS = 0;
+
+ // Is this the first-draw following a window layout?
+ public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
+
+ @IntDef(flag = true, value = {
+ FLAG_WINDOW_LAYOUT_CHANGED })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FrameInfoFlags {}
+
+ // The intended vsync time, unadjusted by jitter
+ private static final int INTENDED_VSYNC = 1;
+
+ // Jitter-adjusted vsync time, this is what was used as input into the
+ // animation & drawing system
+ private static final int VSYNC = 2;
+
+ // The time of the oldest input event
+ private static final int OLDEST_INPUT_EVENT = 3;
+
+ // The time of the newest input event
+ private static final int NEWEST_INPUT_EVENT = 4;
+
+ // When input event handling started
+ private static final int HANDLE_INPUT_START = 5;
+
+ // When animation evaluations started
+ private static final int ANIMATION_START = 6;
+
+ // When ViewRootImpl#performTraversals() started
+ private static final int PERFORM_TRAVERSALS_START = 7;
+
+ // When View:draw() started
+ private static final int DRAW_START = 8;
+
+ public void setVsync(long intendedVsync, long usedVsync) {
+ mFrameInfo[INTENDED_VSYNC] = intendedVsync;
+ mFrameInfo[VSYNC] = usedVsync;
+ mFrameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
+ mFrameInfo[NEWEST_INPUT_EVENT] = 0;
+ mFrameInfo[FLAGS] = 0;
+ }
+
+ public void updateInputEventTime(long inputEventTime, long inputEventOldestTime) {
+ if (inputEventOldestTime < mFrameInfo[OLDEST_INPUT_EVENT]) {
+ mFrameInfo[OLDEST_INPUT_EVENT] = inputEventOldestTime;
+ }
+ if (inputEventTime > mFrameInfo[NEWEST_INPUT_EVENT]) {
+ mFrameInfo[NEWEST_INPUT_EVENT] = inputEventTime;
+ }
+ }
+
+ public void markInputHandlingStart() {
+ mFrameInfo[HANDLE_INPUT_START] = System.nanoTime();
+ }
+
+ public void markAnimationsStart() {
+ mFrameInfo[ANIMATION_START] = System.nanoTime();
+ }
+
+ public void markPerformTraversalsStart() {
+ mFrameInfo[PERFORM_TRAVERSALS_START] = System.nanoTime();
+ }
+
+ public void markDrawStart() {
+ mFrameInfo[DRAW_START] = System.nanoTime();
+ }
+
+ public void addFlags(@FrameInfoFlags long flags) {
+ mFrameInfo[FLAGS] |= flags;
+ }
+
+}
diff --git a/core/java/android/view/FrameStats.java b/core/java/android/view/FrameStats.java
index b3ac1db..3fbe6fe 100644
--- a/core/java/android/view/FrameStats.java
+++ b/core/java/android/view/FrameStats.java
@@ -16,9 +16,6 @@
package android.view;
-import android.os.Parcel;
-import android.os.Parcelable;
-
/**
* This is the base class for frame statistics.
*/
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
deleted file mode 100644
index 60a489b..0000000
--- a/core/java/android/view/GLES20Canvas.java
+++ /dev/null
@@ -1,999 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.CanvasProperty;
-import android.graphics.DrawFilter;
-import android.graphics.Matrix;
-import android.graphics.NinePatch;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Path;
-import android.graphics.Picture;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.Shader;
-import android.graphics.TemporaryBuffer;
-import android.text.GraphicsOperations;
-import android.text.SpannableString;
-import android.text.SpannedString;
-import android.text.TextUtils;
-
-/**
- * An implementation of Canvas on top of OpenGL ES 2.0.
- */
-class GLES20Canvas extends HardwareCanvas {
- private final boolean mOpaque;
- protected long mRenderer;
-
- // The native renderer will be destroyed when this object dies.
- // DO NOT overwrite this reference once it is set.
- @SuppressWarnings({"unused", "FieldCanBeLocal"})
- private CanvasFinalizer mFinalizer;
-
- private int mWidth;
- private int mHeight;
-
- private float[] mPoint;
- private float[] mLine;
-
- private Rect mClipBounds;
- private RectF mPathBounds;
-
- private DrawFilter mFilter;
-
- ///////////////////////////////////////////////////////////////////////////
- // JNI
- ///////////////////////////////////////////////////////////////////////////
-
- private static native boolean nIsAvailable();
- private static boolean sIsAvailable = nIsAvailable();
-
- static boolean isAvailable() {
- return sIsAvailable;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Constructors
- ///////////////////////////////////////////////////////////////////////////
-
- // TODO: Merge with GLES20RecordingCanvas
- protected GLES20Canvas() {
- mOpaque = false;
- mRenderer = nCreateDisplayListRenderer();
- setupFinalizer();
- }
-
- private void setupFinalizer() {
- if (mRenderer == 0) {
- throw new IllegalStateException("Could not create GLES20Canvas renderer");
- } else {
- mFinalizer = new CanvasFinalizer(mRenderer);
- }
- }
-
- private static native long nCreateDisplayListRenderer();
- private static native void nResetDisplayListRenderer(long renderer);
- private static native void nDestroyRenderer(long renderer);
-
- private static final class CanvasFinalizer {
- private final long mRenderer;
-
- public CanvasFinalizer(long renderer) {
- mRenderer = renderer;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- nDestroyRenderer(mRenderer);
- } finally {
- super.finalize();
- }
- }
- }
-
- public static void setProperty(String name, String value) {
- nSetProperty(name, value);
- }
-
- private static native void nSetProperty(String name, String value);
-
- ///////////////////////////////////////////////////////////////////////////
- // Canvas management
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public boolean isOpaque() {
- return mOpaque;
- }
-
- @Override
- public int getWidth() {
- return mWidth;
- }
-
- @Override
- public int getHeight() {
- return mHeight;
- }
-
- @Override
- public int getMaximumBitmapWidth() {
- return nGetMaximumTextureWidth();
- }
-
- @Override
- public int getMaximumBitmapHeight() {
- return nGetMaximumTextureHeight();
- }
-
- private static native int nGetMaximumTextureWidth();
- private static native int nGetMaximumTextureHeight();
-
- /**
- * Returns the native OpenGLRenderer object.
- */
- long getRenderer() {
- return mRenderer;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Setup
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void setViewport(int width, int height) {
- mWidth = width;
- mHeight = height;
-
- nSetViewport(mRenderer, width, height);
- }
-
- private static native void nSetViewport(long renderer,
- int width, int height);
-
- @Override
- public void setHighContrastText(boolean highContrastText) {
- nSetHighContrastText(mRenderer, highContrastText);
- }
-
- private static native void nSetHighContrastText(long renderer, boolean highContrastText);
-
- @Override
- public void insertReorderBarrier() {
- nInsertReorderBarrier(mRenderer, true);
- }
-
- @Override
- public void insertInorderBarrier() {
- nInsertReorderBarrier(mRenderer, false);
- }
-
- private static native void nInsertReorderBarrier(long renderer, boolean enableReorder);
-
- @Override
- public int onPreDraw(Rect dirty) {
- if (dirty != null) {
- return nPrepareDirty(mRenderer, dirty.left, dirty.top, dirty.right, dirty.bottom,
- mOpaque);
- } else {
- return nPrepare(mRenderer, mOpaque);
- }
- }
-
- private static native int nPrepare(long renderer, boolean opaque);
- private static native int nPrepareDirty(long renderer, int left, int top, int right, int bottom,
- boolean opaque);
-
- @Override
- public void onPostDraw() {
- nFinish(mRenderer);
- }
-
- private static native void nFinish(long renderer);
-
- ///////////////////////////////////////////////////////////////////////////
- // Functor
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public int callDrawGLFunction2(long drawGLFunction) {
- return nCallDrawGLFunction(mRenderer, drawGLFunction);
- }
-
- private static native int nCallDrawGLFunction(long renderer, long drawGLFunction);
-
- ///////////////////////////////////////////////////////////////////////////
- // Display list
- ///////////////////////////////////////////////////////////////////////////
-
- protected static native long nFinishRecording(long renderer);
-
- @Override
- public int drawRenderNode(RenderNode renderNode, Rect dirty, int flags) {
- return nDrawRenderNode(mRenderer, renderNode.getNativeDisplayList(), dirty, flags);
- }
-
- private static native int nDrawRenderNode(long renderer, long renderNode,
- Rect dirty, int flags);
-
- ///////////////////////////////////////////////////////////////////////////
- // Hardware layer
- ///////////////////////////////////////////////////////////////////////////
-
- void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
- layer.setLayerPaint(paint);
- nDrawLayer(mRenderer, layer.getLayerHandle(), x, y);
- }
-
- private static native void nDrawLayer(long renderer, long layer, float x, float y);
-
- ///////////////////////////////////////////////////////////////////////////
- // Support
- ///////////////////////////////////////////////////////////////////////////
-
- private Rect getInternalClipBounds() {
- if (mClipBounds == null) mClipBounds = new Rect();
- return mClipBounds;
- }
-
-
- private RectF getPathBounds() {
- if (mPathBounds == null) mPathBounds = new RectF();
- return mPathBounds;
- }
-
- private float[] getPointStorage() {
- if (mPoint == null) mPoint = new float[2];
- return mPoint;
- }
-
- private float[] getLineStorage() {
- if (mLine == null) mLine = new float[4];
- return mLine;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Clipping
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public boolean clipPath(Path path) {
- return nClipPath(mRenderer, path.mNativePath, Region.Op.INTERSECT.nativeInt);
- }
-
- @Override
- public boolean clipPath(Path path, Region.Op op) {
- return nClipPath(mRenderer, path.mNativePath, op.nativeInt);
- }
-
- private static native boolean nClipPath(long renderer, long path, int op);
-
- @Override
- public boolean clipRect(float left, float top, float right, float bottom) {
- return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
- }
-
- private static native boolean nClipRect(long renderer, float left, float top,
- float right, float bottom, int op);
-
- @Override
- public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) {
- return nClipRect(mRenderer, left, top, right, bottom, op.nativeInt);
- }
-
- @Override
- public boolean clipRect(int left, int top, int right, int bottom) {
- return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
- }
-
- private static native boolean nClipRect(long renderer, int left, int top,
- int right, int bottom, int op);
-
- @Override
- public boolean clipRect(Rect rect) {
- return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
- Region.Op.INTERSECT.nativeInt);
- }
-
- @Override
- public boolean clipRect(Rect rect, Region.Op op) {
- return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
- }
-
- @Override
- public boolean clipRect(RectF rect) {
- return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
- Region.Op.INTERSECT.nativeInt);
- }
-
- @Override
- public boolean clipRect(RectF rect, Region.Op op) {
- return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
- }
-
- @Override
- public boolean clipRegion(Region region) {
- return nClipRegion(mRenderer, region.mNativeRegion, Region.Op.INTERSECT.nativeInt);
- }
-
- @Override
- public boolean clipRegion(Region region, Region.Op op) {
- return nClipRegion(mRenderer, region.mNativeRegion, op.nativeInt);
- }
-
- private static native boolean nClipRegion(long renderer, long region, int op);
-
- @Override
- public boolean getClipBounds(Rect bounds) {
- return nGetClipBounds(mRenderer, bounds);
- }
-
- private static native boolean nGetClipBounds(long renderer, Rect bounds);
-
- @Override
- public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) {
- return nQuickReject(mRenderer, left, top, right, bottom);
- }
-
- private static native boolean nQuickReject(long renderer, float left, float top,
- float right, float bottom);
-
- @Override
- public boolean quickReject(Path path, EdgeType type) {
- RectF pathBounds = getPathBounds();
- path.computeBounds(pathBounds, true);
- return nQuickReject(mRenderer, pathBounds.left, pathBounds.top,
- pathBounds.right, pathBounds.bottom);
- }
-
- @Override
- public boolean quickReject(RectF rect, EdgeType type) {
- return nQuickReject(mRenderer, rect.left, rect.top, rect.right, rect.bottom);
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Transformations
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void translate(float dx, float dy) {
- if (dx != 0.0f || dy != 0.0f) nTranslate(mRenderer, dx, dy);
- }
-
- private static native void nTranslate(long renderer, float dx, float dy);
-
- @Override
- public void skew(float sx, float sy) {
- nSkew(mRenderer, sx, sy);
- }
-
- private static native void nSkew(long renderer, float sx, float sy);
-
- @Override
- public void rotate(float degrees) {
- nRotate(mRenderer, degrees);
- }
-
- private static native void nRotate(long renderer, float degrees);
-
- @Override
- public void scale(float sx, float sy) {
- nScale(mRenderer, sx, sy);
- }
-
- private static native void nScale(long renderer, float sx, float sy);
-
- @Override
- public void setMatrix(Matrix matrix) {
- nSetMatrix(mRenderer, matrix == null ? 0 : matrix.native_instance);
- }
-
- private static native void nSetMatrix(long renderer, long matrix);
-
- @SuppressWarnings("deprecation")
- @Override
- public void getMatrix(Matrix matrix) {
- nGetMatrix(mRenderer, matrix.native_instance);
- }
-
- private static native void nGetMatrix(long renderer, long matrix);
-
- @Override
- public void concat(Matrix matrix) {
- if (matrix != null) nConcatMatrix(mRenderer, matrix.native_instance);
- }
-
- private static native void nConcatMatrix(long renderer, long matrix);
-
- ///////////////////////////////////////////////////////////////////////////
- // State management
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public int save() {
- return nSave(mRenderer, Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
- }
-
- @Override
- public int save(int saveFlags) {
- return nSave(mRenderer, saveFlags);
- }
-
- private static native int nSave(long renderer, int flags);
-
- @Override
- public int saveLayer(RectF bounds, Paint paint, int saveFlags) {
- if (bounds != null) {
- return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags);
- }
-
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- return nSaveLayer(mRenderer, nativePaint, saveFlags);
- }
-
- private static native int nSaveLayer(long renderer, long paint, int saveFlags);
-
- @Override
- public int saveLayer(float left, float top, float right, float bottom, Paint paint,
- int saveFlags) {
- if (left < right && top < bottom) {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- return nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
- }
- return save(saveFlags);
- }
-
- private static native int nSaveLayer(long renderer, float left, float top,
- float right, float bottom, long paint, int saveFlags);
-
- @Override
- public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) {
- if (bounds != null) {
- return saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom,
- alpha, saveFlags);
- }
- return nSaveLayerAlpha(mRenderer, alpha, saveFlags);
- }
-
- private static native int nSaveLayerAlpha(long renderer, int alpha, int saveFlags);
-
- @Override
- public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
- int saveFlags) {
- if (left < right && top < bottom) {
- return nSaveLayerAlpha(mRenderer, left, top, right, bottom, alpha, saveFlags);
- }
- return save(saveFlags);
- }
-
- private static native int nSaveLayerAlpha(long renderer, float left, float top, float right,
- float bottom, int alpha, int saveFlags);
-
- @Override
- public void restore() {
- nRestore(mRenderer);
- }
-
- private static native void nRestore(long renderer);
-
- @Override
- public void restoreToCount(int saveCount) {
- nRestoreToCount(mRenderer, saveCount);
- }
-
- private static native void nRestoreToCount(long renderer, int saveCount);
-
- @Override
- public int getSaveCount() {
- return nGetSaveCount(mRenderer);
- }
-
- private static native int nGetSaveCount(long renderer);
-
- ///////////////////////////////////////////////////////////////////////////
- // Filtering
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void setDrawFilter(DrawFilter filter) {
- mFilter = filter;
- if (filter == null) {
- nResetPaintFilter(mRenderer);
- } else if (filter instanceof PaintFlagsDrawFilter) {
- PaintFlagsDrawFilter flagsFilter = (PaintFlagsDrawFilter) filter;
- nSetupPaintFilter(mRenderer, flagsFilter.clearBits, flagsFilter.setBits);
- }
- }
-
- private static native void nResetPaintFilter(long renderer);
- private static native void nSetupPaintFilter(long renderer, int clearBits, int setBits);
-
- @Override
- public DrawFilter getDrawFilter() {
- return mFilter;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Drawing
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void drawArc(float left, float top, float right, float bottom,
- float startAngle, float sweepAngle, boolean useCenter, Paint paint) {
- nDrawArc(mRenderer, left, top, right, bottom,
- startAngle, sweepAngle, useCenter, paint.mNativePaint);
- }
-
- private static native void nDrawArc(long renderer, float left, float top,
- float right, float bottom, float startAngle, float sweepAngle,
- boolean useCenter, long paint);
-
- @Override
- public void drawARGB(int a, int r, int g, int b) {
- drawColor((a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF));
- }
-
- @Override
- public void drawPatch(NinePatch patch, Rect dst, Paint paint) {
- Bitmap bitmap = patch.getBitmap();
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- }
-
- @Override
- public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
- Bitmap bitmap = patch.getBitmap();
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- }
-
- 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, left, top, nativePaint);
- }
-
- 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, matrix.native_instance, nativePaint);
- }
-
- private static native void nDrawBitmap(long renderer, long bitmap,
- long matrix, long paint);
-
- @Override
- public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
-
- int left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
- }
-
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- }
-
- @Override
- public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
-
- float left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
- }
-
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- }
-
- 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);
-
- @Override
- public void drawBitmap(int[] colors, int offset, int stride, float x, float y,
- int width, int height, boolean hasAlpha, Paint paint) {
- if (width < 0) {
- throw new IllegalArgumentException("width must be >= 0");
- }
-
- if (height < 0) {
- throw new IllegalArgumentException("height must be >= 0");
- }
-
- if (Math.abs(stride) < width) {
- throw new IllegalArgumentException("abs(stride) must be >= width");
- }
-
- int lastScanline = offset + (height - 1) * stride;
- int length = colors.length;
-
- if (offset < 0 || (offset + width > length) || lastScanline < 0 ||
- (lastScanline + width > length)) {
- throw new ArrayIndexOutOfBoundsException();
- }
-
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, colors, offset, stride, x, y,
- width, height, hasAlpha, nativePaint);
- }
-
- private static native void nDrawBitmap(long renderer, int[] colors, int offset, int stride,
- float x, float y, int width, int height, boolean hasAlpha, long nativePaint);
-
- @Override
- public void drawBitmap(int[] colors, int offset, int stride, int x, int y,
- int width, int height, boolean hasAlpha, Paint paint) {
- drawBitmap(colors, offset, stride, (float) x, (float) y, width, height, hasAlpha, paint);
- }
-
- @Override
- public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts,
- int vertOffset, int[] colors, int colorOffset, Paint paint) {
- throwIfCannotDraw(bitmap);
- if (meshWidth < 0 || meshHeight < 0 || vertOffset < 0 || colorOffset < 0) {
- throw new ArrayIndexOutOfBoundsException();
- }
-
- if (meshWidth == 0 || meshHeight == 0) {
- return;
- }
-
- final int count = (meshWidth + 1) * (meshHeight + 1);
- checkRange(verts.length, vertOffset, count * 2);
-
- if (colors != null) {
- checkRange(colors.length, colorOffset, count);
- }
-
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, meshWidth, meshHeight,
- verts, vertOffset, colors, colorOffset, nativePaint);
- }
-
- private static native void nDrawBitmapMesh(long renderer, long bitmap,
- int meshWidth, int meshHeight, float[] verts, int vertOffset,
- int[] colors, int colorOffset, long paint);
-
- @Override
- public void drawCircle(float cx, float cy, float radius, Paint paint) {
- nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
- }
-
- private static native void nDrawCircle(long renderer, float cx, float cy,
- float radius, long paint);
-
- @Override
- public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
- CanvasProperty<Float> radius, CanvasProperty<Paint> paint) {
- nDrawCircle(mRenderer, cx.getNativeContainer(), cy.getNativeContainer(),
- radius.getNativeContainer(), paint.getNativeContainer());
- }
-
- private static native void nDrawCircle(long renderer, long propCx,
- long propCy, long propRadius, long propPaint);
-
- @Override
- public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
- CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
- CanvasProperty<Float> ry, CanvasProperty<Paint> paint) {
- nDrawRoundRect(mRenderer, left.getNativeContainer(), top.getNativeContainer(),
- right.getNativeContainer(), bottom.getNativeContainer(),
- rx.getNativeContainer(), ry.getNativeContainer(),
- paint.getNativeContainer());
- }
-
- private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
- long propRight, long propBottom, long propRx, long propRy, long propPaint);
-
- @Override
- public void drawColor(int color) {
- drawColor(color, PorterDuff.Mode.SRC_OVER);
- }
-
- @Override
- public void drawColor(int color, PorterDuff.Mode mode) {
- nDrawColor(mRenderer, color, mode.nativeInt);
- }
-
- private static native void nDrawColor(long renderer, int color, int mode);
-
- @Override
- public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
- float[] line = getLineStorage();
- line[0] = startX;
- line[1] = startY;
- line[2] = stopX;
- line[3] = stopY;
- drawLines(line, 0, 4, paint);
- }
-
- @Override
- public void drawLines(float[] pts, int offset, int count, Paint paint) {
- if (count < 4) return;
-
- if ((offset | count) < 0 || offset + count > pts.length) {
- throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
- }
- nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
- }
-
- private static native void nDrawLines(long renderer, float[] points,
- int offset, int count, long paint);
-
- @Override
- public void drawLines(float[] pts, Paint paint) {
- drawLines(pts, 0, pts.length, paint);
- }
-
- @Override
- public void drawOval(float left, float top, float right, float bottom, Paint paint) {
- nDrawOval(mRenderer, left, top, right, bottom, paint.mNativePaint);
- }
-
- private static native void nDrawOval(long renderer, float left, float top,
- float right, float bottom, long paint);
-
- @Override
- public void drawPaint(Paint paint) {
- final Rect r = getInternalClipBounds();
- nGetClipBounds(mRenderer, r);
- drawRect(r.left, r.top, r.right, r.bottom, paint);
- }
-
- @Override
- public void drawPath(Path path, Paint paint) {
- if (path.isSimplePath) {
- if (path.rects != null) {
- nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
- }
- } else {
- nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
- }
- }
-
- private static native void nDrawPath(long renderer, long path, long paint);
- private static native void nDrawRects(long renderer, long region, long paint);
-
- @Override
- public void drawPicture(Picture picture) {
- picture.endRecording();
- // TODO: Implement rendering
- }
-
- @Override
- public void drawPoint(float x, float y, Paint paint) {
- float[] point = getPointStorage();
- point[0] = x;
- point[1] = y;
- drawPoints(point, 0, 2, paint);
- }
-
- @Override
- public void drawPoints(float[] pts, Paint paint) {
- drawPoints(pts, 0, pts.length, paint);
- }
-
- @Override
- public void drawPoints(float[] pts, int offset, int count, Paint paint) {
- if (count < 2) return;
-
- nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
- }
-
- private static native void nDrawPoints(long renderer, float[] points,
- int offset, int count, long paint);
-
- // Note: drawPosText just uses implementation in Canvas
-
- @Override
- public void drawRect(float left, float top, float right, float bottom, Paint paint) {
- if (left == right || top == bottom) return;
- nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
- }
-
- private static native void nDrawRect(long renderer, float left, float top,
- float right, float bottom, long paint);
-
- @Override
- public void drawRect(Rect r, Paint paint) {
- drawRect(r.left, r.top, r.right, r.bottom, paint);
- }
-
- @Override
- public void drawRect(RectF r, Paint paint) {
- drawRect(r.left, r.top, r.right, r.bottom, paint);
- }
-
- @Override
- public void drawRGB(int r, int g, int b) {
- drawColor(0xFF000000 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF));
- }
-
- @Override
- public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
- Paint paint) {
- nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint);
- }
-
- private static native void nDrawRoundRect(long renderer, float left, float top,
- float right, float bottom, float rx, float y, long paint);
-
- @Override
- public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
- if ((index | count | (index + count) | (text.length - index - count)) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- nDrawText(mRenderer, text, index, count, x, y,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawText(long renderer, char[] text, int index, int count,
- float x, float y, int bidiFlags, long paint, long typeface);
-
- @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,
- paint.mNativePaint, paint.mNativeTypeface);
- } else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawText(this, start, end, x, y, paint);
- } else {
- char[] buf = TemporaryBuffer.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- nDrawText(mRenderer, buf, 0, end - start, x, y,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- TemporaryBuffer.recycle(buf);
- }
- }
-
- @Override
- public void drawText(String text, int start, int end, float x, float y, Paint paint) {
- if ((start | end | (end - start) | (text.length() - end)) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- nDrawText(mRenderer, text, start, end, x, y,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawText(long renderer, String text, int start, int end,
- float x, float y, int bidiFlags, long paint, long typeface);
-
- @Override
- public void drawText(String text, float x, float y, Paint paint) {
- nDrawText(mRenderer, text, 0, text.length(), x, y,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- @Override
- public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset,
- float vOffset, Paint paint) {
- if (index < 0 || index + count > text.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
-
- nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawTextOnPath(long renderer, char[] text, int index, int count,
- long path, float hOffset, float vOffset, int bidiFlags, long nativePaint,
- long typeface);
-
- @Override
- public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
- if (text.length() == 0) return;
-
- nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawTextOnPath(long renderer, String text, int start, int end,
- long path, float hOffset, float vOffset, int bidiFlags, long nativePaint,
- long typeface);
-
- @Override
- public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
- float x, float y, boolean isRtl, Paint paint) {
- if ((index | count | text.length - index - count) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, isRtl,
- paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawTextRun(long renderer, char[] text, int index, int count,
- int contextIndex, int contextCount, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
-
- @Override
- public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
- float x, float y, boolean isRtl, Paint paint) {
- if ((start | end | end - start | text.length() - end) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- if (text instanceof String || text instanceof SpannedString ||
- text instanceof SpannableString) {
- nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
- contextEnd, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
- } else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawTextRun(this, start, end,
- contextStart, contextEnd, x, y, isRtl, paint);
- } else {
- int contextLen = contextEnd - contextStart;
- int len = end - start;
- char[] buf = TemporaryBuffer.obtain(contextLen);
- TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
- nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
- x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
- TemporaryBuffer.recycle(buf);
- }
- }
-
- private static native void nDrawTextRun(long renderer, String text, int start, int end,
- int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
-
- @Override
- public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
- float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices,
- int indexOffset, int indexCount, Paint paint) {
- // TODO: Implement
- }
-}
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
deleted file mode 100644
index 5e49d8e..0000000
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Pools.SynchronizedPool;
-
-/**
- * An implementation of a GL canvas that records drawing operations.
- * This is intended for use with a DisplayList. This class keeps a list of all the Paint and
- * Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
- * the DisplayList is still holding a native reference to the memory.
- */
-class GLES20RecordingCanvas extends GLES20Canvas {
- // The recording canvas pool should be large enough to handle a deeply nested
- // view hierarchy because display lists are generated recursively.
- private static final int POOL_LIMIT = 25;
-
- private static final SynchronizedPool<GLES20RecordingCanvas> sPool =
- new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT);
-
- RenderNode mNode;
-
- private GLES20RecordingCanvas() {
- super();
- }
-
- static GLES20RecordingCanvas obtain(@NonNull RenderNode node) {
- if (node == null) throw new IllegalArgumentException("node cannot be null");
- GLES20RecordingCanvas canvas = sPool.acquire();
- if (canvas == null) {
- canvas = new GLES20RecordingCanvas();
- }
- canvas.mNode = node;
- return canvas;
- }
-
- void recycle() {
- mNode = null;
- sPool.release(this);
- }
-
- long finishRecording() {
- return nFinishRecording(mRenderer);
- }
-
- @Override
- public boolean isRecordingFor(Object o) {
- return o == mNode;
- }
-}
diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java
index 20baad0..d58e7c0 100644
--- a/core/java/android/view/GhostView.java
+++ b/core/java/android/view/GhostView.java
@@ -46,14 +46,14 @@ public class GhostView extends View {
@Override
protected void onDraw(Canvas canvas) {
- if (canvas instanceof HardwareCanvas) {
- HardwareCanvas hwCanvas = (HardwareCanvas) canvas;
+ if (canvas instanceof DisplayListCanvas) {
+ DisplayListCanvas dlCanvas = (DisplayListCanvas) canvas;
mView.mRecreateDisplayList = true;
RenderNode renderNode = mView.getDisplayList();
if (renderNode.isValid()) {
- hwCanvas.insertReorderBarrier(); // enable shadow for this rendernode
- hwCanvas.drawRenderNode(renderNode);
- hwCanvas.insertInorderBarrier(); // re-disable reordering/shadows
+ dlCanvas.insertReorderBarrier(); // enable shadow for this rendernode
+ dlCanvas.drawRenderNode(renderNode);
+ dlCanvas.insertInorderBarrier(); // re-disable reordering/shadows
}
}
}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
deleted file mode 100644
index 18accb8..0000000
--- a/core/java/android/view/HardwareCanvas.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.CanvasProperty;
-import android.graphics.Paint;
-import android.graphics.Rect;
-
-/**
- * Hardware accelerated canvas.
- *
- * @hide
- */
-public abstract class HardwareCanvas extends Canvas {
-
- @Override
- public boolean isHardwareAccelerated() {
- return true;
- }
-
- @Override
- public void setBitmap(Bitmap bitmap) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Invoked before any drawing operation is performed in this canvas.
- *
- * @param dirty The dirty rectangle to update, can be null.
- * @return {@link RenderNode#STATUS_DREW} if anything was drawn (such as a call to clear
- * the canvas).
- *
- * @hide
- */
- public abstract int onPreDraw(Rect dirty);
-
- /**
- * Invoked after all drawing operation have been performed.
- *
- * @hide
- */
- public abstract void onPostDraw();
-
- /**
- * Draws the specified display list onto this canvas. The display list can only
- * be drawn if {@link android.view.RenderNode#isValid()} returns true.
- *
- * @param renderNode The RenderNode to replay.
- */
- public void drawRenderNode(RenderNode renderNode) {
- drawRenderNode(renderNode, null, RenderNode.FLAG_CLIP_CHILDREN);
- }
-
- /**
- * Draws the specified display list onto this canvas.
- *
- * @param renderNode The RenderNode to replay.
- * @param dirty Ignored, can be null.
- * @param flags Optional flags about drawing, see {@link RenderNode} for
- * the possible flags.
- *
- * @return One of {@link RenderNode#STATUS_DONE} or {@link RenderNode#STATUS_DREW}
- * if anything was drawn.
- *
- * @hide
- */
- public abstract int drawRenderNode(RenderNode renderNode, Rect dirty, int flags);
-
- /**
- * Draws the specified layer onto this canvas.
- *
- * @param layer The layer to composite on this canvas
- * @param x The left coordinate of the layer
- * @param y The top coordinate of the layer
- * @param paint The paint used to draw the layer
- *
- * @hide
- */
- abstract void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint);
-
- /**
- * Calls the function specified with the drawGLFunction function pointer. This is
- * functionality used by webkit for calling into their renderer from our display lists.
- * This function may return true if an invalidation is needed after the call.
- *
- * @param drawGLFunction A native function pointer
- *
- * @return {@link RenderNode#STATUS_DONE}
- *
- * @hide
- */
- public abstract int callDrawGLFunction2(long drawGLFunction);
-
- public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
- CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
-
- public abstract void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
- CanvasProperty<Float> right, CanvasProperty<Float> bottom,
- CanvasProperty<Float> rx, CanvasProperty<Float> ry,
- CanvasProperty<Paint> paint);
-
- public static void setProperty(String name, String value) {
- GLES20Canvas.setProperty(name, value);
- }
-}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index a130bda..65ae8a6 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -52,7 +52,7 @@ final class HardwareLayer {
* @see View#setLayerPaint(android.graphics.Paint)
*/
public void setLayerPaint(Paint paint) {
- nSetLayerPaint(mFinalizer.get(), paint.mNativePaint);
+ nSetLayerPaint(mFinalizer.get(), paint.getNativeInstance());
mRenderer.pushLayerUpdate(this);
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index c5c3f83..6632f39 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -19,7 +19,6 @@ package android.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
-import android.util.DisplayMetrics;
import android.view.Surface.OutOfResourcesException;
import java.io.File;
@@ -206,7 +205,7 @@ public abstract class HardwareRenderer {
* false otherwise
*/
public static boolean isAvailable() {
- return GLES20Canvas.isAvailable();
+ return DisplayListCanvas.isAvailable();
}
/**
@@ -278,7 +277,7 @@ public abstract class HardwareRenderer {
/**
* Outputs extra debugging information in the specified file descriptor.
*/
- abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd);
+ abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args);
/**
* Loads system properties used by the renderer. This method is invoked
@@ -327,7 +326,7 @@ public abstract class HardwareRenderer {
*
* @param canvas The Canvas used to render the view.
*/
- void onHardwarePreDraw(HardwareCanvas canvas);
+ void onHardwarePreDraw(DisplayListCanvas canvas);
/**
* Invoked after a view is drawn by a hardware renderer.
@@ -335,7 +334,7 @@ public abstract class HardwareRenderer {
*
* @param canvas The Canvas used to render the view.
*/
- void onHardwarePostDraw(HardwareCanvas canvas);
+ void onHardwarePostDraw(DisplayListCanvas canvas);
}
/**
@@ -424,7 +423,7 @@ public abstract class HardwareRenderer {
*/
static HardwareRenderer create(Context context, boolean translucent) {
HardwareRenderer renderer = null;
- if (GLES20Canvas.isAvailable()) {
+ if (DisplayListCanvas.isAvailable()) {
renderer = new ThreadedRenderer(context, translucent);
}
return renderer;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7b20e72..d6625c8 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -16,6 +16,7 @@
package android.view;
+import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
@@ -81,7 +82,7 @@ interface IWindowManager
void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
int configChanges, boolean voiceInteraction, boolean launchTaskBehind);
- void setAppGroupId(IBinder token, int groupId);
+ void setAppTask(IBinder token, int taskId);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
void setFocusedApp(IBinder token, boolean moveFocusNow);
@@ -91,6 +92,8 @@ interface IWindowManager
IRemoteCallback startedCallback);
void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
int startHeight);
+ void overridePendingAppTransitionClipReveal(int startX, int startY,
+ int startWidth, int startHeight);
void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY,
IRemoteCallback startedCallback, boolean scaleUp);
void overridePendingAppTransitionAspectScaledThumb(in Bitmap srcThumb, int startX,
@@ -218,10 +221,14 @@ interface IWindowManager
boolean isRotationFrozen();
/**
+ * Used only for assist -- request a screenshot of the current application.
+ */
+ boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver);
+
+ /**
* Create a screenshot of the applications currently displayed.
*/
- Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth,
- int maxHeight, boolean force565);
+ Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
/**
* Called by the status bar to notify Views of changes to System UI visiblity.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 63e1a85..fc0148f 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -188,9 +188,6 @@ interface IWindowSession {
void wallpaperCommandComplete(IBinder window, in Bundle result);
- void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
- float dsdx, float dtdx, float dsdy, float dtdy);
-
/**
* Notifies that a rectangle on the screen has been requested.
*/
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 243a0fc..779560c 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -20,7 +20,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.method.MetaKeyKeyListener;
import android.util.Log;
-import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap.KeyData;
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 1546877..1a07aee 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -16,22 +16,26 @@
package android.view;
-import android.graphics.Canvas;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Trace;
-import android.widget.FrameLayout;
+import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.graphics.Canvas;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.TypedValue;
import android.util.Xml;
+import android.widget.FrameLayout;
import java.io.IOException;
import java.lang.reflect.Constructor;
@@ -64,6 +68,7 @@ import java.util.HashMap;
* @see Context#getSystemService
*/
public abstract class LayoutInflater {
+
private static final String TAG = LayoutInflater.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -90,12 +95,16 @@ public abstract class LayoutInflater {
private HashMap<String, Boolean> mFilterMap;
+ private TypedValue mTempValue;
+
private static final String TAG_MERGE = "merge";
private static final String TAG_INCLUDE = "include";
private static final String TAG_1995 = "blink";
private static final String TAG_REQUEST_FOCUS = "requestFocus";
private static final String TAG_TAG = "tag";
+ private static final String ATTR_LAYOUT = "layout";
+
private static final int[] ATTRS_THEME = new int[] {
com.android.internal.R.attr.theme };
@@ -361,7 +370,7 @@ public abstract class LayoutInflater {
* this is the root View; otherwise it is the root of the inflated
* XML file.
*/
- public View inflate(int resource, ViewGroup root) {
+ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
@@ -381,7 +390,7 @@ public abstract class LayoutInflater {
* this is the root View; otherwise it is the root of the inflated
* XML file.
*/
- public View inflate(XmlPullParser parser, ViewGroup root) {
+ public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
@@ -402,7 +411,7 @@ public abstract class LayoutInflater {
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
- public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
+ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
@@ -439,7 +448,7 @@ public abstract class LayoutInflater {
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
- public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
+ public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
@@ -523,10 +532,10 @@ public abstract class LayoutInflater {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
- } catch (IOException e) {
+ } catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
- + ": " + e.getMessage());
+ + ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
@@ -837,7 +846,7 @@ public abstract class LayoutInflater {
throws XmlPullParserException, IOException {
int type;
- final TypedArray ta = mContext.obtainStyledAttributes(
+ final TypedArray ta = view.getContext().obtainStyledAttributes(
attrs, com.android.internal.R.styleable.ViewTag);
final int key = ta.getResourceId(com.android.internal.R.styleable.ViewTag_id, 0);
final CharSequence value = ta.getText(com.android.internal.R.styleable.ViewTag_value);
@@ -856,16 +865,41 @@ public abstract class LayoutInflater {
int type;
if (parent instanceof ViewGroup) {
- final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
+ Context context = inheritContext ? parent.getContext() : mContext;
+
+ // Apply a theme wrapper, if requested.
+ final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ if (themeResId != 0) {
+ context = new ContextThemeWrapper(context, themeResId);
+ }
+ ta.recycle();
+
+ // If the layout is pointing to a theme attribute, we have to
+ // massage the value to get a resource identifier out of it.
+ int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {
- final String value = attrs.getAttributeValue(null, "layout");
- if (value == null) {
- throw new InflateException("You must specifiy a layout in the"
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ if (value == null || value.length() < 1) {
+ throw new InflateException("You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
- } else {
- throw new InflateException("You must specifiy a valid layout "
- + "reference. The layout ID " + value + " is not valid.");
}
+
+ layout = context.getResources().getIdentifier(value.substring(1), null, null);
+ }
+
+ // The layout might be referencing a theme attribute.
+ if (mTempValue == null) {
+ mTempValue = new TypedValue();
+ }
+ if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
+ layout = mTempValue.resourceId;
+ }
+
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ throw new InflateException("You must specify a valid layout "
+ + "reference. The layout ID " + value + " is not valid.");
} else {
final XmlResourceParser childParser =
getContext().getResources().getLayout(layout);
@@ -893,6 +927,14 @@ public abstract class LayoutInflater {
inheritContext);
final ViewGroup group = (ViewGroup) parent;
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.Include);
+ final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
+ final int visibility = a.getInt(R.styleable.Include_visibility, -1);
+ final boolean hasWidth = a.hasValue(R.styleable.Include_layout_width);
+ final boolean hasHeight = a.hasValue(R.styleable.Include_layout_height);
+ a.recycle();
+
// We try to load the layout params set in the <include /> tag. If
// they don't exist, we will rely on the layout params set in the
// included XML file.
@@ -902,28 +944,21 @@ public abstract class LayoutInflater {
// successfully loaded layout params from the <include /> tag,
// false means we need to rely on the included layout params.
ViewGroup.LayoutParams params = null;
- try {
- params = group.generateLayoutParams(attrs);
- } catch (RuntimeException e) {
- params = group.generateLayoutParams(childAttrs);
- } finally {
- if (params != null) {
- view.setLayoutParams(params);
+ if (hasWidth && hasHeight) {
+ try {
+ params = group.generateLayoutParams(attrs);
+ } catch (RuntimeException e) {
+ // Ignore, just fail over to child attrs.
}
}
+ if (params == null) {
+ params = group.generateLayoutParams(childAttrs);
+ }
+ view.setLayoutParams(params);
// Inflate all children.
rInflate(childParser, view, childAttrs, true, true);
- // Attempt to override the included layout's android:id with the
- // one set on the <include /> tag itself.
- TypedArray a = mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.View, 0, 0);
- int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
- // While we're at it, let's try to override android:visibility.
- int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
- a.recycle();
-
if (id != View.NO_ID) {
view.setId(id);
}
diff --git a/core/java/android/view/Menu.java b/core/java/android/view/Menu.java
index 7157bc5..0c2e9cf 100644
--- a/core/java/android/view/Menu.java
+++ b/core/java/android/view/Menu.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.StringRes;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
@@ -148,7 +149,7 @@ public interface Menu {
* @param titleRes Resource identifier of title string.
* @return The newly added menu item.
*/
- public MenuItem add(int titleRes);
+ public MenuItem add(@StringRes int titleRes);
/**
* Add a new item to the menu. This item displays the given title for its
@@ -182,7 +183,7 @@ public interface Menu {
* @param titleRes Resource identifier of title string.
* @return The newly added menu item.
*/
- public MenuItem add(int groupId, int itemId, int order, int titleRes);
+ public MenuItem add(int groupId, int itemId, int order, @StringRes int titleRes);
/**
* Add a new sub-menu to the menu. This item displays the given title for
@@ -202,7 +203,7 @@ public interface Menu {
* @param titleRes Resource identifier of title string.
* @return The newly added sub-menu
*/
- SubMenu addSubMenu(final int titleRes);
+ SubMenu addSubMenu(@StringRes final int titleRes);
/**
* Add a new sub-menu to the menu. This item displays the given
@@ -239,7 +240,7 @@ public interface Menu {
* @param titleRes Resource identifier of title string.
* @return The newly added sub-menu
*/
- SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes);
+ SubMenu addSubMenu(int groupId, int itemId, int order, @StringRes int titleRes);
/**
* Add a group of menu items corresponding to actions that can be performed
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index 5811c17..b49a59e 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -21,11 +21,15 @@ import com.android.internal.view.menu.MenuItemImpl;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.MenuRes;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
@@ -101,7 +105,7 @@ public class MenuInflater {
* @param menu The Menu to inflate into. The items and submenus will be
* added to this Menu.
*/
- public void inflate(int menuRes, Menu menu) {
+ public void inflate(@MenuRes int menuRes, Menu menu) {
XmlResourceParser parser = null;
try {
parser = mContext.getResources().getLayout(menuRes);
@@ -333,6 +337,11 @@ public class MenuInflater {
private ActionProvider itemActionProvider;
+ private ColorStateList itemIconTintList;
+ private boolean itemIconTintListSet;
+ private PorterDuff.Mode itemIconTintMode;
+ private boolean itemIconTintModeSet;
+
private static final int defaultGroupId = NO_ID;
private static final int defaultItemId = NO_ID;
private static final int defaultItemCategory = 0;
@@ -423,6 +432,23 @@ public class MenuInflater {
itemActionProvider = null;
}
+ if (a.hasValueOrEmpty(com.android.internal.R.styleable.MenuItem_iconTint)) {
+ itemIconTintList = a.getColorStateList(
+ com.android.internal.R.styleable.MenuItem_iconTint);
+ itemIconTintListSet = true;
+ } else {
+ itemIconTintList = null;
+ itemIconTintListSet = false;
+ }
+ if (a.hasValueOrEmpty(com.android.internal.R.styleable.MenuItem_iconTintMode)) {
+ itemIconTintMode = Drawable.parseTintMode(
+ a.getInt(com.android.internal.R.styleable.MenuItem_iconTintMode, -1), null);
+ itemIconTintModeSet = true;
+ } else {
+ itemIconTintMode = null;
+ itemIconTintModeSet = false;
+ }
+
a.recycle();
itemAdded = false;
@@ -485,6 +511,13 @@ public class MenuInflater {
if (itemActionProvider != null) {
item.setActionProvider(itemActionProvider);
}
+
+ if (itemIconTintListSet) {
+ item.setIconTintList(itemIconTintList);
+ }
+ if (itemIconTintModeSet) {
+ item.setIconTintMode(itemIconTintMode);
+ }
}
public MenuItem addItem() {
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index e706c9c..2948007 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -16,8 +16,13 @@
package android.view;
+import android.annotation.DrawableRes;
+import android.annotation.LayoutRes;
+import android.annotation.StringRes;
import android.app.Activity;
import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
@@ -165,7 +170,7 @@ public interface MenuItem {
* @see #setTitleCondensed(CharSequence)
*/
- public MenuItem setTitle(int title);
+ public MenuItem setTitle(@StringRes int title);
/**
* Retrieve the current title of the item.
@@ -214,7 +219,7 @@ public interface MenuItem {
* @param iconRes The new icon (as a resource ID) to be displayed.
* @return This Item so additional setters can be called.
*/
- public MenuItem setIcon(int iconRes);
+ public MenuItem setIcon(@DrawableRes int iconRes);
/**
* Returns the icon for this item as a Drawable (getting it from resources if it hasn't been
@@ -511,7 +516,7 @@ public interface MenuItem {
*
* @see #setShowAsAction(int)
*/
- public MenuItem setActionView(int resId);
+ public MenuItem setActionView(@LayoutRes int resId);
/**
* Returns the currently set action view for this menu item.
@@ -596,4 +601,26 @@ public interface MenuItem {
* @return This menu item instance for call chaining
*/
public MenuItem setOnActionExpandListener(OnActionExpandListener listener);
+
+ /**
+ * Applies a tint to the icon drawable. Does not modify the current tint
+ * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link android.view.MenuItem#setIcon(android.graphics.drawable.Drawable)}
+ * will automatically mutate the drawable and apply the specified tint and tint mode.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ * @return This menu item instance for call chaining
+ */
+ public MenuItem setIconTintList(ColorStateList tint);
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by {@link
+ * #setIconTintList(ColorStateList)} to the icon drawable. The default mode is {@link
+ * PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be {@code null} to clear tint
+ * @return This menu item instance for call chaining
+ */
+ public MenuItem setIconTintMode(PorterDuff.Mode tintMode);
}
diff --git a/core/java/android/view/PhoneFallbackEventHandler.java b/core/java/android/view/PhoneFallbackEventHandler.java
new file mode 100644
index 0000000..fbf5732
--- /dev/null
+++ b/core/java/android/view/PhoneFallbackEventHandler.java
@@ -0,0 +1,288 @@
+/*
+ * 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.view;
+
+import android.app.KeyguardManager;
+import android.app.SearchManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.media.session.MediaSessionLegacyHelper;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+/**
+ * @hide
+ */
+public class PhoneFallbackEventHandler implements FallbackEventHandler {
+ private static String TAG = "PhoneFallbackEventHandler";
+ private static final boolean DEBUG = false;
+
+ Context mContext;
+ View mView;
+
+ AudioManager mAudioManager;
+ KeyguardManager mKeyguardManager;
+ SearchManager mSearchManager;
+ TelephonyManager mTelephonyManager;
+
+ public PhoneFallbackEventHandler(Context context) {
+ mContext = context;
+ }
+
+ public void setView(View v) {
+ mView = v;
+ }
+
+ public void preDispatchKeyEvent(KeyEvent event) {
+ getAudioManager().preDispatchKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
+ }
+
+ public boolean dispatchKeyEvent(KeyEvent event) {
+
+ final int action = event.getAction();
+ final int keyCode = event.getKeyCode();
+
+ if (action == KeyEvent.ACTION_DOWN) {
+ return onKeyDown(keyCode, event);
+ } else {
+ return onKeyUp(keyCode, event);
+ }
+ }
+
+ boolean onKeyDown(int keyCode, KeyEvent event) {
+ /* ****************************************************************************
+ * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
+ * See the comment in PhoneWindow.onKeyDown
+ * ****************************************************************************/
+ final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ return true;
+ }
+
+
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ /* Suppress PLAY/PAUSE toggle when phone is ringing or in-call
+ * to avoid music playback */
+ if (getTelephonyManager().getCallState() != TelephonyManager.CALL_STATE_IDLE) {
+ return true; // suppress key event
+ }
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CALL: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode() || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ dispatcher.performedLongPress(event);
+ mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ // launch the VoiceDialer
+ Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ sendCloseSystemWindows();
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ startCallActivity();
+ }
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CAMERA: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode() || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ dispatcher.performedLongPress(event);
+ mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ sendCloseSystemWindows();
+ // Broadcast an intent that the Camera button was longpressed
+ Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF,
+ null, null, null, 0, null, null);
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_SEARCH: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode() || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ Configuration config = mContext.getResources().getConfiguration();
+ if (config.keyboard == Configuration.KEYBOARD_NOKEYS
+ || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ // launch the search activity
+ Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ sendCloseSystemWindows();
+ getSearchManager().stopSearch();
+ mContext.startActivity(intent);
+ // Only clear this if we successfully start the
+ // activity; otherwise we will allow the normal short
+ // press action to be performed.
+ dispatcher.performedLongPress(event);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ // Ignore
+ }
+ }
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (DEBUG) {
+ Slog.d(TAG, "up " + keyCode);
+ }
+ final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
+ if (dispatcher != null) {
+ dispatcher.handleUpEvent(event);
+ }
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ if (!event.isCanceled()) {
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CAMERA: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ // Add short press behavior here if desired
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CALL: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ startCallActivity();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void startCallActivity() {
+ sendCloseSystemWindows();
+ Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Slog.w(TAG, "No activity found for android.intent.action.CALL_BUTTON.");
+ }
+ }
+
+ SearchManager getSearchManager() {
+ if (mSearchManager == null) {
+ mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+ }
+ return mSearchManager;
+ }
+
+ TelephonyManager getTelephonyManager() {
+ if (mTelephonyManager == null) {
+ mTelephonyManager = (TelephonyManager)mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ }
+ return mTelephonyManager;
+ }
+
+ KeyguardManager getKeyguardManager() {
+ if (mKeyguardManager == null) {
+ mKeyguardManager = (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ }
+ return mKeyguardManager;
+ }
+
+ AudioManager getAudioManager() {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+ }
+ return mAudioManager;
+ }
+
+ void sendCloseSystemWindows() {
+ PhoneWindow.sendCloseSystemWindows(mContext, null);
+ }
+
+ private void handleMediaKeyEvent(KeyEvent keyEvent) {
+ MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false);
+ }
+}
+
diff --git a/core/java/android/view/PhoneLayoutInflater.java b/core/java/android/view/PhoneLayoutInflater.java
new file mode 100644
index 0000000..7d89a0b
--- /dev/null
+++ b/core/java/android/view/PhoneLayoutInflater.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * @hide
+ */
+public class PhoneLayoutInflater extends LayoutInflater {
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.webkit.",
+ "android.app."
+ };
+
+ /**
+ * Instead of instantiating directly, you should retrieve an instance
+ * through {@link Context#getSystemService}
+ *
+ * @param context The Context in which in which to find resources and other
+ * application-specific things.
+ *
+ * @see Context#getSystemService
+ */
+ public PhoneLayoutInflater(Context context) {
+ super(context);
+ }
+
+ protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
+ super(original, newContext);
+ }
+
+ /** Override onCreateView to instantiate names that correspond to the
+ widgets known to the Widget factory. If we don't find a match,
+ call through to our super class.
+ */
+ @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ for (String prefix : sClassPrefixList) {
+ try {
+ View view = createView(name, prefix, attrs);
+ if (view != null) {
+ return view;
+ }
+ } catch (ClassNotFoundException e) {
+ // In this case we want to let the base class take a crack
+ // at it.
+ }
+ }
+
+ return super.onCreateView(name, attrs);
+ }
+
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new PhoneLayoutInflater(this, newContext);
+ }
+}
+
diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java
new file mode 100644
index 0000000..05796bb
--- /dev/null
+++ b/core/java/android/view/PhoneWindow.java
@@ -0,0 +1,4818 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.getMode;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.*;
+
+import android.app.ActivityManagerNative;
+import android.app.SearchManager;
+import android.os.UserHandle;
+
+import com.android.internal.R;
+import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.StandaloneActionMode;
+import com.android.internal.view.menu.ContextMenuBuilder;
+import com.android.internal.view.menu.IconMenuPresenter;
+import com.android.internal.view.menu.ListMenuPresenter;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuDialogHelper;
+import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.BackgroundFallback;
+import com.android.internal.widget.DecorContentParent;
+import com.android.internal.widget.SwipeDismissLayout;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionLegacyHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.AndroidRuntimeException;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Android-specific Window.
+ * <p>
+ * todo: need to pull the generic functionality out into a base class
+ * in android.widget.
+ *
+ * @hide
+ */
+public class PhoneWindow extends Window implements MenuBuilder.Callback {
+
+ private final static String TAG = "PhoneWindow";
+
+ private final static boolean SWEEP_OPEN_MENU = false;
+
+ private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
+
+ private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
+ (1 << FEATURE_CUSTOM_TITLE) |
+ (1 << FEATURE_CONTENT_TRANSITIONS) |
+ (1 << FEATURE_ACTIVITY_TRANSITIONS) |
+ (1 << FEATURE_ACTION_MODE_OVERLAY);
+
+ private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet();
+
+ /**
+ * Simple callback used by the context menu and its submenus. The options
+ * menu submenus do not use this (their behavior is more complex).
+ */
+ final DialogMenuCallback mContextMenuCallback = new DialogMenuCallback(FEATURE_CONTEXT_MENU);
+
+ final TypedValue mMinWidthMajor = new TypedValue();
+ final TypedValue mMinWidthMinor = new TypedValue();
+ TypedValue mFixedWidthMajor;
+ TypedValue mFixedWidthMinor;
+ TypedValue mFixedHeightMajor;
+ TypedValue mFixedHeightMinor;
+ TypedValue mOutsetBottom;
+
+ // This is the top-level view of the window, containing the window decor.
+ private DecorView mDecor;
+
+ // This is the view in which the window contents are placed. It is either
+ // mDecor itself, or a child of mDecor where the contents go.
+ private ViewGroup mContentParent;
+
+ private ViewGroup mContentRoot;
+
+ SurfaceHolder.Callback2 mTakeSurfaceCallback;
+
+ InputQueue.Callback mTakeInputQueueCallback;
+
+ private boolean mIsFloating;
+
+ private LayoutInflater mLayoutInflater;
+
+ private TextView mTitleView;
+
+ private DecorContentParent mDecorContentParent;
+ private ActionMenuPresenterCallback mActionMenuPresenterCallback;
+ private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
+
+ private TransitionManager mTransitionManager;
+ private Scene mContentScene;
+
+ // The icon resource has been explicitly set elsewhere
+ // and should not be overwritten with a default.
+ static final int FLAG_RESOURCE_SET_ICON = 1 << 0;
+
+ // The logo resource has been explicitly set elsewhere
+ // and should not be overwritten with a default.
+ static final int FLAG_RESOURCE_SET_LOGO = 1 << 1;
+
+ // The icon resource is currently configured to use the system fallback
+ // as no default was previously specified. Anything can override this.
+ static final int FLAG_RESOURCE_SET_ICON_FALLBACK = 1 << 2;
+
+ int mResourcesSetFlags;
+ int mIconRes;
+ int mLogoRes;
+
+ private DrawableFeatureState[] mDrawables;
+
+ private PanelFeatureState[] mPanels;
+
+ /**
+ * The panel that is prepared or opened (the most recent one if there are
+ * multiple panels). Shortcuts will go to this panel. It gets set in
+ * {@link #preparePanel} and cleared in {@link #closePanel}.
+ */
+ private PanelFeatureState mPreparedPanel;
+
+ /**
+ * The keycode that is currently held down (as a modifier) for chording. If
+ * this is 0, there is no key held down.
+ */
+ private int mPanelChordingKey;
+
+ private ImageView mLeftIconView;
+
+ private ImageView mRightIconView;
+
+ private ProgressBar mCircularProgressBar;
+
+ private ProgressBar mHorizontalProgressBar;
+
+ private int mBackgroundResource = 0;
+ private int mBackgroundFallbackResource = 0;
+
+ private Drawable mBackgroundDrawable;
+
+ private float mElevation;
+
+ /** Whether window content should be clipped to the background outline. */
+ private boolean mClipToOutline;
+
+ private int mFrameResource = 0;
+
+ private int mTextColor = 0;
+ private int mStatusBarColor = 0;
+ private int mNavigationBarColor = 0;
+ private boolean mForcedStatusBarColor = false;
+ private boolean mForcedNavigationBarColor = false;
+
+ private CharSequence mTitle = null;
+
+ private int mTitleColor = 0;
+
+ private boolean mAlwaysReadCloseOnTouchAttr = false;
+
+ private ContextMenuBuilder mContextMenu;
+ private MenuDialogHelper mContextMenuHelper;
+ private boolean mClosingActionMenu;
+
+ private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
+ private MediaController mMediaController;
+
+ private AudioManager mAudioManager;
+ private KeyguardManager mKeyguardManager;
+
+ private int mUiOptions = 0;
+
+ private boolean mInvalidatePanelMenuPosted;
+ private int mInvalidatePanelMenuFeatures;
+ private final Runnable mInvalidatePanelMenuRunnable = new Runnable() {
+ @Override public void run() {
+ for (int i = 0; i <= FEATURE_MAX; i++) {
+ if ((mInvalidatePanelMenuFeatures & 1 << i) != 0) {
+ doInvalidatePanelMenu(i);
+ }
+ }
+ mInvalidatePanelMenuPosted = false;
+ mInvalidatePanelMenuFeatures = 0;
+ }
+ };
+
+ private Transition mEnterTransition = null;
+ private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
+ private Transition mExitTransition = null;
+ private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
+ private Transition mSharedElementEnterTransition = null;
+ private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
+ private Transition mSharedElementExitTransition = null;
+ private Transition mSharedElementReenterTransition = USE_DEFAULT_TRANSITION;
+ private Boolean mAllowReturnTransitionOverlap;
+ private Boolean mAllowEnterTransitionOverlap;
+ private long mBackgroundFadeDurationMillis = -1;
+ private Boolean mSharedElementsUseOverlay;
+
+ private Rect mTempRect;
+
+ static class WindowManagerHolder {
+ static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+ }
+
+ static final RotationWatcher sRotationWatcher = new RotationWatcher();
+
+ public PhoneWindow(Context context) {
+ super(context);
+ mLayoutInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public final void setContainer(Window container) {
+ super.setContainer(container);
+ }
+
+ @Override
+ public boolean requestFeature(int featureId) {
+ if (mContentParent != null) {
+ throw new AndroidRuntimeException("requestFeature() must be called before adding content");
+ }
+ final int features = getFeatures();
+ final int newFeatures = features | (1 << featureId);
+ if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
+ (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
+ // Another feature is enabled and the user is trying to enable the custom title feature
+ // or custom title feature is enabled and the user is trying to enable another feature
+ throw new AndroidRuntimeException(
+ "You cannot combine custom titles with other title features");
+ }
+ if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
+ return false; // Ignore. No title dominates.
+ }
+ if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
+ // Remove the action bar feature if we have no title. No title dominates.
+ removeFeature(FEATURE_ACTION_BAR);
+ }
+
+ if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_SWIPE_TO_DISMISS) {
+ throw new AndroidRuntimeException(
+ "You cannot combine swipe dismissal and the action bar.");
+ }
+ if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0 && featureId == FEATURE_ACTION_BAR) {
+ throw new AndroidRuntimeException(
+ "You cannot combine swipe dismissal and the action bar.");
+ }
+
+ if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
+ getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
+ }
+ return super.requestFeature(featureId);
+ }
+
+ @Override
+ public void setUiOptions(int uiOptions) {
+ mUiOptions = uiOptions;
+ }
+
+ @Override
+ public void setUiOptions(int uiOptions, int mask) {
+ mUiOptions = (mUiOptions & ~mask) | (uiOptions & mask);
+ }
+
+ @Override
+ public TransitionManager getTransitionManager() {
+ return mTransitionManager;
+ }
+
+ @Override
+ public void setTransitionManager(TransitionManager tm) {
+ mTransitionManager = tm;
+ }
+
+ @Override
+ public Scene getContentScene() {
+ return mContentScene;
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
+ // decor, when theme attributes and the like are crystalized. Do not check the feature
+ // before this happens.
+ if (mContentParent == null) {
+ installDecor();
+ } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ mContentParent.removeAllViews();
+ }
+
+ if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
+ getContext());
+ transitionTo(newScene);
+ } else {
+ mLayoutInflater.inflate(layoutResID, mContentParent);
+ }
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onContentChanged();
+ }
+ }
+
+ @Override
+ public void setContentView(View view) {
+ setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
+ // decor, when theme attributes and the like are crystalized. Do not check the feature
+ // before this happens.
+ if (mContentParent == null) {
+ installDecor();
+ } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ mContentParent.removeAllViews();
+ }
+
+ if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ view.setLayoutParams(params);
+ final Scene newScene = new Scene(mContentParent, view);
+ transitionTo(newScene);
+ } else {
+ mContentParent.addView(view, params);
+ }
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onContentChanged();
+ }
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ if (mContentParent == null) {
+ installDecor();
+ }
+ if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ // TODO Augment the scenes/transitions API to support this.
+ Log.v(TAG, "addContentView does not support content transitions");
+ }
+ mContentParent.addView(view, params);
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onContentChanged();
+ }
+ }
+
+ private void transitionTo(Scene scene) {
+ if (mContentScene == null) {
+ scene.enter();
+ } else {
+ mTransitionManager.transitionTo(scene);
+ }
+ mContentScene = scene;
+ }
+
+ @Override
+ public View getCurrentFocus() {
+ return mDecor != null ? mDecor.findFocus() : null;
+ }
+
+ @Override
+ public void takeSurface(SurfaceHolder.Callback2 callback) {
+ mTakeSurfaceCallback = callback;
+ }
+
+ public void takeInputQueue(InputQueue.Callback callback) {
+ mTakeInputQueueCallback = callback;
+ }
+
+ @Override
+ public boolean isFloating() {
+ return mIsFloating;
+ }
+
+ /**
+ * Return a LayoutInflater instance that can be used to inflate XML view layout
+ * resources for use in this Window.
+ *
+ * @return LayoutInflater The shared LayoutInflater.
+ */
+ @Override
+ public LayoutInflater getLayoutInflater() {
+ return mLayoutInflater;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ if (mTitleView != null) {
+ mTitleView.setText(title);
+ } else if (mDecorContentParent != null) {
+ mDecorContentParent.setWindowTitle(title);
+ }
+ mTitle = title;
+ }
+
+ @Override
+ @Deprecated
+ public void setTitleColor(int textColor) {
+ if (mTitleView != null) {
+ mTitleView.setTextColor(textColor);
+ }
+ mTitleColor = textColor;
+ }
+
+ /**
+ * Prepares the panel to either be opened or chorded. This creates the Menu
+ * instance for the panel and populates it via the Activity callbacks.
+ *
+ * @param st The panel state to prepare.
+ * @param event The event that triggered the preparing of the panel.
+ * @return Whether the panel was prepared. If the panel should not be shown,
+ * returns false.
+ */
+ public final boolean preparePanel(PanelFeatureState st, KeyEvent event) {
+ if (isDestroyed()) {
+ return false;
+ }
+
+ // Already prepared (isPrepared will be reset to false later)
+ if (st.isPrepared) {
+ return true;
+ }
+
+ if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
+ // Another Panel is prepared and possibly open, so close it
+ closePanel(mPreparedPanel, false);
+ }
+
+ final Callback cb = getCallback();
+
+ if (cb != null) {
+ st.createdPanelView = cb.onCreatePanelView(st.featureId);
+ }
+
+ final boolean isActionBarMenu =
+ (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR);
+
+ if (isActionBarMenu && mDecorContentParent != null) {
+ // Enforce ordering guarantees around events so that the action bar never
+ // dispatches menu-related events before the panel is prepared.
+ mDecorContentParent.setMenuPrepared();
+ }
+
+ if (st.createdPanelView == null) {
+ // Init the panel state's menu--return false if init failed
+ if (st.menu == null || st.refreshMenuContent) {
+ if (st.menu == null) {
+ if (!initializePanelMenu(st) || (st.menu == null)) {
+ return false;
+ }
+ }
+
+ if (isActionBarMenu && mDecorContentParent != null) {
+ if (mActionMenuPresenterCallback == null) {
+ mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
+ }
+ mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
+ }
+
+ // Call callback, and return if it doesn't want to display menu.
+
+ // Creating the panel menu will involve a lot of manipulation;
+ // don't dispatch change events to presenters until we're done.
+ st.menu.stopDispatchingItemsChanged();
+ if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) {
+ // Ditch the menu created above
+ st.setMenu(null);
+
+ if (isActionBarMenu && mDecorContentParent != null) {
+ // Don't show it in the action bar either
+ mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+ }
+
+ return false;
+ }
+
+ st.refreshMenuContent = false;
+ }
+
+ // Callback and return if the callback does not want to show the menu
+
+ // Preparing the panel menu can involve a lot of manipulation;
+ // don't dispatch change events to presenters until we're done.
+ st.menu.stopDispatchingItemsChanged();
+
+ // Restore action view state before we prepare. This gives apps
+ // an opportunity to override frozen/restored state in onPrepare.
+ if (st.frozenActionViewState != null) {
+ st.menu.restoreActionViewStates(st.frozenActionViewState);
+ st.frozenActionViewState = null;
+ }
+
+ if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {
+ if (isActionBarMenu && mDecorContentParent != null) {
+ // The app didn't want to show the menu for now but it still exists.
+ // Clear it out of the action bar.
+ mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+ }
+ st.menu.startDispatchingItemsChanged();
+ return false;
+ }
+
+ // Set the proper keymap
+ KeyCharacterMap kmap = KeyCharacterMap.load(
+ event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
+ st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
+ st.menu.setQwertyMode(st.qwertyMode);
+ st.menu.startDispatchingItemsChanged();
+ }
+
+ // Set other state
+ st.isPrepared = true;
+ st.isHandled = false;
+ mPreparedPanel = st;
+
+ return true;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Action bars handle their own menu state
+ if (mDecorContentParent == null) {
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if ((st != null) && (st.menu != null)) {
+ if (st.isOpen) {
+ // Freeze state
+ final Bundle state = new Bundle();
+ if (st.iconMenuPresenter != null) {
+ st.iconMenuPresenter.saveHierarchyState(state);
+ }
+ if (st.listMenuPresenter != null) {
+ st.listMenuPresenter.saveHierarchyState(state);
+ }
+
+ // Remove the menu views since they need to be recreated
+ // according to the new configuration
+ clearMenuViews(st);
+
+ // Re-open the same menu
+ reopenMenu(false);
+
+ // Restore state
+ if (st.iconMenuPresenter != null) {
+ st.iconMenuPresenter.restoreHierarchyState(state);
+ }
+ if (st.listMenuPresenter != null) {
+ st.listMenuPresenter.restoreHierarchyState(state);
+ }
+
+ } else {
+ // Clear menu views so on next menu opening, it will use
+ // the proper layout
+ clearMenuViews(st);
+ }
+ }
+ }
+ }
+
+ private static void clearMenuViews(PanelFeatureState st) {
+ // This can be called on config changes, so we should make sure
+ // the views will be reconstructed based on the new orientation, etc.
+
+ // Allow the callback to create a new panel view
+ st.createdPanelView = null;
+
+ // Causes the decor view to be recreated
+ st.refreshDecorView = true;
+
+ st.clearMenuPresenters();
+ }
+
+ @Override
+ public final void openPanel(int featureId, KeyEvent event) {
+ if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+ mDecorContentParent.canShowOverflowMenu() &&
+ !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
+ mDecorContentParent.showOverflowMenu();
+ } else {
+ openPanel(getPanelState(featureId, true), event);
+ }
+ }
+
+ private void openPanel(final PanelFeatureState st, KeyEvent event) {
+ // System.out.println("Open panel: isOpen=" + st.isOpen);
+
+ // Already open, return
+ if (st.isOpen || isDestroyed()) {
+ return;
+ }
+
+ // Don't open an options panel for honeycomb apps on xlarge devices.
+ // (The app should be using an action bar for menu items.)
+ if (st.featureId == FEATURE_OPTIONS_PANEL) {
+ Context context = getContext();
+ Configuration config = context.getResources().getConfiguration();
+ boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) ==
+ Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >=
+ android.os.Build.VERSION_CODES.HONEYCOMB;
+
+ if (isXLarge && isHoneycombApp) {
+ return;
+ }
+ }
+
+ Callback cb = getCallback();
+ if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {
+ // Callback doesn't want the menu to open, reset any state
+ closePanel(st, true);
+ return;
+ }
+
+ final WindowManager wm = getWindowManager();
+ if (wm == null) {
+ return;
+ }
+
+ // Prepare panel (should have been done before, but just in case)
+ if (!preparePanel(st, event)) {
+ return;
+ }
+
+ int width = WRAP_CONTENT;
+ if (st.decorView == null || st.refreshDecorView) {
+ if (st.decorView == null) {
+ // Initialize the panel decor, this will populate st.decorView
+ if (!initializePanelDecor(st) || (st.decorView == null))
+ return;
+ } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
+ // Decor needs refreshing, so remove its views
+ st.decorView.removeAllViews();
+ }
+
+ // This will populate st.shownPanelView
+ if (!initializePanelContent(st) || !st.hasPanelItems()) {
+ return;
+ }
+
+ ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
+ if (lp == null) {
+ lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ }
+
+ int backgroundResId;
+ if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+ // If the contents is fill parent for the width, set the
+ // corresponding background
+ backgroundResId = st.fullBackground;
+ width = MATCH_PARENT;
+ } else {
+ // Otherwise, set the normal panel background
+ backgroundResId = st.background;
+ }
+ st.decorView.setWindowBackground(getContext().getDrawable(
+ backgroundResId));
+
+ ViewParent shownPanelParent = st.shownPanelView.getParent();
+ if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {
+ ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);
+ }
+ st.decorView.addView(st.shownPanelView, lp);
+
+ /*
+ * Give focus to the view, if it or one of its children does not
+ * already have it.
+ */
+ if (!st.shownPanelView.hasFocus()) {
+ st.shownPanelView.requestFocus();
+ }
+ } else if (!st.isInListMode()) {
+ width = MATCH_PARENT;
+ } else if (st.createdPanelView != null) {
+ // If we already had a panel view, carry width=MATCH_PARENT through
+ // as we did above when it was created.
+ ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();
+ if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+ width = MATCH_PARENT;
+ }
+ }
+
+ st.isHandled = false;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ width, WRAP_CONTENT,
+ st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ st.decorView.mDefaultOpacity);
+
+ if (st.isCompact) {
+ lp.gravity = getOptionsPanelGravity();
+ sRotationWatcher.addWindow(this);
+ } else {
+ lp.gravity = st.gravity;
+ }
+
+ lp.windowAnimations = st.windowAnimations;
+
+ wm.addView(st.decorView, lp);
+ st.isOpen = true;
+ // Log.v(TAG, "Adding main menu to window manager.");
+ }
+
+ @Override
+ public final void closePanel(int featureId) {
+ if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+ mDecorContentParent.canShowOverflowMenu() &&
+ !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
+ mDecorContentParent.hideOverflowMenu();
+ } else if (featureId == FEATURE_CONTEXT_MENU) {
+ closeContextMenu();
+ } else {
+ closePanel(getPanelState(featureId, true), true);
+ }
+ }
+
+ /**
+ * Closes the given panel.
+ *
+ * @param st The panel to be closed.
+ * @param doCallback Whether to notify the callback that the panel was
+ * closed. If the panel is in the process of re-opening or
+ * opening another panel (e.g., menu opening a sub menu), the
+ * callback should not happen and this variable should be false.
+ * In addition, this method internally will only perform the
+ * callback if the panel is open.
+ */
+ public final void closePanel(PanelFeatureState st, boolean doCallback) {
+ // System.out.println("Close panel: isOpen=" + st.isOpen);
+ if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
+ mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) {
+ checkCloseActionMenu(st.menu);
+ return;
+ }
+
+ final ViewManager wm = getWindowManager();
+ if ((wm != null) && st.isOpen) {
+ if (st.decorView != null) {
+ wm.removeView(st.decorView);
+ // Log.v(TAG, "Removing main menu from window manager.");
+ if (st.isCompact) {
+ sRotationWatcher.removeWindow(this);
+ }
+ }
+
+ if (doCallback) {
+ callOnPanelClosed(st.featureId, st, null);
+ }
+ }
+
+ st.isPrepared = false;
+ st.isHandled = false;
+ st.isOpen = false;
+
+ // This view is no longer shown, so null it out
+ st.shownPanelView = null;
+
+ if (st.isInExpandedMode) {
+ // Next time the menu opens, it should not be in expanded mode, so
+ // force a refresh of the decor
+ st.refreshDecorView = true;
+ st.isInExpandedMode = false;
+ }
+
+ if (mPreparedPanel == st) {
+ mPreparedPanel = null;
+ mPanelChordingKey = 0;
+ }
+ }
+
+ void checkCloseActionMenu(Menu menu) {
+ if (mClosingActionMenu) {
+ return;
+ }
+
+ mClosingActionMenu = true;
+ mDecorContentParent.dismissPopups();
+ Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onPanelClosed(FEATURE_ACTION_BAR, menu);
+ }
+ mClosingActionMenu = false;
+ }
+
+ @Override
+ public final void togglePanel(int featureId, KeyEvent event) {
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (st.isOpen) {
+ closePanel(st, true);
+ } else {
+ openPanel(st, event);
+ }
+ }
+
+ @Override
+ public void invalidatePanelMenu(int featureId) {
+ mInvalidatePanelMenuFeatures |= 1 << featureId;
+
+ if (!mInvalidatePanelMenuPosted && mDecor != null) {
+ mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
+ mInvalidatePanelMenuPosted = true;
+ }
+ }
+
+ void doPendingInvalidatePanelMenu() {
+ if (mInvalidatePanelMenuPosted) {
+ mDecor.removeCallbacks(mInvalidatePanelMenuRunnable);
+ mInvalidatePanelMenuRunnable.run();
+ }
+ }
+
+ void doInvalidatePanelMenu(int featureId) {
+ PanelFeatureState st = getPanelState(featureId, false);
+ if (st == null) {
+ return;
+ }
+ Bundle savedActionViewStates = null;
+ if (st.menu != null) {
+ savedActionViewStates = new Bundle();
+ st.menu.saveActionViewStates(savedActionViewStates);
+ if (savedActionViewStates.size() > 0) {
+ st.frozenActionViewState = savedActionViewStates;
+ }
+ // This will be started again when the panel is prepared.
+ st.menu.stopDispatchingItemsChanged();
+ st.menu.clear();
+ }
+ st.refreshMenuContent = true;
+ st.refreshDecorView = true;
+
+ // Prepare the options panel if we have an action bar
+ if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
+ && mDecorContentParent != null) {
+ st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+ if (st != null) {
+ st.isPrepared = false;
+ preparePanel(st, null);
+ }
+ }
+ }
+
+ /**
+ * Called when the panel key is pushed down.
+ * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
+ * @param event The key event.
+ * @return Whether the key was handled.
+ */
+ public final boolean onKeyDownPanel(int featureId, KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+
+ if (event.getRepeatCount() == 0) {
+ // The panel key was pushed, so set the chording key
+ mPanelChordingKey = keyCode;
+
+ PanelFeatureState st = getPanelState(featureId, false);
+ if (st != null && !st.isOpen) {
+ return preparePanel(st, event);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when the panel key is released.
+ * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
+ * @param event The key event.
+ */
+ public final void onKeyUpPanel(int featureId, KeyEvent event) {
+ // The panel key was released, so clear the chording key
+ if (mPanelChordingKey != 0) {
+ mPanelChordingKey = 0;
+
+ final PanelFeatureState st = getPanelState(featureId, false);
+
+ if (event.isCanceled() || (mDecor != null && mDecor.mPrimaryActionMode != null) ||
+ (st == null)) {
+ return;
+ }
+
+ boolean playSoundEffect = false;
+ if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+ mDecorContentParent.canShowOverflowMenu() &&
+ !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
+ if (!mDecorContentParent.isOverflowMenuShowing()) {
+ if (!isDestroyed() && preparePanel(st, event)) {
+ playSoundEffect = mDecorContentParent.showOverflowMenu();
+ }
+ } else {
+ playSoundEffect = mDecorContentParent.hideOverflowMenu();
+ }
+ } else {
+ if (st.isOpen || st.isHandled) {
+
+ // Play the sound effect if the user closed an open menu (and not if
+ // they just released a menu shortcut)
+ playSoundEffect = st.isOpen;
+
+ // Close menu
+ closePanel(st, true);
+
+ } else if (st.isPrepared) {
+ boolean show = true;
+ if (st.refreshMenuContent) {
+ // Something may have invalidated the menu since we prepared it.
+ // Re-prepare it to refresh.
+ st.isPrepared = false;
+ show = preparePanel(st, event);
+ }
+
+ if (show) {
+ // Write 'menu opened' to event log
+ EventLog.writeEvent(50001, 0);
+
+ // Show menu
+ openPanel(st, event);
+
+ playSoundEffect = true;
+ }
+ }
+ }
+
+ if (playSoundEffect) {
+ AudioManager audioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ if (audioManager != null) {
+ audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+ } else {
+ Log.w(TAG, "Couldn't get audio manager");
+ }
+ }
+ }
+ }
+
+ @Override
+ public final void closeAllPanels() {
+ final ViewManager wm = getWindowManager();
+ if (wm == null) {
+ return;
+ }
+
+ final PanelFeatureState[] panels = mPanels;
+ final int N = panels != null ? panels.length : 0;
+ for (int i = 0; i < N; i++) {
+ final PanelFeatureState panel = panels[i];
+ if (panel != null) {
+ closePanel(panel, true);
+ }
+ }
+
+ closeContextMenu();
+ }
+
+ /**
+ * Closes the context menu. This notifies the menu logic of the close, along
+ * with dismissing it from the UI.
+ */
+ private synchronized void closeContextMenu() {
+ if (mContextMenu != null) {
+ mContextMenu.close();
+ dismissContextMenu();
+ }
+ }
+
+ /**
+ * Dismisses just the context menu UI. To close the context menu, use
+ * {@link #closeContextMenu()}.
+ */
+ private synchronized void dismissContextMenu() {
+ mContextMenu = null;
+
+ if (mContextMenuHelper != null) {
+ mContextMenuHelper.dismiss();
+ mContextMenuHelper = null;
+ }
+ }
+
+ @Override
+ public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
+ return performPanelShortcut(getPanelState(featureId, false), keyCode, event, flags);
+ }
+
+ private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
+ int flags) {
+ if (event.isSystem() || (st == null)) {
+ return false;
+ }
+
+ boolean handled = false;
+
+ // Only try to perform menu shortcuts if preparePanel returned true (possible false
+ // return value from application not wanting to show the menu).
+ if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) {
+ // The menu is prepared now, perform the shortcut on it
+ handled = st.menu.performShortcut(keyCode, event, flags);
+ }
+
+ if (handled) {
+ // Mark as handled
+ st.isHandled = true;
+
+ // Only close down the menu if we don't have an action bar keeping it open.
+ if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) {
+ closePanel(st, true);
+ }
+ }
+
+ return handled;
+ }
+
+ @Override
+ public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
+
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (!preparePanel(st, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU))) {
+ return false;
+ }
+ if (st.menu == null) {
+ return false;
+ }
+
+ boolean res = st.menu.performIdentifierAction(id, flags);
+
+ // Only close down the menu if we don't have an action bar keeping it open.
+ if (mDecorContentParent == null) {
+ closePanel(st, true);
+ }
+
+ return res;
+ }
+
+ public PanelFeatureState findMenuPanel(Menu menu) {
+ final PanelFeatureState[] panels = mPanels;
+ final int N = panels != null ? panels.length : 0;
+ for (int i = 0; i < N; i++) {
+ final PanelFeatureState panel = panels[i];
+ if (panel != null && panel.menu == menu) {
+ return panel;
+ }
+ }
+ return null;
+ }
+
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());
+ if (panel != null) {
+ return cb.onMenuItemSelected(panel.featureId, item);
+ }
+ }
+ return false;
+ }
+
+ public void onMenuModeChange(MenuBuilder menu) {
+ reopenMenu(true);
+ }
+
+ private void reopenMenu(boolean toggleMenuMode) {
+ if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() &&
+ (!ViewConfiguration.get(getContext()).hasPermanentMenuKey() ||
+ mDecorContentParent.isOverflowMenuShowPending())) {
+ final Callback cb = getCallback();
+ if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
+ if (cb != null && !isDestroyed()) {
+ // If we have a menu invalidation pending, do it now.
+ if (mInvalidatePanelMenuPosted &&
+ (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) {
+ mDecor.removeCallbacks(mInvalidatePanelMenuRunnable);
+ mInvalidatePanelMenuRunnable.run();
+ }
+
+ final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+
+ // If we don't have a menu or we're waiting for a full content refresh,
+ // forget it. This is a lingering event that no longer matters.
+ if (st != null && st.menu != null && !st.refreshMenuContent &&
+ cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
+ cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu);
+ mDecorContentParent.showOverflowMenu();
+ }
+ }
+ } else {
+ mDecorContentParent.hideOverflowMenu();
+ final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (st != null && cb != null && !isDestroyed()) {
+ cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu);
+ }
+ }
+ return;
+ }
+
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+
+ if (st == null) {
+ return;
+ }
+
+ // Save the future expanded mode state since closePanel will reset it
+ boolean newExpandedMode = toggleMenuMode ? !st.isInExpandedMode : st.isInExpandedMode;
+
+ st.refreshDecorView = true;
+ closePanel(st, false);
+
+ // Set the expanded mode state
+ st.isInExpandedMode = newExpandedMode;
+
+ openPanel(st, null);
+ }
+
+ /**
+ * Initializes the menu associated with the given panel feature state. You
+ * must at the very least set PanelFeatureState.menu to the Menu to be
+ * associated with the given panel state. The default implementation creates
+ * a new menu for the panel state.
+ *
+ * @param st The panel whose menu is being initialized.
+ * @return Whether the initialization was successful.
+ */
+ protected boolean initializePanelMenu(final PanelFeatureState st) {
+ Context context = getContext();
+
+ // If we have an action bar, initialize the menu with the right theme.
+ if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) &&
+ mDecorContentParent != null) {
+ final TypedValue outValue = new TypedValue();
+ final Theme baseTheme = context.getTheme();
+ baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+ Theme widgetTheme = null;
+ if (outValue.resourceId != 0) {
+ widgetTheme = context.getResources().newTheme();
+ widgetTheme.setTo(baseTheme);
+ widgetTheme.applyStyle(outValue.resourceId, true);
+ widgetTheme.resolveAttribute(
+ R.attr.actionBarWidgetTheme, outValue, true);
+ } else {
+ baseTheme.resolveAttribute(
+ R.attr.actionBarWidgetTheme, outValue, true);
+ }
+
+ if (outValue.resourceId != 0) {
+ if (widgetTheme == null) {
+ widgetTheme = context.getResources().newTheme();
+ widgetTheme.setTo(baseTheme);
+ }
+ widgetTheme.applyStyle(outValue.resourceId, true);
+ }
+
+ if (widgetTheme != null) {
+ context = new ContextThemeWrapper(context, 0);
+ context.getTheme().setTo(widgetTheme);
+ }
+ }
+
+ final MenuBuilder menu = new MenuBuilder(context);
+ menu.setCallback(this);
+ st.setMenu(menu);
+
+ return true;
+ }
+
+ /**
+ * Perform initial setup of a panel. This should at the very least set the
+ * style information in the PanelFeatureState and must set
+ * PanelFeatureState.decor to the panel's window decor view.
+ *
+ * @param st The panel being initialized.
+ */
+ protected boolean initializePanelDecor(PanelFeatureState st) {
+ st.decorView = new DecorView(getContext(), st.featureId);
+ st.gravity = Gravity.CENTER | Gravity.BOTTOM;
+ st.setStyle(getContext());
+ TypedArray a = getContext().obtainStyledAttributes(null,
+ R.styleable.Window, 0, st.listPresenterTheme);
+ final float elevation = a.getDimension(R.styleable.Window_windowElevation, 0);
+ if (elevation != 0) {
+ st.decorView.setElevation(elevation);
+ }
+ a.recycle();
+
+ return true;
+ }
+
+ /**
+ * Determine the gravity value for the options panel. This can
+ * differ in compact mode.
+ *
+ * @return gravity value to use for the panel window
+ */
+ private int getOptionsPanelGravity() {
+ try {
+ return WindowManagerHolder.sWindowManager.getPreferredOptionsPanelGravity();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Couldn't getOptionsPanelGravity; using default", ex);
+ return Gravity.CENTER | Gravity.BOTTOM;
+ }
+ }
+
+ void onOptionsPanelRotationChanged() {
+ final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (st == null) return;
+
+ final WindowManager.LayoutParams lp = st.decorView != null ?
+ (WindowManager.LayoutParams) st.decorView.getLayoutParams() : null;
+ if (lp != null) {
+ lp.gravity = getOptionsPanelGravity();
+ final ViewManager wm = getWindowManager();
+ if (wm != null) {
+ wm.updateViewLayout(st.decorView, lp);
+ }
+ }
+ }
+
+ /**
+ * Initializes the panel associated with the panel feature state. You must
+ * at the very least set PanelFeatureState.panel to the View implementing
+ * its contents. The default implementation gets the panel from the menu.
+ *
+ * @param st The panel state being initialized.
+ * @return Whether the initialization was successful.
+ */
+ protected boolean initializePanelContent(PanelFeatureState st) {
+ if (st.createdPanelView != null) {
+ st.shownPanelView = st.createdPanelView;
+ return true;
+ }
+
+ if (st.menu == null) {
+ return false;
+ }
+
+ if (mPanelMenuPresenterCallback == null) {
+ mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
+ }
+
+ MenuView menuView = st.isInListMode()
+ ? st.getListMenuView(getContext(), mPanelMenuPresenterCallback)
+ : st.getIconMenuView(getContext(), mPanelMenuPresenterCallback);
+
+ st.shownPanelView = (View) menuView;
+
+ if (st.shownPanelView != null) {
+ // Use the menu View's default animations if it has any
+ final int defaultAnimations = menuView.getWindowAnimations();
+ if (defaultAnimations != 0) {
+ st.windowAnimations = defaultAnimations;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean performContextMenuIdentifierAction(int id, int flags) {
+ return (mContextMenu != null) ? mContextMenu.performIdentifierAction(id, flags) : false;
+ }
+
+ @Override
+ public final void setElevation(float elevation) {
+ mElevation = elevation;
+ if (mDecor != null) {
+ mDecor.setElevation(elevation);
+ }
+ dispatchWindowAttributesChanged(getAttributes());
+ }
+
+ @Override
+ public final void setClipToOutline(boolean clipToOutline) {
+ mClipToOutline = clipToOutline;
+ if (mDecor != null) {
+ mDecor.setClipToOutline(clipToOutline);
+ }
+ }
+
+ @Override
+ public final void setBackgroundDrawable(Drawable drawable) {
+ if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {
+ mBackgroundResource = 0;
+ mBackgroundDrawable = drawable;
+ if (mDecor != null) {
+ mDecor.setWindowBackground(drawable);
+ }
+ if (mBackgroundFallbackResource != 0) {
+ mDecor.setBackgroundFallback(drawable != null ? 0 : mBackgroundFallbackResource);
+ }
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawableResource(int featureId, int resId) {
+ if (resId != 0) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.resid != resId) {
+ st.resid = resId;
+ st.uri = null;
+ st.local = getContext().getDrawable(resId);
+ updateDrawable(featureId, st, false);
+ }
+ } else {
+ setFeatureDrawable(featureId, null);
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawableUri(int featureId, Uri uri) {
+ if (uri != null) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.uri == null || !st.uri.equals(uri)) {
+ st.resid = 0;
+ st.uri = uri;
+ st.local = loadImageURI(uri);
+ updateDrawable(featureId, st, false);
+ }
+ } else {
+ setFeatureDrawable(featureId, null);
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ st.resid = 0;
+ st.uri = null;
+ if (st.local != drawable) {
+ st.local = drawable;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ @Override
+ public void setFeatureDrawableAlpha(int featureId, int alpha) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.alpha != alpha) {
+ st.alpha = alpha;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ protected final void setFeatureDefaultDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.def != drawable) {
+ st.def = drawable;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ @Override
+ public final void setFeatureInt(int featureId, int value) {
+ // XXX Should do more management (as with drawable features) to
+ // deal with interactions between multiple window policies.
+ updateInt(featureId, value, false);
+ }
+
+ /**
+ * Update the state of a drawable feature. This should be called, for every
+ * drawable feature supported, as part of onActive(), to make sure that the
+ * contents of a containing window is properly updated.
+ *
+ * @see #onActive
+ * @param featureId The desired drawable feature to change.
+ * @param fromActive Always true when called from onActive().
+ */
+ protected final void updateDrawable(int featureId, boolean fromActive) {
+ final DrawableFeatureState st = getDrawableState(featureId, false);
+ if (st != null) {
+ updateDrawable(featureId, st, fromActive);
+ }
+ }
+
+ /**
+ * Called when a Drawable feature changes, for the window to update its
+ * graphics.
+ *
+ * @param featureId The feature being changed.
+ * @param drawable The new Drawable to show, or null if none.
+ * @param alpha The new alpha blending of the Drawable.
+ */
+ protected void onDrawableChanged(int featureId, Drawable drawable, int alpha) {
+ ImageView view;
+ if (featureId == FEATURE_LEFT_ICON) {
+ view = getLeftIconView();
+ } else if (featureId == FEATURE_RIGHT_ICON) {
+ view = getRightIconView();
+ } else {
+ return;
+ }
+
+ if (drawable != null) {
+ drawable.setAlpha(alpha);
+ view.setImageDrawable(drawable);
+ view.setVisibility(View.VISIBLE);
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Called when an int feature changes, for the window to update its
+ * graphics.
+ *
+ * @param featureId The feature being changed.
+ * @param value The new integer value.
+ */
+ protected void onIntChanged(int featureId, int value) {
+ if (featureId == FEATURE_PROGRESS || featureId == FEATURE_INDETERMINATE_PROGRESS) {
+ updateProgressBars(value);
+ } else if (featureId == FEATURE_CUSTOM_TITLE) {
+ FrameLayout titleContainer = (FrameLayout) findViewById(R.id.title_container);
+ if (titleContainer != null) {
+ mLayoutInflater.inflate(value, titleContainer);
+ }
+ }
+ }
+
+ /**
+ * Updates the progress bars that are shown in the title bar.
+ *
+ * @param value Can be one of {@link Window#PROGRESS_VISIBILITY_ON},
+ * {@link Window#PROGRESS_VISIBILITY_OFF},
+ * {@link Window#PROGRESS_INDETERMINATE_ON},
+ * {@link Window#PROGRESS_INDETERMINATE_OFF}, or a value
+ * starting at {@link Window#PROGRESS_START} through
+ * {@link Window#PROGRESS_END} for setting the default
+ * progress (if {@link Window#PROGRESS_END} is given,
+ * the progress bar widgets in the title will be hidden after an
+ * animation), a value between
+ * {@link Window#PROGRESS_SECONDARY_START} -
+ * {@link Window#PROGRESS_SECONDARY_END} for the
+ * secondary progress (if
+ * {@link Window#PROGRESS_SECONDARY_END} is given, the
+ * progress bar widgets will still be shown with the secondary
+ * progress bar will be completely filled in.)
+ */
+ private void updateProgressBars(int value) {
+ ProgressBar circularProgressBar = getCircularProgressBar(true);
+ ProgressBar horizontalProgressBar = getHorizontalProgressBar(true);
+
+ final int features = getLocalFeatures();
+ if (value == PROGRESS_VISIBILITY_ON) {
+ if ((features & (1 << FEATURE_PROGRESS)) != 0) {
+ if (horizontalProgressBar != null) {
+ int level = horizontalProgressBar.getProgress();
+ int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ?
+ View.VISIBLE : View.INVISIBLE;
+ horizontalProgressBar.setVisibility(visibility);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+ }
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ if (circularProgressBar != null) {
+ circularProgressBar.setVisibility(View.VISIBLE);
+ } else {
+ Log.e(TAG, "Circular progress bar not located in current window decor");
+ }
+ }
+ } else if (value == PROGRESS_VISIBILITY_OFF) {
+ if ((features & (1 << FEATURE_PROGRESS)) != 0) {
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setVisibility(View.GONE);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+ }
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ if (circularProgressBar != null) {
+ circularProgressBar.setVisibility(View.GONE);
+ } else {
+ Log.e(TAG, "Circular progress bar not located in current window decor");
+ }
+ }
+ } else if (value == PROGRESS_INDETERMINATE_ON) {
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setIndeterminate(true);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+ } else if (value == PROGRESS_INDETERMINATE_OFF) {
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setIndeterminate(false);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+ } else if (PROGRESS_START <= value && value <= PROGRESS_END) {
+ // We want to set the progress value before testing for visibility
+ // so that when the progress bar becomes visible again, it has the
+ // correct level.
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setProgress(value - PROGRESS_START);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+
+ if (value < PROGRESS_END) {
+ showProgressBars(horizontalProgressBar, circularProgressBar);
+ } else {
+ hideProgressBars(horizontalProgressBar, circularProgressBar);
+ }
+ } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) {
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+
+ showProgressBars(horizontalProgressBar, circularProgressBar);
+ }
+
+ }
+
+ private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) {
+ final int features = getLocalFeatures();
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
+ spinnyProgressBar != null && spinnyProgressBar.getVisibility() == View.INVISIBLE) {
+ spinnyProgressBar.setVisibility(View.VISIBLE);
+ }
+ // Only show the progress bars if the primary progress is not complete
+ if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null &&
+ horizontalProgressBar.getProgress() < 10000) {
+ horizontalProgressBar.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) {
+ final int features = getLocalFeatures();
+ Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.fade_out);
+ anim.setDuration(1000);
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
+ spinnyProgressBar != null &&
+ spinnyProgressBar.getVisibility() == View.VISIBLE) {
+ spinnyProgressBar.startAnimation(anim);
+ spinnyProgressBar.setVisibility(View.INVISIBLE);
+ }
+ if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null &&
+ horizontalProgressBar.getVisibility() == View.VISIBLE) {
+ horizontalProgressBar.startAnimation(anim);
+ horizontalProgressBar.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ @Override
+ public void setIcon(int resId) {
+ mIconRes = resId;
+ mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON;
+ mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK;
+ if (mDecorContentParent != null) {
+ mDecorContentParent.setIcon(resId);
+ }
+ }
+
+ @Override
+ public void setDefaultIcon(int resId) {
+ if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0) {
+ return;
+ }
+ mIconRes = resId;
+ if (mDecorContentParent != null && (!mDecorContentParent.hasIcon() ||
+ (mResourcesSetFlags & FLAG_RESOURCE_SET_ICON_FALLBACK) != 0)) {
+ if (resId != 0) {
+ mDecorContentParent.setIcon(resId);
+ mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK;
+ } else {
+ mDecorContentParent.setIcon(
+ getContext().getPackageManager().getDefaultActivityIcon());
+ mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
+ }
+ }
+ }
+
+ @Override
+ public void setLogo(int resId) {
+ mLogoRes = resId;
+ mResourcesSetFlags |= FLAG_RESOURCE_SET_LOGO;
+ if (mDecorContentParent != null) {
+ mDecorContentParent.setLogo(resId);
+ }
+ }
+
+ @Override
+ public void setDefaultLogo(int resId) {
+ if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0) {
+ return;
+ }
+ mLogoRes = resId;
+ if (mDecorContentParent != null && !mDecorContentParent.hasLogo()) {
+ mDecorContentParent.setLogo(resId);
+ }
+ }
+
+ @Override
+ public void setLocalFocus(boolean hasFocus, boolean inTouchMode) {
+ getViewRootImpl().windowFocusChanged(hasFocus, inTouchMode);
+
+ }
+
+ @Override
+ public void injectInputEvent(InputEvent event) {
+ getViewRootImpl().dispatchInputEvent(event);
+ }
+
+ private ViewRootImpl getViewRootImpl() {
+ if (mDecor != null) {
+ ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
+ if (viewRootImpl != null) {
+ return viewRootImpl;
+ }
+ }
+ throw new IllegalStateException("view not added");
+ }
+
+ /**
+ * Request that key events come to this activity. Use this if your activity
+ * has no views with focus, but the activity still wants a chance to process
+ * key events.
+ */
+ @Override
+ public void takeKeyEvents(boolean get) {
+ mDecor.setFocusable(get);
+ }
+
+ @Override
+ public boolean superDispatchKeyEvent(KeyEvent event) {
+ return mDecor.superDispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+ return mDecor.superDispatchKeyShortcutEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchTouchEvent(MotionEvent event) {
+ return mDecor.superDispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchTrackballEvent(MotionEvent event) {
+ return mDecor.superDispatchTrackballEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+ return mDecor.superDispatchGenericMotionEvent(event);
+ }
+
+ /**
+ * A key was pressed down and not handled by anything else in the window.
+ *
+ * @see #onKeyUp
+ * @see android.view.KeyEvent
+ */
+ protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
+ /* ****************************************************************************
+ * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
+ *
+ * If your key handling must happen before the app gets a crack at the event,
+ * it goes in PhoneWindowManager.
+ *
+ * If your key handling should happen in all windows, and does not depend on
+ * the state of the current application, other than that the current
+ * application can override the behavior by handling the event itself, it
+ * should go in PhoneFallbackEventHandler.
+ *
+ * Only if your handling depends on the window, and the fact that it has
+ * a DecorView, should it go here.
+ * ****************************************************************************/
+
+ final KeyEvent.DispatcherState dispatcher =
+ mDecor != null ? mDecor.getKeyDispatcherState() : null;
+ //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
+ // + " flags=0x" + Integer.toHexString(event.getFlags()));
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ int direction = 0;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ direction = AudioManager.ADJUST_RAISE;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ direction = AudioManager.ADJUST_LOWER;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ direction = AudioManager.ADJUST_TOGGLE_MUTE;
+ break;
+ }
+ // If we have a session send it the volume command, otherwise
+ // use the suggested stream.
+ if (mMediaController != null) {
+ mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
+ } else {
+ MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
+ mVolumeControlStreamType, direction,
+ AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE
+ | AudioManager.FLAG_FROM_KEY);
+ }
+ return true;
+ }
+ // These are all the recognized media key codes in
+ // KeyEvent.isMediaKey()
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ if (mMediaController != null) {
+ if (mMediaController.dispatchMediaButtonEvent(event)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ case KeyEvent.KEYCODE_MENU: {
+ onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_BACK: {
+ if (event.getRepeatCount() > 0) break;
+ if (featureId < 0) break;
+ // Currently don't do anything with long press.
+ if (dispatcher != null) {
+ dispatcher.startTracking(event, this);
+ }
+ return true;
+ }
+
+ }
+
+ return false;
+ }
+
+ private KeyguardManager getKeyguardManager() {
+ if (mKeyguardManager == null) {
+ mKeyguardManager = (KeyguardManager) getContext().getSystemService(
+ Context.KEYGUARD_SERVICE);
+ }
+ return mKeyguardManager;
+ }
+
+ AudioManager getAudioManager() {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
+ }
+ return mAudioManager;
+ }
+
+ /**
+ * A key was released and not handled by anything else in the window.
+ *
+ * @see #onKeyDown
+ * @see android.view.KeyEvent
+ */
+ protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {
+ final KeyEvent.DispatcherState dispatcher =
+ mDecor != null ? mDecor.getKeyDispatcherState() : null;
+ if (dispatcher != null) {
+ dispatcher.handleUpEvent(event);
+ }
+ //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()
+ // + " flags=0x" + Integer.toHexString(event.getFlags()));
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN: {
+ final int flags = AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE
+ | AudioManager.FLAG_FROM_KEY;
+ // If we have a session send it the volume command, otherwise
+ // use the suggested stream.
+ if (mMediaController != null) {
+ mMediaController.adjustVolume(0, flags);
+ } else {
+ MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
+ mVolumeControlStreamType, 0, flags);
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ // Similar code is in PhoneFallbackEventHandler in case the window
+ // doesn't have one of these. In this case, we execute it here and
+ // eat the event instead, because we have mVolumeControlStreamType
+ // and they don't.
+ getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
+ return true;
+ }
+ // These are all the recognized media key codes in
+ // KeyEvent.isMediaKey()
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ if (mMediaController != null) {
+ if (mMediaController.dispatchMediaButtonEvent(event)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ case KeyEvent.KEYCODE_MENU: {
+ onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
+ event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_BACK: {
+ if (featureId < 0) break;
+ if (event.isTracking() && !event.isCanceled()) {
+ if (featureId == FEATURE_OPTIONS_PANEL) {
+ PanelFeatureState st = getPanelState(featureId, false);
+ if (st != null && st.isInExpandedMode) {
+ // If the user is in an expanded menu and hits back, it
+ // should go back to the icon menu
+ reopenMenu(true);
+ return true;
+ }
+ }
+ closePanel(featureId);
+ return true;
+ }
+ break;
+ }
+
+ case KeyEvent.KEYCODE_SEARCH: {
+ /*
+ * Do this in onKeyUp since the Search key is also used for
+ * chording quick launch shortcuts.
+ */
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ launchDefaultSearch();
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void onActive() {
+ }
+
+ @Override
+ public final View getDecorView() {
+ if (mDecor == null) {
+ installDecor();
+ }
+ return mDecor;
+ }
+
+ @Override
+ public final View peekDecorView() {
+ return mDecor;
+ }
+
+ static private final String FOCUSED_ID_TAG = "android:focusedViewId";
+ static private final String VIEWS_TAG = "android:views";
+ static private final String PANELS_TAG = "android:Panels";
+ static private final String ACTION_BAR_TAG = "android:ActionBar";
+
+ /** {@inheritDoc} */
+ @Override
+ public Bundle saveHierarchyState() {
+ Bundle outState = new Bundle();
+ if (mContentParent == null) {
+ return outState;
+ }
+
+ SparseArray<Parcelable> states = new SparseArray<Parcelable>();
+ mContentParent.saveHierarchyState(states);
+ outState.putSparseParcelableArray(VIEWS_TAG, states);
+
+ // save the focused view id
+ View focusedView = mContentParent.findFocus();
+ if (focusedView != null) {
+ if (focusedView.getId() != View.NO_ID) {
+ outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
+ } else {
+ if (false) {
+ Log.d(TAG, "couldn't save which view has focus because the focused view "
+ + focusedView + " has no id.");
+ }
+ }
+ }
+
+ // save the panels
+ SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
+ savePanelState(panelStates);
+ if (panelStates.size() > 0) {
+ outState.putSparseParcelableArray(PANELS_TAG, panelStates);
+ }
+
+ if (mDecorContentParent != null) {
+ SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
+ mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
+ outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
+ }
+
+ return outState;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void restoreHierarchyState(Bundle savedInstanceState) {
+ if (mContentParent == null) {
+ return;
+ }
+
+ SparseArray<Parcelable> savedStates
+ = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
+ if (savedStates != null) {
+ mContentParent.restoreHierarchyState(savedStates);
+ }
+
+ // restore the focused view
+ int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
+ if (focusedViewId != View.NO_ID) {
+ View needsFocus = mContentParent.findViewById(focusedViewId);
+ if (needsFocus != null) {
+ needsFocus.requestFocus();
+ } else {
+ Log.w(TAG,
+ "Previously focused view reported id " + focusedViewId
+ + " during save, but can't be found during restore.");
+ }
+ }
+
+ // restore the panels
+ SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
+ if (panelStates != null) {
+ restorePanelState(panelStates);
+ }
+
+ if (mDecorContentParent != null) {
+ SparseArray<Parcelable> actionBarStates =
+ savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
+ if (actionBarStates != null) {
+ doPendingInvalidatePanelMenu();
+ mDecorContentParent.restoreToolbarHierarchyState(actionBarStates);
+ } else {
+ Log.w(TAG, "Missing saved instance states for action bar views! " +
+ "State will not be restored.");
+ }
+ }
+ }
+
+ /**
+ * Invoked when the panels should freeze their state.
+ *
+ * @param icicles Save state into this. This is usually indexed by the
+ * featureId. This will be given to {@link #restorePanelState} in the
+ * future.
+ */
+ private void savePanelState(SparseArray<Parcelable> icicles) {
+ PanelFeatureState[] panels = mPanels;
+ if (panels == null) {
+ return;
+ }
+
+ for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) {
+ if (panels[curFeatureId] != null) {
+ icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState());
+ }
+ }
+ }
+
+ /**
+ * Invoked when the panels should thaw their state from a previously frozen state.
+ *
+ * @param icicles The state saved by {@link #savePanelState} that needs to be thawed.
+ */
+ private void restorePanelState(SparseArray<Parcelable> icicles) {
+ PanelFeatureState st;
+ int curFeatureId;
+ for (int i = icicles.size() - 1; i >= 0; i--) {
+ curFeatureId = icicles.keyAt(i);
+ st = getPanelState(curFeatureId, false /* required */);
+ if (st == null) {
+ // The panel must not have been required, and is currently not around, skip it
+ continue;
+ }
+
+ st.onRestoreInstanceState(icicles.get(curFeatureId));
+ invalidatePanelMenu(curFeatureId);
+ }
+
+ /*
+ * Implementation note: call openPanelsAfterRestore later to actually open the
+ * restored panels.
+ */
+ }
+
+ /**
+ * Opens the panels that have had their state restored. This should be
+ * called sometime after {@link #restorePanelState} when it is safe to add
+ * to the window manager.
+ */
+ private void openPanelsAfterRestore() {
+ PanelFeatureState[] panels = mPanels;
+
+ if (panels == null) {
+ return;
+ }
+
+ PanelFeatureState st;
+ for (int i = panels.length - 1; i >= 0; i--) {
+ st = panels[i];
+ // We restore the panel if it was last open; we skip it if it
+ // now is open, to avoid a race condition if the user immediately
+ // opens it when we are resuming.
+ if (st != null) {
+ st.applyFrozenState();
+ if (!st.isOpen && st.wasLastOpen) {
+ st.isInExpandedMode = st.wasLastExpanded;
+ openPanel(st, null);
+ }
+ }
+ }
+ }
+
+ private class PanelMenuPresenterCallback implements MenuPresenter.Callback {
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ final Menu parentMenu = menu.getRootMenu();
+ final boolean isSubMenu = parentMenu != menu;
+ final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu);
+ if (panel != null) {
+ if (isSubMenu) {
+ callOnPanelClosed(panel.featureId, panel, parentMenu);
+ closePanel(panel, true);
+ } else {
+ // Close the panel and only do the callback if the menu is being
+ // closed completely, not if opening a sub menu
+ closePanel(panel, allMenusAreClosing);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (subMenu == null && hasFeature(FEATURE_ACTION_BAR)) {
+ Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ Callback cb = getCallback();
+ if (cb != null) {
+ cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ checkCloseActionMenu(menu);
+ }
+ }
+
+ private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
+
+ /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
+
+ /** The feature ID of the panel, or -1 if this is the application's DecorView */
+ private final int mFeatureId;
+
+ private final Rect mDrawingBounds = new Rect();
+
+ private final Rect mBackgroundPadding = new Rect();
+
+ private final Rect mFramePadding = new Rect();
+
+ private final Rect mFrameOffsets = new Rect();
+
+ private boolean mChanging;
+
+ private Drawable mMenuBackground;
+ private boolean mWatchingForMenu;
+ private int mDownY;
+
+ private ActionMode mPrimaryActionMode;
+ private ActionMode mFloatingActionMode;
+ private ActionBarContextView mPrimaryActionModeView;
+ private PopupWindow mPrimaryActionModePopup;
+ private Runnable mShowPrimaryActionModePopup;
+
+ // View added at runtime to draw under the status bar area
+ private View mStatusGuard;
+ // View added at runtime to draw under the navigation bar area
+ private View mNavigationGuard;
+
+ private final ColorViewState mStatusColorViewState = new ColorViewState(
+ SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
+ Gravity.TOP,
+ STATUS_BAR_BACKGROUND_TRANSITION_NAME,
+ com.android.internal.R.id.statusBarBackground,
+ FLAG_FULLSCREEN);
+ private final ColorViewState mNavigationColorViewState = new ColorViewState(
+ SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
+ Gravity.BOTTOM,
+ NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
+ com.android.internal.R.id.navigationBarBackground,
+ 0 /* hideWindowFlag */);
+
+ private final Interpolator mShowInterpolator;
+ private final Interpolator mHideInterpolator;
+ private final int mBarEnterExitDuration;
+
+ private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
+
+ private int mLastTopInset = 0;
+ private int mLastBottomInset = 0;
+ private int mLastRightInset = 0;
+ private boolean mLastHasTopStableInset = false;
+ private boolean mLastHasBottomStableInset = false;
+ private int mLastWindowFlags = 0;
+
+ private int mRootScrollY = 0;
+
+ public DecorView(Context context, int featureId) {
+ super(context);
+ mFeatureId = featureId;
+
+ mShowInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.linear_out_slow_in);
+ mHideInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_linear_in);
+
+ mBarEnterExitDuration = context.getResources().getInteger(
+ R.integer.dock_enter_exit_duration);
+ }
+
+ public void setBackgroundFallback(int resId) {
+ mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
+ setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ super.onDraw(c);
+ mBackgroundFallback.draw(mContentRoot, c, mContentParent);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ final int action = event.getAction();
+ final boolean isDown = action == KeyEvent.ACTION_DOWN;
+
+ if (isDown && (event.getRepeatCount() == 0)) {
+ // First handle chording of panel key: if a panel key is held
+ // but not released, try to execute a shortcut in it.
+ if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
+ boolean handled = dispatchKeyShortcutEvent(event);
+ if (handled) {
+ return true;
+ }
+ }
+
+ // If a panel is open, perform a shortcut on it without the
+ // chorded panel key
+ if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
+ if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
+ return true;
+ }
+ }
+ }
+
+ if (!isDestroyed()) {
+ final Callback cb = getCallback();
+ final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
+ : super.dispatchKeyEvent(event);
+ if (handled) {
+ return true;
+ }
+ }
+
+ return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
+ : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
+ // If the panel is already prepared, then perform the shortcut using it.
+ boolean handled;
+ if (mPreparedPanel != null) {
+ handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev,
+ Menu.FLAG_PERFORM_NO_CLOSE);
+ if (handled) {
+ if (mPreparedPanel != null) {
+ mPreparedPanel.isHandled = true;
+ }
+ return true;
+ }
+ }
+
+ // Shortcut not handled by the panel. Dispatch to the view hierarchy.
+ final Callback cb = getCallback();
+ handled = cb != null && !isDestroyed() && mFeatureId < 0
+ ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
+ if (handled) {
+ return true;
+ }
+
+ // If the panel is not prepared, then we may be trying to handle a shortcut key
+ // combination such as Control+C. Temporarily prepare the panel then mark it
+ // unprepared again when finished to ensure that the panel will again be prepared
+ // the next time it is shown for real.
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (st != null && mPreparedPanel == null) {
+ preparePanel(st, ev);
+ handled = performPanelShortcut(st, ev.getKeyCode(), ev,
+ Menu.FLAG_PERFORM_NO_CLOSE);
+ st.isPrepared = false;
+ if (handled) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ final Callback cb = getCallback();
+ return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
+ : super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent ev) {
+ final Callback cb = getCallback();
+ return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev)
+ : super.dispatchTrackballEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ final Callback cb = getCallback();
+ return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev)
+ : super.dispatchGenericMotionEvent(ev);
+ }
+
+ public boolean superDispatchKeyEvent(KeyEvent event) {
+ // Give priority to closing action modes if applicable.
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ final int action = event.getAction();
+ // Back cancels action modes first.
+ if (mPrimaryActionMode != null) {
+ if (action == KeyEvent.ACTION_UP) {
+ mPrimaryActionMode.finish();
+ }
+ return true;
+ }
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+
+ public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+ return super.dispatchKeyShortcutEvent(event);
+ }
+
+ public boolean superDispatchTouchEvent(MotionEvent event) {
+ return super.dispatchTouchEvent(event);
+ }
+
+ public boolean superDispatchTrackballEvent(MotionEvent event) {
+ return super.dispatchTrackballEvent(event);
+ }
+
+ public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+ return super.dispatchGenericMotionEvent(event);
+ }
+
+ @Override
+ public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+ if (mOutsetBottom != null) {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ int bottom = (int) mOutsetBottom.getDimension(metrics);
+ WindowInsets newInsets = insets.replaceSystemWindowInsets(
+ insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight(), bottom);
+ return super.dispatchApplyWindowInsets(newInsets);
+ } else {
+ return super.dispatchApplyWindowInsets(insets);
+ }
+ }
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return onInterceptTouchEvent(event);
+ }
+
+ private boolean isOutOfBounds(int x, int y) {
+ return x < -5 || y < -5 || x > (getWidth() + 5)
+ || y > (getHeight() + 5);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+ if (mFeatureId >= 0) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ int x = (int)event.getX();
+ int y = (int)event.getY();
+ if (isOutOfBounds(x, y)) {
+ closePanel(mFeatureId);
+ return true;
+ }
+ }
+ }
+
+ if (!SWEEP_OPEN_MENU) {
+ return false;
+ }
+
+ if (mFeatureId >= 0) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ Log.i(TAG, "Watchiing!");
+ mWatchingForMenu = true;
+ mDownY = (int) event.getY();
+ return false;
+ }
+
+ if (!mWatchingForMenu) {
+ return false;
+ }
+
+ int y = (int)event.getY();
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (y > (mDownY+30)) {
+ Log.i(TAG, "Closing!");
+ closePanel(mFeatureId);
+ mWatchingForMenu = false;
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ mWatchingForMenu = false;
+ }
+
+ return false;
+ }
+
+ //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
+ // + " (in " + getHeight() + ")");
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ int y = (int)event.getY();
+ if (y >= (getHeight()-5) && !hasChildren()) {
+ Log.i(TAG, "Watchiing!");
+ mWatchingForMenu = true;
+ }
+ return false;
+ }
+
+ if (!mWatchingForMenu) {
+ return false;
+ }
+
+ int y = (int)event.getY();
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (y < (getHeight()-30)) {
+ Log.i(TAG, "Opening!");
+ openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
+ mWatchingForMenu = false;
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ mWatchingForMenu = false;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void sendAccessibilityEvent(int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return;
+ }
+
+ // if we are showing a feature that should be announced and one child
+ // make this child the event source since this is the feature itself
+ // otherwise the callback will take over and announce its client
+ if ((mFeatureId == FEATURE_OPTIONS_PANEL ||
+ mFeatureId == FEATURE_CONTEXT_MENU ||
+ mFeatureId == FEATURE_PROGRESS ||
+ mFeatureId == FEATURE_INDETERMINATE_PROGRESS)
+ && getChildCount() == 1) {
+ getChildAt(0).sendAccessibilityEvent(eventType);
+ } else {
+ super.sendAccessibilityEvent(eventType);
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ if (cb.dispatchPopulateAccessibilityEvent(event)) {
+ return true;
+ }
+ }
+ return super.dispatchPopulateAccessibilityEventInternal(event);
+ }
+
+ @Override
+ protected boolean setFrame(int l, int t, int r, int b) {
+ boolean changed = super.setFrame(l, t, r, b);
+ if (changed) {
+ final Rect drawingBounds = mDrawingBounds;
+ getDrawingRect(drawingBounds);
+
+ Drawable fg = getForeground();
+ if (fg != null) {
+ final Rect frameOffsets = mFrameOffsets;
+ drawingBounds.left += frameOffsets.left;
+ drawingBounds.top += frameOffsets.top;
+ drawingBounds.right -= frameOffsets.right;
+ drawingBounds.bottom -= frameOffsets.bottom;
+ fg.setBounds(drawingBounds);
+ final Rect framePadding = mFramePadding;
+ drawingBounds.left += framePadding.left - frameOffsets.left;
+ drawingBounds.top += framePadding.top - frameOffsets.top;
+ drawingBounds.right -= framePadding.right - frameOffsets.right;
+ drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
+ }
+
+ Drawable bg = getBackground();
+ if (bg != null) {
+ bg.setBounds(drawingBounds);
+ }
+
+ if (SWEEP_OPEN_MENU) {
+ if (mMenuBackground == null && mFeatureId < 0
+ && getAttributes().height
+ == WindowManager.LayoutParams.MATCH_PARENT) {
+ mMenuBackground = getContext().getDrawable(
+ R.drawable.menu_background);
+ }
+ if (mMenuBackground != null) {
+ mMenuBackground.setBounds(drawingBounds.left,
+ drawingBounds.bottom-6, drawingBounds.right,
+ drawingBounds.bottom+20);
+ }
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+
+ final int widthMode = getMode(widthMeasureSpec);
+ final int heightMode = getMode(heightMeasureSpec);
+
+ boolean fixedWidth = false;
+ if (widthMode == AT_MOST) {
+ final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
+ if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
+ final int w;
+ if (tvw.type == TypedValue.TYPE_DIMENSION) {
+ w = (int) tvw.getDimension(metrics);
+ } else if (tvw.type == TypedValue.TYPE_FRACTION) {
+ w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
+ } else {
+ w = 0;
+ }
+
+ if (w > 0) {
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(w, widthSize), EXACTLY);
+ fixedWidth = true;
+ }
+ }
+ }
+
+ if (heightMode == AT_MOST) {
+ final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
+ if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
+ final int h;
+ if (tvh.type == TypedValue.TYPE_DIMENSION) {
+ h = (int) tvh.getDimension(metrics);
+ } else if (tvh.type == TypedValue.TYPE_FRACTION) {
+ h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
+ } else {
+ h = 0;
+ }
+ if (h > 0) {
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(h, heightSize), EXACTLY);
+ }
+ }
+ }
+
+ if (mOutsetBottom != null) {
+ int mode = MeasureSpec.getMode(heightMeasureSpec);
+ if (mode != MeasureSpec.UNSPECIFIED) {
+ int outset = (int) mOutsetBottom.getDimension(metrics);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + outset, mode);
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = getMeasuredWidth();
+ boolean measure = false;
+
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
+
+ if (!fixedWidth && widthMode == AT_MOST) {
+ final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
+ if (tv.type != TypedValue.TYPE_NULL) {
+ final int min;
+ if (tv.type == TypedValue.TYPE_DIMENSION) {
+ min = (int)tv.getDimension(metrics);
+ } else if (tv.type == TypedValue.TYPE_FRACTION) {
+ min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
+ } else {
+ min = 0;
+ }
+
+ if (width < min) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
+ measure = true;
+ }
+ }
+ }
+
+ // TODO: Support height?
+
+ if (measure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mMenuBackground != null) {
+ mMenuBackground.draw(canvas);
+ }
+ }
+
+
+ @Override
+ public boolean showContextMenuForChild(View originalView) {
+ // Reuse the context menu builder
+ if (mContextMenu == null) {
+ mContextMenu = new ContextMenuBuilder(getContext());
+ mContextMenu.setCallback(mContextMenuCallback);
+ } else {
+ mContextMenu.clearAll();
+ }
+
+ final MenuDialogHelper helper = mContextMenu.show(originalView,
+ originalView.getWindowToken());
+ if (helper != null) {
+ helper.setPresenterCallback(mContextMenuCallback);
+ } else if (mContextMenuHelper != null) {
+ // No menu to show, but if we have a menu currently showing it just became blank.
+ // Close it.
+ mContextMenuHelper.dismiss();
+ }
+ mContextMenuHelper = helper;
+ return helper != null;
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(View originalView,
+ ActionMode.Callback callback) {
+ return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(
+ View child, ActionMode.Callback callback, int type) {
+ return startActionMode(child, callback, type);
+ }
+
+ @Override
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ return startActionMode(callback, ActionMode.TYPE_PRIMARY);
+ }
+
+ @Override
+ public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+ return startActionMode(this, callback, type);
+ }
+
+ private ActionMode startActionMode(
+ View originatingView, ActionMode.Callback callback, int type) {
+ ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback);
+ ActionMode mode = null;
+ if (getCallback() != null && !isDestroyed()) {
+ try {
+ mode = getCallback().onWindowStartingActionMode(wrappedCallback, type);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement this callback method.
+ }
+ }
+ if (mode != null) {
+ if (mode.getType() == ActionMode.TYPE_PRIMARY) {
+ cleanupPrimaryActionMode();
+ mPrimaryActionMode = mode;
+ } else {
+ mFloatingActionMode = mode;
+ }
+ } else {
+ if (type == ActionMode.TYPE_PRIMARY) {
+ cleanupPrimaryActionMode();
+ mode = createStandaloneActionMode(wrappedCallback);
+ if (mode != null && callback.onCreateActionMode(mode, mode.getMenu())) {
+ setHandledPrimaryActionMode(mode);
+ } else {
+ mode = null;
+ }
+ }
+ }
+ if (mode != null && getCallback() != null && !isDestroyed()) {
+ try {
+ getCallback().onActionModeStarted(mode);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement this callback method.
+ }
+ }
+ return mode;
+ }
+
+ private void cleanupPrimaryActionMode() {
+ if (mPrimaryActionMode != null) {
+ mPrimaryActionMode.finish();
+ mPrimaryActionMode = null;
+ }
+ if (mPrimaryActionModeView != null) {
+ mPrimaryActionModeView.killMode();
+ }
+ }
+
+ public void startChanging() {
+ mChanging = true;
+ }
+
+ public void finishChanging() {
+ mChanging = false;
+ drawableChanged();
+ }
+
+ public void setWindowBackground(Drawable drawable) {
+ if (getBackground() != drawable) {
+ setBackgroundDrawable(drawable);
+ if (drawable != null) {
+ drawable.getPadding(mBackgroundPadding);
+ } else {
+ mBackgroundPadding.setEmpty();
+ }
+ drawableChanged();
+ }
+ }
+
+ @Override
+ public void setBackgroundDrawable(Drawable d) {
+ super.setBackgroundDrawable(d);
+ if (getWindowToken() != null) {
+ updateWindowResizeState();
+ }
+ }
+
+ public void setWindowFrame(Drawable drawable) {
+ if (getForeground() != drawable) {
+ setForeground(drawable);
+ if (drawable != null) {
+ drawable.getPadding(mFramePadding);
+ } else {
+ mFramePadding.setEmpty();
+ }
+ drawableChanged();
+ }
+ }
+
+ @Override
+ public void onWindowSystemUiVisibilityChanged(int visible) {
+ updateColorViews(null /* insets */, true /* animate */);
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mFrameOffsets.set(insets.getSystemWindowInsets());
+ insets = updateColorViews(insets, true /* animate */);
+ insets = updateStatusGuard(insets);
+ updateNavigationGuard(insets);
+ if (getForeground() != null) {
+ drawableChanged();
+ }
+ return insets;
+ }
+
+ @Override
+ public boolean isTransitionGroup() {
+ return false;
+ }
+
+ private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
+ WindowManager.LayoutParams attrs = getAttributes();
+ int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+
+ if (!mIsFloating && ActivityManager.isHighEndGfx()) {
+ boolean disallowAnimate = !isLaidOut();
+ disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
+ & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ mLastWindowFlags = attrs.flags;
+
+ if (insets != null) {
+ mLastTopInset = Math.min(insets.getStableInsetTop(),
+ insets.getSystemWindowInsetTop());
+ mLastBottomInset = Math.min(insets.getStableInsetBottom(),
+ insets.getSystemWindowInsetBottom());
+ mLastRightInset = Math.min(insets.getStableInsetRight(),
+ insets.getSystemWindowInsetRight());
+
+ // Don't animate if the presence of stable insets has changed, because that
+ // indicates that the window was either just added and received them for the
+ // first time, or the window size or position has changed.
+ boolean hasTopStableInset = insets.getStableInsetTop() != 0;
+ disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
+ mLastHasTopStableInset = hasTopStableInset;
+
+ boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
+ disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
+ mLastHasBottomStableInset = hasBottomStableInset;
+ }
+
+ updateColorViewInt(mStatusColorViewState, sysUiVisibility, mStatusBarColor,
+ mLastTopInset, animate && !disallowAnimate);
+ updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mNavigationBarColor,
+ mLastBottomInset, animate && !disallowAnimate);
+ }
+
+ // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
+ // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
+ // explicitly asked for it.
+
+ boolean consumingNavBar =
+ (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+ && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+ && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+
+ int consumedRight = consumingNavBar ? mLastRightInset : 0;
+ int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
+
+ if (mContentRoot != null
+ && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
+ MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
+ if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
+ lp.rightMargin = consumedRight;
+ lp.bottomMargin = consumedBottom;
+ mContentRoot.setLayoutParams(lp);
+
+ if (insets == null) {
+ // The insets have changed, but we're not currently in the process
+ // of dispatching them.
+ requestApplyInsets();
+ }
+ }
+ if (insets != null) {
+ insets = insets.replaceSystemWindowInsets(
+ insets.getSystemWindowInsetLeft(),
+ insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight() - consumedRight,
+ insets.getSystemWindowInsetBottom() - consumedBottom);
+ }
+ }
+
+ if (insets != null) {
+ insets = insets.consumeStableInsets();
+ }
+ return insets;
+ }
+
+ private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
+ int height, boolean animate) {
+ boolean show = height > 0 && (sysUiVis & state.systemUiHideFlag) == 0
+ && (getAttributes().flags & state.hideWindowFlag) == 0
+ && (getAttributes().flags & state.translucentFlag) == 0
+ && (color & Color.BLACK) != 0
+ && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+
+ boolean visibilityChanged = false;
+ View view = state.view;
+
+ if (view == null) {
+ if (show) {
+ state.view = view = new View(mContext);
+ view.setBackgroundColor(color);
+ view.setTransitionName(state.transitionName);
+ view.setId(state.id);
+ visibilityChanged = true;
+ view.setVisibility(INVISIBLE);
+ state.targetVisibility = VISIBLE;
+
+ addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, height,
+ Gravity.START | state.verticalGravity));
+ updateColorViewTranslations();
+ }
+ } else {
+ int vis = show ? VISIBLE : INVISIBLE;
+ visibilityChanged = state.targetVisibility != vis;
+ state.targetVisibility = vis;
+ if (show) {
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (lp.height != height) {
+ lp.height = height;
+ view.setLayoutParams(lp);
+ }
+ view.setBackgroundColor(color);
+ }
+ }
+ if (visibilityChanged) {
+ view.animate().cancel();
+ if (animate) {
+ if (show) {
+ if (view.getVisibility() != VISIBLE) {
+ view.setVisibility(VISIBLE);
+ view.setAlpha(0.0f);
+ }
+ view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
+ setDuration(mBarEnterExitDuration);
+ } else {
+ view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
+ .setDuration(mBarEnterExitDuration)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ state.view.setAlpha(1.0f);
+ state.view.setVisibility(INVISIBLE);
+ }
+ });
+ }
+ } else {
+ view.setAlpha(1.0f);
+ view.setVisibility(show ? VISIBLE : INVISIBLE);
+ }
+ }
+ }
+
+ private void updateColorViewTranslations() {
+ // Put the color views back in place when they get moved off the screen
+ // due to the the ViewRootImpl panning.
+ int rootScrollY = mRootScrollY;
+ if (mStatusColorViewState.view != null) {
+ mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
+ }
+ if (mNavigationColorViewState.view != null) {
+ mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
+ }
+ }
+
+ private WindowInsets updateStatusGuard(WindowInsets insets) {
+ boolean showStatusGuard = false;
+ // Show the status guard when the non-overlay contextual action bar is showing
+ if (mPrimaryActionModeView != null) {
+ if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
+ // Insets are magic!
+ final MarginLayoutParams mlp = (MarginLayoutParams)
+ mPrimaryActionModeView.getLayoutParams();
+ boolean mlpChanged = false;
+ if (mPrimaryActionModeView.isShown()) {
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ final Rect rect = mTempRect;
+
+ // If the parent doesn't consume the insets, manually
+ // apply the default system window insets.
+ mContentParent.computeSystemWindowInsets(insets, rect);
+ final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
+ if (mlp.topMargin != newMargin) {
+ mlpChanged = true;
+ mlp.topMargin = insets.getSystemWindowInsetTop();
+
+ if (mStatusGuard == null) {
+ mStatusGuard = new View(mContext);
+ mStatusGuard.setBackgroundColor(mContext.getColor(
+ R.color.input_method_navigation_guard));
+ addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
+ new LayoutParams(LayoutParams.MATCH_PARENT,
+ mlp.topMargin, Gravity.START | Gravity.TOP));
+ } else {
+ final LayoutParams lp = (LayoutParams)
+ mStatusGuard.getLayoutParams();
+ if (lp.height != mlp.topMargin) {
+ lp.height = mlp.topMargin;
+ mStatusGuard.setLayoutParams(lp);
+ }
+ }
+ }
+
+ // The action mode's theme may differ from the app, so
+ // always show the status guard above it if we have one.
+ showStatusGuard = mStatusGuard != null;
+
+ // We only need to consume the insets if the action
+ // mode is overlaid on the app content (e.g. it's
+ // sitting in a FrameLayout, see
+ // screen_simple_overlay_action_mode.xml).
+ final boolean nonOverlay = (getLocalFeatures()
+ & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0;
+ insets = insets.consumeSystemWindowInsets(
+ false, nonOverlay && showStatusGuard /* top */, false, false);
+ } else {
+ // reset top margin
+ if (mlp.topMargin != 0) {
+ mlpChanged = true;
+ mlp.topMargin = 0;
+ }
+ }
+ if (mlpChanged) {
+ mPrimaryActionModeView.setLayoutParams(mlp);
+ }
+ }
+ }
+ if (mStatusGuard != null) {
+ mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
+ }
+ return insets;
+ }
+
+ private void updateNavigationGuard(WindowInsets insets) {
+ // IMEs lay out below the nav bar, but the content view must not (for back compat)
+ if (getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+ // prevent the content view from including the nav bar height
+ if (mContentParent != null) {
+ if (mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
+ MarginLayoutParams mlp =
+ (MarginLayoutParams) mContentParent.getLayoutParams();
+ mlp.bottomMargin = insets.getSystemWindowInsetBottom();
+ mContentParent.setLayoutParams(mlp);
+ }
+ }
+ // position the navigation guard view, creating it if necessary
+ if (mNavigationGuard == null) {
+ mNavigationGuard = new View(mContext);
+ mNavigationGuard.setBackgroundColor(mContext.getColor(
+ R.color.input_method_navigation_guard));
+ addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
+ new LayoutParams(LayoutParams.MATCH_PARENT,
+ insets.getSystemWindowInsetBottom(),
+ Gravity.START | Gravity.BOTTOM));
+ } else {
+ LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
+ lp.height = insets.getSystemWindowInsetBottom();
+ mNavigationGuard.setLayoutParams(lp);
+ }
+ }
+ }
+
+ private void drawableChanged() {
+ if (mChanging) {
+ return;
+ }
+
+ setPadding(mFramePadding.left + mBackgroundPadding.left, mFramePadding.top
+ + mBackgroundPadding.top, mFramePadding.right + mBackgroundPadding.right,
+ mFramePadding.bottom + mBackgroundPadding.bottom);
+ requestLayout();
+ invalidate();
+
+ int opacity = PixelFormat.OPAQUE;
+ // Note: if there is no background, we will assume opaque. The
+ // common case seems to be that an application sets there to be
+ // no background so it can draw everything itself. For that,
+ // we would like to assume OPAQUE and let the app force it to
+ // the slower TRANSLUCENT mode if that is really what it wants.
+ Drawable bg = getBackground();
+ Drawable fg = getForeground();
+ if (bg != null) {
+ if (fg == null) {
+ opacity = bg.getOpacity();
+ } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
+ && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
+ // If the frame padding is zero, then we can be opaque
+ // if either the frame -or- the background is opaque.
+ int fop = fg.getOpacity();
+ int bop = bg.getOpacity();
+ if (false)
+ Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
+ if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
+ opacity = PixelFormat.OPAQUE;
+ } else if (fop == PixelFormat.UNKNOWN) {
+ opacity = bop;
+ } else if (bop == PixelFormat.UNKNOWN) {
+ opacity = fop;
+ } else {
+ opacity = Drawable.resolveOpacity(fop, bop);
+ }
+ } else {
+ // For now we have to assume translucent if there is a
+ // frame with padding... there is no way to tell if the
+ // frame and background together will draw all pixels.
+ if (false)
+ Log.v(TAG, "Padding: " + mFramePadding);
+ opacity = PixelFormat.TRANSLUCENT;
+ }
+ }
+
+ if (false)
+ Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
+ if (false)
+ Log.v(TAG, "Selected default opacity: " + opacity);
+
+ mDefaultOpacity = opacity;
+ if (mFeatureId < 0) {
+ setDefaultWindowFormat(opacity);
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+
+ // If the user is chording a menu shortcut, release the chord since
+ // this window lost focus
+ if (hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus && mPanelChordingKey != 0) {
+ closePanel(FEATURE_OPTIONS_PANEL);
+ }
+
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed() && mFeatureId < 0) {
+ cb.onWindowFocusChanged(hasWindowFocus);
+ }
+ }
+
+ void updateWindowResizeState() {
+ Drawable bg = getBackground();
+ hackTurnOffWindowResizeAnim(bg == null || bg.getOpacity()
+ != PixelFormat.OPAQUE);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ updateWindowResizeState();
+
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed() && mFeatureId < 0) {
+ cb.onAttachedToWindow();
+ }
+
+ if (mFeatureId == -1) {
+ /*
+ * The main window has been attached, try to restore any panels
+ * that may have been open before. This is called in cases where
+ * an activity is being killed for configuration change and the
+ * menu was open. When the activity is recreated, the menu
+ * should be shown again.
+ */
+ openPanelsAfterRestore();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ final Callback cb = getCallback();
+ if (cb != null && mFeatureId < 0) {
+ cb.onDetachedFromWindow();
+ }
+
+ if (mDecorContentParent != null) {
+ mDecorContentParent.dismissPopups();
+ }
+
+ if (mPrimaryActionModePopup != null) {
+ removeCallbacks(mShowPrimaryActionModePopup);
+ if (mPrimaryActionModePopup.isShowing()) {
+ mPrimaryActionModePopup.dismiss();
+ }
+ mPrimaryActionModePopup = null;
+ }
+
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (st != null && st.menu != null && mFeatureId < 0) {
+ st.menu.close();
+ }
+ }
+
+ @Override
+ public void onCloseSystemDialogs(String reason) {
+ if (mFeatureId >= 0) {
+ closeAllPanels();
+ }
+ }
+
+ public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
+ return mFeatureId < 0 ? mTakeSurfaceCallback : null;
+ }
+
+ public InputQueue.Callback willYouTakeTheInputQueue() {
+ return mFeatureId < 0 ? mTakeInputQueueCallback : null;
+ }
+
+ public void setSurfaceType(int type) {
+ PhoneWindow.this.setType(type);
+ }
+
+ public void setSurfaceFormat(int format) {
+ PhoneWindow.this.setFormat(format);
+ }
+
+ public void setSurfaceKeepScreenOn(boolean keepOn) {
+ if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ public void onRootViewScrollYChanged(int rootScrollY) {
+ mRootScrollY = rootScrollY;
+ updateColorViewTranslations();
+ }
+
+ private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
+ if (mPrimaryActionModeView == null) {
+ if (isFloating()) {
+ // Use the action bar theme.
+ final TypedValue outValue = new TypedValue();
+ final Theme baseTheme = mContext.getTheme();
+ baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+ final Context actionBarContext;
+ if (outValue.resourceId != 0) {
+ final Theme actionBarTheme = mContext.getResources().newTheme();
+ actionBarTheme.setTo(baseTheme);
+ actionBarTheme.applyStyle(outValue.resourceId, true);
+
+ actionBarContext = new ContextThemeWrapper(mContext, 0);
+ actionBarContext.getTheme().setTo(actionBarTheme);
+ } else {
+ actionBarContext = mContext;
+ }
+
+ mPrimaryActionModeView = new ActionBarContextView(actionBarContext);
+ mPrimaryActionModePopup = new PopupWindow(actionBarContext, null,
+ R.attr.actionModePopupWindowStyle);
+ mPrimaryActionModePopup.setWindowLayoutType(
+ WindowManager.LayoutParams.TYPE_APPLICATION);
+ mPrimaryActionModePopup.setContentView(mPrimaryActionModeView);
+ mPrimaryActionModePopup.setWidth(MATCH_PARENT);
+
+ actionBarContext.getTheme().resolveAttribute(
+ R.attr.actionBarSize, outValue, true);
+ final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
+ actionBarContext.getResources().getDisplayMetrics());
+ mPrimaryActionModeView.setContentHeight(height);
+ mPrimaryActionModePopup.setHeight(WRAP_CONTENT);
+ mShowPrimaryActionModePopup = new Runnable() {
+ public void run() {
+ mPrimaryActionModePopup.showAtLocation(
+ mPrimaryActionModeView.getApplicationWindowToken(),
+ Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
+ }
+ };
+ } else {
+ ViewStub stub = (ViewStub) findViewById(
+ R.id.action_mode_bar_stub);
+ if (stub != null) {
+ mPrimaryActionModeView = (ActionBarContextView) stub.inflate();
+ }
+ }
+ }
+ if (mPrimaryActionModeView != null) {
+ mPrimaryActionModeView.killMode();
+ ActionMode mode = new StandaloneActionMode(
+ mPrimaryActionModeView.getContext(), mPrimaryActionModeView,
+ callback, mPrimaryActionModePopup == null);
+ return mode;
+ }
+ return null;
+ }
+
+ private void setHandledPrimaryActionMode(ActionMode mode) {
+ mPrimaryActionMode = mode;
+ mPrimaryActionMode.invalidate();
+ mPrimaryActionModeView.initForMode(mPrimaryActionMode);
+ mPrimaryActionModeView.setVisibility(View.VISIBLE);
+ if (mPrimaryActionModePopup != null) {
+ post(mShowPrimaryActionModePopup);
+ }
+ mPrimaryActionModeView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ }
+
+ /**
+ * Clears out internal references when the action mode is destroyed.
+ */
+ private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
+ private final ActionMode.Callback mWrapped;
+
+ public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) {
+ mWrapped = wrapped;
+ }
+
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return mWrapped.onCreateActionMode(mode, menu);
+ }
+
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ requestFitSystemWindows();
+ return mWrapped.onPrepareActionMode(mode, menu);
+ }
+
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return mWrapped.onActionItemClicked(mode, item);
+ }
+
+ public void onDestroyActionMode(ActionMode mode) {
+ mWrapped.onDestroyActionMode(mode);
+ if (mode == mPrimaryActionMode) {
+ if (mPrimaryActionModePopup != null) {
+ removeCallbacks(mShowPrimaryActionModePopup);
+ mPrimaryActionModePopup.dismiss();
+ } else if (mPrimaryActionModeView != null) {
+ mPrimaryActionModeView.setVisibility(GONE);
+ }
+ if (mPrimaryActionModeView != null) {
+ mPrimaryActionModeView.removeAllViews();
+ }
+ mPrimaryActionMode = null;
+ } else if (mode == mFloatingActionMode) {
+ mFloatingActionMode = null;
+ }
+ if (getCallback() != null && !isDestroyed()) {
+ try {
+ getCallback().onActionModeFinished(mode);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement this callback method.
+ }
+ }
+ requestFitSystemWindows();
+ }
+ }
+ }
+
+ protected DecorView generateDecor() {
+ return new DecorView(getContext(), -1);
+ }
+
+ protected void setFeatureFromAttrs(int featureId, TypedArray attrs,
+ int drawableAttr, int alphaAttr) {
+ Drawable d = attrs.getDrawable(drawableAttr);
+ if (d != null) {
+ requestFeature(featureId);
+ setFeatureDefaultDrawable(featureId, d);
+ }
+ if ((getFeatures() & (1 << featureId)) != 0) {
+ int alpha = attrs.getInt(alphaAttr, -1);
+ if (alpha >= 0) {
+ setFeatureDrawableAlpha(featureId, alpha);
+ }
+ }
+ }
+
+ protected ViewGroup generateLayout(DecorView decor) {
+ // Apply data from current theme.
+
+ TypedArray a = getWindowStyle();
+
+ if (false) {
+ System.out.println("From style:");
+ String s = "Attrs:";
+ for (int i = 0; i < R.styleable.Window.length; i++) {
+ s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
+ + a.getString(i);
+ }
+ System.out.println(s);
+ }
+
+ mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
+ int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
+ & (~getForcedWindowFlags());
+ if (mIsFloating) {
+ setLayout(WRAP_CONTENT, WRAP_CONTENT);
+ setFlags(0, flagsToUpdate);
+ } else {
+ setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
+ requestFeature(FEATURE_NO_TITLE);
+ } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
+ // Don't allow an action bar if there is no title.
+ requestFeature(FEATURE_ACTION_BAR);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
+ requestFeature(FEATURE_ACTION_BAR_OVERLAY);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
+ requestFeature(FEATURE_ACTION_MODE_OVERLAY);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
+ requestFeature(FEATURE_SWIPE_TO_DISMISS);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
+ setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
+ false)) {
+ setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
+ & (~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
+ false)) {
+ setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
+ & (~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowOverscan, false)) {
+ setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
+ setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
+ getContext().getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
+ setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
+ }
+
+ a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
+ a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
+ if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
+ if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
+ a.getValue(R.styleable.Window_windowFixedWidthMajor,
+ mFixedWidthMajor);
+ }
+ if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
+ if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
+ a.getValue(R.styleable.Window_windowFixedWidthMinor,
+ mFixedWidthMinor);
+ }
+ if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
+ if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
+ a.getValue(R.styleable.Window_windowFixedHeightMajor,
+ mFixedHeightMajor);
+ }
+ if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
+ if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
+ a.getValue(R.styleable.Window_windowFixedHeightMinor,
+ mFixedHeightMinor);
+ }
+ if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
+ requestFeature(FEATURE_CONTENT_TRANSITIONS);
+ }
+ if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
+ requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
+ }
+
+ final WindowManager windowService = (WindowManager) getContext().getSystemService(
+ Context.WINDOW_SERVICE);
+ if (windowService != null) {
+ final Display display = windowService.getDefaultDisplay();
+ final boolean shouldUseBottomOutset =
+ display.getDisplayId() == Display.DEFAULT_DISPLAY
+ || (getForcedWindowFlags() & FLAG_FULLSCREEN) != 0;
+ if (shouldUseBottomOutset && a.hasValue(R.styleable.Window_windowOutsetBottom)) {
+ if (mOutsetBottom == null) mOutsetBottom = new TypedValue();
+ a.getValue(R.styleable.Window_windowOutsetBottom,
+ mOutsetBottom);
+ }
+ }
+
+ final Context context = getContext();
+ final int targetSdk = context.getApplicationInfo().targetSdkVersion;
+ final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
+ final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+ final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
+ final boolean targetHcNeedsOptions = context.getResources().getBoolean(
+ R.bool.target_honeycomb_needs_options_menu);
+ final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
+
+ if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
+ setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
+ } else {
+ setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
+ }
+
+ // Non-floating windows on high end devices must put up decor beneath the system bars and
+ // therefore must know about visibility changes of those.
+ if (!mIsFloating && ActivityManager.isHighEndGfx()) {
+ if (!targetPreL && a.getBoolean(
+ R.styleable.Window_windowDrawsSystemBarBackgrounds,
+ false)) {
+ setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
+ }
+ }
+ if (!mForcedStatusBarColor) {
+ mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
+ }
+ if (!mForcedNavigationBarColor) {
+ mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
+ }
+ if (a.getBoolean(R.styleable.Window_windowHasLightStatusBar, false)) {
+ decor.setSystemUiVisibility(
+ decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ }
+
+ if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+ if (a.getBoolean(
+ R.styleable.Window_windowCloseOnTouchOutside,
+ false)) {
+ setCloseOnTouchOutsideIfNotSet(true);
+ }
+ }
+
+ WindowManager.LayoutParams params = getAttributes();
+
+ if (!hasSoftInputMode()) {
+ params.softInputMode = a.getInt(
+ R.styleable.Window_windowSoftInputMode,
+ params.softInputMode);
+ }
+
+ if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
+ mIsFloating)) {
+ /* All dialogs should have the window dimmed */
+ if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
+ params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+ }
+ if (!haveDimAmount()) {
+ params.dimAmount = a.getFloat(
+ android.R.styleable.Window_backgroundDimAmount, 0.5f);
+ }
+ }
+
+ if (params.windowAnimations == 0) {
+ params.windowAnimations = a.getResourceId(
+ R.styleable.Window_windowAnimationStyle, 0);
+ }
+
+ // The rest are only done if this window is not embedded; otherwise,
+ // the values are inherited from our container.
+ if (getContainer() == null) {
+ if (mBackgroundDrawable == null) {
+ if (mBackgroundResource == 0) {
+ mBackgroundResource = a.getResourceId(
+ R.styleable.Window_windowBackground, 0);
+ }
+ if (mFrameResource == 0) {
+ mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
+ }
+ mBackgroundFallbackResource = a.getResourceId(
+ R.styleable.Window_windowBackgroundFallback, 0);
+ if (false) {
+ System.out.println("Background: "
+ + Integer.toHexString(mBackgroundResource) + " Frame: "
+ + Integer.toHexString(mFrameResource));
+ }
+ }
+ mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
+ mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
+ mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
+ }
+
+ // Inflate the window decor.
+
+ int layoutResource;
+ int features = getLocalFeatures();
+ // System.out.println("Features: 0x" + Integer.toHexString(features));
+ if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
+ layoutResource = R.layout.screen_swipe_dismiss;
+ } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
+ if (mIsFloating) {
+ TypedValue res = new TypedValue();
+ getContext().getTheme().resolveAttribute(
+ R.attr.dialogTitleIconsDecorLayout, res, true);
+ layoutResource = res.resourceId;
+ } else {
+ layoutResource = R.layout.screen_title_icons;
+ }
+ // XXX Remove this once action bar supports these features.
+ removeFeature(FEATURE_ACTION_BAR);
+ // System.out.println("Title Icons!");
+ } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
+ && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
+ // Special case for a window with only a progress bar (and title).
+ // XXX Need to have a no-title version of embedded windows.
+ layoutResource = R.layout.screen_progress;
+ // System.out.println("Progress!");
+ } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
+ // Special case for a window with a custom title.
+ // If the window is floating, we need a dialog layout
+ if (mIsFloating) {
+ TypedValue res = new TypedValue();
+ getContext().getTheme().resolveAttribute(
+ R.attr.dialogCustomTitleDecorLayout, res, true);
+ layoutResource = res.resourceId;
+ } else {
+ layoutResource = R.layout.screen_custom_title;
+ }
+ // XXX Remove this once action bar supports these features.
+ removeFeature(FEATURE_ACTION_BAR);
+ } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
+ // If no other features and not embedded, only need a title.
+ // If the window is floating, we need a dialog layout
+ if (mIsFloating) {
+ TypedValue res = new TypedValue();
+ getContext().getTheme().resolveAttribute(
+ R.attr.dialogTitleDecorLayout, res, true);
+ layoutResource = res.resourceId;
+ } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
+ layoutResource = a.getResourceId(
+ R.styleable.Window_windowActionBarFullscreenDecorLayout,
+ R.layout.screen_action_bar);
+ } else {
+ layoutResource = R.layout.screen_title;
+ }
+ // System.out.println("Title!");
+ } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
+ layoutResource = R.layout.screen_simple_overlay_action_mode;
+ } else {
+ // Embedded, so no decoration is needed.
+ layoutResource = R.layout.screen_simple;
+ // System.out.println("Simple!");
+ }
+
+ mDecor.startChanging();
+
+ View in = mLayoutInflater.inflate(layoutResource, null);
+ decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ mContentRoot = (ViewGroup) in;
+
+ ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
+ if (contentParent == null) {
+ throw new RuntimeException("Window couldn't find content container view");
+ }
+
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ ProgressBar progress = getCircularProgressBar(false);
+ if (progress != null) {
+ progress.setIndeterminate(true);
+ }
+ }
+
+ if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
+ registerSwipeCallbacks();
+ }
+
+ // Remaining setup -- of background and title -- that only applies
+ // to top-level windows.
+ if (getContainer() == null) {
+ final Drawable background;
+ if (mBackgroundResource != 0) {
+ background = getContext().getDrawable(mBackgroundResource);
+ } else {
+ background = mBackgroundDrawable;
+ }
+ mDecor.setWindowBackground(background);
+
+ final Drawable frame;
+ if (mFrameResource != 0) {
+ frame = getContext().getDrawable(mFrameResource);
+ } else {
+ frame = null;
+ }
+ mDecor.setWindowFrame(frame);
+
+ mDecor.setElevation(mElevation);
+ mDecor.setClipToOutline(mClipToOutline);
+
+ if (mTitle != null) {
+ setTitle(mTitle);
+ }
+
+ if (mTitleColor == 0) {
+ mTitleColor = mTextColor;
+ }
+ setTitleColor(mTitleColor);
+ }
+
+ mDecor.finishChanging();
+
+ return contentParent;
+ }
+
+ /** @hide */
+ public void alwaysReadCloseOnTouchAttr() {
+ mAlwaysReadCloseOnTouchAttr = true;
+ }
+
+ private void installDecor() {
+ if (mDecor == null) {
+ mDecor = generateDecor();
+ mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ mDecor.setIsRootNamespace(true);
+ if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
+ mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
+ }
+ }
+ if (mContentParent == null) {
+ mContentParent = generateLayout(mDecor);
+
+ // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
+ mDecor.makeOptionalFitsSystemWindows();
+
+ final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
+ R.id.decor_content_parent);
+
+ if (decorContentParent != null) {
+ mDecorContentParent = decorContentParent;
+ mDecorContentParent.setWindowCallback(getCallback());
+ if (mDecorContentParent.getTitle() == null) {
+ mDecorContentParent.setWindowTitle(mTitle);
+ }
+
+ final int localFeatures = getLocalFeatures();
+ for (int i = 0; i < FEATURE_MAX; i++) {
+ if ((localFeatures & (1 << i)) != 0) {
+ mDecorContentParent.initFeature(i);
+ }
+ }
+
+ mDecorContentParent.setUiOptions(mUiOptions);
+
+ if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
+ (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
+ mDecorContentParent.setIcon(mIconRes);
+ } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
+ mIconRes == 0 && !mDecorContentParent.hasIcon()) {
+ mDecorContentParent.setIcon(
+ getContext().getPackageManager().getDefaultActivityIcon());
+ mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
+ }
+ if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
+ (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
+ mDecorContentParent.setLogo(mLogoRes);
+ }
+
+ // Invalidate if the panel menu hasn't been created before this.
+ // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
+ // being called in the middle of onCreate or similar.
+ // A pending invalidation will typically be resolved before the posted message
+ // would run normally in order to satisfy instance state restoration.
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (!isDestroyed() && (st == null || st.menu == null)) {
+ invalidatePanelMenu(FEATURE_ACTION_BAR);
+ }
+ } else {
+ mTitleView = (TextView)findViewById(R.id.title);
+ if (mTitleView != null) {
+ mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
+ if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
+ View titleContainer = findViewById(
+ R.id.title_container);
+ if (titleContainer != null) {
+ titleContainer.setVisibility(View.GONE);
+ } else {
+ mTitleView.setVisibility(View.GONE);
+ }
+ if (mContentParent instanceof FrameLayout) {
+ ((FrameLayout)mContentParent).setForeground(null);
+ }
+ } else {
+ mTitleView.setText(mTitle);
+ }
+ }
+ }
+
+ if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
+ mDecor.setBackgroundFallback(mBackgroundFallbackResource);
+ }
+
+ // Only inflate or create a new TransitionManager if the caller hasn't
+ // already set a custom one.
+ if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
+ if (mTransitionManager == null) {
+ final int transitionRes = getWindowStyle().getResourceId(
+ R.styleable.Window_windowContentTransitionManager,
+ 0);
+ if (transitionRes != 0) {
+ final TransitionInflater inflater = TransitionInflater.from(getContext());
+ mTransitionManager = inflater.inflateTransitionManager(transitionRes,
+ mContentParent);
+ } else {
+ mTransitionManager = new TransitionManager();
+ }
+ }
+
+ mEnterTransition = getTransition(mEnterTransition, null,
+ R.styleable.Window_windowEnterTransition);
+ mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
+ R.styleable.Window_windowReturnTransition);
+ mExitTransition = getTransition(mExitTransition, null,
+ R.styleable.Window_windowExitTransition);
+ mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
+ R.styleable.Window_windowReenterTransition);
+ mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
+ R.styleable.Window_windowSharedElementEnterTransition);
+ mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
+ USE_DEFAULT_TRANSITION,
+ R.styleable.Window_windowSharedElementReturnTransition);
+ mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
+ R.styleable.Window_windowSharedElementExitTransition);
+ mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
+ USE_DEFAULT_TRANSITION,
+ R.styleable.Window_windowSharedElementReenterTransition);
+ if (mAllowEnterTransitionOverlap == null) {
+ mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
+ R.styleable.Window_windowAllowEnterTransitionOverlap, true);
+ }
+ if (mAllowReturnTransitionOverlap == null) {
+ mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
+ R.styleable.Window_windowAllowReturnTransitionOverlap, true);
+ }
+ if (mBackgroundFadeDurationMillis < 0) {
+ mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
+ R.styleable.Window_windowTransitionBackgroundFadeDuration,
+ DEFAULT_BACKGROUND_FADE_DURATION_MS);
+ }
+ if (mSharedElementsUseOverlay == null) {
+ mSharedElementsUseOverlay = getWindowStyle().getBoolean(
+ R.styleable.Window_windowSharedElementsUseOverlay, true);
+ }
+ }
+ }
+ }
+
+ private Transition getTransition(Transition currentValue, Transition defaultValue, int id) {
+ if (currentValue != defaultValue) {
+ return currentValue;
+ }
+ int transitionId = getWindowStyle().getResourceId(id, -1);
+ Transition transition = defaultValue;
+ if (transitionId != -1 && transitionId != R.transition.no_transition) {
+ TransitionInflater inflater = TransitionInflater.from(getContext());
+ transition = inflater.inflateTransition(transitionId);
+ if (transition instanceof TransitionSet &&
+ ((TransitionSet)transition).getTransitionCount() == 0) {
+ transition = null;
+ }
+ }
+ return transition;
+ }
+
+ private Drawable loadImageURI(Uri uri) {
+ try {
+ return Drawable.createFromStream(
+ getContext().getContentResolver().openInputStream(uri), null);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to open content: " + uri);
+ }
+ return null;
+ }
+
+ private DrawableFeatureState getDrawableState(int featureId, boolean required) {
+ if ((getFeatures() & (1 << featureId)) == 0) {
+ if (!required) {
+ return null;
+ }
+ throw new RuntimeException("The feature has not been requested");
+ }
+
+ DrawableFeatureState[] ar;
+ if ((ar = mDrawables) == null || ar.length <= featureId) {
+ DrawableFeatureState[] nar = new DrawableFeatureState[featureId + 1];
+ if (ar != null) {
+ System.arraycopy(ar, 0, nar, 0, ar.length);
+ }
+ mDrawables = ar = nar;
+ }
+
+ DrawableFeatureState st = ar[featureId];
+ if (st == null) {
+ ar[featureId] = st = new DrawableFeatureState(featureId);
+ }
+ return st;
+ }
+
+ /**
+ * Gets a panel's state based on its feature ID.
+ *
+ * @param featureId The feature ID of the panel.
+ * @param required Whether the panel is required (if it is required and it
+ * isn't in our features, this throws an exception).
+ * @return The panel state.
+ */
+ private PanelFeatureState getPanelState(int featureId, boolean required) {
+ return getPanelState(featureId, required, null);
+ }
+
+ /**
+ * Gets a panel's state based on its feature ID.
+ *
+ * @param featureId The feature ID of the panel.
+ * @param required Whether the panel is required (if it is required and it
+ * isn't in our features, this throws an exception).
+ * @param convertPanelState Optional: If the panel state does not exist, use
+ * this as the panel state.
+ * @return The panel state.
+ */
+ private PanelFeatureState getPanelState(int featureId, boolean required,
+ PanelFeatureState convertPanelState) {
+ if ((getFeatures() & (1 << featureId)) == 0) {
+ if (!required) {
+ return null;
+ }
+ throw new RuntimeException("The feature has not been requested");
+ }
+
+ PanelFeatureState[] ar;
+ if ((ar = mPanels) == null || ar.length <= featureId) {
+ PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
+ if (ar != null) {
+ System.arraycopy(ar, 0, nar, 0, ar.length);
+ }
+ mPanels = ar = nar;
+ }
+
+ PanelFeatureState st = ar[featureId];
+ if (st == null) {
+ ar[featureId] = st = (convertPanelState != null)
+ ? convertPanelState
+ : new PanelFeatureState(featureId);
+ }
+ return st;
+ }
+
+ @Override
+ public final void setChildDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ st.child = drawable;
+ updateDrawable(featureId, st, false);
+ }
+
+ @Override
+ public final void setChildInt(int featureId, int value) {
+ updateInt(featureId, value, false);
+ }
+
+ @Override
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ return st != null && st.menu != null && st.menu.isShortcutKey(keyCode, event);
+ }
+
+ private void updateDrawable(int featureId, DrawableFeatureState st, boolean fromResume) {
+ // Do nothing if the decor is not yet installed... an update will
+ // need to be forced when we eventually become active.
+ if (mContentParent == null) {
+ return;
+ }
+
+ final int featureMask = 1 << featureId;
+
+ if ((getFeatures() & featureMask) == 0 && !fromResume) {
+ return;
+ }
+
+ Drawable drawable = null;
+ if (st != null) {
+ drawable = st.child;
+ if (drawable == null)
+ drawable = st.local;
+ if (drawable == null)
+ drawable = st.def;
+ }
+ if ((getLocalFeatures() & featureMask) == 0) {
+ if (getContainer() != null) {
+ if (isActive() || fromResume) {
+ getContainer().setChildDrawable(featureId, drawable);
+ }
+ }
+ } else if (st != null && (st.cur != drawable || st.curAlpha != st.alpha)) {
+ // System.out.println("Drawable changed: old=" + st.cur
+ // + ", new=" + drawable);
+ st.cur = drawable;
+ st.curAlpha = st.alpha;
+ onDrawableChanged(featureId, drawable, st.alpha);
+ }
+ }
+
+ private void updateInt(int featureId, int value, boolean fromResume) {
+
+ // Do nothing if the decor is not yet installed... an update will
+ // need to be forced when we eventually become active.
+ if (mContentParent == null) {
+ return;
+ }
+
+ final int featureMask = 1 << featureId;
+
+ if ((getFeatures() & featureMask) == 0 && !fromResume) {
+ return;
+ }
+
+ if ((getLocalFeatures() & featureMask) == 0) {
+ if (getContainer() != null) {
+ getContainer().setChildInt(featureId, value);
+ }
+ } else {
+ onIntChanged(featureId, value);
+ }
+ }
+
+ private ImageView getLeftIconView() {
+ if (mLeftIconView != null) {
+ return mLeftIconView;
+ }
+ if (mContentParent == null) {
+ installDecor();
+ }
+ return (mLeftIconView = (ImageView)findViewById(R.id.left_icon));
+ }
+
+ @Override
+ protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
+ super.dispatchWindowAttributesChanged(attrs);
+ if (mDecor != null) {
+ mDecor.updateColorViews(null /* insets */, true /* animate */);
+ }
+ }
+
+ private ProgressBar getCircularProgressBar(boolean shouldInstallDecor) {
+ if (mCircularProgressBar != null) {
+ return mCircularProgressBar;
+ }
+ if (mContentParent == null && shouldInstallDecor) {
+ installDecor();
+ }
+ mCircularProgressBar = (ProgressBar) findViewById(R.id.progress_circular);
+ if (mCircularProgressBar != null) {
+ mCircularProgressBar.setVisibility(View.INVISIBLE);
+ }
+ return mCircularProgressBar;
+ }
+
+ private ProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) {
+ if (mHorizontalProgressBar != null) {
+ return mHorizontalProgressBar;
+ }
+ if (mContentParent == null && shouldInstallDecor) {
+ installDecor();
+ }
+ mHorizontalProgressBar = (ProgressBar) findViewById(R.id.progress_horizontal);
+ if (mHorizontalProgressBar != null) {
+ mHorizontalProgressBar.setVisibility(View.INVISIBLE);
+ }
+ return mHorizontalProgressBar;
+ }
+
+ private ImageView getRightIconView() {
+ if (mRightIconView != null) {
+ return mRightIconView;
+ }
+ if (mContentParent == null) {
+ installDecor();
+ }
+ return (mRightIconView = (ImageView)findViewById(R.id.right_icon));
+ }
+
+ private void registerSwipeCallbacks() {
+ SwipeDismissLayout swipeDismiss =
+ (SwipeDismissLayout) findViewById(R.id.content);
+ swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() {
+ @Override
+ public void onDismissed(SwipeDismissLayout layout) {
+ dispatchOnWindowDismissed();
+ }
+ });
+ swipeDismiss.setOnSwipeProgressChangedListener(
+ new SwipeDismissLayout.OnSwipeProgressChangedListener() {
+ private static final float ALPHA_DECREASE = 0.5f;
+ private boolean mIsTranslucent = false;
+ @Override
+ public void onSwipeProgressChanged(
+ SwipeDismissLayout layout, float progress, float translate) {
+ WindowManager.LayoutParams newParams = getAttributes();
+ newParams.x = (int) translate;
+ newParams.alpha = 1 - (progress * ALPHA_DECREASE);
+ setAttributes(newParams);
+
+ int flags = 0;
+ if (newParams.x == 0) {
+ flags = FLAG_FULLSCREEN;
+ } else {
+ flags = FLAG_LAYOUT_NO_LIMITS;
+ }
+ setFlags(flags, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
+ }
+
+ @Override
+ public void onSwipeCancelled(SwipeDismissLayout layout) {
+ WindowManager.LayoutParams newParams = getAttributes();
+ newParams.x = 0;
+ newParams.alpha = 1;
+ setAttributes(newParams);
+ setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
+ }
+ });
+ }
+
+ /**
+ * Helper method for calling the {@link Callback#onPanelClosed(int, Menu)}
+ * callback. This method will grab whatever extra state is needed for the
+ * callback that isn't given in the parameters. If the panel is not open,
+ * this will not perform the callback.
+ *
+ * @param featureId Feature ID of the panel that was closed. Must be given.
+ * @param panel Panel that was closed. Optional but useful if there is no
+ * menu given.
+ * @param menu The menu that was closed. Optional, but give if you have.
+ */
+ private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
+ final Callback cb = getCallback();
+ if (cb == null)
+ return;
+
+ // Try to get a menu
+ if (menu == null) {
+ // Need a panel to grab the menu, so try to get that
+ if (panel == null) {
+ if ((featureId >= 0) && (featureId < mPanels.length)) {
+ panel = mPanels[featureId];
+ }
+ }
+
+ if (panel != null) {
+ // menu still may be null, which is okay--we tried our best
+ menu = panel.menu;
+ }
+ }
+
+ // If the panel is not open, do not callback
+ if ((panel != null) && (!panel.isOpen))
+ return;
+
+ if (!isDestroyed()) {
+ cb.onPanelClosed(featureId, menu);
+ }
+ }
+
+ /**
+ * Helper method for adding launch-search to most applications. Opens the
+ * search window using default settings.
+ *
+ * @return true if search window opened
+ */
+ private boolean launchDefaultSearch() {
+ boolean result;
+ final Callback cb = getCallback();
+ if (cb == null || isDestroyed()) {
+ result = false;
+ } else {
+ sendCloseSystemWindows("search");
+ result = cb.onSearchRequested();
+ }
+ if (!result && (getContext().getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) {
+ // On TVs, if the app doesn't implement search, we want to launch assist.
+ return ((SearchManager)getContext().getSystemService(Context.SEARCH_SERVICE))
+ .launchAssistAction(null, UserHandle.myUserId());
+ }
+ return result;
+ }
+
+ @Override
+ public void setVolumeControlStream(int streamType) {
+ mVolumeControlStreamType = streamType;
+ }
+
+ @Override
+ public int getVolumeControlStream() {
+ return mVolumeControlStreamType;
+ }
+
+ @Override
+ public void setMediaController(MediaController controller) {
+ mMediaController = controller;
+ }
+
+ @Override
+ public MediaController getMediaController() {
+ return mMediaController;
+ }
+
+ @Override
+ public void setEnterTransition(Transition enterTransition) {
+ mEnterTransition = enterTransition;
+ }
+
+ @Override
+ public void setReturnTransition(Transition transition) {
+ mReturnTransition = transition;
+ }
+
+ @Override
+ public void setExitTransition(Transition exitTransition) {
+ mExitTransition = exitTransition;
+ }
+
+ @Override
+ public void setReenterTransition(Transition transition) {
+ mReenterTransition = transition;
+ }
+
+ @Override
+ public void setSharedElementEnterTransition(Transition sharedElementEnterTransition) {
+ mSharedElementEnterTransition = sharedElementEnterTransition;
+ }
+
+ @Override
+ public void setSharedElementReturnTransition(Transition transition) {
+ mSharedElementReturnTransition = transition;
+ }
+
+ @Override
+ public void setSharedElementExitTransition(Transition sharedElementExitTransition) {
+ mSharedElementExitTransition = sharedElementExitTransition;
+ }
+
+ @Override
+ public void setSharedElementReenterTransition(Transition transition) {
+ mSharedElementReenterTransition = transition;
+ }
+
+ @Override
+ public Transition getEnterTransition() {
+ return mEnterTransition;
+ }
+
+ @Override
+ public Transition getReturnTransition() {
+ return mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
+ : mReturnTransition;
+ }
+
+ @Override
+ public Transition getExitTransition() {
+ return mExitTransition;
+ }
+
+ @Override
+ public Transition getReenterTransition() {
+ return mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
+ : mReenterTransition;
+ }
+
+ @Override
+ public Transition getSharedElementEnterTransition() {
+ return mSharedElementEnterTransition;
+ }
+
+ @Override
+ public Transition getSharedElementReturnTransition() {
+ return mSharedElementReturnTransition == USE_DEFAULT_TRANSITION
+ ? getSharedElementEnterTransition() : mSharedElementReturnTransition;
+ }
+
+ @Override
+ public Transition getSharedElementExitTransition() {
+ return mSharedElementExitTransition;
+ }
+
+ @Override
+ public Transition getSharedElementReenterTransition() {
+ return mSharedElementReenterTransition == USE_DEFAULT_TRANSITION
+ ? getSharedElementExitTransition() : mSharedElementReenterTransition;
+ }
+
+ @Override
+ public void setAllowEnterTransitionOverlap(boolean allow) {
+ mAllowEnterTransitionOverlap = allow;
+ }
+
+ @Override
+ public boolean getAllowEnterTransitionOverlap() {
+ return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap;
+ }
+
+ @Override
+ public void setAllowReturnTransitionOverlap(boolean allowExitTransitionOverlap) {
+ mAllowReturnTransitionOverlap = allowExitTransitionOverlap;
+ }
+
+ @Override
+ public boolean getAllowReturnTransitionOverlap() {
+ return (mAllowReturnTransitionOverlap == null) ? true : mAllowReturnTransitionOverlap;
+ }
+
+ @Override
+ public long getTransitionBackgroundFadeDuration() {
+ return (mBackgroundFadeDurationMillis < 0) ? DEFAULT_BACKGROUND_FADE_DURATION_MS
+ : mBackgroundFadeDurationMillis;
+ }
+
+ @Override
+ public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) {
+ if (fadeDurationMillis < 0) {
+ throw new IllegalArgumentException("negative durations are not allowed");
+ }
+ mBackgroundFadeDurationMillis = fadeDurationMillis;
+ }
+
+ @Override
+ public void setSharedElementsUseOverlay(boolean sharedElementsUseOverlay) {
+ mSharedElementsUseOverlay = sharedElementsUseOverlay;
+ }
+
+ @Override
+ public boolean getSharedElementsUseOverlay() {
+ return (mSharedElementsUseOverlay == null) ? true : mSharedElementsUseOverlay;
+ }
+
+ private static final class DrawableFeatureState {
+ DrawableFeatureState(int _featureId) {
+ featureId = _featureId;
+ }
+
+ final int featureId;
+
+ int resid;
+
+ Uri uri;
+
+ Drawable local;
+
+ Drawable child;
+
+ Drawable def;
+
+ Drawable cur;
+
+ int alpha = 255;
+
+ int curAlpha = 255;
+ }
+
+ private static final class PanelFeatureState {
+
+ /** Feature ID for this panel. */
+ int featureId;
+
+ // Information pulled from the style for this panel.
+
+ int background;
+
+ /** The background when the panel spans the entire available width. */
+ int fullBackground;
+
+ int gravity;
+
+ int x;
+
+ int y;
+
+ int windowAnimations;
+
+ /** Dynamic state of the panel. */
+ DecorView decorView;
+
+ /** The panel that was returned by onCreatePanelView(). */
+ View createdPanelView;
+
+ /** The panel that we are actually showing. */
+ View shownPanelView;
+
+ /** Use {@link #setMenu} to set this. */
+ MenuBuilder menu;
+
+ IconMenuPresenter iconMenuPresenter;
+ ListMenuPresenter listMenuPresenter;
+
+ /** true if this menu will show in single-list compact mode */
+ boolean isCompact;
+
+ /** Theme resource ID for list elements of the panel menu */
+ int listPresenterTheme;
+
+ /**
+ * Whether the panel has been prepared (see
+ * {@link PhoneWindow#preparePanel}).
+ */
+ boolean isPrepared;
+
+ /**
+ * Whether an item's action has been performed. This happens in obvious
+ * scenarios (user clicks on menu item), but can also happen with
+ * chording menu+(shortcut key).
+ */
+ boolean isHandled;
+
+ boolean isOpen;
+
+ /**
+ * True if the menu is in expanded mode, false if the menu is in icon
+ * mode
+ */
+ boolean isInExpandedMode;
+
+ public boolean qwertyMode;
+
+ boolean refreshDecorView;
+
+ boolean refreshMenuContent;
+
+ boolean wasLastOpen;
+
+ boolean wasLastExpanded;
+
+ /**
+ * Contains the state of the menu when told to freeze.
+ */
+ Bundle frozenMenuState;
+
+ /**
+ * Contains the state of associated action views when told to freeze.
+ * These are saved across invalidations.
+ */
+ Bundle frozenActionViewState;
+
+ PanelFeatureState(int featureId) {
+ this.featureId = featureId;
+
+ refreshDecorView = false;
+ }
+
+ public boolean isInListMode() {
+ return isInExpandedMode || isCompact;
+ }
+
+ public boolean hasPanelItems() {
+ if (shownPanelView == null) return false;
+ if (createdPanelView != null) return true;
+
+ if (isCompact || isInExpandedMode) {
+ return listMenuPresenter.getAdapter().getCount() > 0;
+ } else {
+ return ((ViewGroup) shownPanelView).getChildCount() > 0;
+ }
+ }
+
+ /**
+ * Unregister and free attached MenuPresenters. They will be recreated as needed.
+ */
+ public void clearMenuPresenters() {
+ if (menu != null) {
+ menu.removeMenuPresenter(iconMenuPresenter);
+ menu.removeMenuPresenter(listMenuPresenter);
+ }
+ iconMenuPresenter = null;
+ listMenuPresenter = null;
+ }
+
+ void setStyle(Context context) {
+ TypedArray a = context.obtainStyledAttributes(R.styleable.Theme);
+ background = a.getResourceId(
+ R.styleable.Theme_panelBackground, 0);
+ fullBackground = a.getResourceId(
+ R.styleable.Theme_panelFullBackground, 0);
+ windowAnimations = a.getResourceId(
+ R.styleable.Theme_windowAnimationStyle, 0);
+ isCompact = a.getBoolean(
+ R.styleable.Theme_panelMenuIsCompact, false);
+ listPresenterTheme = a.getResourceId(
+ R.styleable.Theme_panelMenuListTheme,
+ R.style.Theme_ExpandedMenu);
+ a.recycle();
+ }
+
+ void setMenu(MenuBuilder menu) {
+ if (menu == this.menu) return;
+
+ if (this.menu != null) {
+ this.menu.removeMenuPresenter(iconMenuPresenter);
+ this.menu.removeMenuPresenter(listMenuPresenter);
+ }
+ this.menu = menu;
+ if (menu != null) {
+ if (iconMenuPresenter != null) menu.addMenuPresenter(iconMenuPresenter);
+ if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter);
+ }
+ }
+
+ MenuView getListMenuView(Context context, MenuPresenter.Callback cb) {
+ if (menu == null) return null;
+
+ if (!isCompact) {
+ getIconMenuView(context, cb); // Need this initialized to know where our offset goes
+ }
+
+ if (listMenuPresenter == null) {
+ listMenuPresenter = new ListMenuPresenter(
+ R.layout.list_menu_item_layout, listPresenterTheme);
+ listMenuPresenter.setCallback(cb);
+ listMenuPresenter.setId(R.id.list_menu_presenter);
+ menu.addMenuPresenter(listMenuPresenter);
+ }
+
+ if (iconMenuPresenter != null) {
+ listMenuPresenter.setItemIndexOffset(
+ iconMenuPresenter.getNumActualItemsShown());
+ }
+ MenuView result = listMenuPresenter.getMenuView(decorView);
+
+ return result;
+ }
+
+ MenuView getIconMenuView(Context context, MenuPresenter.Callback cb) {
+ if (menu == null) return null;
+
+ if (iconMenuPresenter == null) {
+ iconMenuPresenter = new IconMenuPresenter(context);
+ iconMenuPresenter.setCallback(cb);
+ iconMenuPresenter.setId(R.id.icon_menu_presenter);
+ menu.addMenuPresenter(iconMenuPresenter);
+ }
+
+ MenuView result = iconMenuPresenter.getMenuView(decorView);
+
+ return result;
+ }
+
+ Parcelable onSaveInstanceState() {
+ SavedState savedState = new SavedState();
+ savedState.featureId = featureId;
+ savedState.isOpen = isOpen;
+ savedState.isInExpandedMode = isInExpandedMode;
+
+ if (menu != null) {
+ savedState.menuState = new Bundle();
+ menu.savePresenterStates(savedState.menuState);
+ }
+
+ return savedState;
+ }
+
+ void onRestoreInstanceState(Parcelable state) {
+ SavedState savedState = (SavedState) state;
+ featureId = savedState.featureId;
+ wasLastOpen = savedState.isOpen;
+ wasLastExpanded = savedState.isInExpandedMode;
+ frozenMenuState = savedState.menuState;
+
+ /*
+ * A LocalActivityManager keeps the same instance of this class around.
+ * The first time the menu is being shown after restoring, the
+ * Activity.onCreateOptionsMenu should be called. But, if it is the
+ * same instance then menu != null and we won't call that method.
+ * We clear any cached views here. The caller should invalidatePanelMenu.
+ */
+ createdPanelView = null;
+ shownPanelView = null;
+ decorView = null;
+ }
+
+ void applyFrozenState() {
+ if (menu != null && frozenMenuState != null) {
+ menu.restorePresenterStates(frozenMenuState);
+ frozenMenuState = null;
+ }
+ }
+
+ private static class SavedState implements Parcelable {
+ int featureId;
+ boolean isOpen;
+ boolean isInExpandedMode;
+ Bundle menuState;
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(featureId);
+ dest.writeInt(isOpen ? 1 : 0);
+ dest.writeInt(isInExpandedMode ? 1 : 0);
+
+ if (isOpen) {
+ dest.writeBundle(menuState);
+ }
+ }
+
+ private static SavedState readFromParcel(Parcel source) {
+ SavedState savedState = new SavedState();
+ savedState.featureId = source.readInt();
+ savedState.isOpen = source.readInt() == 1;
+ savedState.isInExpandedMode = source.readInt() == 1;
+
+ if (savedState.isOpen) {
+ savedState.menuState = source.readBundle();
+ }
+
+ return savedState;
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ }
+
+ static class RotationWatcher extends IRotationWatcher.Stub {
+ private Handler mHandler;
+ private final Runnable mRotationChanged = new Runnable() {
+ public void run() {
+ dispatchRotationChanged();
+ }
+ };
+ private final ArrayList<WeakReference<PhoneWindow>> mWindows =
+ new ArrayList<WeakReference<PhoneWindow>>();
+ private boolean mIsWatching;
+
+ @Override
+ public void onRotationChanged(int rotation) throws RemoteException {
+ mHandler.post(mRotationChanged);
+ }
+
+ public void addWindow(PhoneWindow phoneWindow) {
+ synchronized (mWindows) {
+ if (!mIsWatching) {
+ try {
+ WindowManagerHolder.sWindowManager.watchRotation(this);
+ mHandler = new Handler();
+ mIsWatching = true;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Couldn't start watching for device rotation", ex);
+ }
+ }
+ mWindows.add(new WeakReference<PhoneWindow>(phoneWindow));
+ }
+ }
+
+ public void removeWindow(PhoneWindow phoneWindow) {
+ synchronized (mWindows) {
+ int i = 0;
+ while (i < mWindows.size()) {
+ final WeakReference<PhoneWindow> ref = mWindows.get(i);
+ final PhoneWindow win = ref.get();
+ if (win == null || win == phoneWindow) {
+ mWindows.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
+ }
+
+ void dispatchRotationChanged() {
+ synchronized (mWindows) {
+ int i = 0;
+ while (i < mWindows.size()) {
+ final WeakReference<PhoneWindow> ref = mWindows.get(i);
+ final PhoneWindow win = ref.get();
+ if (win != null) {
+ win.onOptionsPanelRotationChanged();
+ i++;
+ } else {
+ mWindows.remove(i);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Simple implementation of MenuBuilder.Callback that:
+ * <li> Opens a submenu when selected.
+ * <li> Calls back to the callback's onMenuItemSelected when an item is
+ * selected.
+ */
+ private final class DialogMenuCallback implements MenuBuilder.Callback, MenuPresenter.Callback {
+ private int mFeatureId;
+ private MenuDialogHelper mSubMenuHelper;
+
+ public DialogMenuCallback(int featureId) {
+ mFeatureId = featureId;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (menu.getRootMenu() != menu) {
+ onCloseSubMenu(menu);
+ }
+
+ if (allMenusAreClosing) {
+ Callback callback = getCallback();
+ if (callback != null && !isDestroyed()) {
+ callback.onPanelClosed(mFeatureId, menu);
+ }
+
+ if (menu == mContextMenu) {
+ dismissContextMenu();
+ }
+
+ // Dismiss the submenu, if it is showing
+ if (mSubMenuHelper != null) {
+ mSubMenuHelper.dismiss();
+ mSubMenuHelper = null;
+ }
+ }
+ }
+
+ public void onCloseSubMenu(MenuBuilder menu) {
+ Callback callback = getCallback();
+ if (callback != null && !isDestroyed()) {
+ callback.onPanelClosed(mFeatureId, menu.getRootMenu());
+ }
+ }
+
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ Callback callback = getCallback();
+ return (callback != null && !isDestroyed())
+ && callback.onMenuItemSelected(mFeatureId, item);
+ }
+
+ public void onMenuModeChange(MenuBuilder menu) {
+ }
+
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (subMenu == null) return false;
+
+ // Set a simple callback for the submenu
+ subMenu.setCallback(this);
+
+ // The window manager will give us a valid window token
+ mSubMenuHelper = new MenuDialogHelper(subMenu);
+ mSubMenuHelper.show(null);
+
+ return true;
+ }
+ }
+
+ private static class ColorViewState {
+ View view = null;
+ int targetVisibility = View.INVISIBLE;
+
+ final int id;
+ final int systemUiHideFlag;
+ final int translucentFlag;
+ final int verticalGravity;
+ final String transitionName;
+ final int hideWindowFlag;
+
+ ColorViewState(int systemUiHideFlag,
+ int translucentFlag, int verticalGravity,
+ String transitionName, int id, int hideWindowFlag) {
+ this.id = id;
+ this.systemUiHideFlag = systemUiHideFlag;
+ this.translucentFlag = translucentFlag;
+ this.verticalGravity = verticalGravity;
+ this.transitionName = transitionName;
+ this.hideWindowFlag = hideWindowFlag;
+ }
+ }
+
+ void sendCloseSystemWindows() {
+ sendCloseSystemWindows(getContext(), null);
+ }
+
+ void sendCloseSystemWindows(String reason) {
+ sendCloseSystemWindows(getContext(), reason);
+ }
+
+ public static void sendCloseSystemWindows(Context context, String reason) {
+ if (ActivityManagerNative.isSystemReady()) {
+ try {
+ ActivityManagerNative.getDefault().closeSystemDialogs(reason);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public int getStatusBarColor() {
+ return mStatusBarColor;
+ }
+
+ @Override
+ public void setStatusBarColor(int color) {
+ mStatusBarColor = color;
+ mForcedStatusBarColor = true;
+ if (mDecor != null) {
+ mDecor.updateColorViews(null, false /* animate */);
+ }
+ }
+
+ @Override
+ public int getNavigationBarColor() {
+ return mNavigationBarColor;
+ }
+
+ @Override
+ public void setNavigationBarColor(int color) {
+ mNavigationBarColor = color;
+ mForcedNavigationBarColor = true;
+ if (mDecor != null) {
+ mDecor.updateColorViews(null, false /* animate */);
+ }
+ }
+}
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 7dcad68..cf35ce5 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -18,6 +18,7 @@ package android.view;
import com.android.internal.util.XmlUtils;
+import android.annotation.XmlRes;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -192,7 +193,7 @@ public final class PointerIcon implements Parcelable {
* @throws Resources.NotFoundException if the resource was not found or the drawable
* linked in the resource was not found.
*/
- public static PointerIcon loadCustomIcon(Resources resources, int resourceId) {
+ public static PointerIcon loadCustomIcon(Resources resources, @XmlRes int resourceId) {
if (resources == null) {
throw new IllegalArgumentException("resources must not be null");
}
@@ -373,7 +374,7 @@ public final class PointerIcon implements Parcelable {
return true;
}
- private void loadResource(Context context, Resources resources, int resourceId) {
+ private void loadResource(Context context, Resources resources, @XmlRes int resourceId) {
final XmlResourceParser parser = resources.getXml(resourceId);
final int bitmapRes;
final float hotSpotX;
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 47f72a8..ef98bbc 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -26,7 +26,7 @@ import android.graphics.Rect;
/**
* <p>A display list records a series of graphics related operations and can replay
* them later. Display lists are usually built by recording operations on a
- * {@link HardwareCanvas}. Replaying the operations from a display list avoids
+ * {@link DisplayListCanvas}. Replaying the operations from a display list avoids
* executing application code on every frame, and is thus much more efficient.</p>
*
* <p>Display lists are used internally for all views by default, and are not
@@ -43,7 +43,7 @@ import android.graphics.Rect;
* affected paragraph needs to be recorded again.</p>
*
* <h3>Hardware acceleration</h3>
- * <p>Display lists can only be replayed using a {@link HardwareCanvas}. They are not
+ * <p>Display lists can only be replayed using a {@link DisplayListCanvas}. They are not
* supported in software. Always make sure that the {@link android.graphics.Canvas}
* you are using to render a display list is hardware accelerated using
* {@link android.graphics.Canvas#isHardwareAccelerated()}.</p>
@@ -53,7 +53,7 @@ import android.graphics.Rect;
* HardwareRenderer renderer = myView.getHardwareRenderer();
* if (renderer != null) {
* DisplayList displayList = renderer.createDisplayList();
- * HardwareCanvas canvas = displayList.start(width, height);
+ * DisplayListCanvas canvas = displayList.start(width, height);
* try {
* // Draw onto the canvas
* // For instance: canvas.drawBitmap(...);
@@ -67,8 +67,8 @@ import android.graphics.Rect;
* <pre class="prettyprint">
* protected void onDraw(Canvas canvas) {
* if (canvas.isHardwareAccelerated()) {
- * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
- * hardwareCanvas.drawDisplayList(mDisplayList);
+ * DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
+ * displayListCanvas.drawDisplayList(mDisplayList);
* }
* }
* </pre>
@@ -92,7 +92,7 @@ import android.graphics.Rect;
* <pre class="prettyprint">
* private void createDisplayList() {
* mDisplayList = DisplayList.create("MyDisplayList");
- * HardwareCanvas canvas = mDisplayList.start(width, height);
+ * DisplayListCanvas canvas = mDisplayList.start(width, height);
* try {
* for (Bitmap b : mBitmaps) {
* canvas.drawBitmap(b, 0.0f, 0.0f, null);
@@ -105,8 +105,8 @@ import android.graphics.Rect;
*
* protected void onDraw(Canvas canvas) {
* if (canvas.isHardwareAccelerated()) {
- * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
- * hardwareCanvas.drawDisplayList(mDisplayList);
+ * DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
+ * displayListCanvas.drawDisplayList(mDisplayList);
* }
* }
*
@@ -128,7 +128,7 @@ import android.graphics.Rect;
public class RenderNode {
/**
* Flag used when calling
- * {@link HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int)}
+ * {@link DisplayListCanvas#drawRenderNode
* When this flag is set, draw operations lying outside of the bounds of the
* display list will be culled early. It is recommeneded to always set this
* flag.
@@ -140,29 +140,29 @@ public class RenderNode {
/**
* Indicates that the display list is done drawing.
*
- * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int)
+ * @see DisplayListCanvas#drawRenderNode(RenderNode, int)
*/
public static final int STATUS_DONE = 0x0;
/**
* Indicates that the display list needs another drawing pass.
*
- * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int)
+ * @see DisplayListCanvas#drawRenderNode(RenderNode, int)
*/
public static final int STATUS_DRAW = 0x1;
/**
* Indicates that the display list needs to re-execute its GL functors.
*
- * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int)
- * @see HardwareCanvas#callDrawGLFunction(long)
+ * @see DisplayListCanvas#drawRenderNode(RenderNode, int)
+ * @see DisplayListCanvas#callDrawGLFunction2(long)
*/
public static final int STATUS_INVOKE = 0x2;
/**
* Indicates that the display list performed GL drawing operations.
*
- * @see HardwareCanvas#drawRenderNode(RenderNode, android.graphics.Rect, int)
+ * @see DisplayListCanvas#drawRenderNode(RenderNode, int)
*/
public static final int STATUS_DREW = 0x4;
@@ -213,7 +213,7 @@ public class RenderNode {
* stored in this display list.
*
* Calling this method will mark the render node invalid until
- * {@link #end(HardwareCanvas)} is called.
+ * {@link #end(DisplayListCanvas)} is called.
* Only valid render nodes can be replayed.
*
* @param width The width of the recording viewport
@@ -221,11 +221,11 @@ public class RenderNode {
*
* @return A canvas to record drawing operations.
*
- * @see #end(HardwareCanvas)
+ * @see #end(DisplayListCanvas)
* @see #isValid()
*/
- public HardwareCanvas start(int width, int height) {
- HardwareCanvas canvas = GLES20RecordingCanvas.obtain(this);
+ public DisplayListCanvas start(int width, int height) {
+ DisplayListCanvas canvas = DisplayListCanvas.obtain(this);
canvas.setViewport(width, height);
// The dirty rect should always be null for a display list
canvas.onPreDraw(null);
@@ -240,12 +240,12 @@ public class RenderNode {
* @see #start(int, int)
* @see #isValid()
*/
- public void end(HardwareCanvas endCanvas) {
- if (!(endCanvas instanceof GLES20RecordingCanvas)) {
+ public void end(DisplayListCanvas endCanvas) {
+ if (!(endCanvas instanceof DisplayListCanvas)) {
throw new IllegalArgumentException("Passed an invalid canvas to end!");
}
- GLES20RecordingCanvas canvas = (GLES20RecordingCanvas) endCanvas;
+ DisplayListCanvas canvas = (DisplayListCanvas) endCanvas;
canvas.onPostDraw();
long renderNodeData = canvas.finishRecording();
nSetDisplayListData(mNativeRenderNode, renderNodeData);
@@ -305,7 +305,7 @@ public class RenderNode {
}
public boolean setLayerPaint(Paint paint) {
- return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.mNativePaint : 0);
+ return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.getNativeInstance() : 0);
}
public boolean setClipBounds(@Nullable Rect rect) {
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 7b35a3b..379796d 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -283,10 +283,10 @@ public class RenderNodeAnimator extends Animator {
}
public void setTarget(Canvas canvas) {
- if (!(canvas instanceof GLES20RecordingCanvas)) {
+ if (!(canvas instanceof DisplayListCanvas)) {
throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
}
- final GLES20RecordingCanvas recordingCanvas = (GLES20RecordingCanvas) canvas;
+ final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas;
setTarget(recordingCanvas.mNode);
}
diff --git a/core/java/android/view/SubMenu.java b/core/java/android/view/SubMenu.java
index 196a183..38662b0 100644
--- a/core/java/android/view/SubMenu.java
+++ b/core/java/android/view/SubMenu.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.graphics.drawable.Drawable;
/**
@@ -38,7 +40,7 @@ public interface SubMenu extends Menu {
* @param titleRes The string resource identifier used for the title.
* @return This SubMenu so additional setters can be called.
*/
- public SubMenu setHeaderTitle(int titleRes);
+ public SubMenu setHeaderTitle(@StringRes int titleRes);
/**
* Sets the submenu header's title to the title given in <var>title</var>.
@@ -55,7 +57,7 @@ public interface SubMenu extends Menu {
* @param iconRes The resource identifier used for the icon.
* @return This SubMenu so additional setters can be called.
*/
- public SubMenu setHeaderIcon(int iconRes);
+ public SubMenu setHeaderIcon(@DrawableRes int iconRes);
/**
* Sets the submenu header's icon to the icon given in <var>icon</var>
@@ -88,7 +90,7 @@ public interface SubMenu extends Menu {
* @param iconRes The new icon (as a resource ID) to be displayed.
* @return This SubMenu so additional setters can be called.
*/
- public SubMenu setIcon(int iconRes);
+ public SubMenu setIcon(@DrawableRes int iconRes);
/**
* Change the icon associated with this submenu's item in its parent menu.
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 33ce517..6de4d3e 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -322,7 +322,6 @@ public class Surface implements Parcelable {
* @return A canvas for drawing into the surface.
*
* @throws IllegalStateException If the canvas cannot be locked.
- * @hide
*/
public Canvas lockHardwareCanvas() {
synchronized (mLock) {
@@ -573,7 +572,7 @@ public class Surface implements Parcelable {
private final class HwuiContext {
private final RenderNode mRenderNode;
private long mHwuiRenderer;
- private HardwareCanvas mCanvas;
+ private DisplayListCanvas mCanvas;
HwuiContext() {
mRenderNode = RenderNode.create("HwuiCanvas", null);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index ad4a048..031be07 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -16,8 +16,7 @@
package android.view;
-import com.android.internal.R;
-
+import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -27,16 +26,18 @@ import android.graphics.drawable.Drawable;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.Trace;
import android.util.Log;
import android.util.LongSparseArray;
-import android.util.TimeUtils;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
+import com.android.internal.R;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashSet;
@@ -74,6 +75,14 @@ public class ThreadedRenderer extends HardwareRenderer {
PROFILE_PROPERTY_VISUALIZE_BARS,
};
+ private static final int FLAG_DUMP_FRAMESTATS = 1 << 0;
+ private static final int FLAG_DUMP_RESET = 1 << 1;
+
+ @IntDef(flag = true, value = {
+ FLAG_DUMP_FRAMESTATS, FLAG_DUMP_RESET })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DumpFlags {}
+
// Size of the rendered content.
private int mWidth, mHeight;
@@ -98,7 +107,6 @@ public class ThreadedRenderer extends HardwareRenderer {
private boolean mInitialized = false;
private RenderNode mRootNode;
private Choreographer mChoreographer;
- private boolean mProfilingEnabled;
private boolean mRootNodeNeedsUpdate;
ThreadedRenderer(Context context, boolean translucent) {
@@ -118,10 +126,6 @@ public class ThreadedRenderer extends HardwareRenderer {
AtlasInitializer.sInstance.init(context, mNativeProxy);
- // Setup timing
- mChoreographer = Choreographer.getInstance();
- nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos());
-
loadSystemProperties();
}
@@ -233,32 +237,25 @@ public class ThreadedRenderer extends HardwareRenderer {
}
@Override
- void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
+ void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args) {
pw.flush();
- nDumpProfileInfo(mNativeProxy, fd);
- }
-
- private static int search(String[] values, String value) {
- for (int i = 0; i < values.length; i++) {
- if (values[i].equals(value)) return i;
+ int flags = 0;
+ for (int i = 0; i < args.length; i++) {
+ switch (args[i]) {
+ case "framestats":
+ flags |= FLAG_DUMP_FRAMESTATS;
+ break;
+ case "reset":
+ flags |= FLAG_DUMP_RESET;
+ break;
+ }
}
- return -1;
- }
-
- private static boolean checkIfProfilingRequested() {
- String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY);
- int graphType = search(VISUALIZERS, profiling);
- return (graphType >= 0) || Boolean.parseBoolean(profiling);
+ nDumpProfileInfo(mNativeProxy, fd, flags);
}
@Override
boolean loadSystemProperties() {
boolean changed = nLoadSystemProperties(mNativeProxy);
- boolean wantProfiling = checkIfProfilingRequested();
- if (wantProfiling != mProfilingEnabled) {
- mProfilingEnabled = wantProfiling;
- changed = true;
- }
if (changed) {
invalidateRoot();
}
@@ -279,7 +276,7 @@ public class ThreadedRenderer extends HardwareRenderer {
updateViewTreeDisplayList(view);
if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
- HardwareCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
+ DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
try {
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
@@ -307,20 +304,12 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
attachInfo.mIgnoreDirtyState = true;
- long frameTimeNanos = mChoreographer.getFrameTimeNanos();
- attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
- long recordDuration = 0;
- if (mProfilingEnabled) {
- recordDuration = System.nanoTime();
- }
+ final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
+ choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
- if (mProfilingEnabled) {
- recordDuration = System.nanoTime() - recordDuration;
- }
-
attachInfo.mIgnoreDirtyState = false;
// register animating rendernodes which started animating prior to renderer
@@ -337,8 +326,8 @@ public class ThreadedRenderer extends HardwareRenderer {
attachInfo.mPendingAnimatingRenderNodes = null;
}
- int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
- recordDuration, view.getResources().getDisplayMetrics().density);
+ final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
+ int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
@@ -369,7 +358,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
boolean copyLayerInto(final HardwareLayer layer, final Bitmap bitmap) {
return nCopyLayerInto(mNativeProxy,
- layer.getDeferredLayerUpdater(), bitmap.mNativeBitmap);
+ layer.getDeferredLayerUpdater(), bitmap.getSkBitmap());
}
@Override
@@ -384,6 +373,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void setName(String name) {
+ nSetName(mNativeProxy, name);
}
@Override
@@ -470,7 +460,7 @@ public class ThreadedRenderer extends HardwareRenderer {
for (int i = 0; i < count; i++) {
drawables.valueAt(i).addAtlasableBitmaps(tmpList);
for (int j = 0; j < tmpList.size(); j++) {
- preloadedPointers.add(tmpList.get(j).mNativeBitmap);
+ preloadedPointers.add(tmpList.get(j).getSkBitmap());
}
tmpList.clear();
}
@@ -492,8 +482,8 @@ public class ThreadedRenderer extends HardwareRenderer {
private static native long nCreateProxy(boolean translucent, long rootRenderNode);
private static native void nDeleteProxy(long nativeProxy);
- private static native void nSetFrameInterval(long nativeProxy, long frameIntervalNanos);
private static native boolean nLoadSystemProperties(long nativeProxy);
+ private static native void nSetName(long nativeProxy, String name);
private static native boolean nInitialize(long nativeProxy, Surface window);
private static native void nUpdateSurface(long nativeProxy, Surface window);
@@ -502,8 +492,7 @@ public class ThreadedRenderer extends HardwareRenderer {
float lightX, float lightY, float lightZ, float lightRadius,
int ambientShadowAlpha, int spotShadowAlpha);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
- private static native int nSyncAndDrawFrame(long nativeProxy,
- long frameTimeNanos, long recordDuration, float density);
+ private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
private static native void nDestroy(long nativeProxy);
private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
@@ -523,5 +512,6 @@ public class ThreadedRenderer extends HardwareRenderer {
private static native void nStopDrawing(long nativeProxy);
private static native void nNotifyFramePending(long nativeProxy);
- private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd);
+ private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
+ @DumpFlags int dumpFlags);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3c05872..f5de8e3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -18,9 +18,16 @@ package android.view;
import android.animation.AnimatorInflater;
import android.animation.StateListAnimator;
+import android.annotation.CallSuper;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.FloatRange;
+import android.annotation.IdRes;
import android.annotation.IntDef;
+import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Size;
import android.content.ClipData;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -35,8 +42,6 @@ import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PathMeasure;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PorterDuff;
@@ -66,6 +71,7 @@ import android.util.LongSparseLongArray;
import android.util.Pools.SynchronizedPool;
import android.util.Property;
import android.util.SparseArray;
+import android.util.StateSet;
import android.util.SuperNotCalledException;
import android.util.TypedValue;
import android.view.ContextMenu.ContextMenuInfo;
@@ -439,7 +445,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* </p>
*
* <p>
- * To intiate a layout, call {@link #requestLayout}. This method is typically
+ * To initiate a layout, call {@link #requestLayout}. This method is typically
* called by a view on itself when it believes that is can no longer fit within
* its current bounds.
* </p>
@@ -1438,140 +1444,87 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
- /**
- * The order here is very important to {@link #getDrawableState()}
- */
- private static final int[][] VIEW_STATE_SETS;
-
- static final int VIEW_STATE_WINDOW_FOCUSED = 1;
- static final int VIEW_STATE_SELECTED = 1 << 1;
- static final int VIEW_STATE_FOCUSED = 1 << 2;
- static final int VIEW_STATE_ENABLED = 1 << 3;
- static final int VIEW_STATE_PRESSED = 1 << 4;
- static final int VIEW_STATE_ACTIVATED = 1 << 5;
- static final int VIEW_STATE_ACCELERATED = 1 << 6;
- static final int VIEW_STATE_HOVERED = 1 << 7;
- static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
- static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
-
- static final int[] VIEW_STATE_IDS = new int[] {
- R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED,
- R.attr.state_selected, VIEW_STATE_SELECTED,
- R.attr.state_focused, VIEW_STATE_FOCUSED,
- R.attr.state_enabled, VIEW_STATE_ENABLED,
- R.attr.state_pressed, VIEW_STATE_PRESSED,
- R.attr.state_activated, VIEW_STATE_ACTIVATED,
- R.attr.state_accelerated, VIEW_STATE_ACCELERATED,
- R.attr.state_hovered, VIEW_STATE_HOVERED,
- R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT,
- R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED
- };
-
static {
- if ((VIEW_STATE_IDS.length/2) != R.styleable.ViewDrawableStates.length) {
- throw new IllegalStateException(
- "VIEW_STATE_IDs array length does not match ViewDrawableStates style array");
- }
- int[] orderedIds = new int[VIEW_STATE_IDS.length];
- for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) {
- int viewState = R.styleable.ViewDrawableStates[i];
- for (int j = 0; j<VIEW_STATE_IDS.length; j += 2) {
- if (VIEW_STATE_IDS[j] == viewState) {
- orderedIds[i * 2] = viewState;
- orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1];
- }
- }
- }
- final int NUM_BITS = VIEW_STATE_IDS.length / 2;
- VIEW_STATE_SETS = new int[1 << NUM_BITS][];
- for (int i = 0; i < VIEW_STATE_SETS.length; i++) {
- int numBits = Integer.bitCount(i);
- int[] set = new int[numBits];
- int pos = 0;
- for (int j = 0; j < orderedIds.length; j += 2) {
- if ((i & orderedIds[j+1]) != 0) {
- set[pos++] = orderedIds[j];
- }
- }
- VIEW_STATE_SETS[i] = set;
- }
-
- EMPTY_STATE_SET = VIEW_STATE_SETS[0];
- WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_WINDOW_FOCUSED];
- SELECTED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_SELECTED];
- SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED];
- FOCUSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_FOCUSED];
- FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED];
- FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED];
- FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_FOCUSED];
- ENABLED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_ENABLED];
- ENABLED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_ENABLED];
- ENABLED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_ENABLED];
- ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_ENABLED];
- ENABLED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_FOCUSED | VIEW_STATE_ENABLED];
- ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
- | VIEW_STATE_ENABLED];
- ENABLED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
- | VIEW_STATE_ENABLED];
- ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_FOCUSED| VIEW_STATE_ENABLED];
-
- PRESSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_PRESSED];
- PRESSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_PRESSED];
- PRESSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_PRESSED];
- PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_PRESSED];
- PRESSED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_FOCUSED | VIEW_STATE_PRESSED];
- PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
- | VIEW_STATE_PRESSED];
- PRESSED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
- | VIEW_STATE_PRESSED];
- PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_FOCUSED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_ENABLED
- | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_ENABLED
- | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_FOCUSED | VIEW_STATE_ENABLED
- | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
- | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
- | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_FOCUSED| VIEW_STATE_ENABLED
- | VIEW_STATE_PRESSED];
+ EMPTY_STATE_SET = StateSet.get(0);
+
+ WINDOW_FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_WINDOW_FOCUSED);
+
+ SELECTED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_SELECTED);
+ SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED);
+
+ FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED);
+
+ ENABLED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_ENABLED);
+ ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED);
+
+ PRESSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_PRESSED);
+ PRESSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
}
/**
@@ -1645,6 +1598,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setId(int)
* @see #getId()
*/
+ @IdRes
@ViewDebug.ExportedProperty(resolveId = true)
int mID = NO_ID;
@@ -1977,7 +1931,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR;
/**
- * Text direction is inherited thru {@link ViewGroup}
+ * Text direction is inherited through {@link ViewGroup}
*/
public static final int TEXT_DIRECTION_INHERIT = 0;
@@ -2545,7 +2499,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Flag for {@link #setSystemUiVisibility(int)}: View would like its window
- * to be layed out as if it has requested
+ * to be laid out as if it has requested
* {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, even if it currently hasn't. This
* allows it to avoid artifacts when switching in and out of that mode, at
* the expense that some of its user interface may be covered by screen
@@ -2557,7 +2511,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Flag for {@link #setSystemUiVisibility(int)}: View would like its window
- * to be layed out as if it has requested
+ * to be laid out as if it has requested
* {@link #SYSTEM_UI_FLAG_FULLSCREEN}, even if it currently hasn't. This
* allows it to avoid artifacts when switching in and out of that mode, at
* the expense that some of its user interface may be covered by screen
@@ -2596,6 +2550,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000;
/**
+ * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
+ * is compatible with light status bar backgrounds.
+ *
+ * <p>For this to take effect, the window must request
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+ * FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS
+ * FLAG_TRANSLUCENT_STATUS}.
+ *
+ * @see android.R.attr#windowHasLightStatusBar
+ */
+ public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
+
+ /**
* @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead.
*/
public static final int STATUS_BAR_HIDDEN = SYSTEM_UI_FLAG_LOW_PROFILE;
@@ -3620,7 +3588,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param attrs The attributes of the XML tag that is inflating the view.
* @see #View(Context, AttributeSet, int)
*/
- public View(Context context, AttributeSet attrs) {
+ public View(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -3641,7 +3609,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* the view. Can be 0 to not look for defaults.
* @see #View(Context, AttributeSet)
*/
- public View(Context context, AttributeSet attrs, int defStyleAttr) {
+ public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@@ -3678,7 +3646,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* to not look for defaults.
* @see #View(Context, AttributeSet, int)
*/
- public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
@@ -4508,6 +4476,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (scrollabilityCache.scrollBar == null) {
scrollabilityCache.scrollBar = new ScrollBarDrawable();
+ scrollabilityCache.scrollBar.setCallback(this);
+ scrollabilityCache.scrollBar.setState(getDrawableState());
}
final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, true);
@@ -4619,11 +4589,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Register a callback to be invoked when the scroll position of this view
- * changed.
+ * Register a callback to be invoked when the scroll X or Y positions of
+ * this view change.
+ * <p>
+ * <b>Note:</b> Some views handle scrolling independently from View and may
+ * have their own separate listeners for scroll-type events. For example,
+ * {@link android.widget.ListView ListView} allows clients to register an
+ * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener}
+ * to listen for changes in list scroll position.
*
- * @param l The callback that will run.
- * @hide Only used internally.
+ * @param l The listener to notify when the scroll X or Y position changes.
+ * @see android.view.View#getScrollX()
+ * @see android.view.View#getScrollY()
*/
public void setOnScrollChangeListener(OnScrollChangeListener l) {
getListenerInfo().mOnScrollChangeListener = l;
@@ -4719,7 +4696,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #setClickable(boolean)
*/
- public void setOnClickListener(OnClickListener l) {
+ public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
@@ -4743,7 +4720,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #setLongClickable(boolean)
*/
- public void setOnLongClickListener(OnLongClickListener l) {
+ public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
@@ -4868,17 +4845,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Start an action mode.
+ * Start an action mode with the default type {@link ActionMode#TYPE_PRIMARY}.
*
* @param callback Callback that will control the lifecycle of the action mode
* @return The new action mode if it is started, null otherwise
*
* @see ActionMode
+ * @see #startActionMode(android.view.ActionMode.Callback, int)
*/
public ActionMode startActionMode(ActionMode.Callback callback) {
+ return startActionMode(callback, ActionMode.TYPE_PRIMARY);
+ }
+
+ /**
+ * Start an action mode with the given type.
+ *
+ * @param callback Callback that will control the lifecycle of the action mode
+ * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+ * @return The new action mode if it is started, null otherwise
+ *
+ * @see ActionMode
+ */
+ public ActionMode startActionMode(ActionMode.Callback callback, int type) {
ViewParent parent = getParent();
if (parent == null) return null;
- return parent.startActionModeForChild(this, callback);
+ try {
+ return parent.startActionModeForChild(this, callback, type);
+ } catch (AbstractMethodError ame) {
+ // Older implementations of custom views might not implement this.
+ return parent.startActionModeForChild(this, callback);
+ }
}
/**
@@ -5123,7 +5119,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Returns true if this view has focus iteself, or is the ancestor of the
+ * Returns true if this view has focus itself, or is the ancestor of the
* view that has focus.
*
* @return True if this view has or contains focus, false otherwise.
@@ -5176,6 +5172,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* passed in as finer grained information about where the focus is coming
* from (in addition to direction). Will be <code>null</code> otherwise.
*/
+ @CallSuper
protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
@Nullable Rect previouslyFocusedRect) {
if (gainFocus) {
@@ -5220,7 +5217,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* populate the text content of the event source including its descendants,
* and last calls
* {@link ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
- * on its parent to resuest sending of the event to interested parties.
+ * on its parent to request sending of the event to interested parties.
* <p>
* If an {@link AccessibilityDelegate} has been specified via calling
* {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
@@ -5270,8 +5267,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #sendAccessibilityEvent(int)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void sendAccessibilityEventInternal(int eventType) {
+ public void sendAccessibilityEventInternal(int eventType) {
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
}
@@ -5304,8 +5303,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #sendAccessibilityEventUnchecked(AccessibilityEvent)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
+ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
if (!isShown()) {
return;
}
@@ -5355,8 +5356,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return false;
}
@@ -5392,6 +5395,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #sendAccessibilityEvent(int)
* @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*/
+ @CallSuper
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.onPopulateAccessibilityEvent(this, event);
@@ -5404,8 +5408,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #onPopulateAccessibilityEvent(AccessibilityEvent)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
}
/**
@@ -5434,6 +5440,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #sendAccessibilityEvent(int)
* @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*/
+ @CallSuper
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.onInitializeAccessibilityEvent(this, event);
@@ -5446,10 +5453,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #onInitializeAccessibilityEvent(AccessibilityEvent)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
event.setSource(this);
- event.setClassName(View.class.getName());
+ event.setClassName(getAccessibilityClassName());
event.setPackageName(getContext().getPackageName());
event.setEnabled(isEnabled());
event.setContentDescription(mContentDescription);
@@ -5502,8 +5511,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @see #createAccessibilityNodeInfo()
+ *
+ * @hide
*/
- AccessibilityNodeInfo createAccessibilityNodeInfoInternal() {
+ public AccessibilityNodeInfo createAccessibilityNodeInfoInternal() {
AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
if (provider != null) {
return provider.createAccessibilityNodeInfo(View.NO_ID);
@@ -5544,6 +5555,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param info The instance to initialize.
*/
+ @CallSuper
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(this, info);
@@ -5617,11 +5629,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Return the class name of this object to be used for accessibility purposes.
+ * Subclasses should only override this if they are implementing something that
+ * should be seen as a completely new class of view when used by accessibility,
+ * unrelated to the class it is deriving from. This is used to fill in
+ * {@link AccessibilityNodeInfo#setClassName AccessibilityNodeInfo.setClassName}.
+ */
+ public CharSequence getAccessibilityClassName() {
+ return View.class.getName();
+ }
+
+ /**
+ * Called when assist structure is being retrieved from a view as part of
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
+ * @param structure Additional standard structured view structure to supply.
+ * @param extras Non-standard extensions.
+ */
+ public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) {
+ }
+
+ /**
* @see #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
Rect bounds = mAttachInfo.mTmpInvalRect;
getDrawingRect(bounds);
@@ -5696,7 +5730,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
info.setVisibleToUser(isVisibleToUser());
info.setPackageName(mContext.getPackageName());
- info.setClassName(View.class.getName());
+ info.setClassName(getAccessibilityClassName());
info.setContentDescription(getContentDescription());
info.setEnabled(isEnabled());
@@ -5843,7 +5877,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see AccessibilityDelegate
*/
- public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
+ public void setAccessibilityDelegate(@Nullable AccessibilityDelegate delegate) {
mAccessibilityDelegate = delegate;
}
@@ -6057,7 +6091,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param id The labeled view id.
*/
@RemotableViewMethod
- public void setLabelFor(int id) {
+ public void setLabelFor(@IdRes int id) {
if (mLabelForId == id) {
return;
}
@@ -6083,6 +6117,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @hide pending API council approval
*/
+ @CallSuper
protected void onFocusLost() {
resetPressedState();
}
@@ -6549,6 +6584,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Provide original WindowInsets that are dispatched to the view hierarchy. The insets are
+ * only available if the view is attached.
+ *
+ * @return WindowInsets from the top of the view hierarchy or null if View is detached
+ */
+ public WindowInsets getRootWindowInsets() {
+ if (mAttachInfo != null) {
+ return mAttachInfo.mViewRootImpl.getWindowInsets(false /* forceConstruct */);
+ }
+ return null;
+ }
+
+ /**
* @hide Compute the insets that should be consumed by this view and the ones
* that should propagate to those under it.
*/
@@ -6691,7 +6739,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@RemotableViewMethod
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
- if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false);
}
/**
@@ -7394,8 +7441,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* including this view if it is focusable itself) to views. This method
* adds all focusable views regardless if we are in touch mode or
* only views focusable in touch mode if we are in touch mode or
- * only views that can take accessibility focus if accessibility is enabeld
- * depending on the focusable mode paramater.
+ * only views that can take accessibility focus if accessibility is enabled
+ * depending on the focusable mode parameter.
*
* @param views Focusable views found so far or null if all we are interested is
* the number of focusables.
@@ -7681,7 +7728,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Call this to try to give focus to a specific view or to one of its descendants. This is a
- * special variant of {@link #requestFocus() } that will allow views that are not focuable in
+ * special variant of {@link #requestFocus() } that will allow views that are not focusable in
* touch mode to request focus when they are touched.
*
* @return Whether this view or one of its descendants actually took focus.
@@ -8083,7 +8130,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* Note: Called from the default {@link AccessibilityDelegate}.
*
- * @hide Until we've refactored all accessibility delegation methods.
+ * @hide
*/
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (isNestedScrollingEnabled()
@@ -8732,20 +8779,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Called when the visibility of the view or an ancestor of the view is changed.
- * @param changedView The view whose visibility changed. Could be 'this' or
- * an ancestor view.
- * @param visibility The new visibility of changedView: {@link #VISIBLE},
- * {@link #INVISIBLE} or {@link #GONE}.
+ * Called when the visibility of the view or an ancestor of the view has
+ * changed.
+ *
+ * @param changedView The view whose visibility changed. May be
+ * {@code this} or an ancestor view.
+ * @param visibility The new visibility, one of {@link #VISIBLE},
+ * {@link #INVISIBLE} or {@link #GONE}.
*/
protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) {
- if (visibility == VISIBLE) {
+ final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE;
+ if (visible) {
if (mAttachInfo != null) {
initialAwakenScrollBars();
} else {
mPrivateFlags |= PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH;
}
}
+
+ final Drawable dr = mBackground;
+ if (dr != null && visible != dr.isVisible()) {
+ dr.setVisible(visible, false);
+ }
}
/**
@@ -8868,7 +8923,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Called when the current configuration of the resources being used
* by the application have changed. You can use this to decide when
* to reload resources that can changed based on orientation and other
- * configuration characterstics. You only need to use this if you are
+ * configuration characteristics. You only need to use this if you are
* not relying on the normal {@link android.app.Activity} mechanism of
* recreating the activity instance upon a configuration change.
*
@@ -9875,9 +9930,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Interface definition for a callback to be invoked when the scroll
- * position of a view changes.
+ * X or Y positions of a view change.
+ * <p>
+ * <b>Note:</b> Some views handle scrolling independently from View and may
+ * have their own separate listeners for scroll-type events. For example,
+ * {@link android.widget.ListView ListView} allows clients to register an
+ * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener}
+ * to listen for changes in list scroll position.
*
- * @hide Only used internally.
+ * @see #setOnScrollChangeListener(View.OnScrollChangeListener)
*/
public interface OnScrollChangeListener {
/**
@@ -10046,6 +10107,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @return The measured width of this view as a bit mask.
*/
+ @ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
+ @ViewDebug.FlagToString(mask = MEASURED_STATE_MASK, equals = MEASURED_STATE_TOO_SMALL,
+ name = "MEASURED_STATE_TOO_SMALL"),
+ })
public final int getMeasuredWidthAndState() {
return mMeasuredWidth;
}
@@ -10070,6 +10135,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @return The measured width of this view as a bit mask.
*/
+ @ViewDebug.ExportedProperty(category = "measurement", flagMapping = {
+ @ViewDebug.FlagToString(mask = MEASURED_STATE_MASK, equals = MEASURED_STATE_TOO_SMALL,
+ name = "MEASURED_STATE_TOO_SMALL"),
+ })
public final int getMeasuredHeightAndState() {
return mMeasuredHeight;
}
@@ -10537,7 +10606,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Note that if the view is backed by a
* {@link #setLayerType(int, android.graphics.Paint) layer} and is associated with a
* {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an alpha value less than
- * 1.0 will supercede the alpha of the layer paint.</p>
+ * 1.0 will supersede the alpha of the layer paint.</p>
*
* @param alpha The opacity of the view.
*
@@ -10546,7 +10615,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @attr ref android.R.styleable#View_alpha
*/
- public void setAlpha(float alpha) {
+ public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
ensureTransformationInfo();
if (mTransformationInfo.mAlpha != alpha) {
mTransformationInfo.mAlpha = alpha;
@@ -11585,7 +11654,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </p>
*
* <p>
- * This method should be invoked everytime a subclass directly updates the
+ * This method should be invoked every time a subclass directly updates the
* scroll parameters.
* </p>
*
@@ -11624,7 +11693,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </p>
*
* <p>
- * This method should be invoked everytime a subclass directly updates the
+ * This method should be invoked every time a subclass directly updates the
* scroll parameters.
* </p>
*
@@ -11632,7 +11701,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* should start; when the delay is 0, the animation starts
* immediately
*
- * @param invalidate Wheter this method should call invalidate
+ * @param invalidate Whether this method should call invalidate
*
* @return true if the animation is played, false otherwise
*
@@ -11652,6 +11721,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (scrollCache.scrollBar == null) {
scrollCache.scrollBar = new ScrollBarDrawable();
+ scrollCache.scrollBar.setCallback(this);
+ scrollCache.scrollBar.setState(getDrawableState());
}
if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {
@@ -12537,7 +12608,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Define whether scrollbars will fade when the view is not scrolling.
*
- * @param fadeScrollbars wheter to enable fading
+ * @param fadeScrollbars whether to enable fading
*
* @attr ref android.R.styleable#View_fadeScrollbars
*/
@@ -13051,6 +13122,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #onDetachedFromWindow()
*/
+ @CallSuper
protected void onAttachedToWindow() {
if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
mParent.requestTransparentRegion(this);
@@ -13072,7 +13144,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (isFocused()) {
InputMethodManager imm = InputMethodManager.peekInstance();
- imm.focusIn(this);
+ if (imm != null) {
+ imm.focusIn(this);
+ }
}
}
@@ -13381,6 +13455,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #onAttachedToWindow()
*/
+ @CallSuper
protected void onDetachedFromWindow() {
}
@@ -13389,11 +13464,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* after onDetachedFromWindow().
*
* If you override this you *MUST* call super.onDetachedFromWindowInternal()!
- * The super method should be called at the end of the overriden method to ensure
+ * The super method should be called at the end of the overridden method to ensure
* subclasses are destroyed first
*
* @hide
*/
+ @CallSuper
protected void onDetachedFromWindowInternal() {
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
@@ -13700,6 +13776,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #setSaveEnabled(boolean)
*/
+ @CallSuper
protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
return BaseSavedState.EMPTY_STATE;
@@ -13758,6 +13835,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
*/
+ @CallSuper
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (state != BaseSavedState.EMPTY_STATE && state != null) {
@@ -13790,7 +13868,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Note: if this view's parent addStateFromChildren property is enabled and this
* property is enabled, an exception will be thrown.</p>
*
- * <p>Note: if the child view uses and updates additionnal states which are unknown to the
+ * <p>Note: if the child view uses and updates additional states which are unknown to the
* parent, these states should not be affected by this method.</p>
*
* @param enabled True to enable duplication of the parent's drawable state, false
@@ -13831,7 +13909,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </ul>
*
* <p>If this view has an alpha value set to < 1.0 by calling
- * {@link #setAlpha(float)}, the alpha value of the layer's paint is superceded
+ * {@link #setAlpha(float)}, the alpha value of the layer's paint is superseded
* by this view's alpha value.</p>
*
* <p>Refer to the documentation of {@link #LAYER_TYPE_NONE},
@@ -13899,7 +13977,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </ul>
*
* <p>If this view has an alpha value set to < 1.0 by calling {@link #setAlpha(float)}, the
- * alpha value of the layer's paint is superceded by this view's alpha value.</p>
+ * alpha value of the layer's paint is superseded by this view's alpha value.</p>
*
* @param paint The paint used to compose the layer. This argument is optional
* and can be null. It is ignored when the layer type is
@@ -14008,6 +14086,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @hide
*/
+ @CallSuper
protected void destroyHardwareResources() {
// Although the Layer will be destroyed by RenderNode, we want to release
// the staging display list, which is also a signal to RenderNode that it's
@@ -14137,7 +14216,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int height = mBottom - mTop;
int layerType = getLayerType();
- final HardwareCanvas canvas = renderNode.start(width, height);
+ final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.setHighContrastText(mAttachInfo.mHighContrastText);
try {
@@ -14280,7 +14359,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #buildDrawingCache()
* @see #getDrawingCache()
*/
- public void setDrawingCacheBackgroundColor(int color) {
+ public void setDrawingCacheBackgroundColor(@ColorInt int color) {
if (color != mDrawingCacheBackgroundColor) {
mDrawingCacheBackgroundColor = color;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
@@ -14292,6 +14371,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @return The background color to used for the drawing cache's bitmap
*/
+ @ColorInt
public int getDrawingCacheBackgroundColor() {
return mDrawingCacheBackgroundColor;
}
@@ -14803,10 +14883,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
void setDisplayListProperties(RenderNode renderNode) {
if (renderNode != null) {
renderNode.setHasOverlappingRendering(hasOverlappingRendering());
- if (mParent instanceof ViewGroup) {
- renderNode.setClipToBounds(
- (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
- }
+ renderNode.setClipToBounds(mParent instanceof ViewGroup
+ && ((ViewGroup) mParent).getClipChildren());
+
float alpha = 1;
if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags &
ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
@@ -15118,7 +15197,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (layer != null && layer.isValid()) {
int restoreAlpha = mLayerPaint.getAlpha();
mLayerPaint.setAlpha((int) (alpha * 255));
- ((HardwareCanvas) canvas).drawHardwareLayer(layer, 0, 0, mLayerPaint);
+ ((DisplayListCanvas) canvas).drawHardwareLayer(layer, 0, 0, mLayerPaint);
mLayerPaint.setAlpha(restoreAlpha);
layerRendered = true;
} else {
@@ -15141,7 +15220,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
} else {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- ((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags);
+ ((DisplayListCanvas) canvas).drawRenderNode(renderNode, flags);
}
}
} else if (cache != null) {
@@ -15197,6 +15276,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param canvas The Canvas to which the View is rendered.
*/
+ @CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
@@ -15414,7 +15494,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
- ((HardwareCanvas) canvas).drawRenderNode(renderNode);
+ ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
@@ -15451,7 +15531,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final Rect bounds = drawable.getBounds();
final int width = bounds.width();
final int height = bounds.height();
- final HardwareCanvas canvas = renderNode.start(width, height);
+ final DisplayListCanvas 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
@@ -15506,6 +15586,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return The known solid color background for this view, or 0 if the color may vary
*/
@ViewDebug.ExportedProperty(category = "drawing")
+ @ColorInt
public int getSolidColor() {
return 0;
}
@@ -15798,6 +15879,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Even if the subclass overrides onFinishInflate, they should always be
* sure to call the super method, so that we get called.
*/
+ @CallSuper
protected void onFinishInflate() {
}
@@ -15963,8 +16045,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #unscheduleDrawable(android.graphics.drawable.Drawable)
* @see #drawableStateChanged()
*/
+ @CallSuper
protected boolean verifyDrawable(Drawable who) {
- return who == mBackground;
+ return who == mBackground || (mScrollCache != null && mScrollCache.scrollBar == who);
}
/**
@@ -15978,14 +16061,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see Drawable#setState(int[])
*/
+ @CallSuper
protected void drawableStateChanged() {
+ final int[] state = getDrawableState();
+
final Drawable d = mBackground;
if (d != null && d.isStateful()) {
- d.setState(getDrawableState());
+ d.setState(state);
+ }
+
+ if (mScrollCache != null) {
+ final Drawable scrollBar = mScrollCache.scrollBar;
+ if (scrollBar != null && scrollBar.isStateful()) {
+ scrollBar.setState(state);
+ }
}
if (mStateListAnimator != null) {
- mStateListAnimator.setState(getDrawableState());
+ mStateListAnimator.setState(state);
}
}
@@ -16001,6 +16094,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param x hotspot x coordinate
* @param y hotspot y coordinate
*/
+ @CallSuper
public void drawableHotspotChanged(float x, float y) {
if (mBackground != null) {
mBackground.setHotspot(x, y);
@@ -16083,26 +16177,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int privateFlags = mPrivateFlags;
int viewStateIndex = 0;
- if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
- if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED;
- if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED;
- if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
- if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
- if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
+ if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
+ if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
+ if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
+ if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
+ if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
+ if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
HardwareRenderer.isAvailable()) {
// This is set if HW acceleration is requested, even if the current
// process doesn't allow it. This is just to allow app preview
// windows to better match their app.
- viewStateIndex |= VIEW_STATE_ACCELERATED;
+ viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
}
- if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED;
+ if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;
final int privateFlags2 = mPrivateFlags2;
- if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT;
- if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED;
+ if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
+ viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
+ }
+ if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
+ viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
+ }
- drawableState = VIEW_STATE_SETS[viewStateIndex];
+ drawableState = StateSet.get(viewStateIndex);
//noinspection ConstantIfStatement
if (false) {
@@ -16179,7 +16277,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param color the color of the background
*/
@RemotableViewMethod
- public void setBackgroundColor(int color) {
+ public void setBackgroundColor(@ColorInt int color) {
if (mBackground instanceof ColorDrawable) {
((ColorDrawable) mBackground.mutate()).setColor(color);
computeOpaqueFlags();
@@ -16190,6 +16288,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * If the view has a ColorDrawable background, returns the color of that
+ * drawable.
+ *
+ * @return The color of the ColorDrawable background, if set, otherwise 0.
+ */
+ @ColorInt
+ public int getBackgroundColor() {
+ if (mBackground instanceof ColorDrawable) {
+ return ((ColorDrawable) mBackground).getColor();
+ }
+ return 0;
+ }
+
+ /**
* Set the background to a given resource. The resource should refer to
* a Drawable object or 0 to remove the background.
* @param resid The identifier of the resource.
@@ -16197,7 +16309,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_background
*/
@RemotableViewMethod
- public void setBackgroundResource(int resid) {
+ public void setBackgroundResource(@DrawableRes int resid) {
if (resid != 0 && resid == mBackgroundResource) {
return;
}
@@ -16652,8 +16764,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Return if the padding as been set thru relative values
- * {@link #setPaddingRelative(int, int, int, int)} or thru
+ * Return if the padding has been set through relative values
+ * {@link #setPaddingRelative(int, int, int, int)} or through
* @attr ref android.R.styleable#View_paddingStart or
* @attr ref android.R.styleable#View_paddingEnd
*
@@ -16955,7 +17067,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param location an array of two integers in which to hold the coordinates
*/
- public void getLocationOnScreen(int[] location) {
+ public void getLocationOnScreen(@Size(2) int[] location) {
getLocationInWindow(location);
final AttachInfo info = mAttachInfo;
@@ -16972,7 +17084,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param location an array of two integers in which to hold the coordinates
*/
- public void getLocationInWindow(int[] location) {
+ public void getLocationInWindow(@Size(2) int[] location) {
if (location == null || location.length < 2) {
throw new IllegalArgumentException("location must be an array of two integers");
}
@@ -17025,7 +17137,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param id the id of the view to be found
* @return the view of the specified id, null if cannot be found
*/
- protected View findViewTraversal(int id) {
+ protected View findViewTraversal(@IdRes int id) {
if (id == mID) {
return this;
}
@@ -17064,7 +17176,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param id The id to search for.
* @return The view that has the given id in the hierarchy or null
*/
- public final View findViewById(int id) {
+ @Nullable
+ public final View findViewById(@IdRes int id) {
if (id < 0) {
return null;
}
@@ -17179,7 +17292,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @attr ref android.R.styleable#View_id
*/
- public void setId(int id) {
+ public void setId(@IdRes int id) {
mID = id;
if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
mID = generateViewId();
@@ -17219,6 +17332,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #findViewById(int)
* @attr ref android.R.styleable#View_id
*/
+ @IdRes
@ViewDebug.CapturedViewProperty
public int getId() {
return mID;
@@ -17277,7 +17391,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* The specified key should be an id declared in the resources of the
* application to ensure it is unique (see the <a
- * href={@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>).
+ * href="{@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>).
* Keys identified as belonging to
* the Android framework or not associated with any package will cause
* an {@link IllegalArgumentException} to be thrown.
@@ -17454,6 +17568,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.</p>
*/
+ @CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
@@ -17575,7 +17690,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
- * should be overriden by subclasses to provide accurate and efficient
+ * should be overridden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
@@ -17688,37 +17803,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Utility to reconcile a desired size and state, with constraints imposed
- * by a MeasureSpec. Will take the desired size, unless a different size
- * is imposed by the constraints. The returned value is a compound integer,
+ * by a MeasureSpec. Will take the desired size, unless a different size
+ * is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
- * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
- * size is smaller than the size the view wants to be.
+ * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
+ * resulting size is smaller than the size the view wants to be.
*
- * @param size How big the view wants to be
- * @param measureSpec Constraints imposed by the parent
+ * @param size How big the view wants to be.
+ * @param measureSpec Constraints imposed by the parent.
+ * @param childMeasuredState Size information bit mask for the view's
+ * children.
* @return Size information bit mask as defined by
- * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
+ * {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
*/
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
- int result = size;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
+ final int specMode = MeasureSpec.getMode(measureSpec);
+ final int specSize = MeasureSpec.getSize(measureSpec);
+ final int result;
switch (specMode) {
- case MeasureSpec.UNSPECIFIED:
- result = size;
- break;
- case MeasureSpec.AT_MOST:
- if (specSize < size) {
- result = specSize | MEASURED_STATE_TOO_SMALL;
- } else {
+ case MeasureSpec.AT_MOST:
+ if (specSize < size) {
+ result = specSize | MEASURED_STATE_TOO_SMALL;
+ } else {
+ result = size;
+ }
+ break;
+ case MeasureSpec.EXACTLY:
+ result = specSize;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
result = size;
- }
- break;
- case MeasureSpec.EXACTLY:
- result = specSize;
- break;
}
- return result | (childMeasuredState&MEASURED_STATE_MASK);
+ return result | (childMeasuredState & MEASURED_STATE_MASK);
}
/**
@@ -17906,6 +18024,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setAnimation(android.view.animation.Animation)
* @see #getAnimation()
*/
+ @CallSuper
protected void onAnimationStart() {
mPrivateFlags |= PFLAG_ANIMATION_STARTED;
}
@@ -17918,6 +18037,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setAnimation(android.view.animation.Animation)
* @see #getAnimation()
*/
+ @CallSuper
protected void onAnimationEnd() {
mPrivateFlags &= ~PFLAG_ANIMATION_STARTED;
}
@@ -18181,7 +18301,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* appearance as the given View. The default also positions the center of the drag shadow
* directly under the touch point. If no View is provided (the constructor with no parameters
* is used), and {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} and
- * {@link #onDrawShadow(Canvas) onDrawShadow()} are not overriden, then the
+ * {@link #onDrawShadow(Canvas) onDrawShadow()} are not overridden, then the
* default is an invisible drag shadow.
* <p>
* You are not required to use the View you provide to the constructor as the basis of the
@@ -18527,7 +18647,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* layout_* parameters.
* @see LayoutInflater
*/
- public static View inflate(Context context, int resource, ViewGroup root) {
+ public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
@@ -18810,7 +18930,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #dispatchNestedPreScroll(int, int, int[], int[])
*/
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
- int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
+ int dxUnconsumed, int dyUnconsumed, @Nullable @Size(2) int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
int startX = 0;
@@ -18858,7 +18978,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return true if the parent consumed some or all of the scroll delta
* @see #dispatchNestedScroll(int, int, int, int, int[])
*/
- public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
+ public boolean dispatchNestedPreScroll(int dx, int dy,
+ @Nullable @Size(2) int[] consumed, @Nullable @Size(2) int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
if (dx != 0 || dy != 0) {
int startX = 0;
diff --git a/core/java/android/view/ViewAssistStructure.java b/core/java/android/view/ViewAssistStructure.java
new file mode 100644
index 0000000..5132bb9
--- /dev/null
+++ b/core/java/android/view/ViewAssistStructure.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.text.TextPaint;
+
+/**
+ * Container for storing additional per-view data generated by {@link View#onProvideAssistStructure
+ * View.onProvideAssistStructure}.
+ */
+public abstract class ViewAssistStructure {
+ public abstract void setText(CharSequence text);
+ public abstract void setText(CharSequence text, int selectionStart, int selectionEnd);
+ public abstract void setTextPaint(TextPaint paint);
+ public abstract void setHint(CharSequence hint);
+
+ public abstract CharSequence getText();
+ public abstract int getTextSelectionStart();
+ public abstract int getTextSelectionEnd();
+ public abstract CharSequence getHint();
+}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f8026d1..87f3e94 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -17,6 +17,7 @@
package android.view;
import android.animation.LayoutTransition;
+import android.annotation.IdRes;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@@ -52,11 +53,8 @@ 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;
/**
@@ -371,6 +369,26 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
static final int FLAG_TOUCHSCREEN_BLOCKS_FOCUS = 0x4000000;
/**
+ * When true, indicates that a call to startActionModeForChild was made with the type parameter
+ * and should not be ignored. This helps in backwards compatibility with the existing method
+ * without a type.
+ *
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+ */
+ private static final int FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED = 0x8000000;
+
+ /**
+ * When true, indicates that a call to startActionModeForChild was made without the type
+ * parameter. This helps in backwards compatibility with the existing method
+ * without a type.
+ *
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+ */
+ private static final int FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED = 0x10000000;
+
+ /**
* Indicates which types of drawing caches are to be kept in memory.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
@@ -481,6 +499,60 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
private int mNestedScrollAxes;
+ /**
+ * Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild.
+ *
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback)
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
+ */
+ private static final ActionMode SENTINEL_ACTION_MODE = new ActionMode() {
+ @Override
+ public void setTitle(CharSequence title) {}
+
+ @Override
+ public void setTitle(int resId) {}
+
+ @Override
+ public void setSubtitle(CharSequence subtitle) {}
+
+ @Override
+ public void setSubtitle(int resId) {}
+
+ @Override
+ public void setCustomView(View view) {}
+
+ @Override
+ public void invalidate() {}
+
+ @Override
+ public void finish() {}
+
+ @Override
+ public Menu getMenu() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getSubtitle() {
+ return null;
+ }
+
+ @Override
+ public View getCustomView() {
+ return null;
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return null;
+ }
+ };
+
public ViewGroup(Context context) {
this(context, null);
}
@@ -696,8 +768,49 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* {@inheritDoc}
*/
+ @Override
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
- return mParent != null ? mParent.startActionModeForChild(originalView, callback) : null;
+ if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED) == 0) {
+ // This is the original call.
+ try {
+ mGroupFlags |= FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;
+ return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY);
+ } finally {
+ mGroupFlags &= ~FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED;
+ }
+ } else {
+ // We are being called from the new method with type.
+ return SENTINEL_ACTION_MODE;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ActionMode startActionModeForChild(
+ View originalView, ActionMode.Callback callback, int type) {
+ if ((mGroupFlags & FLAG_START_ACTION_MODE_FOR_CHILD_IS_NOT_TYPED) == 0) {
+ ActionMode mode;
+ try {
+ mGroupFlags |= FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
+ mode = startActionModeForChild(originalView, callback);
+ } finally {
+ mGroupFlags &= ~FLAG_START_ACTION_MODE_FOR_CHILD_IS_TYPED;
+ }
+ if (mode != SENTINEL_ACTION_MODE) {
+ return mode;
+ }
+ }
+ if (mParent != null) {
+ try {
+ return mParent.startActionModeForChild(originalView, callback, type);
+ } catch (AbstractMethodError ame) {
+ // Custom view parents might not implement this method.
+ return mParent.startActionModeForChild(originalView, callback);
+ }
+ }
+ return null;
}
/**
@@ -771,8 +884,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @see #onRequestSendAccessibilityEvent(View, AccessibilityEvent)
*
* Note: Called from the default {@link View.AccessibilityDelegate}.
+ *
+ * @hide
*/
- boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
return true;
}
@@ -1206,7 +1321,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@inheritDoc}
*/
public void bringChildToFront(View child) {
- int index = indexOfChild(child);
+ final int index = indexOfChild(child);
if (index >= 0) {
removeFromArray(index);
addInArray(child, mChildrenCount);
@@ -2708,8 +2823,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ /** @hide */
@Override
- boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
boolean handled = false;
if (includeForAccessibility()) {
handled = super.dispatchPopulateAccessibilityEventInternal(event);
@@ -2736,8 +2852,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return false;
}
+ /** @hide */
@Override
- void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
if (getAccessibilityNodeProvider() != null) {
return;
@@ -2755,8 +2872,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ /** @hide */
@Override
- void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ViewGroup.class.getName());
}
@@ -3607,7 +3725,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@hide}
*/
@Override
- protected View findViewTraversal(int id) {
+ protected View findViewTraversal(@IdRes int id) {
if (id == mID) {
return this;
}
@@ -3766,7 +3884,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*
* @param child the child view to add
- * @param index the position at which to add the child
+ * @param index the position at which to add the child or -1 to add last
* @param params the layout parameters to set on the child
*/
public void addView(View child, int index, LayoutParams params) {
@@ -3882,7 +4000,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* If index is negative, it means put it at the end of the list.
*
* @param child the view to add to the group
- * @param index the index at which the child must be added
+ * @param index the index at which the child must be added or -1 to add last
* @param params the layout parameters to associate with the child
* @return true if the child was added, false otherwise
*/
@@ -3897,7 +4015,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* If index is negative, it means put it at the end of the list.
*
* @param child the view to add to the group
- * @param index the index at which the child must be added
+ * @param index the index at which the child must be added or -1 to add last
* @param params the layout parameters to associate with the child
* @param preventRequestLayout if true, calling this method will not trigger a
* layout request on child
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 035871d..15b86d1 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -190,7 +190,8 @@ public interface ViewParent {
public void createContextMenu(ContextMenu menu);
/**
- * Start an action mode for the specified view.
+ * Start an action mode for the specified view with the default type
+ * {@link ActionMode#TYPE_PRIMARY}.
*
* <p>In most cases, a subclass does not need to override this. However, if the
* subclass is added directly to the window manager (for example,
@@ -200,17 +201,35 @@ public interface ViewParent {
* @param originalView The source view where the action mode was first invoked
* @param callback The callback that will handle lifecycle events for the action mode
* @return The new action mode if it was started, null otherwise
+ *
+ * @see #startActionModeForChild(View, android.view.ActionMode.Callback, int)
*/
public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback);
/**
+ * Start an action mode of a specific type for the specified view.
+ *
+ * <p>In most cases, a subclass does not need to override this. However, if the
+ * subclass is added directly to the window manager (for example,
+ * {@link ViewManager#addView(View, android.view.ViewGroup.LayoutParams)})
+ * then it should override this and start the action mode.</p>
+ *
+ * @param originalView The source view where the action mode was first invoked
+ * @param callback The callback that will handle lifecycle events for the action mode
+ * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+ * @return The new action mode if it was started, null otherwise
+ */
+ public ActionMode startActionModeForChild(
+ View originalView, ActionMode.Callback callback, int type);
+
+ /**
* This method is called on the parent when a child's drawable state
* has changed.
*
* @param child The child whose drawable state has changed.
*/
public void childDrawableStateChanged(View child);
-
+
/**
* Called when a child does not want this parent and its ancestors to
* intercept touch events with
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index b73b9fa..f18b7ac 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -19,8 +19,6 @@ package android.view;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.animation.TimeInterpolator;
-import android.os.Build;
-
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 90c2bd1..e790d4c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -47,7 +47,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -57,6 +56,7 @@ import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
+import android.util.TimeUtils;
import android.util.TypedValue;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
@@ -77,7 +77,6 @@ import android.widget.Scroller;
import com.android.internal.R;
import com.android.internal.os.SomeArgs;
-import com.android.internal.policy.PolicyManager;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.RootViewSurfaceTaker;
@@ -266,6 +265,8 @@ public final class ViewRootImpl implements ViewParent,
final Rect mDispatchContentInsets = new Rect();
final Rect mDispatchStableInsets = new Rect();
+ private WindowInsets mLastWindowInsets;
+
final Configuration mLastConfiguration = new Configuration();
final Configuration mPendingConfiguration = new Configuration();
@@ -387,7 +388,7 @@ public final class ViewRootImpl implements ViewParent,
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
- mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
+ mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
loadSystemProperties();
@@ -473,12 +474,13 @@ public final class ViewRootImpl implements ViewParent,
// Compute surface insets required to draw at specified Z value.
// TODO: Use real shadow insets for a constant max Z.
- final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
- attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+ if (!attrs.hasManualSurfaceInsets) {
+ final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
+ attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+ }
CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();
mTranslator = compatibilityInfo.getTranslator();
- mDisplayAdjustments.setActivityToken(attrs.token);
// If the application owns the surface, don't enable hardware acceleration
if (mSurfaceHolder == null) {
@@ -550,6 +552,11 @@ public final class ViewRootImpl implements ViewParent,
mPendingContentInsets.set(mAttachInfo.mContentInsets);
mPendingStableInsets.set(mAttachInfo.mStableInsets);
mPendingVisibleInsets.set(0, 0, 0, 0);
+ try {
+ relayoutWindow(attrs, getHostVisibility(), false);
+ } catch (RemoteException e) {
+ if (DEBUG_LAYOUT) Log.e(TAG, "failed to relayoutWindow", e);
+ }
if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
@@ -650,6 +657,10 @@ public final class ViewRootImpl implements ViewParent,
return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
}
+ public CharSequence getTitle() {
+ return mWindowAttributes.getTitle();
+ }
+
void destroyHardwareResources() {
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView);
@@ -761,6 +772,7 @@ public final class ViewRootImpl implements ViewParent,
final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
final int oldSoftInputMode = mWindowAttributes.softInputMode;
+ final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
@@ -787,6 +799,7 @@ public final class ViewRootImpl implements ViewParent,
// Restore old surface insets.
mWindowAttributes.surfaceInsets.set(
oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
+ mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
applyKeepScreenOnFlag(mWindowAttributes);
@@ -1043,7 +1056,7 @@ public final class ViewRootImpl implements ViewParent,
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
- mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
+ mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
@@ -1057,7 +1070,7 @@ public final class ViewRootImpl implements ViewParent,
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
- mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
+ mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
@@ -1066,7 +1079,7 @@ public final class ViewRootImpl implements ViewParent,
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
- mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
+ mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
@@ -1221,13 +1234,29 @@ public final class ViewRootImpl implements ViewParent,
m.postTranslate(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
}
+ /* package */ WindowInsets getWindowInsets(boolean forceConstruct) {
+ if (mLastWindowInsets == null || forceConstruct) {
+ mDispatchContentInsets.set(mAttachInfo.mContentInsets);
+ mDispatchStableInsets.set(mAttachInfo.mStableInsets);
+ Rect contentInsets = mDispatchContentInsets;
+ Rect stableInsets = mDispatchStableInsets;
+ // For dispatch we preserve old logic, but for direct requests from Views we allow to
+ // immediately use pending insets.
+ if (!forceConstruct
+ && (!mPendingContentInsets.equals(contentInsets) ||
+ !mPendingStableInsets.equals(stableInsets))) {
+ contentInsets = mPendingContentInsets;
+ stableInsets = mPendingStableInsets;
+ }
+ final boolean isRound = (mIsEmulator && mIsCircularEmulator) || mWindowIsRound;
+ mLastWindowInsets = new WindowInsets(contentInsets,
+ null /* windowDecorInsets */, stableInsets, isRound);
+ }
+ return mLastWindowInsets;
+ }
+
void dispatchApplyInsets(View host) {
- mDispatchContentInsets.set(mAttachInfo.mContentInsets);
- mDispatchStableInsets.set(mAttachInfo.mStableInsets);
- final boolean isRound = (mIsEmulator && mIsCircularEmulator) || mWindowIsRound;
- host.dispatchApplyWindowInsets(new WindowInsets(
- mDispatchContentInsets, null /* windowDecorInsets */,
- mDispatchStableInsets, isRound));
+ host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));
}
private void performTraversals() {
@@ -1342,12 +1371,17 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ // Non-visible windows can't hold accessibility focus.
+ if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
+ host.clearAccessibilityFocus();
+ }
+
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
boolean insetsChanged = false;
- boolean layoutRequested = mLayoutRequested && !mStopped;
+ boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
@@ -1518,6 +1552,7 @@ public final class ViewRootImpl implements ViewParent,
// to resume them
mDirty.set(0, 0, mWidth, mHeight);
}
+ mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
}
final int surfaceGenerationId = mSurface.getGenerationId();
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
@@ -1779,7 +1814,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
- if (!mStopped) {
+ if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
@@ -1852,7 +1887,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
- final boolean didLayout = layoutRequested && !mStopped;
+ final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
@@ -2274,12 +2309,12 @@ public final class ViewRootImpl implements ViewParent,
final Paint mResizePaint = new Paint();
@Override
- public void onHardwarePreDraw(HardwareCanvas canvas) {
+ public void onHardwarePreDraw(DisplayListCanvas canvas) {
canvas.translate(-mHardwareXOffset, -mHardwareYOffset);
}
@Override
- public void onHardwarePostDraw(HardwareCanvas canvas) {
+ public void onHardwarePostDraw(DisplayListCanvas canvas) {
if (mResizeBuffer != null) {
mResizePaint.setAlpha(mResizeAlpha);
canvas.drawHardwareLayer(mResizeBuffer, mHardwareXOffset, mHardwareYOffset,
@@ -2521,6 +2556,9 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ mAttachInfo.mDrawingTime =
+ mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
+
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
// If accessibility focus moved, always invalidate the root.
@@ -2640,7 +2678,6 @@ public final class ViewRootImpl implements ViewParent,
dirty.setEmpty();
mIsAnimating = false;
- attachInfo.mDrawingTime = SystemClock.uptimeMillis();
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
@@ -3085,17 +3122,6 @@ public final class ViewRootImpl implements ViewParent,
return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
}
- private static void forceLayout(View view) {
- view.forceLayout();
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
- final int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- forceLayout(group.getChildAt(i));
- }
- }
- }
-
private final static int MSG_INVALIDATE = 1;
private final static int MSG_INVALIDATE_RECT = 2;
private final static int MSG_DIE = 3;
@@ -3229,10 +3255,6 @@ public final class ViewRootImpl implements ViewParent,
mReportNextDraw = true;
}
- if (mView != null) {
- forceLayout(mView);
- }
-
requestLayout();
}
break;
@@ -3247,9 +3269,6 @@ public final class ViewRootImpl implements ViewParent,
mWinFrame.top = t;
mWinFrame.bottom = t + h;
- if (mView != null) {
- forceLayout(mView);
- }
requestLayout();
}
break;
@@ -5794,6 +5813,16 @@ public final class ViewRootImpl implements ViewParent,
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
+ long eventTime = q.mEvent.getEventTimeNano();
+ long oldestEventTime = eventTime;
+ if (q.mEvent instanceof MotionEvent) {
+ MotionEvent me = (MotionEvent)q.mEvent;
+ if (me.getHistorySize() > 0) {
+ oldestEventTime = me.getHistoricalEventTimeNano(0);
+ }
+ }
+ mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
+
deliverInputEvent(q);
}
@@ -6198,6 +6227,12 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
+ public ActionMode startActionModeForChild(
+ View originalView, ActionMode.Callback callback, int type) {
+ return null;
+ }
+
+ @Override
public void createContextMenu(ContextMenu menu) {
}
diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java
index d68a860..ec852e8 100644
--- a/core/java/android/view/ViewStub.java
+++ b/core/java/android/view/ViewStub.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -69,8 +71,8 @@ import java.lang.ref.WeakReference;
*/
@RemoteView
public final class ViewStub extends View {
- private int mLayoutResource = 0;
private int mInflatedId;
+ private int mLayoutResource;
private WeakReference<View> mInflatedViewRef;
@@ -78,7 +80,7 @@ public final class ViewStub extends View {
private OnInflateListener mInflateListener;
public ViewStub(Context context) {
- initialize(context);
+ this(context, 0);
}
/**
@@ -87,39 +89,30 @@ public final class ViewStub extends View {
* @param context The application's environment.
* @param layoutResource The reference to a layout resource that will be inflated.
*/
- public ViewStub(Context context, int layoutResource) {
+ public ViewStub(Context context, @LayoutRes int layoutResource) {
+ this(context, null);
+
mLayoutResource = layoutResource;
- initialize(context);
}
public ViewStub(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- @SuppressWarnings({"UnusedDeclaration"})
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes);
+ super(context);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.ViewStub, defStyleAttr, defStyleRes);
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
-
- a.recycle();
-
- a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
- mID = a.getResourceId(R.styleable.View_id, NO_ID);
+ mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
- initialize(context);
- }
-
- private void initialize(Context context) {
- mContext = context;
setVisibility(GONE);
setWillNotDraw(true);
}
@@ -134,6 +127,7 @@ public final class ViewStub extends View {
* @see #setInflatedId(int)
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
+ @IdRes
public int getInflatedId() {
return mInflatedId;
}
@@ -149,7 +143,7 @@ public final class ViewStub extends View {
* @attr ref android.R.styleable#ViewStub_inflatedId
*/
@android.view.RemotableViewMethod
- public void setInflatedId(int inflatedId) {
+ public void setInflatedId(@IdRes int inflatedId) {
mInflatedId = inflatedId;
}
@@ -165,6 +159,7 @@ public final class ViewStub extends View {
* @see #inflate()
* @attr ref android.R.styleable#ViewStub_layout
*/
+ @LayoutRes
public int getLayoutResource() {
return mLayoutResource;
}
@@ -182,7 +177,7 @@ public final class ViewStub extends View {
* @attr ref android.R.styleable#ViewStub_layout
*/
@android.view.RemotableViewMethod
- public void setLayoutResource(int layoutResource) {
+ public void setLayoutResource(@LayoutRes int layoutResource) {
mLayoutResource = layoutResource;
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8704356..9a92932 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -16,8 +16,13 @@
package android.view;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StyleRes;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.res.Configuration;
@@ -42,10 +47,8 @@ import android.view.accessibility.AccessibilityEvent;
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
- * android.policy.PhoneWindow, which you should instantiate when needing a
- * Window. Eventually that class will be refactored and a factory method
- * added for creating Window instances without knowing about a particular
- * implementation.
+ * android.view.PhoneWindow, which you should instantiate when needing a
+ * Window.
*/
public abstract class Window {
/** Flag for the "options panel" feature. This is enabled by default. */
@@ -414,7 +417,9 @@ public abstract class Window {
* Called when an action mode is being started for this window. Gives the
* callback an opportunity to handle the action mode in its own unique and
* beautiful way. If this method returns null the system can choose a way
- * to present the mode or choose not to start the mode at all.
+ * to present the mode or choose not to start the mode at all. This is equivalent
+ * to {@link #onWindowStartingActionMode(android.view.ActionMode.Callback, int)}
+ * with type {@link ActionMode#TYPE_PRIMARY}.
*
* @param callback Callback to control the lifecycle of this action mode
* @return The ActionMode that was started, or null if the system should present it
@@ -423,6 +428,19 @@ public abstract class Window {
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback);
/**
+ * Called when an action mode is being started for this window. Gives the
+ * callback an opportunity to handle the action mode in its own unique and
+ * beautiful way. If this method returns null the system can choose a way
+ * to present the mode or choose not to start the mode at all.
+ *
+ * @param callback Callback to control the lifecycle of this action mode
+ * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+ * @return The ActionMode that was started, or null if the system should present it
+ */
+ @Nullable
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type);
+
+ /**
* Called when an action mode has been started. The appropriate mode callback
* method will have already been invoked.
*
@@ -736,7 +754,7 @@ public abstract class Window {
* 0 here will override the animations the window would
* normally retrieve from its theme.
*/
- public void setWindowAnimations(int resId) {
+ public void setWindowAnimations(@StyleRes int resId) {
final WindowManager.LayoutParams attrs = getAttributes();
attrs.windowAnimations = resId;
dispatchWindowAttributesChanged(attrs);
@@ -986,7 +1004,8 @@ public abstract class Window {
*
* @return The view if found or null otherwise.
*/
- public View findViewById(int id) {
+ @Nullable
+ public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
@@ -999,7 +1018,7 @@ public abstract class Window {
* @param layoutResID Resource ID to be inflated.
* @see #setContentView(View, android.view.ViewGroup.LayoutParams)
*/
- public abstract void setContentView(int layoutResID);
+ public abstract void setContentView(@LayoutRes int layoutResID);
/**
* Convenience for
@@ -1067,7 +1086,7 @@ public abstract class Window {
public abstract void setTitle(CharSequence title);
@Deprecated
- public abstract void setTitleColor(int textColor);
+ public abstract void setTitleColor(@ColorInt int textColor);
public abstract void openPanel(int featureId, KeyEvent event);
@@ -1129,7 +1148,7 @@ public abstract class Window {
* @param resId The resource identifier of a drawable resource which will
* be installed as the new background.
*/
- public void setBackgroundDrawableResource(int resId) {
+ public void setBackgroundDrawableResource(@DrawableRes int resId) {
setBackgroundDrawable(mContext.getDrawable(resId));
}
@@ -1145,7 +1164,7 @@ public abstract class Window {
/**
* Set the value for a drawable feature of this window, from a resource
- * identifier. You must have called requestFeauture(featureId) before
+ * identifier. You must have called requestFeature(featureId) before
* calling this function.
*
* @see android.content.res.Resources#getDrawable(int)
@@ -1154,7 +1173,7 @@ public abstract class Window {
* constant by Window.
* @param resId Resource identifier of the desired image.
*/
- public abstract void setFeatureDrawableResource(int featureId, int resId);
+ public abstract void setFeatureDrawableResource(int featureId, @DrawableRes int resId);
/**
* Set the value for a drawable feature of this window, from a URI. You
@@ -1424,7 +1443,7 @@ public abstract class Window {
*
* @param resId resource ID of a drawable to set
*/
- public void setIcon(int resId) { }
+ public void setIcon(@DrawableRes int resId) { }
/**
* Set the default icon for this window.
@@ -1433,7 +1452,7 @@ public abstract class Window {
*
* @hide
*/
- public void setDefaultIcon(int resId) { }
+ public void setDefaultIcon(@DrawableRes int resId) { }
/**
* Set the logo for this window. A logo is often shown in place of an
@@ -1442,7 +1461,7 @@ public abstract class Window {
*
* @param resId resource ID of a drawable to set
*/
- public void setLogo(int resId) { }
+ public void setLogo(@DrawableRes int resId) { }
/**
* Set the default logo for this window.
@@ -1451,7 +1470,7 @@ public abstract class Window {
*
* @hide
*/
- public void setDefaultLogo(int resId) { }
+ public void setDefaultLogo(@DrawableRes int resId) { }
/**
* Set focus locally. The window should have the
@@ -1833,6 +1852,7 @@ public abstract class Window {
/**
* @return the color of the status bar.
*/
+ @ColorInt
public abstract int getStatusBarColor();
/**
@@ -1850,11 +1870,12 @@ public abstract class Window {
* The transitionName for the view background will be "android:status:background".
* </p>
*/
- public abstract void setStatusBarColor(int color);
+ public abstract void setStatusBarColor(@ColorInt int color);
/**
* @return the color of the navigation bar.
*/
+ @ColorInt
public abstract int getNavigationBarColor();
/**
@@ -1872,7 +1893,7 @@ public abstract class Window {
* The transitionName for the view background will be "android:navigation:background".
* </p>
*/
- public abstract void setNavigationBarColor(int color);
+ public abstract void setNavigationBarColor(@ColorInt int color);
}
diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java
index 35a6a76..979ee95 100644
--- a/core/java/android/view/WindowCallbackWrapper.java
+++ b/core/java/android/view/WindowCallbackWrapper.java
@@ -132,6 +132,11 @@ public class WindowCallbackWrapper implements Window.Callback {
}
@Override
+ public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+ return mWrapped.onWindowStartingActionMode(callback, type);
+ }
+
+ @Override
public void onActionModeStarted(ActionMode mode) {
mWrapped.onActionModeStarted(mode);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d26bcd3..66dae7b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -502,13 +502,6 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
/**
- * Window type: Behind the universe of the real windows.
- * In multiuser systems shows on all users' windows.
- * @hide
- */
- public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;
-
- /**
* Window type: Display overlay window. Used to simulate secondary display devices.
* In multiuser systems shows on all users' windows.
* @hide
@@ -1333,6 +1326,16 @@ public interface WindowManager extends ViewManager {
* @hide
*/
public final Rect surfaceInsets = new Rect();
+
+ /**
+ * Whether the surface insets have been manually set. When set to
+ * {@code false}, the view root will automatically determine the
+ * appropriate surface insets.
+ *
+ * @see #surfaceInsets
+ * @hide
+ */
+ public boolean hasManualSurfaceInsets;
/**
* The desired bitmap format. May be one of the constants in
@@ -1641,6 +1644,7 @@ public interface WindowManager extends ViewManager {
out.writeInt(surfaceInsets.top);
out.writeInt(surfaceInsets.right);
out.writeInt(surfaceInsets.bottom);
+ out.writeInt(hasManualSurfaceInsets ? 1 : 0);
out.writeInt(needsMenuKey);
}
@@ -1689,6 +1693,7 @@ public interface WindowManager extends ViewManager {
surfaceInsets.top = in.readInt();
surfaceInsets.right = in.readInt();
surfaceInsets.bottom = in.readInt();
+ hasManualSurfaceInsets = in.readInt() != 0;
needsMenuKey = in.readInt();
}
@@ -1871,6 +1876,11 @@ public interface WindowManager extends ViewManager {
changes |= SURFACE_INSETS_CHANGED;
}
+ if (hasManualSurfaceInsets != o.hasManualSurfaceInsets) {
+ hasManualSurfaceInsets = o.hasManualSurfaceInsets;
+ changes |= SURFACE_INSETS_CHANGED;
+ }
+
if (needsMenuKey != o.needsMenuKey) {
needsMenuKey = o.needsMenuKey;
changes |= NEEDS_MENU_KEY_CHANGED;
@@ -1980,8 +1990,11 @@ public interface WindowManager extends ViewManager {
sb.append(" userActivityTimeout=").append(userActivityTimeout);
}
if (surfaceInsets.left != 0 || surfaceInsets.top != 0 || surfaceInsets.right != 0 ||
- surfaceInsets.bottom != 0) {
+ surfaceInsets.bottom != 0 || hasManualSurfaceInsets) {
sb.append(" surfaceInsets=").append(surfaceInsets);
+ if (hasManualSurfaceInsets) {
+ sb.append(" (manual)");
+ }
}
if (needsMenuKey != NEEDS_MENU_UNSET) {
sb.append(" needsMenuKey=");
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index a14c766..57558ff 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -190,6 +190,39 @@ public final class WindowManagerGlobal {
}
}
+ public ArrayList<ViewRootImpl> getRootViews(IBinder token) {
+ ArrayList<ViewRootImpl> views = new ArrayList<>();
+ synchronized (mLock) {
+ final int numRoots = mRoots.size();
+ for (int i = 0; i < numRoots; ++i) {
+ WindowManager.LayoutParams params = mParams.get(i);
+ if (params.token == null) {
+ continue;
+ }
+ if (params.token != token) {
+ boolean isChild = false;
+ if (params.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
+ && params.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ for (int j = 0 ; j < numRoots; ++j) {
+ View viewj = mViews.get(j);
+ WindowManager.LayoutParams paramsj = mParams.get(j);
+ if (params.token == viewj.getWindowToken()
+ && paramsj.token == token) {
+ isChild = true;
+ break;
+ }
+ }
+ }
+ if (!isChild) {
+ continue;
+ }
+ }
+ views.add(mRoots.get(i));
+ }
+ }
+ return views;
+ }
+
public View getRootView(String name) {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
@@ -213,15 +246,15 @@ public final class WindowManagerGlobal {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
- final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
+ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
- // If there's no parent and we're running on L or above (or in the
- // system context), assume we want hardware acceleration.
+ // If there's no parent, then hardware acceleration for this view is
+ // set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
- && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
+ && context.getApplicationInfo().hardwareAccelerated) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
@@ -459,7 +492,7 @@ public final class WindowManagerGlobal {
}
}
- public void dumpGfxInfo(FileDescriptor fd) {
+ public void dumpGfxInfo(FileDescriptor fd, String[] args) {
FileOutputStream fout = new FileOutputStream(fd);
PrintWriter pw = new FastPrintWriter(fout);
try {
@@ -476,7 +509,7 @@ public final class WindowManagerGlobal {
HardwareRenderer renderer =
root.getView().mAttachInfo.mHardwareRenderer;
if (renderer != null) {
- renderer.dumpGfxInfo(pw, fd);
+ renderer.dumpGfxInfo(pw, fd, args);
}
}
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index f557b97..7b4640b 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -20,7 +20,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
import android.os.IBinder;
-import android.os.IRemoteCallback;
+import android.view.animation.Animation;
import java.util.List;
@@ -85,6 +85,41 @@ public abstract class WindowManagerInternal {
}
/**
+ * Abstract class to be notified about {@link com.android.server.wm.AppTransition} events. Held
+ * as an abstract class so a listener only needs to implement the methods of its interest.
+ */
+ public static abstract class AppTransitionListener {
+
+ /**
+ * Called when an app transition is being setup and about to be executed.
+ */
+ public void onAppTransitionPendingLocked() {}
+
+ /**
+ * Called when a pending app transition gets cancelled.
+ */
+ public void onAppTransitionCancelledLocked() {}
+
+ /**
+ * Called when an app transition gets started
+ *
+ * @param openToken the token for the opening app
+ * @param closeToken the token for the closing app
+ * @param openAnimation the animation for the opening app
+ * @param closeAnimation the animation for the closing app
+ */
+ public void onAppTransitionStartingLocked(IBinder openToken, IBinder closeToken,
+ Animation openAnimation, Animation closeAnimation) {}
+
+ /**
+ * Called when an app transition is finished running.
+ *
+ * @param token the token for app whose transition has finished
+ */
+ public void onAppTransitionFinishedLocked(IBinder token) {}
+ }
+
+ /**
* Request that the window manager call
* {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
* within a surface transaction at a later time.
@@ -189,4 +224,11 @@ public abstract class WindowManagerInternal {
* @param removeWindows Whether to also remove the windows associated with the token.
*/
public abstract void removeWindowToken(android.os.IBinder token, boolean removeWindows);
+
+ /**
+ * Registers a listener to be notified about app transition events.
+ *
+ * @param listener The listener to register.
+ */
+ public abstract void registerAppTransitionListener(AppTransitionListener listener);
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 780ca99..9199af1 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -365,6 +365,11 @@ public interface WindowManagerPolicy {
* @return true if window is on default display.
*/
public boolean isDefaultDisplay();
+
+ /**
+ * Check whether the window is currently dimming.
+ */
+ public boolean isDimming();
}
/**
@@ -587,13 +592,6 @@ public interface WindowManagerPolicy {
public int getMaxWallpaperLayer();
/**
- * Return the window layer at which windows appear above the normal
- * universe (that is no longer impacted by the universe background
- * transform).
- */
- public int getAboveUniverseLayer();
-
- /**
* Return the display width available after excluding any screen
* decorations that can never be removed. That is, system bar or
* button bar.
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index cefd34d..db78ec5 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,7 +17,6 @@
package android.view.accessibility;
import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.graphics.Point;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index b5afdf7..6096d7d 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -721,7 +721,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return Whether the refresh succeeded.
*/
public boolean refresh() {
- return refresh(false);
+ return refresh(true);
}
/**
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 85d77cb..be43952 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -16,6 +16,8 @@
package android.view.animation;
+import android.annotation.ColorInt;
+import android.annotation.InterpolatorRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.RectF;
@@ -387,7 +389,7 @@ public abstract class Animation implements Cloneable {
* @param resID The resource identifier of the interpolator to load
* @attr ref android.R.styleable#Animation_interpolator
*/
- public void setInterpolator(Context context, int resID) {
+ public void setInterpolator(Context context, @InterpolatorRes int resID) {
setInterpolator(AnimationUtils.loadInterpolator(context, resID));
}
@@ -622,7 +624,7 @@ public abstract class Animation implements Cloneable {
* @param bg The background color. If 0, no background. Currently must
* be black, with any desired alpha level.
*/
- public void setBackgroundColor(int bg) {
+ public void setBackgroundColor(@ColorInt int bg) {
mBackgroundColor = bg;
}
@@ -753,6 +755,7 @@ public abstract class Animation implements Cloneable {
/**
* Returns the background color behind the animation.
*/
+ @ColorInt
public int getBackgroundColor() {
return mBackgroundColor;
}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 606c83e..4d1209a 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -19,6 +19,8 @@ package android.view.animation;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.AnimRes;
+import android.annotation.InterpolatorRes;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -65,7 +67,7 @@ public class AnimationUtils {
* @return The animation object reference by the specified id
* @throws NotFoundException when the animation cannot be loaded
*/
- public static Animation loadAnimation(Context context, int id)
+ public static Animation loadAnimation(Context context, @AnimRes int id)
throws NotFoundException {
XmlResourceParser parser = null;
@@ -143,7 +145,7 @@ public class AnimationUtils {
* @return The animation object reference by the specified id
* @throws NotFoundException when the layout animation controller cannot be loaded
*/
- public static LayoutAnimationController loadLayoutAnimation(Context context, int id)
+ public static LayoutAnimationController loadLayoutAnimation(Context context, @AnimRes int id)
throws NotFoundException {
XmlResourceParser parser = null;
@@ -266,7 +268,8 @@ public class AnimationUtils {
* @return The animation object reference by the specified id
* @throws NotFoundException
*/
- public static Interpolator loadInterpolator(Context context, int id) throws NotFoundException {
+ public static Interpolator loadInterpolator(Context context, @InterpolatorRes int id)
+ throws NotFoundException {
XmlResourceParser parser = null;
try {
parser = context.getResources().getAnimation(id);
diff --git a/core/java/android/view/animation/ClipRectAnimation.java b/core/java/android/view/animation/ClipRectAnimation.java
index 2361501..e194927 100644
--- a/core/java/android/view/animation/ClipRectAnimation.java
+++ b/core/java/android/view/animation/ClipRectAnimation.java
@@ -26,8 +26,8 @@ import android.graphics.Rect;
* @hide
*/
public class ClipRectAnimation extends Animation {
- private Rect mFromRect = new Rect();
- private Rect mToRect = new Rect();
+ protected Rect mFromRect = new Rect();
+ protected Rect mToRect = new Rect();
/**
* Constructor to use when building a ClipRectAnimation from code
@@ -43,6 +43,15 @@ public class ClipRectAnimation extends Animation {
mToRect.set(toClip);
}
+ /**
+ * Constructor to use when building a ClipRectAnimation from code
+ */
+ public ClipRectAnimation(int fromL, int fromT, int fromR, int fromB,
+ int toL, int toT, int toR, int toB) {
+ mFromRect.set(fromL, fromT, fromR, fromB);
+ mToRect.set(toL, toT, toR, toB);
+ }
+
@Override
protected void applyTransformation(float it, Transformation tr) {
int l = mFromRect.left + (int) ((mToRect.left - mFromRect.left) * it);
diff --git a/core/java/android/view/animation/ClipRectLRAnimation.java b/core/java/android/view/animation/ClipRectLRAnimation.java
new file mode 100644
index 0000000..8993cd3
--- /dev/null
+++ b/core/java/android/view/animation/ClipRectLRAnimation.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.graphics.Rect;
+
+/**
+ * Special case of ClipRectAnimation that animates only the left/right
+ * dimensions of the clip, picking up the other dimensions from whatever is
+ * set on the transform already.
+ *
+ * @hide
+ */
+public class ClipRectLRAnimation extends ClipRectAnimation {
+
+ /**
+ * Constructor. Passes in 0 for Top/Bottom parameters of ClipRectAnimation
+ */
+ public ClipRectLRAnimation(int fromL, int fromR, int toL, int toR) {
+ super(fromL, 0, fromR, 0, toL, 0, toR, 0);
+ }
+
+ /**
+ * Calculates and sets clip rect on given transformation. It uses existing values
+ * on the Transformation for Top/Bottom clip parameters.
+ */
+ @Override
+ protected void applyTransformation(float it, Transformation tr) {
+ Rect oldClipRect = tr.getClipRect();
+ tr.setClipRect(mFromRect.left + (int) ((mToRect.left - mFromRect.left) * it),
+ oldClipRect.top,
+ mFromRect.right + (int) ((mToRect.right - mFromRect.right) * it),
+ oldClipRect.bottom);
+ }
+}
diff --git a/core/java/android/view/animation/ClipRectTBAnimation.java b/core/java/android/view/animation/ClipRectTBAnimation.java
new file mode 100644
index 0000000..06f86ce
--- /dev/null
+++ b/core/java/android/view/animation/ClipRectTBAnimation.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.graphics.Rect;
+
+/**
+ * Special case of ClipRectAnimation that animates only the top/bottom
+ * dimensions of the clip, picking up the other dimensions from whatever is
+ * set on the transform already.
+ *
+ * @hide
+ */
+public class ClipRectTBAnimation extends ClipRectAnimation {
+
+ /**
+ * Constructor. Passes in 0 for Left/Right parameters of ClipRectAnimation
+ */
+ public ClipRectTBAnimation(int fromT, int fromB, int toT, int toB) {
+ super(0, fromT, 0, fromB, 0, toT, 0, toB);
+ }
+
+ /**
+ * Calculates and sets clip rect on given transformation. It uses existing values
+ * on the Transformation for Left/Right clip parameters.
+ */
+ @Override
+ protected void applyTransformation(float it, Transformation tr) {
+ Rect oldClipRect = tr.getClipRect();
+ tr.setClipRect(oldClipRect.left, mFromRect.top + (int) ((mToRect.top - mFromRect.top) * it),
+ oldClipRect.right,
+ mFromRect.bottom + (int) ((mToRect.bottom - mFromRect.bottom) * it));
+ }
+
+}
diff --git a/core/java/android/view/animation/LayoutAnimationController.java b/core/java/android/view/animation/LayoutAnimationController.java
index 882e738..df2f18c 100644
--- a/core/java/android/view/animation/LayoutAnimationController.java
+++ b/core/java/android/view/animation/LayoutAnimationController.java
@@ -16,6 +16,8 @@
package android.view.animation;
+import android.annotation.AnimRes;
+import android.annotation.InterpolatorRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@@ -180,7 +182,7 @@ public class LayoutAnimationController {
*
* @attr ref android.R.styleable#LayoutAnimation_animation
*/
- public void setAnimation(Context context, int resourceID) {
+ public void setAnimation(Context context, @AnimRes int resourceID) {
setAnimation(AnimationUtils.loadAnimation(context, resourceID));
}
@@ -225,7 +227,7 @@ public class LayoutAnimationController {
*
* @attr ref android.R.styleable#LayoutAnimation_interpolator
*/
- public void setInterpolator(Context context, int resourceID) {
+ public void setInterpolator(Context context, @InterpolatorRes int resourceID) {
setInterpolator(AnimationUtils.loadInterpolator(context, resourceID));
}
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index 2f4fe73..8eb5b5c 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -16,6 +16,7 @@
package android.view.animation;
+import android.annotation.FloatRange;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -122,7 +123,13 @@ public class Transformation {
mAlpha *= t.getAlpha();
mMatrix.preConcat(t.getMatrix());
if (t.mHasClipRect) {
- setClipRect(t.getClipRect());
+ Rect bounds = t.getClipRect();
+ if (mHasClipRect) {
+ setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
+ mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
+ } else {
+ setClipRect(bounds);
+ }
}
}
@@ -135,7 +142,13 @@ public class Transformation {
mAlpha *= t.getAlpha();
mMatrix.postConcat(t.getMatrix());
if (t.mHasClipRect) {
- setClipRect(t.getClipRect());
+ Rect bounds = t.getClipRect();
+ if (mHasClipRect) {
+ setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
+ mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
+ } else {
+ setClipRect(bounds);
+ }
}
}
@@ -151,7 +164,7 @@ public class Transformation {
* Sets the degree of transparency
* @param alpha 1.0 means fully opaqe and 0.0 means fully transparent
*/
- public void setAlpha(float alpha) {
+ public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
mAlpha = alpha;
}
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
index d2ff754..216022b 100644
--- a/core/java/android/view/animation/TranslateAnimation.java
+++ b/core/java/android/view/animation/TranslateAnimation.java
@@ -24,7 +24,7 @@ import android.util.AttributeSet;
* An animation that controls the position of an object. See the
* {@link android.view.animation full package} description for details and
* sample code.
- *
+ *
*/
public class TranslateAnimation extends Animation {
private int mFromXType = ABSOLUTE;
@@ -33,20 +33,28 @@ public class TranslateAnimation extends Animation {
private int mFromYType = ABSOLUTE;
private int mToYType = ABSOLUTE;
- private float mFromXValue = 0.0f;
- private float mToXValue = 0.0f;
-
- private float mFromYValue = 0.0f;
- private float mToYValue = 0.0f;
-
- private float mFromXDelta;
- private float mToXDelta;
- private float mFromYDelta;
- private float mToYDelta;
+ /** @hide */
+ protected float mFromXValue = 0.0f;
+ /** @hide */
+ protected float mToXValue = 0.0f;
+
+ /** @hide */
+ protected float mFromYValue = 0.0f;
+ /** @hide */
+ protected float mToYValue = 0.0f;
+
+ /** @hide */
+ protected float mFromXDelta;
+ /** @hide */
+ protected float mToXDelta;
+ /** @hide */
+ protected float mFromYDelta;
+ /** @hide */
+ protected float mToYDelta;
/**
* Constructor used when a TranslateAnimation is loaded from a resource.
- *
+ *
* @param context Application context to use
* @param attrs Attribute set from which to read values
*/
@@ -81,7 +89,7 @@ public class TranslateAnimation extends Animation {
/**
* Constructor to use when building a TranslateAnimation from code
- *
+ *
* @param fromXDelta Change in X coordinate to apply at the start of the
* animation
* @param toXDelta Change in X coordinate to apply at the end of the
diff --git a/core/java/android/view/animation/TranslateXAnimation.java b/core/java/android/view/animation/TranslateXAnimation.java
new file mode 100644
index 0000000..d75323f
--- /dev/null
+++ b/core/java/android/view/animation/TranslateXAnimation.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.graphics.Matrix;
+
+/**
+ * Special case of TranslateAnimation that translates only horizontally, picking up the
+ * vertical values from whatever is set on the Transformation already. When used in
+ * conjunction with a TranslateYAnimation, allows independent animation of x and y
+ * position.
+ * @hide
+ */
+public class TranslateXAnimation extends TranslateAnimation {
+ float[] mTmpValues = new float[9];
+
+ /**
+ * Constructor. Passes in 0 for the y parameters of TranslateAnimation
+ */
+ public TranslateXAnimation(float fromXDelta, float toXDelta) {
+ super(fromXDelta, toXDelta, 0, 0);
+ }
+
+ /**
+ * Constructor. Passes in 0 for the y parameters of TranslateAnimation
+ */
+ public TranslateXAnimation(int fromXType, float fromXValue, int toXType, float toXValue) {
+ super(fromXType, fromXValue, toXType, toXValue, ABSOLUTE, 0, ABSOLUTE, 0);
+ }
+
+ /**
+ * Calculates and sets x translation values on given transformation.
+ */
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ Matrix m = t.getMatrix();
+ m.getValues(mTmpValues);
+ float dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
+ t.getMatrix().setTranslate(dx, mTmpValues[Matrix.MTRANS_Y]);
+ }
+}
diff --git a/core/java/android/view/animation/TranslateYAnimation.java b/core/java/android/view/animation/TranslateYAnimation.java
new file mode 100644
index 0000000..714558d
--- /dev/null
+++ b/core/java/android/view/animation/TranslateYAnimation.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+
+import android.graphics.Matrix;
+
+/**
+ * Special case of TranslateAnimation that translates only vertically, picking up the
+ * horizontal values from whatever is set on the Transformation already. When used in
+ * conjunction with a TranslateXAnimation, allows independent animation of x and y
+ * position.
+ * @hide
+ */
+public class TranslateYAnimation extends TranslateAnimation {
+ float[] mTmpValues = new float[9];
+
+ /**
+ * Constructor. Passes in 0 for the x parameters of TranslateAnimation
+ */
+ public TranslateYAnimation(float fromYDelta, float toYDelta) {
+ super(0, 0, fromYDelta, toYDelta);
+ }
+
+ /**
+ * Constructor. Passes in 0 for the x parameters of TranslateAnimation
+ */
+ public TranslateYAnimation(int fromYType, float fromYValue, int toYType, float toYValue) {
+ super(ABSOLUTE, 0, ABSOLUTE, 0, fromYType, fromYValue, toYType, toYValue);
+ }
+
+ /**
+ * Calculates and sets y translation values on given transformation.
+ */
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ Matrix m = t.getMatrix();
+ m.getValues(mTmpValues);
+ float dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
+ t.getMatrix().setTranslate(mTmpValues[Matrix.MTRANS_X], dy);
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 1416e1b..78604bf 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -27,7 +27,6 @@ import com.android.internal.view.InputBindResult;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 1671faa..c2f3777 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -23,6 +23,8 @@ import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.inputmethod.InputMethodUtils;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -379,7 +381,7 @@ public final class InputMethodSubtype implements Parcelable {
*/
public CharSequence getDisplayName(
Context context, String packageName, ApplicationInfo appInfo) {
- final Locale locale = constructLocaleFromString(mSubtypeLocale);
+ final Locale locale = InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
if (mSubtypeNameResId == 0) {
return localeStr;
@@ -503,22 +505,6 @@ public final class InputMethodSubtype implements Parcelable {
}
};
- private static Locale constructLocaleFromString(String localeStr) {
- if (TextUtils.isEmpty(localeStr))
- return null;
- String[] localeParams = localeStr.split("_", 3);
- // The length of localeStr is guaranteed to always return a 1 <= value <= 3
- // because localeStr is not empty.
- if (localeParams.length == 1) {
- return new Locale(localeParams[0]);
- } else if (localeParams.length == 2) {
- return new Locale(localeParams[0], localeParams[1]);
- } else if (localeParams.length == 3) {
- return new Locale(localeParams[0], localeParams[1], localeParams[2]);
- }
- return null;
- }
-
private static int hashCodeInternal(String locale, String mode, String extraValue,
boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
boolean isAsciiCapable) {
diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
index 5bef71f..6a748ce 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -17,15 +17,10 @@
package android.view.inputmethod;
import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.AndroidRuntimeException;
import android.util.Slog;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@@ -203,43 +198,20 @@ public class InputMethodSubtypeArray {
}
private static byte[] compress(final byte[] data) {
- ByteArrayOutputStream resultStream = null;
- GZIPOutputStream zipper = null;
- try {
- resultStream = new ByteArrayOutputStream();
- zipper = new GZIPOutputStream(resultStream);
+ try (final ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
+ final GZIPOutputStream zipper = new GZIPOutputStream(resultStream)) {
zipper.write(data);
- } catch(IOException e) {
+ zipper.finish();
+ return resultStream.toByteArray();
+ } catch(Exception e) {
+ Slog.e(TAG, "Failed to compress the data.", e);
return null;
- } finally {
- try {
- if (zipper != null) {
- zipper.close();
- }
- } catch (IOException e) {
- zipper = null;
- Slog.e(TAG, "Failed to close the stream.", e);
- // swallowed, not propagated back to the caller
- }
- try {
- if (resultStream != null) {
- resultStream.close();
- }
- } catch (IOException e) {
- resultStream = null;
- Slog.e(TAG, "Failed to close the stream.", e);
- // swallowed, not propagated back to the caller
- }
}
- return resultStream != null ? resultStream.toByteArray() : null;
}
private static byte[] decompress(final byte[] data, final int expectedSize) {
- ByteArrayInputStream inputStream = null;
- GZIPInputStream unzipper = null;
- try {
- inputStream = new ByteArrayInputStream(data);
- unzipper = new GZIPInputStream(inputStream);
+ try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+ final GZIPInputStream unzipper = new GZIPInputStream(inputStream)) {
final byte [] result = new byte[expectedSize];
int totalReadBytes = 0;
while (totalReadBytes < result.length) {
@@ -254,25 +226,9 @@ public class InputMethodSubtypeArray {
return null;
}
return result;
- } catch(IOException e) {
+ } catch(Exception e) {
+ Slog.e(TAG, "Failed to decompress the data.", e);
return null;
- } finally {
- try {
- if (unzipper != null) {
- unzipper.close();
- }
- } catch (IOException e) {
- Slog.e(TAG, "Failed to close the stream.", e);
- // swallowed, not propagated back to the caller
- }
- try {
- if (inputStream != null) {
- inputStream.close();
- }
- } catch (IOException e) {
- Slog.e(TAG, "Failed to close the stream.", e);
- // swallowed, not propagated back to the caller
- }
}
}
}
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index eca96f9..6d5fac1 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -31,10 +31,7 @@ public abstract class CookieManager {
}
/**
- * Gets the singleton CookieManager instance. If this method is used
- * before the application instantiates a {@link WebView} instance,
- * {@link CookieSyncManager#createInstance CookieSyncManager.createInstance(Context)}
- * must be called first.
+ * Gets the singleton CookieManager instance.
*
* @return the singleton CookieManager instance
*/
diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java
index d9546ca..eda8d36 100644
--- a/core/java/android/webkit/CookieSyncManager.java
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -17,7 +17,6 @@
package android.webkit;
import android.content.Context;
-import android.util.Log;
/**
diff --git a/core/java/android/webkit/LegacyErrorStrings.java b/core/java/android/webkit/LegacyErrorStrings.java
index 11fc05d..60a6ee1 100644
--- a/core/java/android/webkit/LegacyErrorStrings.java
+++ b/core/java/android/webkit/LegacyErrorStrings.java
@@ -17,7 +17,6 @@
package android.webkit;
import android.content.Context;
-import android.net.http.EventHandler;
import android.util.Log;
/**
@@ -44,52 +43,52 @@ class LegacyErrorStrings {
*/
private static int getResource(int errorCode) {
switch(errorCode) {
- case EventHandler.OK:
+ case 0: /* EventHandler.OK: */
return com.android.internal.R.string.httpErrorOk;
- case EventHandler.ERROR:
+ case -1: /* EventHandler.ERROR: */
return com.android.internal.R.string.httpError;
- case EventHandler.ERROR_LOOKUP:
+ case -2: /* EventHandler.ERROR_LOOKUP: */
return com.android.internal.R.string.httpErrorLookup;
- case EventHandler.ERROR_UNSUPPORTED_AUTH_SCHEME:
+ case -3: /* EventHandler.ERROR_UNSUPPORTED_AUTH_SCHEME: */
return com.android.internal.R.string.httpErrorUnsupportedAuthScheme;
- case EventHandler.ERROR_AUTH:
+ case -4: /* EventHandler.ERROR_AUTH: */
return com.android.internal.R.string.httpErrorAuth;
- case EventHandler.ERROR_PROXYAUTH:
+ case -5: /* EventHandler.ERROR_PROXYAUTH: */
return com.android.internal.R.string.httpErrorProxyAuth;
- case EventHandler.ERROR_CONNECT:
+ case -6: /* EventHandler.ERROR_CONNECT: */
return com.android.internal.R.string.httpErrorConnect;
- case EventHandler.ERROR_IO:
+ case -7: /* EventHandler.ERROR_IO: */
return com.android.internal.R.string.httpErrorIO;
- case EventHandler.ERROR_TIMEOUT:
+ case -8: /* EventHandler.ERROR_TIMEOUT: */
return com.android.internal.R.string.httpErrorTimeout;
- case EventHandler.ERROR_REDIRECT_LOOP:
+ case -9: /* EventHandler.ERROR_REDIRECT_LOOP: */
return com.android.internal.R.string.httpErrorRedirectLoop;
- case EventHandler.ERROR_UNSUPPORTED_SCHEME:
+ case -10: /* EventHandler.ERROR_UNSUPPORTED_SCHEME: */
return com.android.internal.R.string.httpErrorUnsupportedScheme;
- case EventHandler.ERROR_FAILED_SSL_HANDSHAKE:
+ case -11: /* EventHandler.ERROR_FAILED_SSL_HANDSHAKE: */
return com.android.internal.R.string.httpErrorFailedSslHandshake;
- case EventHandler.ERROR_BAD_URL:
+ case -12: /* EventHandler.ERROR_BAD_URL: */
return com.android.internal.R.string.httpErrorBadUrl;
- case EventHandler.FILE_ERROR:
+ case -13: /* EventHandler.FILE_ERROR: */
return com.android.internal.R.string.httpErrorFile;
- case EventHandler.FILE_NOT_FOUND_ERROR:
+ case -14: /* EventHandler.FILE_NOT_FOUND_ERROR: */
return com.android.internal.R.string.httpErrorFileNotFound;
- case EventHandler.TOO_MANY_REQUESTS_ERROR:
+ case -15: /* EventHandler.TOO_MANY_REQUESTS_ERROR: */
return com.android.internal.R.string.httpErrorTooManyRequests;
default:
diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java
index e671376..6f763dc 100644
--- a/core/java/android/webkit/WebBackForwardList.java
+++ b/core/java/android/webkit/WebBackForwardList.java
@@ -16,7 +16,6 @@
package android.webkit;
-import android.annotation.SystemApi;
import java.io.Serializable;
/**
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 768dc9f..4737e9b 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -70,13 +70,14 @@ public class WebChromeClient {
}
/**
- * Notify the host application that the current page would
- * like to show a custom View. This is used for Fullscreen
- * video playback; see "HTML5 Video support" documentation on
+ * Notify the host application that the current page has entered full
+ * screen mode. The host application must show the custom View which
+ * contains the web contents &mdash; video or other HTML content &mdash;
+ * in full screen mode. Also see "Full screen support" documentation on
* {@link WebView}.
* @param view is the View object to be shown.
- * @param callback is the callback to be invoked if and when the view
- * is dismissed.
+ * @param callback invoke this callback to request the page to exit
+ * full screen mode.
*/
public void onShowCustomView(View view, CustomViewCallback callback) {};
@@ -96,8 +97,10 @@ public class WebChromeClient {
CustomViewCallback callback) {};
/**
- * Notify the host application that the current page would
- * like to hide its custom view.
+ * Notify the host application that the current page has exited full
+ * screen mode. The host application must hide the custom View, ie. the
+ * View passed to {@link #onShowCustomView} when the content entered fullscreen.
+ * Also see "Full screen support" documentation on {@link WebView}.
*/
public void onHideCustomView() {}
@@ -377,10 +380,9 @@ public class WebChromeClient {
}
/**
- * When the user starts to playback a video element, it may take time for enough
- * data to be buffered before the first frames can be rendered. While this buffering
- * is taking place, the ChromeClient can use this function to provide a View to be
- * displayed. For example, the ChromeClient could show a spinner animation.
+ * Obtains a View to be displayed while buffering of full screen video is taking
+ * place. The host application can override this method to provide a View
+ * containing a spinner or similar.
*
* @return View The View to be displayed whilst the video is loading.
*/
diff --git a/core/java/android/webkit/WebMessage.java b/core/java/android/webkit/WebMessage.java
new file mode 100644
index 0000000..7683a40
--- /dev/null
+++ b/core/java/android/webkit/WebMessage.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+/**
+ * The Java representation of the HTML5 PostMessage event. See
+ * https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces
+ * for definition of a MessageEvent in HTML5.
+ *
+ */
+public class WebMessage {
+
+ private String mData;
+ private WebMessagePort[] mPorts;
+
+ /**
+ * Creates a WebMessage.
+ * @param data the data of the message.
+ */
+ public WebMessage(String data) {
+ mData = data;
+ }
+
+ /**
+ * Creates a WebMessage.
+ * @param data the data of the message.
+ * @param ports the ports that are sent with the message.
+ */
+ public WebMessage(String data, WebMessagePort[] ports) {
+ mData = data;
+ mPorts = ports;
+ }
+
+ /**
+ * Returns the data of the message.
+ */
+ public String getData() {
+ return mData;
+ }
+
+ /**
+ * Returns the ports that are sent with the message, or null if no port
+ * is sent.
+ */
+ public WebMessagePort[] getPorts() {
+ return mPorts;
+ }
+}
diff --git a/core/java/android/webkit/WebMessagePort.java b/core/java/android/webkit/WebMessagePort.java
new file mode 100644
index 0000000..eab27bd
--- /dev/null
+++ b/core/java/android/webkit/WebMessagePort.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.os.Handler;
+
+/**
+ * The Java representation of the HTML5 Message Port. See
+ * https://html.spec.whatwg.org/multipage/comms.html#messageport
+ * for definition of MessagePort in HTML5.
+ *
+ * A Message port represents one endpoint of a Message Channel. In Android
+ * webview, there is no separate Message Channel object. When a message channel
+ * is created, both ports are tangled to each other and started, and then
+ * returned in a MessagePort array, see {@link WebView#createWebMessageChannel}
+ * for creating a message channel.
+ *
+ * When a message port is first created or received via transfer, it does not
+ * have a WebMessageCallback to receive web messages. The messages are queued until
+ * a WebMessageCallback is set.
+ */
+public abstract class WebMessagePort {
+
+ /**
+ * The listener for handling MessagePort events. The message callback
+ * methods are called on the main thread. If the embedder application
+ * wants to receive the messages on a different thread, it can do this
+ * by passing a Handler in
+ * {@link WebMessagePort#setWebMessageCallback(WebMessageCallback, Handler)}.
+ * In the latter case, the application should be extra careful for thread safety
+ * since WebMessagePort methods should be called on main thread.
+ */
+ public static abstract class WebMessageCallback {
+ /**
+ * Message callback for receiving onMessage events.
+ *
+ * @param port the WebMessagePort that the message is destined for
+ * @param message the message from the entangled port.
+ */
+ public void onMessage(WebMessagePort port, WebMessage message) { }
+ }
+
+ /**
+ * Post a WebMessage to the entangled port.
+ *
+ * @param message the message from Java to JS.
+ *
+ * @throws IllegalStateException If message port is already transferred or closed.
+ */
+ public abstract void postMessage(WebMessage message);
+
+ /**
+ * Close the message port and free any resources associated with it.
+ */
+ public abstract void close();
+
+ /**
+ * Sets a callback to receive message events on the main thread.
+ *
+ * @param callback the message callback.
+ */
+ public abstract void setWebMessageCallback(WebMessageCallback callback);
+
+ /**
+ * Sets a callback to receive message events on the handler that is provided
+ * by the application.
+ *
+ * @param callback the message callback.
+ * @param handler the handler to receive the message messages.
+ */
+ public abstract void setWebMessageCallback(WebMessageCallback callback, Handler handler);
+}
diff --git a/core/java/android/webkit/WebResourceError.java b/core/java/android/webkit/WebResourceError.java
new file mode 100644
index 0000000..080d174
--- /dev/null
+++ b/core/java/android/webkit/WebResourceError.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+/**
+ * Encapsulates information about errors occured during loading of web resources. See
+ * {@link WebViewClient#onReceivedError(WebView, WebResourceRequest, WebResourceError) WebViewClient.onReceivedError(WebView, WebResourceRequest, WebResourceError)}
+ */
+public abstract class WebResourceError {
+ /**
+ * Gets the error code of the error. The code corresponds to one
+ * of the ERROR_* constants in {@link WebViewClient}.
+ *
+ * @return The error code of the error
+ */
+ public abstract int getErrorCode();
+
+ /**
+ * Gets the string describing the error. Descriptions are localized,
+ * and thus can be used for communicating the problem to the user.
+ *
+ * @return The description of the error
+ */
+ public abstract String getDescription();
+}
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
index 2185658..0760d2b 100644
--- a/core/java/android/webkit/WebResourceRequest.java
+++ b/core/java/android/webkit/WebResourceRequest.java
@@ -18,7 +18,6 @@ package android.webkit;
import android.net.Uri;
-import java.io.InputStream;
import java.util.Map;
/**
@@ -42,6 +41,8 @@ public interface WebResourceRequest {
/**
* Gets whether a gesture (such as a click) was associated with the request.
+ * For security reasons in certain situations this method may return false even though the
+ * sequence of events which caused the request to be created was initiated by a user gesture.
*
* @return whether a gesture was associated with the request.
*/
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index ad6e9aa..a42aaa7 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -17,6 +17,7 @@
package android.webkit;
import java.io.InputStream;
+import java.io.StringBufferInputStream;
import java.util.Map;
/**
@@ -24,7 +25,7 @@ import java.util.Map;
* class from {@link WebViewClient#shouldInterceptRequest} to provide a custom
* response when the WebView requests a particular resource.
*/
-public class WebResourceResponse {
+public class WebResourceResponse extends WebResourceResponseBase {
private String mMimeType;
private String mEncoding;
private int mStatusCode;
@@ -40,13 +41,14 @@ public class WebResourceResponse {
*
* @param mimeType the resource response's MIME type, for example text/html
* @param encoding the resource response's encoding
- * @param data the input stream that provides the resource response's data
+ * @param data the input stream that provides the resource response's data. Must not be a
+ * StringBufferInputStream.
*/
public WebResourceResponse(String mimeType, String encoding,
InputStream data) {
mMimeType = mimeType;
mEncoding = encoding;
- mInputStream = data;
+ setData(data);
}
/**
@@ -62,7 +64,8 @@ public class WebResourceResponse {
* and not empty.
* @param responseHeaders the resource response's headers represented as a mapping of header
* name -> header value.
- * @param data the input stream that provides the resource response's data
+ * @param data the input stream that provides the resource response's data. Must not be a
+ * StringBufferInputStream.
*/
public WebResourceResponse(String mimeType, String encoding, int statusCode,
String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
@@ -72,38 +75,36 @@ public class WebResourceResponse {
}
/**
- * Sets the resource response's MIME type, for example text/html.
+ * Sets the resource response's MIME type, for example &quot;text/html&quot;.
*
- * @param mimeType the resource response's MIME type
+ * @param mimeType The resource response's MIME type
*/
public void setMimeType(String mimeType) {
mMimeType = mimeType;
}
/**
- * Gets the resource response's MIME type.
- *
- * @return the resource response's MIME type
+ * {@inheritDoc}
*/
+ @Override
public String getMimeType() {
return mMimeType;
}
/**
- * Sets the resource response's encoding, for example UTF-8. This is used
+ * Sets the resource response's encoding, for example &quot;UTF-8&quot;. This is used
* to decode the data from the input stream.
*
- * @param encoding the resource response's encoding
+ * @param encoding The resource response's encoding
*/
public void setEncoding(String encoding) {
mEncoding = encoding;
}
/**
- * Gets the resource response's encoding.
- *
- * @return the resource response's encoding
+ * {@inheritDoc}
*/
+ @Override
public String getEncoding() {
return mEncoding;
}
@@ -139,19 +140,17 @@ public class WebResourceResponse {
}
/**
- * Gets the resource response's status code.
- *
- * @return the resource response's status code.
+ * {@inheritDoc}
*/
+ @Override
public int getStatusCode() {
return mStatusCode;
}
/**
- * Gets the description of the resource response's status code.
- *
- * @return the description of the resource response's status code.
+ * {@inheritDoc}
*/
+ @Override
public String getReasonPhrase() {
return mReasonPhrase;
}
@@ -159,17 +158,16 @@ public class WebResourceResponse {
/**
* Sets the headers for the resource response.
*
- * @param headers mapping of header name -> header value.
+ * @param headers Mapping of header name -> header value.
*/
public void setResponseHeaders(Map<String, String> headers) {
mResponseHeaders = headers;
}
/**
- * Gets the headers for the resource response.
- *
- * @return the headers for the resource response.
+ * {@inheritDoc}
*/
+ @Override
public Map<String, String> getResponseHeaders() {
return mResponseHeaders;
}
@@ -178,17 +176,22 @@ public class WebResourceResponse {
* Sets the input stream that provides the resource response's data. Callers
* must implement {@link InputStream#read(byte[]) InputStream.read(byte[])}.
*
- * @param data the input stream that provides the resource response's data
+ * @param data the input stream that provides the resource response's data. Must not be a
+ * StringBufferInputStream.
*/
public void setData(InputStream data) {
+ // If data is (or is a subclass of) StringBufferInputStream
+ if (data != null && StringBufferInputStream.class.isAssignableFrom(data.getClass())) {
+ throw new IllegalArgumentException("StringBufferInputStream is deprecated and must " +
+ "not be passed to a WebResourceResponse");
+ }
mInputStream = data;
}
/**
- * Gets the input stream that provides the resource response's data.
- *
- * @return the input stream that provides the resource response's data
+ * {@inheritDoc}
*/
+ @Override
public InputStream getData() {
return mInputStream;
}
diff --git a/core/java/android/webkit/WebResourceResponseBase.java b/core/java/android/webkit/WebResourceResponseBase.java
new file mode 100644
index 0000000..cffde82
--- /dev/null
+++ b/core/java/android/webkit/WebResourceResponseBase.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * Encapsulates a resource response received from the server.
+ * This is an abstract class used by WebView callbacks.
+ */
+public abstract class WebResourceResponseBase {
+ /**
+ * Gets the resource response's MIME type.
+ *
+ * @return The resource response's MIME type
+ */
+ public abstract String getMimeType();
+
+ /**
+ * Gets the resource response's encoding.
+ *
+ * @return The resource response's encoding
+ */
+ public abstract String getEncoding();
+
+ /**
+ * Gets the resource response's status code.
+ *
+ * @return The resource response's status code.
+ */
+ public abstract int getStatusCode();
+
+ /**
+ * Gets the description of the resource response's status code.
+ *
+ * @return The description of the resource response's status code.
+ */
+ public abstract String getReasonPhrase();
+
+ /**
+ * Gets the headers for the resource response.
+ *
+ * @return The headers for the resource response.
+ */
+ public abstract Map<String, String> getResponseHeaders();
+
+ /**
+ * Gets the input stream that provides the resource response's data.
+ *
+ * @return The input stream that provides the resource response's data
+ */
+ public abstract InputStream getData();
+}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 1d2c311..943beb0 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1285,7 +1285,7 @@ public abstract class WebSettings {
* strongly discouraged.
*
* @param mode The mixed content mode to use. One of {@link #MIXED_CONTENT_NEVER_ALLOW},
- * {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
+ * {@link #MIXED_CONTENT_ALWAYS_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
*/
public abstract void setMixedContentMode(int mode);
@@ -1293,7 +1293,7 @@ public abstract class WebSettings {
* Gets the current behavior of the WebView with regard to loading insecure content from a
* secure origin.
* @return The current setting, one of {@link #MIXED_CONTENT_NEVER_ALLOW},
- * {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
+ * {@link #MIXED_CONTENT_ALWAYS_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
*/
public abstract int getMixedContentMode();
@@ -1330,4 +1330,25 @@ public abstract class WebSettings {
*/
@SystemApi
public abstract boolean getVideoOverlayForEmbeddedEncryptedVideoEnabled();
+
+ /**
+ * Sets whether this WebView should raster tiles when it is
+ * offscreen but attached to a window. Turning this on can avoid
+ * rendering artifacts when animating an offscreen WebView on-screen.
+ * Offscreen WebViews in this mode use more memory. The default value is
+ * false.
+ * Please follow these guidelines to limit memory usage:
+ * - WebView size should be not be larger than the device screen size.
+ * - Limit use of this mode to a small number of WebViews. Use it for
+ * visible WebViews and WebViews about to be animated to visible.
+ */
+ public abstract void setOffscreenPreRaster(boolean enabled);
+
+ /**
+ * Gets whether this WebView should raster tiles when it is
+ * offscreen but attached to a window.
+ * @return true if this WebView will raster tiles when it is
+ * offscreen but attached to a window.
+ */
+ public abstract boolean getOffscreenPreRaster();
}
diff --git a/core/java/android/webkit/WebSyncManager.java b/core/java/android/webkit/WebSyncManager.java
index 402394f..801be12 100644
--- a/core/java/android/webkit/WebSyncManager.java
+++ b/core/java/android/webkit/WebSyncManager.java
@@ -17,11 +17,6 @@
package android.webkit;
import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Process;
-import android.util.Log;
/*
* @deprecated The WebSyncManager no longer does anything.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index bab1f3b..6711a6b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -27,6 +27,7 @@ import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
@@ -233,12 +234,48 @@ import java.util.Map;
*
* <h3>HTML5 Video support</h3>
*
- * <p>In order to support inline HTML5 video in your application, you need to have hardware
- * acceleration turned on, and set a {@link android.webkit.WebChromeClient}. For full screen support,
- * implementations of {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
- * and {@link WebChromeClient#onHideCustomView()} are required,
- * {@link WebChromeClient#getVideoLoadingProgressView()} is optional.
+ * <p>In order to support inline HTML5 video in your application you need to have hardware
+ * acceleration turned on.
* </p>
+ *
+ * <h3>Full screen support</h3>
+ *
+ * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
+ * {@link android.webkit.WebChromeClient} and implement both
+ * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
+ * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
+ * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
+ * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
+ * is loading.
+ * </p>
+ *
+ * <h3>Layout size</h3>
+ * <p>
+ * It is recommended to set the WebView layout height to a fixed value or to
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
+ * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+ * for the height none of the WebView's parents should use a
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
+ * incorrect sizing of the views.
+ * </p>
+ *
+ * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * enables the following behaviors:
+ * <ul>
+ * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
+ * relative to the HTML body may not be sized correctly. </li>
+ * <li>For applications targetting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
+ * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
+ * supported. If such a width is used the WebView will attempt to use the width of the parent
+ * instead.
+ * </p>
+ *
*/
// Implementation notes.
// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
@@ -327,6 +364,20 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Callback interface supplied to {@link #insertVisualStateCallback} for receiving
+ * notifications about the visual state.
+ */
+ public static abstract class VisualStateCallback {
+ /**
+ * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}.
+ *
+ * @param requestId the id supplied to the corresponding {@link #insertVisualStateCallback}
+ * request
+ */
+ public abstract void onComplete(long requestId);
+ }
+
+ /**
* Interface to listen for new pictures as they change.
*
* @deprecated This interface is now obsolete.
@@ -1107,6 +1158,60 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Inserts a {@link VisualStateCallback}.
+ *
+ * <p>Updates to the the DOM are reflected asynchronously such that when the DOM is updated the
+ * subsequent {@link WebView#onDraw} invocation might not reflect those updates. The
+ * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of
+ * the DOM at the current time are ready to be drawn the next time the {@link WebView} draws.
+ * By current time we mean the time at which this API was called. The next draw after the
+ * callback completes is guaranteed to reflect all the updates to the DOM applied before the
+ * current time, but it may also contain updates applied after the current time.</p>
+ *
+ * <p>The state of the DOM covered by this API includes the following:
+ * <ul>
+ * <li>primitive HTML elements (div, img, span, etc..)</li>
+ * <li>images</li>
+ * <li>CSS animations</li>
+ * <li>WebGL</li>
+ * <li>canvas</li>
+ * </ul>
+ * It does not include the state of:
+ * <ul>
+ * <li>the video tag</li>
+ * </ul></p>
+ *
+ * <p>To guarantee that the {@link WebView} will successfully render the first frame
+ * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions
+ * must be met:
+ * <ul>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then
+ * the {@link WebView} must be attached to the view hierarchy.</li>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE}
+ * then the {@link WebView} must be attached to the view hierarchy and must be made
+ * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the
+ * {@link WebView} must be attached to the view hierarchy and its
+ * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed
+ * values and must be made {@link View#VISIBLE VISIBLE} from the
+ * {@link VisualStateCallback#onComplete} method.</li>
+ * </ul></p>
+ *
+ * <p>When using this API it is also recommended to enable pre-rasterization if the
+ * {@link WebView} is offscreen to avoid flickering. See WebSettings#setOffscreenPreRaster for
+ * more details and do consider its caveats.</p>
+ *
+ * @param requestId an id that will be returned in the callback to allow callers to match
+ * requests with callbacks.
+ * @param callback the callback to be invoked.
+ */
+ public void insertVisualStateCallback(long requestId, VisualStateCallback callback) {
+ checkThread();
+ if (TRACE) Log.d(LOGTAG, "insertVisualStateCallback");
+ mProvider.insertVisualStateCallback(requestId, callback);
+ }
+
+ /**
* Clears this WebView so that onDraw() will draw nothing but white background,
* and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
* @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
@@ -1789,6 +1894,37 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Creates a message channel to communicate with JS and returns the message
+ * ports that represent the endpoints of this message channel. The HTML5 message
+ * channel functionality is described here:
+ * https://html.spec.whatwg.org/multipage/comms.html#messagechannel
+ *
+ * The returned message channels are entangled and already in started state.
+ *
+ * @return the two message ports that form the message channel.
+ */
+ public WebMessagePort[] createWebMessageChannel() {
+ checkThread();
+ if (TRACE) Log.d(LOGTAG, "createWebMessageChannel");
+ return mProvider.createWebMessageChannel();
+ }
+
+ /**
+ * Post a message to main frame. The embedded application can restrict the
+ * messages to a certain target origin. See
+ * https://html.spec.whatwg.org/multipage/comms.html#posting-messages
+ * for how target origin can be used.
+ *
+ * @param message the WebMessage
+ * @param targetOrigin the target origin.
+ */
+ public void postMessageToMainFrame(WebMessage message, Uri targetOrigin) {
+ checkThread();
+ if (TRACE) Log.d(LOGTAG, "postMessageToMainFrame. TargetOrigin=" + targetOrigin);
+ mProvider.postMessageToMainFrame(message, targetOrigin);
+ }
+
+ /**
* Gets the WebSettings object used to control the settings for this
* WebView.
*
@@ -2043,7 +2179,7 @@ public class WebView extends AbsoluteLayout
}
public boolean super_performAccessibilityAction(int action, Bundle arguments) {
- return WebView.super.performAccessibilityAction(action, arguments);
+ return WebView.super.performAccessibilityActionInternal(action, arguments);
}
public boolean super_performLongClick() {
@@ -2351,22 +2487,27 @@ public class WebView extends AbsoluteLayout
return mProvider.getViewDelegate().shouldDelayChildPressedState();
}
+ public CharSequence getAccessibilityClassName() {
+ return WebView.class.getName();
+ }
+
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(WebView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(WebView.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
}
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index d52dd60..53c7e04 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -23,8 +23,6 @@ import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.ViewRootImpl;
-import java.security.Principal;
-
public class WebViewClient {
/**
@@ -50,7 +48,9 @@ public class WebViewClient {
* is called once for each main frame load so a page with iframes or
* framesets will call onPageStarted one time for the main frame. This also
* means that onPageStarted will not be called when the contents of an
- * embedded frame changes, i.e. clicking a link whose target is an iframe.
+ * embedded frame changes, i.e. clicking a link whose target is an iframe,
+ * it will also not be called for fragment navigations (navigations to
+ * #fragment_id).
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
@@ -83,6 +83,32 @@ public class WebViewClient {
}
/**
+ * Notify the host application that the page commit is visible.
+ *
+ * <p>This is the earliest point at which we can guarantee that the contents of the previously
+ * loaded page will not longer be drawn in the next {@link WebView#onDraw}. The next draw will
+ * render the {@link WebView#setBackgroundColor background color} of the WebView or some of the
+ * contents from the committed page already. This callback may be useful when reusing
+ * {@link WebView}s to ensure that no stale content is shown. This method is only called for
+ * the main frame.</p>
+ *
+ * <p>This method is called when the state of the DOM at the point at which the
+ * body of the HTTP response (commonly the string of html) had started loading will be visible.
+ * If you set a background color for the page in the HTTP response body this will most likely
+ * be visible and perhaps some other elements. At that point no other resources had usually
+ * been loaded, so you can expect images for example to not be visible. If you want
+ * a finer level of granularity consider calling {@link WebView#insertVisualStateCallback}
+ * directly.</p>
+ *
+ * <p>Please note that all the conditions and recommendations presented in
+ * {@link WebView#insertVisualStateCallback} also apply to this API.<p>
+ *
+ * @param url the url of the committed page
+ */
+ public void onPageCommitVisible(WebView view, String url) {
+ }
+
+ /**
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is null, the WebView
* will continue to load the resource as usual. Otherwise, the return
@@ -174,6 +200,8 @@ public class WebViewClient {
public static final int ERROR_FILE_NOT_FOUND = -14;
/** Too many requests during this load */
public static final int ERROR_TOO_MANY_REQUESTS = -15;
+ /** Request blocked by the browser */
+ public static final int ERROR_BLOCKED = -16;
/**
* Report an error to the host application. These errors are unrecoverable
@@ -183,12 +211,45 @@ public class WebViewClient {
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
+ * @deprecated Use {@link #onReceivedError(WebView, WebResourceRequest, WebResourceError)
+ * onReceivedError(WebView, WebResourceRequest, WebResourceError)} instead.
*/
+ @Deprecated
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
}
/**
+ * Report web resource loading error to the host application. These errors usually indicate
+ * inability to connect to the server. Note that unlike the deprecated version of the callback,
+ * the new version will be called for any resource (iframe, image, etc), not just for the main
+ * page. Thus, it is recommended to perform minimum required work in this callback.
+ * @param view The WebView that is initiating the callback.
+ * @param request The originating request.
+ * @param error Information about the error occured.
+ */
+ public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
+ if (request.isForMainFrame()) {
+ onReceivedError(view,
+ error.getErrorCode(), error.getDescription(), request.getUrl().toString());
+ }
+ }
+
+ /**
+ * Notify the host application that an HTTP error has been received from the server while
+ * loading a resource. HTTP errors have status codes &gt;= 400. This callback will be called
+ * for any resource (iframe, image, etc), not just for the main page. Thus, it is recommended to
+ * perform minimum required work in this callback. Note that the content of the server
+ * response may not be provided within the <b>errorResponse</b> parameter.
+ * @param view The WebView that is initiating the callback.
+ * @param request The originating request.
+ * @param errorResponse Information about the error occured.
+ */
+ public void onReceivedHttpError(
+ WebView view, WebResourceRequest request, WebResourceResponseBase errorResponse) {
+ }
+
+ /**
* As the host application if the browser should resend data as the
* requested page was a result of a POST. The default is to not resend the
* data.
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index bfea481..cdff416 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -16,7 +16,6 @@
package android.webkit;
-import android.annotation.SystemApi;
import android.content.Context;
/**
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index ac360fa..23af384 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -25,7 +25,7 @@ import android.graphics.Canvas;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.SparseArray;
-import android.view.HardwareCanvas;
+import android.view.DisplayListCanvas;
import android.view.View;
import android.view.ViewRootImpl;
@@ -101,12 +101,12 @@ public final class WebViewDelegate {
* @throws IllegalArgumentException if the canvas is not hardware accelerated
*/
public void callDrawGlFunction(Canvas canvas, long nativeDrawGLFunctor) {
- if (!(canvas instanceof HardwareCanvas)) {
+ if (!(canvas instanceof DisplayListCanvas)) {
// Canvas#isHardwareAccelerated() is only true for subclasses of HardwareCanvas.
throw new IllegalArgumentException(canvas.getClass().getName()
- + " is not hardware accelerated");
+ + " is not a DisplayList canvas");
}
- ((HardwareCanvas) canvas).callDrawGLFunction2(nativeDrawGLFunctor);
+ ((DisplayListCanvas) canvas).callDrawGLFunction2(nativeDrawGLFunctor);
}
/**
@@ -153,7 +153,7 @@ public final class WebViewDelegate {
}
/**
- * Adds the WebView asset path to {@link AssetManager}.
+ * Adds the WebView asset path to {@link android.content.res.AssetManager}.
*/
public void addWebViewAssetPath(Context context) {
context.getAssets().addAssetPath(
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 474ef42..cafe053 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -117,12 +117,8 @@ public final class WebViewFactory {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
try {
- try {
- sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
- .newInstance(new WebViewDelegate());
- } catch (Exception e) {
- sProviderInstance = providerClass.newInstance();
- }
+ sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
+ .newInstance(new WebViewDelegate());
if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
return sProviderInstance;
} catch (Exception e) {
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 2aee57b..fa2ce1b 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -24,8 +24,8 @@ import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
import android.net.http.SslCertificate;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.print.PrintDocumentAdapter;
@@ -40,6 +40,8 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.webkit.WebView.HitTestResult;
import android.webkit.WebView.PictureListener;
+import android.webkit.WebView.VisualStateCallback;
+
import java.io.BufferedWriter;
import java.io.File;
@@ -146,6 +148,8 @@ public interface WebViewProvider {
public boolean pageDown(boolean bottom);
+ public void insertVisualStateCallback(long requestId, VisualStateCallback callback);
+
public void clearView();
public Picture capturePicture();
@@ -228,6 +232,10 @@ public interface WebViewProvider {
public void removeJavascriptInterface(String interfaceName);
+ public WebMessagePort[] createWebMessageChannel();
+
+ public void postMessageToMainFrame(WebMessage message, Uri targetOrigin);
+
public WebSettings getSettings();
public void setMapTrackballToArrowKeys(boolean setMap);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1e269a3..168066a 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -16,11 +16,12 @@
package android.widget;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
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;
@@ -578,6 +579,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private boolean mIsChildViewEnabled;
/**
+ * The cached drawable state for the selector. Accounts for child enabled
+ * state, but otherwise identical to the view's own drawable state.
+ */
+ private int[] mSelectorState;
+
+ /**
* The last scroll state reported to clients through {@link OnScrollListener}.
*/
private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
@@ -1466,8 +1473,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
}
+ /** @hide */
@Override
- public void sendAccessibilityEvent(int eventType) {
+ public void sendAccessibilityEventInternal(int eventType) {
// Since this class calls onScrollChanged even if the mFirstPosition and the
// child count have not changed we will avoid sending duplicate accessibility
// events.
@@ -1482,19 +1490,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
}
}
- super.sendAccessibilityEvent(eventType);
+ super.sendAccessibilityEventInternal(eventType);
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AbsListView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AbsListView.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AbsListView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (isEnabled()) {
if (canScrollUp()) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
@@ -1522,9 +1529,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
switch (action) {
@@ -2705,7 +2713,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*
* @attr ref android.R.styleable#AbsListView_listSelector
*/
- public void setSelector(int resID) {
+ public void setSelector(@DrawableRes int resID) {
setSelector(getContext().getDrawable(resID));
}
@@ -2785,7 +2793,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
void updateSelectorState() {
if (mSelector != null) {
if (shouldShowSelector()) {
- mSelector.setState(getDrawableState());
+ mSelector.setState(getDrawableStateForSelector());
} else {
mSelector.setState(StateSet.NOTHING);
}
@@ -2798,12 +2806,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
updateSelectorState();
}
- @Override
- protected int[] onCreateDrawableState(int extraSpace) {
+ private int[] getDrawableStateForSelector() {
// If the child view is enabled then do the default behavior.
if (mIsChildViewEnabled) {
// Common case
- return super.onCreateDrawableState(extraSpace);
+ return super.getDrawableState();
}
// The selector uses this View's drawable state. The selected child view
@@ -2811,10 +2818,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// states.
final int enabledState = ENABLED_STATE_SET[0];
- // If we don't have any extra space, it will return one of the static state arrays,
- // and clearing the enabled state on those arrays is a bad thing! If we specify
- // we need extra space, it will create+copy into a new array that safely mutable.
- int[] state = super.onCreateDrawableState(extraSpace + 1);
+ // If we don't have any extra space, it will return one of the static
+ // state arrays, and clearing the enabled state on those arrays is a
+ // bad thing! If we specify we need extra space, it will create+copy
+ // into a new array that is safely mutable.
+ final int[] state = onCreateDrawableState(1);
+
int enabledPos = -1;
for (int i = state.length - 1; i >= 0; i--) {
if (state[i] == enabledState) {
@@ -5974,7 +5983,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*
* @param color The background color
*/
- public void setCacheColorHint(int color) {
+ public void setCacheColorHint(@ColorInt int color) {
if (color != mCacheColorHint) {
mCacheColorHint = color;
int count = getChildCount();
@@ -5992,6 +6001,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @return The cache color hint
*/
@ViewDebug.ExportedProperty(category = "drawing")
+ @ColorInt
public int getCacheColorHint() {
return mCacheColorHint;
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index a3ce808..d6f9f78 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -31,7 +31,6 @@ import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.R;
@@ -381,8 +380,8 @@ public abstract class AbsSeekBar extends ProgressBar {
}
@Override
- void onProgressRefresh(float scale, boolean fromUser) {
- super.onProgressRefresh(scale, fromUser);
+ void onProgressRefresh(float scale, boolean fromUser, int progress) {
+ super.onProgressRefresh(scale, fromUser, progress);
final Drawable thumb = mThumb;
if (thumb != null) {
@@ -403,28 +402,31 @@ public abstract class AbsSeekBar extends ProgressBar {
}
private void updateThumbAndTrackPos(int w, int h) {
+ final int paddedHeight = h - mPaddingTop - mPaddingBottom;
final Drawable track = getCurrentDrawable();
final Drawable thumb = mThumb;
// The max height does not incorporate padding, whereas the height
// parameter does.
- final int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
+ final int trackHeight = Math.min(mMaxHeight, paddedHeight);
final int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
// Apply offset to whichever item is taller.
final int trackOffset;
final int thumbOffset;
if (thumbHeight > trackHeight) {
- trackOffset = (thumbHeight - trackHeight) / 2;
- thumbOffset = 0;
+ final int offsetHeight = (paddedHeight - thumbHeight) / 2;
+ trackOffset = offsetHeight + (thumbHeight - trackHeight) / 2;
+ thumbOffset = offsetHeight + 0;
} else {
- trackOffset = 0;
- thumbOffset = (trackHeight - thumbHeight) / 2;
+ final int offsetHeight = (paddedHeight - trackHeight) / 2;
+ trackOffset = offsetHeight + 0;
+ thumbOffset = offsetHeight + (trackHeight - thumbHeight) / 2;
}
if (track != null) {
- track.setBounds(0, trackOffset, w - mPaddingRight - mPaddingLeft,
- h - mPaddingBottom - trackOffset - mPaddingTop);
+ final int trackWidth = w - mPaddingRight - mPaddingLeft;
+ track.setBounds(0, trackOffset, trackWidth, trackOffset + trackHeight);
}
if (thumb != null) {
@@ -472,7 +474,6 @@ public abstract class AbsSeekBar extends ProgressBar {
final Drawable background = getBackground();
if (background != null) {
- final Rect bounds = thumb.getBounds();
final int offsetX = mPaddingLeft - mThumbOffset;
final int offsetY = mPaddingTop;
background.setHotspotBounds(left + offsetX, top + offsetY,
@@ -716,15 +717,14 @@ public abstract class AbsSeekBar extends ProgressBar {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AbsSeekBar.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AbsSeekBar.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AbsSeekBar.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (isEnabled()) {
final int progress = getProgress();
@@ -737,9 +737,10 @@ public abstract class AbsSeekBar extends ProgressBar {
}
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
if (!isEnabled()) {
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 6a4ad75..1cb7f2a 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -28,8 +28,6 @@ import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* An abstract base class for spinner widgets. SDK users will probably not
@@ -73,13 +71,12 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
initAbsSpinner();
final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.AbsSpinner, defStyleAttr, defStyleRes);
+ attrs, R.styleable.AbsSpinner, defStyleAttr, defStyleRes);
- CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
+ final CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
if (entries != null) {
- ArrayAdapter<CharSequence> adapter =
- new ArrayAdapter<CharSequence>(context,
- R.layout.simple_spinner_item, entries);
+ final ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(
+ context, R.layout.simple_spinner_item, entries);
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
setAdapter(adapter);
}
@@ -472,14 +469,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AbsSpinner.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AbsSpinner.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AbsSpinner.class.getName();
}
}
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 18f15a0..4fadc19 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -17,10 +17,10 @@
package android.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Rect;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -55,7 +55,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
implements ActionProvider.SubUiVisibilityListener {
private static final String TAG = "ActionMenuPresenter";
- private View mOverflowButton;
+ private OverflowMenuButton mOverflowButton;
private boolean mReserveOverflow;
private boolean mReserveOverflowSet;
private int mWidthLimit;
@@ -79,6 +79,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter
private OpenOverflowRunnable mPostedOpenRunnable;
private ActionMenuPopupCallback mPopupCallback;
+ private TintInfo mOverflowTintInfo;
+
final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
int mOpenSubMenuId;
@@ -113,6 +115,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
mOverflowButton = new OverflowMenuButton(mSystemContext);
final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
mOverflowButton.measure(spec, spec);
+ applyOverflowTint();
}
width -= mOverflowButton.getMeasuredWidth();
} else {
@@ -236,6 +239,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
if (hasOverflow) {
if (mOverflowButton == null) {
mOverflowButton = new OverflowMenuButton(mSystemContext);
+ applyOverflowTint();
}
ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
if (parent != mMenuView) {
@@ -550,6 +554,40 @@ public class ActionMenuPresenter extends BaseMenuPresenter
menuView.initialize(mMenu);
}
+ public void setOverflowTintList(ColorStateList tint) {
+ if (mOverflowTintInfo == null) {
+ mOverflowTintInfo = new TintInfo();
+ }
+ mOverflowTintInfo.mTintList = tint;
+ mOverflowTintInfo.mHasTintList = true;
+
+ applyOverflowTint();
+ }
+
+ public void setOverflowTintMode(PorterDuff.Mode tintMode) {
+ if (mOverflowTintInfo == null) {
+ mOverflowTintInfo = new TintInfo();
+ }
+ mOverflowTintInfo.mTintMode = tintMode;
+ mOverflowTintInfo.mHasTintMode = true;
+
+ applyOverflowTint();
+ }
+
+ private void applyOverflowTint() {
+ final TintInfo tintInfo = mOverflowTintInfo;
+ if (tintInfo != null && (tintInfo.mHasTintList || tintInfo.mHasTintMode)) {
+ if (mOverflowButton != null) {
+ if (tintInfo.mHasTintList) {
+ mOverflowButton.setImageTintList(tintInfo.mTintList);
+ }
+ if (tintInfo.mHasTintMode) {
+ mOverflowButton.setImageTintMode(tintInfo.mTintMode);
+ }
+ }
+ }
+ }
+
private static class SavedState implements Parcelable {
public int openSubMenuId;
@@ -645,9 +683,10 @@ public class ActionMenuPresenter extends BaseMenuPresenter
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setCanOpenPopup(true);
}
@@ -773,4 +812,11 @@ public class ActionMenuPresenter extends BaseMenuPresenter
return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
}
}
+
+ private static class TintInfo {
+ ColorStateList mTintList;
+ PorterDuff.Mode mTintMode;
+ boolean mHasTintMode;
+ boolean mHasTintList;
+ }
}
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 0a8a01f..d6f2276 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -15,8 +15,11 @@
*/
package android.widget;
+import android.annotation.StyleRes;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
@@ -84,7 +87,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
* @param resId theme used to inflate popup menus
* @see #getPopupTheme()
*/
- public void setPopupTheme(int resId) {
+ public void setPopupTheme(@StyleRes int resId) {
if (mPopupTheme != resId) {
mPopupTheme = resId;
if (resId == 0) {
@@ -116,11 +119,14 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- mPresenter.updateMenuView(false);
- if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
- mPresenter.hideOverflowMenu();
- mPresenter.showOverflowMenu();
+ if (mPresenter != null) {
+ mPresenter.updateMenuView(false);
+
+ if (mPresenter.isOverflowMenuShowing()) {
+ mPresenter.hideOverflowMenu();
+ mPresenter.showOverflowMenu();
+ }
}
}
@@ -543,6 +549,31 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
mReserveOverflow = reserveOverflow;
}
+ /**
+ * Applies a tint to the overflow drawable. Does not modify the current tint
+ * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ */
+ public void setOverflowTintList(ColorStateList tint) {
+ if (mPresenter != null) {
+ mPresenter.setOverflowTintList(tint);
+ }
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by {@link
+ * #setOverflowTintList(ColorStateList)} to the overflow drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be {@code null} to clear tint
+ */
+ public void setOverflowTintMode(PorterDuff.Mode tintMode) {
+ if (mPresenter != null) {
+ mPresenter.setOverflowTintMode(tintMode);
+ }
+ }
+
@Override
protected LayoutParams generateDefaultLayoutParams() {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
@@ -700,7 +731,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return result;
}
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ /** @hide */
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
return false;
}
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index f9af2f9..f34ad71 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -18,6 +18,7 @@ package android.widget;
import com.android.internal.R;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -334,7 +335,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
*
* @param resourceId The content description resource id.
*/
- public void setExpandActivityOverflowButtonContentDescription(int resourceId) {
+ public void setExpandActivityOverflowButtonContentDescription(@StringRes int resourceId) {
CharSequence contentDescription = mContext.getString(resourceId);
mExpandActivityOverflowButtonImage.setContentDescription(contentDescription);
}
@@ -514,7 +515,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
*
* @param resourceId The resource id.
*/
- public void setDefaultActionButtonContentDescription(int resourceId) {
+ public void setDefaultActionButtonContentDescription(@StringRes int resourceId) {
mDefaultActionButtonContentDescription = resourceId;
}
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 5e2394c..72cb0b5 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.Nullable;
import android.content.Context;
import android.database.DataSetObserver;
import android.os.Parcelable;
@@ -276,7 +277,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
*
* @param listener The callback that will be invoked.
*/
- public void setOnItemClickListener(OnItemClickListener listener) {
+ public void setOnItemClickListener(@Nullable OnItemClickListener listener) {
mOnItemClickListener = listener;
}
@@ -284,6 +285,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
* @return The callback to be invoked with an item in this AdapterView has
* been clicked, or null id no callback has been set.
*/
+ @Nullable
public final OnItemClickListener getOnItemClickListener() {
return mOnItemClickListener;
}
@@ -394,10 +396,11 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
*
* @param listener The callback that will run
*/
- public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
mOnItemSelectedListener = listener;
}
+ @Nullable
public final OnItemSelectedListener getOnItemSelectedListener() {
return mOnItemSelectedListener;
}
@@ -929,8 +932,9 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
+ /** @hide */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
View selectedView = getSelectedView();
if (selectedView != null && selectedView.getVisibility() == VISIBLE
&& selectedView.dispatchPopulateAccessibilityEvent(event)) {
@@ -939,9 +943,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
return false;
}
+ /** @hide */
@Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (super.onRequestSendAccessibilityEvent(child, event)) {
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ if (super.onRequestSendAccessibilityEventInternal(child, event)) {
// Add a record for ourselves as well.
AccessibilityEvent record = AccessibilityEvent.obtain();
onInitializeAccessibilityEvent(record);
@@ -954,9 +959,14 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AdapterView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AdapterView.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setScrollable(isScrollableForAccessibility());
View selectedView = getSelectedView();
if (selectedView != null) {
@@ -964,10 +974,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AdapterView.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setScrollable(isScrollableForAccessibility());
View selectedView = getSelectedView();
if (selectedView != null) {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 1bc2f4b..932b354 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -29,8 +29,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.OnClickHandler;
import java.util.ArrayList;
@@ -1085,14 +1083,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AdapterViewAnimator.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AdapterViewAnimator.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AdapterViewAnimator.class.getName();
}
}
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 285dee8..a105b40 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -26,8 +26,6 @@ import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.RemotableViewMethod;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
/**
@@ -305,14 +303,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AdapterViewFlipper.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AdapterViewFlipper.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AdapterViewFlipper.class.getName();
}
}
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index 97926a7..ae94a10 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -16,8 +16,14 @@
package android.widget;
+import android.annotation.ArrayRes;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
import android.content.Context;
+import android.content.res.Resources;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -44,7 +50,8 @@ import java.util.List;
* or to have some of data besides toString() results fill the views,
* override {@link #getView(int, View, ViewGroup)} to return the type of view you want.
*/
-public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
+public class ArrayAdapter<T> extends BaseAdapter implements Filterable,
+ Spinner.ThemedSpinnerAdapter {
/**
* Contains the list of objects that represent the data of this ArrayAdapter.
* The content of this list is referred to as "the array" in the documentation.
@@ -93,6 +100,9 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
private LayoutInflater mInflater;
+ /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */
+ private LayoutInflater mDropDownInflater;
+
/**
* Constructor
*
@@ -100,8 +110,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
*/
- public ArrayAdapter(Context context, int resource) {
- init(context, resource, 0, new ArrayList<T>());
+ public ArrayAdapter(Context context, @LayoutRes int resource) {
+ this(context, resource, 0, new ArrayList<T>());
}
/**
@@ -112,8 +122,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* instantiating views.
* @param textViewResourceId The id of the TextView within the layout resource to be populated
*/
- public ArrayAdapter(Context context, int resource, int textViewResourceId) {
- init(context, resource, textViewResourceId, new ArrayList<T>());
+ public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId) {
+ this(context, resource, textViewResourceId, new ArrayList<T>());
}
/**
@@ -124,8 +134,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, int resource, T[] objects) {
- init(context, resource, 0, Arrays.asList(objects));
+ public ArrayAdapter(Context context, @LayoutRes int resource, @NonNull T[] objects) {
+ this(context, resource, 0, Arrays.asList(objects));
}
/**
@@ -137,8 +147,9 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param textViewResourceId The id of the TextView within the layout resource to be populated
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) {
- init(context, resource, textViewResourceId, Arrays.asList(objects));
+ public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId,
+ @NonNull T[] objects) {
+ this(context, resource, textViewResourceId, Arrays.asList(objects));
}
/**
@@ -149,8 +160,8 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, int resource, List<T> objects) {
- init(context, resource, 0, objects);
+ public ArrayAdapter(Context context, @LayoutRes int resource, @NonNull List<T> objects) {
+ this(context, resource, 0, objects);
}
/**
@@ -162,8 +173,13 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param textViewResourceId The id of the TextView within the layout resource to be populated
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
- init(context, resource, textViewResourceId, objects);
+ public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId,
+ @NonNull List<T> objects) {
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+ mResource = mDropDownResource = resource;
+ mObjects = objects;
+ mFieldId = textViewResourceId;
}
/**
@@ -305,14 +321,6 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
mNotifyOnChange = notifyOnChange;
}
- private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
- mContext = context;
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mResource = mDropDownResource = resource;
- mObjects = objects;
- mFieldId = textViewResourceId;
- }
-
/**
* Returns the context associated with this array adapter. The context is used
* to create views from the resource passed to the constructor.
@@ -359,16 +367,16 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* {@inheritDoc}
*/
public View getView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mResource);
+ return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
- private View createViewFromResource(int position, View convertView, ViewGroup parent,
- int resource) {
+ private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
+ ViewGroup parent, int resource) {
View view;
TextView text;
if (convertView == null) {
- view = mInflater.inflate(resource, parent, false);
+ view = inflater.inflate(resource, parent, false);
} else {
view = convertView;
}
@@ -403,16 +411,45 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param resource the layout resource defining the drop down views
* @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
*/
- public void setDropDownViewResource(int resource) {
+ public void setDropDownViewResource(@LayoutRes int resource) {
this.mDropDownResource = resource;
}
/**
+ * Sets the {@link Resources.Theme} against which drop-down views are
+ * inflated.
+ * <p>
+ * By default, drop-down views are inflated against the theme of the
+ * {@link Context} passed to the adapter's constructor.
+ *
+ * @param theme the theme against which to inflate drop-down views or
+ * {@code null} to use the theme from the adapter's context
+ * @see #getDropDownView(int, View, ViewGroup)
+ */
+ @Override
+ public void setDropDownViewTheme(Resources.Theme theme) {
+ if (theme == null) {
+ mDropDownInflater = null;
+ } else if (theme == mInflater.getContext().getTheme()) {
+ mDropDownInflater = mInflater;
+ } else {
+ final Context context = new ContextThemeWrapper(mContext, theme);
+ mDropDownInflater = LayoutInflater.from(context);
+ }
+ }
+
+ @Override
+ public Resources.Theme getDropDownViewTheme() {
+ return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme();
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mDropDownResource);
+ final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater;
+ return createViewFromResource(inflater, position, convertView, parent, mDropDownResource);
}
/**
@@ -426,7 +463,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @return An ArrayAdapter<CharSequence>.
*/
public static ArrayAdapter<CharSequence> createFromResource(Context context,
- int textArrayResId, int textViewResId) {
+ @ArrayRes int textArrayResId, @LayoutRes int textViewResId) {
CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
return new ArrayAdapter<CharSequence>(context, textViewResId, strings);
}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index e6392b9..01767d5 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.DrawableRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
@@ -356,7 +357,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
*
* @attr ref android.R.styleable#PopupWindow_popupBackground
*/
- public void setDropDownBackgroundResource(int id) {
+ public void setDropDownBackgroundResource(@DrawableRes int id) {
mPopup.setBackgroundDrawable(getContext().getDrawable(id));
}
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 1663620..154cc33 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -18,8 +18,6 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
@@ -112,14 +110,7 @@ public class Button extends TextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Button.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Button.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Button.class.getName();
}
}
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index ed59ea6..5bc16cb 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -16,6 +16,8 @@
package android.widget;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
import android.annotation.Widget;
import android.content.Context;
import android.content.res.Configuration;
@@ -23,9 +25,6 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
import com.android.internal.R;
import java.text.DateFormat;
@@ -142,7 +141,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
*/
- public void setSelectedWeekBackgroundColor(int color) {
+ public void setSelectedWeekBackgroundColor(@ColorInt int color) {
mDelegate.setSelectedWeekBackgroundColor(color);
}
@@ -153,6 +152,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
*/
+ @ColorInt
public int getSelectedWeekBackgroundColor() {
return mDelegate.getSelectedWeekBackgroundColor();
}
@@ -164,7 +164,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
*/
- public void setFocusedMonthDateColor(int color) {
+ public void setFocusedMonthDateColor(@ColorInt int color) {
mDelegate.setFocusedMonthDateColor(color);
}
@@ -175,6 +175,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
*/
+ @ColorInt
public int getFocusedMonthDateColor() {
return mDelegate.getFocusedMonthDateColor();
}
@@ -186,7 +187,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
*/
- public void setUnfocusedMonthDateColor(int color) {
+ public void setUnfocusedMonthDateColor(@ColorInt int color) {
mDelegate.setUnfocusedMonthDateColor(color);
}
@@ -197,6 +198,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
*/
+ @ColorInt
public int getUnfocusedMonthDateColor() {
return mDelegate.getUnfocusedMonthDateColor();
}
@@ -208,7 +210,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_weekNumberColor
*/
- public void setWeekNumberColor(int color) {
+ public void setWeekNumberColor(@ColorInt int color) {
mDelegate.setWeekNumberColor(color);
}
@@ -219,6 +221,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_weekNumberColor
*/
+ @ColorInt
public int getWeekNumberColor() {
return mDelegate.getWeekNumberColor();
}
@@ -230,7 +233,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
*/
- public void setWeekSeparatorLineColor(int color) {
+ public void setWeekSeparatorLineColor(@ColorInt int color) {
mDelegate.setWeekSeparatorLineColor(color);
}
@@ -241,6 +244,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
*/
+ @ColorInt
public int getWeekSeparatorLineColor() {
return mDelegate.getWeekSeparatorLineColor();
}
@@ -253,7 +257,7 @@ public class CalendarView extends FrameLayout {
*
* @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
*/
- public void setSelectedDateVerticalBar(int resourceId) {
+ public void setSelectedDateVerticalBar(@DrawableRes int resourceId) {
mDelegate.setSelectedDateVerticalBar(resourceId);
}
@@ -502,13 +506,8 @@ public class CalendarView extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(CalendarView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(CalendarView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return CalendarView.class.getName();
}
/**
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 71438c9..15bbdd2 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -18,8 +18,6 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
@@ -73,14 +71,7 @@ public class CheckBox extends CompoundButton {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(CheckBox.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(CheckBox.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return CheckBox.class.getName();
}
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 69969a9..22e079c 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -18,6 +18,7 @@ package android.widget;
import com.android.internal.R;
+import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -32,12 +33,14 @@ import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-
/**
- * An extension to TextView that supports the {@link android.widget.Checkable} interface.
- * This is useful when used in a {@link android.widget.ListView ListView} where the it's
- * {@link android.widget.ListView#setChoiceMode(int) setChoiceMode} has been set to
- * something other than {@link android.widget.ListView#CHOICE_MODE_NONE CHOICE_MODE_NONE}.
+ * An extension to {@link TextView} that supports the {@link Checkable}
+ * interface and displays.
+ * <p>
+ * This is useful when used in a {@link android.widget.ListView ListView} where
+ * the {@link android.widget.ListView#setChoiceMode(int) setChoiceMode} has
+ * been set to something other than
+ * {@link android.widget.ListView#CHOICE_MODE_NONE CHOICE_MODE_NONE}.
*
* @attr ref android.R.styleable#CheckedTextView_checked
* @attr ref android.R.styleable#CheckedTextView_checkMark
@@ -116,9 +119,10 @@ public class CheckedTextView extends TextView implements Checkable {
}
/**
- * <p>Changes the checked state of this text view.</p>
+ * Sets the checked state of this view.
*
- * @param checked true to check the text, false to uncheck it
+ * @param checked {@code true} set the state to checked, {@code false} to
+ * uncheck
*/
public void setChecked(boolean checked) {
if (mChecked != checked) {
@@ -129,24 +133,24 @@ public class CheckedTextView extends TextView implements Checkable {
}
}
-
/**
- * Set the checkmark to a given Drawable, identified by its resourece id. This will be drawn
- * when {@link #isChecked()} is true.
- *
- * @param resid The Drawable to use for the checkmark.
+ * Sets the check mark to the drawable with the specified resource ID.
+ * <p>
+ * When this view is checked, the drawable's state set will include
+ * {@link android.R.attr#state_checked}.
*
+ * @param resId the resource identifier of drawable to use as the check
+ * mark
+ * @attr ref android.R.styleable#CheckedTextView_checkMark
* @see #setCheckMarkDrawable(Drawable)
* @see #getCheckMarkDrawable()
- *
- * @attr ref android.R.styleable#CheckedTextView_checkMark
*/
- public void setCheckMarkDrawable(int resid) {
- if (resid != 0 && resid == mCheckMarkResource) {
+ public void setCheckMarkDrawable(@DrawableRes int resId) {
+ if (resId != 0 && resId == mCheckMarkResource) {
return;
}
- mCheckMarkResource = resid;
+ mCheckMarkResource = resId;
Drawable d = null;
if (mCheckMarkResource != 0) {
@@ -156,14 +160,15 @@ public class CheckedTextView extends TextView implements Checkable {
}
/**
- * Set the checkmark to a given Drawable. This will be drawn when {@link #isChecked()} is true.
- *
- * @param d The Drawable to use for the checkmark.
+ * Set the check mark to the specified drawable.
+ * <p>
+ * When this view is checked, the drawable's state set will include
+ * {@link android.R.attr#state_checked}.
*
+ * @param d the drawable to use for the check mark
+ * @attr ref android.R.styleable#CheckedTextView_checkMark
* @see #setCheckMarkDrawable(int)
* @see #getCheckMarkDrawable()
- *
- * @attr ref android.R.styleable#CheckedTextView_checkMark
*/
public void setCheckMarkDrawable(Drawable d) {
if (mCheckMarkDrawable != null) {
@@ -436,16 +441,21 @@ public class CheckedTextView extends TextView implements Checkable {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(CheckedTextView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return CheckedTextView.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setChecked(mChecked);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(CheckedTextView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setCheckable(true);
info.setChecked(mChecked);
}
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index f94789d..a15080e 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -24,8 +24,6 @@ import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
import java.util.Formatter;
@@ -282,14 +280,7 @@ public class Chronometer extends TextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Chronometer.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Chronometer.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Chronometer.class.getName();
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 447ccc2..f2afeeb 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.graphics.PorterDuff;
import com.android.internal.R;
@@ -50,7 +51,6 @@ import android.view.accessibility.AccessibilityNodeInfo;
*/
public abstract class CompoundButton extends Button implements Checkable {
private boolean mChecked;
- private int mButtonResource;
private boolean mBroadcasting;
private Drawable mButtonDrawable;
@@ -197,54 +197,62 @@ public abstract class CompoundButton extends Button implements Checkable {
}
/**
- * Set the button graphic to a given Drawable, identified by its resource
- * id.
+ * Sets a drawable as the compound button image given its resource
+ * identifier.
*
- * @param resid the resource id of the drawable to use as the button
- * graphic
+ * @param resId the resource identifier of the drawable
+ * @attr ref android.R.styleable#CompoundButton_button
*/
- public void setButtonDrawable(int resid) {
- if (resid != 0 && resid == mButtonResource) {
- return;
- }
-
- mButtonResource = resid;
-
- Drawable d = null;
- if (mButtonResource != 0) {
- d = getContext().getDrawable(mButtonResource);
+ public void setButtonDrawable(@DrawableRes int resId) {
+ final Drawable d;
+ if (resId != 0) {
+ d = getContext().getDrawable(resId);
+ } else {
+ d = null;
}
setButtonDrawable(d);
}
/**
- * Set the button graphic to a given Drawable
+ * Sets a drawable as the compound button image.
*
- * @param d The Drawable to use as the button graphic
+ * @param drawable the drawable to set
+ * @attr ref android.R.styleable#CompoundButton_button
*/
- public void setButtonDrawable(Drawable d) {
- if (mButtonDrawable != d) {
+ @Nullable
+ public void setButtonDrawable(@Nullable Drawable drawable) {
+ if (mButtonDrawable != drawable) {
if (mButtonDrawable != null) {
mButtonDrawable.setCallback(null);
unscheduleDrawable(mButtonDrawable);
}
- mButtonDrawable = d;
+ mButtonDrawable = drawable;
- if (d != null) {
- d.setCallback(this);
- d.setLayoutDirection(getLayoutDirection());
- if (d.isStateful()) {
- d.setState(getDrawableState());
+ if (drawable != null) {
+ drawable.setCallback(this);
+ drawable.setLayoutDirection(getLayoutDirection());
+ if (drawable.isStateful()) {
+ drawable.setState(getDrawableState());
}
- d.setVisible(getVisibility() == VISIBLE, false);
- setMinHeight(d.getIntrinsicHeight());
+ drawable.setVisible(getVisibility() == VISIBLE, false);
+ setMinHeight(drawable.getIntrinsicHeight());
applyButtonTint();
}
}
}
/**
+ * @return the drawable used as the compound button image
+ * @see #setButtonDrawable(Drawable)
+ * @see #setButtonDrawable(int)
+ */
+ @Nullable
+ public Drawable getButtonDrawable() {
+ return mButtonDrawable;
+ }
+
+ /**
* Applies a tint to the button drawable. Does not modify the current tint
* mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
* <p>
@@ -325,16 +333,21 @@ public abstract class CompoundButton extends Button implements Checkable {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(CompoundButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return CompoundButton.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setChecked(mChecked);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(CompoundButton.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setCheckable(true);
info.setChecked(mChecked);
}
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index d4c5be0..30c74c0 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -17,11 +17,13 @@
package android.widget;
import android.content.Context;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
@@ -35,7 +37,7 @@ import android.view.ViewGroup;
* columns.
*/
public abstract class CursorAdapter extends BaseAdapter implements Filterable,
- CursorFilter.CursorFilterClient {
+ CursorFilter.CursorFilterClient, Spinner.ThemedSpinnerAdapter {
/**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
@@ -57,6 +59,11 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
*/
protected Context mContext;
/**
+ * Context used for {@link #getDropDownView(int, View, ViewGroup)}.
+ * {@hide}
+ */
+ protected Context mDropDownContext;
+ /**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
@@ -185,6 +192,33 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
}
/**
+ * Sets the {@link Resources.Theme} against which drop-down views are
+ * inflated.
+ * <p>
+ * By default, drop-down views are inflated against the theme of the
+ * {@link Context} passed to the adapter's constructor.
+ *
+ * @param theme the theme against which to inflate drop-down views or
+ * {@code null} to use the theme from the adapter's context
+ * @see #newDropDownView(Context, Cursor, ViewGroup)
+ */
+ @Override
+ public void setDropDownViewTheme(Resources.Theme theme) {
+ if (theme == null) {
+ mDropDownContext = null;
+ } else if (theme == mContext.getTheme()) {
+ mDropDownContext = mContext;
+ } else {
+ mDropDownContext = new ContextThemeWrapper(mContext, theme);
+ }
+ }
+
+ @Override
+ public Resources.Theme getDropDownViewTheme() {
+ return mDropDownContext == null ? null : mDropDownContext.getTheme();
+ }
+
+ /**
* Returns the cursor.
* @return the cursor.
*/
@@ -258,20 +292,21 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
if (mDataValid) {
+ final Context context = mDropDownContext == null ? mContext : mDropDownContext;
mCursor.moveToPosition(position);
- View v;
+ final View v;
if (convertView == null) {
- v = newDropDownView(mContext, mCursor, parent);
+ v = newDropDownView(context, mCursor, parent);
} else {
v = convertView;
}
- bindView(v, mContext, mCursor);
+ bindView(v, context, mCursor);
return v;
} else {
return null;
}
}
-
+
/**
* Makes a new view to hold the data pointed to by cursor.
* @param context Interface to application's global information
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 0ca08e1..45998f7 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -33,7 +33,6 @@ import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.NumberPicker.OnValueChangeListener;
@@ -283,27 +282,22 @@ public class DatePicker extends FrameLayout {
return mDelegate.isEnabled();
}
+ /** @hide */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
return mDelegate.dispatchPopulateAccessibilityEvent(event);
}
+ /** @hide */
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
mDelegate.onPopulateAccessibilityEvent(event);
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- mDelegate.onInitializeAccessibilityEvent(event);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- mDelegate.onInitializeAccessibilityNodeInfo(info);
+ public CharSequence getAccessibilityClassName() {
+ return DatePicker.class.getName();
}
@Override
@@ -472,8 +466,6 @@ public class DatePicker extends FrameLayout {
boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
void onPopulateAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
/**
@@ -888,16 +880,6 @@ public class DatePicker extends FrameLayout {
event.getText().add(selectedDateUtterance);
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(DatePicker.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(DatePicker.class.getName());
- }
-
/**
* Sets the current locale.
*
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 1d50eb2..0e3ec7f 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -30,7 +30,6 @@ import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@@ -145,8 +144,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
// Use Theme attributes if possible
final int dayOfWeekTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_dayOfWeekTextAppearance, -1);
- if (dayOfWeekTextAppearanceResId != -1) {
+ R.styleable.DatePicker_dayOfWeekTextAppearance, 0);
+ if (dayOfWeekTextAppearanceResId != 0) {
mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId);
}
@@ -154,34 +153,23 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
dateLayout.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
- final int headerSelectedTextColor = a.getColor(
- R.styleable.DatePicker_headerSelectedTextColor, defaultHighlightColor);
final int monthTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_headerMonthTextAppearance, -1);
- if (monthTextAppearanceResId != -1) {
+ R.styleable.DatePicker_headerMonthTextAppearance, 0);
+ if (monthTextAppearanceResId != 0) {
mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId);
}
- mHeaderMonthTextView.setTextColor(ColorStateList.addFirstIfMissing(
- mHeaderMonthTextView.getTextColors(), R.attr.state_selected,
- headerSelectedTextColor));
final int dayOfMonthTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_headerDayOfMonthTextAppearance, -1);
- if (dayOfMonthTextAppearanceResId != -1) {
+ R.styleable.DatePicker_headerDayOfMonthTextAppearance, 0);
+ if (dayOfMonthTextAppearanceResId != 0) {
mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId);
}
- mHeaderDayOfMonthTextView.setTextColor(ColorStateList.addFirstIfMissing(
- mHeaderDayOfMonthTextView.getTextColors(), R.attr.state_selected,
- headerSelectedTextColor));
- final int yearTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_headerYearTextAppearance, -1);
- if (yearTextAppearanceResId != -1) {
- mHeaderYearTextView.setTextAppearance(context, yearTextAppearanceResId);
+ final int headerYearTextAppearanceResId = a.getResourceId(
+ R.styleable.DatePicker_headerYearTextAppearance, 0);
+ if (headerYearTextAppearanceResId != 0) {
+ mHeaderYearTextView.setTextAppearance(context, headerYearTextAppearanceResId);
}
- mHeaderYearTextView.setTextColor(ColorStateList.addFirstIfMissing(
- mHeaderYearTextView.getTextColors(), R.attr.state_selected,
- headerSelectedTextColor));
mDayPickerView = new DayPickerView(mContext);
mDayPickerView.setFirstDayOfWeek(mFirstDayOfWeek);
@@ -194,16 +182,23 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
mYearPickerView.init(this);
mYearPickerView.setRange(mMinDate, mMaxDate);
- final int yearSelectedCircleColor = a.getColor(R.styleable.DatePicker_yearListSelectorColor,
- defaultHighlightColor);
- mYearPickerView.setYearSelectedCircleColor(yearSelectedCircleColor);
+ final ColorStateList yearBackgroundColor = a.getColorStateList(
+ R.styleable.DatePicker_yearListSelectorColor);
+ mYearPickerView.setYearBackgroundColor(yearBackgroundColor);
+
+ final int yearTextAppearanceResId = a.getResourceId(
+ R.styleable.DatePicker_yearListItemTextAppearance, 0);
+ if (yearTextAppearanceResId != 0) {
+ mYearPickerView.setYearTextAppearance(yearTextAppearanceResId);
+ }
final ColorStateList calendarTextColor = a.getColorStateList(
R.styleable.DatePicker_calendarTextColor);
- final int calendarSelectedTextColor = a.getColor(
- R.styleable.DatePicker_calendarSelectedTextColor, defaultHighlightColor);
- mDayPickerView.setCalendarTextColor(ColorStateList.addFirstIfMissing(
- calendarTextColor, R.attr.state_selected, calendarSelectedTextColor));
+ mDayPickerView.setCalendarTextColor(calendarTextColor);
+
+ final ColorStateList calendarDayBackgroundColor = a.getColorStateList(
+ R.styleable.DatePicker_calendarDayBackgroundColor);
+ mDayPickerView.setCalendarDayBackgroundColor(calendarDayBackgroundColor);
mDayPickerDescription = res.getString(R.string.day_picker_description);
mSelectDay = res.getString(R.string.select_day);
@@ -578,14 +573,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
event.getText().add(mCurrentDate.getTime().toString());
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(DatePicker.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(DatePicker.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return DatePicker.class.getName();
}
@Override
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index db17df7..0b5824a 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -21,17 +21,14 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Handler;
import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
-import android.provider.Settings;
import android.widget.TextView;
import android.widget.RemoteViews.RemoteView;
import java.text.DateFormat;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index 7db3fb9..65af45d 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -305,6 +305,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
mAdapter.setCalendarTextColor(colors);
}
+ void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) {
+ mAdapter.setCalendarDayBackgroundColor(dayBackgroundColor);
+ }
+
void setCalendarTextAppearance(int resId) {
mAdapter.setCalendarTextAppearance(resId);
}
@@ -459,9 +463,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setItemCount(-1);
}
@@ -476,22 +481,26 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
/**
* Necessary for accessibility, to ensure we support "scrolling" forward and backward
* in the month list.
+ *
+ * @hide
*/
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
}
/**
* When scroll forward/backward events are received, announce the newly scrolled-to month.
+ *
+ * @hide
*/
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
- return super.performAccessibilityAction(action, arguments);
+ return super.performAccessibilityActionInternal(action, arguments);
}
// Figure out what month is showing.
diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java
index b6c1e5b..9e442f9 100644
--- a/core/java/android/widget/DigitalClock.java
+++ b/core/java/android/widget/DigitalClock.java
@@ -23,9 +23,6 @@ import android.os.SystemClock;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
import java.util.Calendar;
/**
@@ -116,16 +113,8 @@ public class DigitalClock extends TextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- //noinspection deprecation
- event.setClassName(DigitalClock.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public CharSequence getAccessibilityClassName() {
//noinspection deprecation
- info.setClassName(DigitalClock.class.getName());
+ return DigitalClock.class.getName();
}
}
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 391347e..9019f31 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.ColorInt;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.PorterDuff;
@@ -292,7 +293,7 @@ public class EdgeEffect {
*
* @param color Color in argb
*/
- public void setColor(int color) {
+ public void setColor(@ColorInt int color) {
mPaint.setColor(color);
}
@@ -300,6 +301,7 @@ public class EdgeEffect {
* Return the color of this edge effect in argb.
* @return The color of this edge effect in argb
*/
+ @ColorInt
public int getColor() {
return mPaint.getColor();
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index a8ff562..d21a5f7 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -25,7 +25,6 @@ import android.text.TextUtils;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.MovementMethod;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -123,19 +122,13 @@ public class EditText extends TextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(EditText.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return EditText.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(EditText.class.getName());
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_SET_TEXT: {
CharSequence text = (arguments != null) ? arguments.getCharSequence(
@@ -147,7 +140,7 @@ public class EditText extends TextView {
return true;
}
default: {
- return super.performAccessibilityAction(action, arguments);
+ return super.performAccessibilityActionInternal(action, arguments);
}
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2aaad7a..fd34415 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -23,8 +23,6 @@ import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputFilter;
-import android.text.SpannableString;
-
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.view.menu.MenuBuilder;
@@ -50,6 +48,7 @@ import android.graphics.drawable.Drawable;
import android.inputmethodservice.ExtractEditText;
import android.os.Bundle;
import android.os.Handler;
+import android.os.ParcelableParcel;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.DynamicLayout;
@@ -78,14 +77,14 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ActionMode;
import android.view.ActionMode.Callback;
-import android.view.RenderNode;
+import android.view.DisplayListCanvas;
import android.view.DragEvent;
import android.view.Gravity;
-import android.view.HardwareCanvas;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.RenderNode;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnClickListener;
@@ -118,15 +117,19 @@ import java.util.HashMap;
*/
public class Editor {
private static final String TAG = "Editor";
- static final boolean DEBUG_UNDO = false;
+ private static final boolean DEBUG_UNDO = false;
static final int BLINK = 500;
private static final float[] TEMP_POSITION = new float[2];
private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
+ // Tag used when the Editor maintains its own separate UndoManager.
+ private static final String UNDO_OWNER_TAG = "Editor";
- UndoManager mUndoManager;
- UndoOwner mUndoOwner;
- InputFilter mUndoInputFilter;
+ // Each Editor manages its own undo stack.
+ private final UndoManager mUndoManager = new UndoManager();
+ private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
+ final UndoInputFilter mUndoInputFilter = new UndoInputFilter(this);
+ boolean mAllowUndo = true;
// Cursor Controllers.
InsertionPointCursorController mInsertionPointCursorController;
@@ -214,6 +217,12 @@ public class Editor {
WordIterator mWordIterator;
SpellChecker mSpellChecker;
+ // This word iterator is set with text and used to determine word boundaries
+ // when a user is selecting text.
+ private WordIterator mWordIteratorWithText;
+ // Indicate that the text in the word iterator needs to be updated.
+ private boolean mUpdateWordIteratorText;
+
private Rect mTempRect;
private TextView mTextView;
@@ -222,6 +231,59 @@ public class Editor {
Editor(TextView textView) {
mTextView = textView;
+ // Synchronize the filter list, which places the undo input filter at the end.
+ mTextView.setFilters(mTextView.getFilters());
+ }
+
+ ParcelableParcel saveInstanceState() {
+ ParcelableParcel state = new ParcelableParcel(getClass().getClassLoader());
+ Parcel parcel = state.getParcel();
+ mUndoManager.saveInstanceState(parcel);
+ mUndoInputFilter.saveInstanceState(parcel);
+ return state;
+ }
+
+ void restoreInstanceState(ParcelableParcel state) {
+ Parcel parcel = state.getParcel();
+ mUndoManager.restoreInstanceState(parcel, state.getClassLoader());
+ mUndoInputFilter.restoreInstanceState(parcel);
+ // Re-associate this object as the owner of undo state.
+ mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
+ }
+
+ /**
+ * Forgets all undo and redo operations for this Editor.
+ */
+ void forgetUndoRedo() {
+ UndoOwner[] owners = { mUndoOwner };
+ mUndoManager.forgetUndos(owners, -1 /* all */);
+ mUndoManager.forgetRedos(owners, -1 /* all */);
+ }
+
+ boolean canUndo() {
+ UndoOwner[] owners = { mUndoOwner };
+ return mAllowUndo && mUndoManager.countUndos(owners) > 0;
+ }
+
+ boolean canRedo() {
+ UndoOwner[] owners = { mUndoOwner };
+ return mAllowUndo && mUndoManager.countRedos(owners) > 0;
+ }
+
+ void undo() {
+ if (!mAllowUndo) {
+ return;
+ }
+ UndoOwner[] owners = { mUndoOwner };
+ mUndoManager.undo(owners, 1); // Undo 1 action.
+ }
+
+ void redo() {
+ if (!mAllowUndo) {
+ return;
+ }
+ UndoOwner[] owners = { mUndoOwner };
+ mUndoManager.redo(owners, 1); // Redo 1 action.
}
void onAttachedToWindow() {
@@ -252,7 +314,7 @@ public class Editor {
mTextView.setHasTransientState(false);
// We had an active selection from before, start the selection mode.
- startSelectionActionMode();
+ startSelectionActionModeWithSelection();
}
getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
@@ -647,9 +709,52 @@ public class Editor {
return mTextView.getTransformationMethod() instanceof PasswordTransformationMethod;
}
+ private int getWordStart(int offset) {
+ // FIXME - For this and similar methods we're not doing anything to check if there's
+ // a LocaleSpan in the text, this may be something we should try handling or checking for.
+ int retOffset = getWordIteratorWithText().getBeginning(offset);
+ if (retOffset == BreakIterator.DONE) retOffset = offset;
+ return retOffset;
+ }
+
+ private int getWordEnd(int offset, boolean includePunctuation) {
+ int retOffset = getWordIteratorWithText().getEnd(offset);
+ if (retOffset == BreakIterator.DONE) {
+ retOffset = offset;
+ } else if (includePunctuation) {
+ retOffset = handlePunctuation(retOffset);
+ }
+ return retOffset;
+ }
+
+ private boolean isEndBoundary(int offset) {
+ int thisEnd = getWordEnd(offset, false);
+ return offset == thisEnd;
+ }
+
+ private boolean isStartBoundary(int offset) {
+ int thisStart = getWordStart(offset);
+ return thisStart == offset;
+ }
+
+ private int handlePunctuation(int offset) {
+ // FIXME - Check with UX how repeated ending punctuation should be handled.
+ // FIXME - Check with UX if / how we would handle non sentence ending characters.
+ // FIXME - Consider punctuation in different languages.
+ CharSequence text = mTextView.getText();
+ if (offset < text.length()) {
+ int c = Character.codePointAt(text, offset);
+ if (c == 0x002e /* period */|| c == 0x003f /* question mark */
+ || c == 0x0021 /* exclamation mark */) {
+ offset = Character.offsetByCodePoints(text, offset, 1);
+ }
+ }
+ return offset;
+ }
+
/**
- * Adjusts selection to the word under last touch offset.
- * Return true if the operation was successfully performed.
+ * Adjusts selection to the word under last touch offset. Return true if the operation was
+ * successfully performed.
*/
private boolean selectCurrentWord() {
if (!canSelectText()) {
@@ -696,6 +801,8 @@ public class Editor {
selectionStart = ((Spanned) mTextView.getText()).getSpanStart(urlSpan);
selectionEnd = ((Spanned) mTextView.getText()).getSpanEnd(urlSpan);
} else {
+ // FIXME - We should check if there's a LocaleSpan in the text, this may be
+ // something we should try handling or checking for.
final WordIterator wordIterator = getWordIterator();
wordIterator.setCharSequence(mTextView.getText(), minOffset, maxOffset);
@@ -718,6 +825,7 @@ public class Editor {
void onLocaleChanged() {
// Will be re-created on demand in getWordIterator with the proper new locale
mWordIterator = null;
+ mWordIteratorWithText = null;
}
/**
@@ -730,6 +838,23 @@ public class Editor {
return mWordIterator;
}
+ private WordIterator getWordIteratorWithText() {
+ if (mWordIteratorWithText == null) {
+ mWordIteratorWithText = new WordIterator(mTextView.getTextServicesLocale());
+ mUpdateWordIteratorText = true;
+ }
+ if (mUpdateWordIteratorText) {
+ // FIXME - Shouldn't copy all of the text as only the area of the text relevant
+ // to the user's selection is needed. A possible solution would be to
+ // copy some number N of characters near the selection and then when the
+ // user approaches N then we'd do another copy of the next N characters.
+ CharSequence text = mTextView.getText();
+ mWordIteratorWithText.setCharSequence(text, 0, text.length());
+ mUpdateWordIteratorText = false;
+ }
+ return mWordIteratorWithText;
+ }
+
private long getCharRange(int offset) {
final int textLength = mTextView.getText().length();
if (offset + 1 < textLength) {
@@ -856,14 +981,15 @@ public class Editor {
}
public boolean performLongClick(boolean handled) {
- // Long press in empty space moves cursor and shows the Paste affordance if available.
+ // Long press in empty space moves cursor and starts the selection action mode.
if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
mInsertionControllerEnabled) {
final int offset = mTextView.getOffsetForPosition(mLastDownPositionX,
mLastDownPositionY);
stopSelectionActionMode();
Selection.setSelection((Spannable) mTextView.getText(), offset);
- getInsertionController().showWithActionPopup();
+ getInsertionController().show();
+ startSelectionActionModeWithoutSelection();
handled = true;
}
@@ -878,16 +1004,15 @@ public class Editor {
mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0);
stopSelectionActionMode();
} else {
- getSelectionController().hide();
- selectCurrentWord();
- getSelectionController().show();
+ stopSelectionActionMode();
+ startSelectionActionModeWithSelection();
}
handled = true;
}
// Start a new selection
if (!handled) {
- handled = startSelectionActionMode();
+ handled = startSelectionActionModeWithSelection();
}
return handled;
@@ -1016,6 +1141,9 @@ public class Editor {
void sendOnTextChanged(int start, int after) {
updateSpellCheckSpans(start, start + after, false);
+ // Flip flag to indicate the word iterator needs to have the text reset.
+ mUpdateWordIteratorText = true;
+
// Hide the controllers as soon as text is modified (typing, procedural...)
// We do not hide the span controllers, since they can be added when a new text is
// inserted into the text view (voice IME).
@@ -1101,6 +1229,7 @@ public class Editor {
ims.mChangedEnd = EXTRACT_UNKNOWN;
ims.mContentChanged = false;
}
+ mUndoInputFilter.beginBatchEdit();
mTextView.onBeginBatchEdit();
}
}
@@ -1127,6 +1256,7 @@ public class Editor {
void finishBatchEdit(final InputMethodState ims) {
mTextView.onEndBatchEdit();
+ mUndoInputFilter.endBatchEdit();
if (ims.mContentChanged || ims.mSelectionModeChanged) {
mTextView.updateAfterEdit();
@@ -1352,6 +1482,9 @@ public class Editor {
searchStartIndex);
// Note how dynamic layout's internal block indices get updated from Editor
blockIndices[i] = blockIndex;
+ if (mTextDisplayLists[blockIndex] != null) {
+ mTextDisplayLists[blockIndex].isDirty = true;
+ }
searchStartIndex = blockIndex + 1;
}
@@ -1381,17 +1514,18 @@ public class Editor {
// Rebuild display list if it is invalid
if (blockDisplayListIsInvalid) {
- final HardwareCanvas hardwareCanvas = blockDisplayList.start(
+ final DisplayListCanvas displayListCanvas = blockDisplayList.start(
right - left, bottom - top);
try {
// drawText is always relative to TextView's origin, this translation
// brings this range of text back to the top left corner of the viewport
- hardwareCanvas.translate(-left, -top);
- layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine);
+ displayListCanvas.translate(-left, -top);
+ layout.drawText(displayListCanvas, blockBeginLine, blockEndLine);
+ mTextDisplayLists[blockIndex].isDirty = false;
// No need to untranslate, previous context is popped after
// drawDisplayList
} finally {
- blockDisplayList.end(hardwareCanvas);
+ blockDisplayList.end(displayListCanvas);
// Same as drawDisplayList below, handled by our TextView's parent
blockDisplayList.setClipToBounds(false);
}
@@ -1402,7 +1536,7 @@ public class Editor {
blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
}
- ((HardwareCanvas) canvas).drawRenderNode(blockDisplayList, null,
+ ((DisplayListCanvas) canvas).drawRenderNode(blockDisplayList,
0 /* no child clipping, our TextView parent enforces it */);
endOfPreviousBlock = blockEndLine;
@@ -1529,7 +1663,21 @@ public class Editor {
/**
* @return true if the selection mode was actually started.
*/
- boolean startSelectionActionMode() {
+ private boolean startSelectionActionModeWithoutSelection() {
+ if (mSelectionActionMode != null) {
+ // Selection action mode is already started
+ // TODO: revisit invocations to minimize this case.
+ return false;
+ }
+ ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
+ mSelectionActionMode = mTextView.startActionMode(actionModeCallback);
+ return mSelectionActionMode != null;
+ }
+
+ /**
+ * @return true if the selection mode was actually started.
+ */
+ boolean startSelectionActionModeWithSelection() {
if (mSelectionActionMode != null) {
// Selection action mode is already started
return false;
@@ -1567,6 +1715,9 @@ public class Editor {
}
}
+ if (selectionStarted) {
+ getSelectionController().enterDrag();
+ }
return selectionStarted;
}
@@ -1702,7 +1853,7 @@ public class Editor {
/**
* Called by the framework in response to a text auto-correction (such as fixing a typo using a
- * a dictionnary) from the current input method, provided by it calling
+ * a dictionary) from the current input method, provided by it calling
* {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
* implementation flashes the background of the corrected word to provide feedback to the user.
*
@@ -2838,6 +2989,12 @@ public class Editor {
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
+ if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
+ menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
+ setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+
styledAttributes.recycle();
if (mCustomSelectionActionModeCallback != null) {
@@ -2848,7 +3005,6 @@ public class Editor {
}
if (menu.hasVisibleItems() || mode.getCustomView() != null) {
- getSelectionController().show();
mTextView.setHasTransientState(true);
return true;
} else {
@@ -2870,6 +3026,10 @@ public class Editor {
mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
return true;
}
+ if (item.getItemId() == TextView.ID_REPLACE) {
+ onReplace();
+ return true;
+ }
return mTextView.onTextContextMenuItem(item.getItemId());
}
@@ -2898,6 +3058,13 @@ public class Editor {
}
}
+ private void onReplace() {
+ int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
+ stopSelectionActionMode();
+ Selection.setSelection((Spannable) mTextView.getText(), middle);
+ showSuggestions();
+ }
+
private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
private static final int POPUP_TEXT_LAYOUT =
com.android.internal.R.layout.text_edit_action_popup_text;
@@ -2956,10 +3123,7 @@ public class Editor {
mTextView.onTextContextMenuItem(TextView.ID_PASTE);
hide();
} else if (view == mReplaceTextView) {
- int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
- stopSelectionActionMode();
- Selection.setSelection((Spannable) mTextView.getText(), middle);
- showSuggestions();
+ onReplace();
}
}
@@ -3176,16 +3340,14 @@ public class Editor {
private float mIdealVerticalOffset;
// Parent's (TextView) previous position in window
private int mLastParentX, mLastParentY;
- // Transient action popup window for Paste and Replace actions
- protected ActionPopupWindow mActionPopupWindow;
// Previous text character offset
private int mPreviousOffset = -1;
// Previous text character offset
private boolean mPositionHasChanged = true;
- // Used to delay the appearance of the action popup window
- private Runnable mActionPopupShower;
// Minimum touch target size for handles
private int mMinSize;
+ // Indicates the line of text that the handle is on.
+ protected int mLine = -1;
public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(mTextView.getContext());
@@ -3194,6 +3356,8 @@ public class Editor {
mContainer.setSplitTouchEnabled(true);
mContainer.setClippingEnabled(false);
mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+ mContainer.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
+ mContainer.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
mContainer.setContentView(this);
mDrawableLtr = drawableLtr;
@@ -3281,8 +3445,6 @@ public class Editor {
// Make sure the offset is always considered new, even when focusing at same position
mPreviousOffset = -1;
positionAtCursorOffset(getCurrentCursorOffset(), false);
-
- hideActionPopupWindow();
}
protected void dismiss() {
@@ -3297,31 +3459,6 @@ public class Editor {
getPositionListener().removeSubscriber(this);
}
- void showActionPopupWindow(int delay) {
- if (mActionPopupWindow == null) {
- mActionPopupWindow = new ActionPopupWindow();
- }
- if (mActionPopupShower == null) {
- mActionPopupShower = new Runnable() {
- public void run() {
- mActionPopupWindow.show();
- }
- };
- } else {
- mTextView.removeCallbacks(mActionPopupShower);
- }
- mTextView.postDelayed(mActionPopupShower, delay);
- }
-
- protected void hideActionPopupWindow() {
- if (mActionPopupShower != null) {
- mTextView.removeCallbacks(mActionPopupShower);
- }
- if (mActionPopupWindow != null) {
- mActionPopupWindow.hide();
- }
- }
-
public boolean isShowing() {
return mContainer.isShowing();
}
@@ -3361,6 +3498,7 @@ public class Editor {
addPositionToTouchUpFilter(offset);
}
final int line = layout.getLineForOffset(offset);
+ mLine = line;
mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX -
getHorizontalOffset() + getCursorOffset());
@@ -3410,6 +3548,30 @@ public class Editor {
}
}
+ public void showAtLocation(int offset) {
+ // TODO - investigate if there's a better way to show the handles
+ // after the drag accelerator has occured.
+ int[] tmpCords = new int[2];
+ mTextView.getLocationInWindow(tmpCords);
+
+ Layout layout = mTextView.getLayout();
+ int posX = tmpCords[0];
+ int posY = tmpCords[1];
+
+ final int line = layout.getLineForOffset(offset);
+
+ int startX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f
+ - mHotspotX - getHorizontalOffset() + getCursorOffset());
+ int startY = layout.getLineBottom(line);
+
+ // Take TextView's padding and scroll into account.
+ startX += mTextView.viewportToContentHorizontalOffset();
+ startY += mTextView.viewportToContentVerticalOffset();
+
+ mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY,
+ startX + posX, startY + posY);
+ }
+
@Override
protected void onDraw(Canvas c) {
final int drawWidth = mDrawable.getIntrinsicWidth();
@@ -3497,20 +3659,16 @@ public class Editor {
return mIsDragging;
}
- void onHandleMoved() {
- hideActionPopupWindow();
- }
+ void onHandleMoved() {}
- public void onDetached() {
- hideActionPopupWindow();
- }
+ public void onDetached() {}
}
private class InsertionHandleView extends HandleView {
private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000;
private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds
- // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow
+ // Used to detect taps on the insertion handle, which will affect the selection action mode
private float mDownPositionX, mDownPositionY;
private Runnable mHider;
@@ -3525,17 +3683,12 @@ public class Editor {
final long durationSinceCutOrCopy =
SystemClock.uptimeMillis() - TextView.LAST_CUT_OR_COPY_TIME;
if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
- showActionPopupWindow(0);
+ startSelectionActionModeWithoutSelection();
}
hideAfterDelay();
}
- public void showWithActionPopup() {
- show();
- showActionPopupWindow(0);
- }
-
private void hideAfterDelay() {
if (mHider == null) {
mHider = new Runnable() {
@@ -3597,11 +3750,11 @@ public class Editor {
final int touchSlop = viewConfiguration.getScaledTouchSlop();
if (distanceSquared < touchSlop * touchSlop) {
- if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
- // Tapping on the handle dismisses the displayed action popup
- mActionPopupWindow.hide();
+ // Tapping on the handle toggles the selection action mode.
+ if (mSelectionActionMode != null) {
+ mSelectionActionMode.finish();
} else {
- showWithActionPopup();
+ startSelectionActionModeWithoutSelection();
}
}
}
@@ -3648,6 +3801,12 @@ public class Editor {
}
private class SelectionStartHandleView extends HandleView {
+ // The previous offset this handle was at.
+ private int mPrevOffset;
+ // Indicates whether the cursor is making adjustments within a word.
+ private boolean mInWord = false;
+ // Offset to track difference between touch and word boundary.
+ protected int mTouchWordOffset;
public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(drawableLtr, drawableRtl);
@@ -3655,11 +3814,7 @@ public class Editor {
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
- return drawable.getIntrinsicWidth() / 4;
- } else {
- return (drawable.getIntrinsicWidth() * 3) / 4;
- }
+ return isRtlRun ? 0 : drawable.getIntrinsicWidth();
}
@Override
@@ -3681,21 +3836,77 @@ public class Editor {
@Override
public void updatePosition(float x, float y) {
- int offset = mTextView.getOffsetForPosition(x, y);
-
- // Handles can not cross and selection is at least one character
- final int selectionEnd = mTextView.getSelectionEnd();
- if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1);
+ final int trueOffset = mTextView.getOffsetForPosition(x, y);
+ final int currLine = mTextView.getLineAtCoordinate(y);
+ int offset = trueOffset;
+ boolean positionCursor = false;
+
+ int end = getWordEnd(offset, true);
+ int start = getWordStart(offset);
+
+ if (offset < mPrevOffset) {
+ // User is increasing the selection.
+ if (!mInWord || currLine < mLine) {
+ // We're not in a word, or we're on a different line so we'll expand by
+ // word. First ensure the user has at least entered the next word.
+ int offsetToWord = Math.min((end - start) / 2, 2);
+ if (offset <= end - offsetToWord || currLine < mLine) {
+ offset = start;
+ } else {
+ offset = mPrevOffset;
+ }
+ }
+ mPrevOffset = offset;
+ mTouchWordOffset = Math.max(trueOffset - offset, 0);
+ mInWord = !isStartBoundary(offset);
+ positionCursor = true;
+ } else if (offset - mTouchWordOffset > mPrevOffset) {
+ // User is shrinking the selection.
+ if (currLine > mLine) {
+ // We're on a different line, so we'll snap to word boundaries.
+ offset = end;
+ }
+ offset -= mTouchWordOffset;
+ mPrevOffset = offset;
+ mInWord = !isEndBoundary(offset);
+ positionCursor = true;
+ }
- positionAtCursorOffset(offset, false);
+ // Handles can not cross and selection is at least one character.
+ if (positionCursor) {
+ final int selectionEnd = mTextView.getSelectionEnd();
+ if (offset >= selectionEnd) {
+ // We can't cross the handles so let's just constrain the Y value.
+ int alteredOffset = mTextView.getOffsetAtCoordinate(mLine, x);
+ if (alteredOffset >= selectionEnd) {
+ // Can't pass the other drag handle.
+ offset = Math.max(0, selectionEnd - 1);
+ } else {
+ offset = alteredOffset;
+ }
+ }
+ positionAtCursorOffset(offset, false);
+ }
}
- public ActionPopupWindow getActionPopupWindow() {
- return mActionPopupWindow;
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean superResult = super.onTouchEvent(event);
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ // Reset the touch word offset when the user has lifted their finger.
+ mTouchWordOffset = 0;
+ }
+ return superResult;
}
}
private class SelectionEndHandleView extends HandleView {
+ // The previous offset this handle was at.
+ private int mPrevOffset;
+ // Indicates whether the cursor is making adjustments within a word.
+ private boolean mInWord = false;
+ // Offset to track difference between touch and word boundary.
+ protected int mTouchWordOffset;
public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
super(drawableLtr, drawableRtl);
@@ -3703,11 +3914,7 @@ public class Editor {
@Override
protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
- if (isRtlRun) {
- return (drawable.getIntrinsicWidth() * 3) / 4;
- } else {
- return drawable.getIntrinsicWidth() / 4;
- }
+ return isRtlRun ? drawable.getIntrinsicWidth() : 0;
}
@Override
@@ -3729,19 +3936,67 @@ public class Editor {
@Override
public void updatePosition(float x, float y) {
- int offset = mTextView.getOffsetForPosition(x, y);
-
- // Handles can not cross and selection is at least one character
- final int selectionStart = mTextView.getSelectionStart();
- if (offset <= selectionStart) {
- offset = Math.min(selectionStart + 1, mTextView.getText().length());
+ final int trueOffset = mTextView.getOffsetForPosition(x, y);
+ final int currLine = mTextView.getLineAtCoordinate(y);
+ int offset = trueOffset;
+ boolean positionCursor = false;
+
+ int end = getWordEnd(offset, true);
+ int start = getWordStart(offset);
+
+ if (offset > mPrevOffset) {
+ // User is increasing the selection.
+ if (!mInWord || currLine > mLine) {
+ // We're not in a word, or we're on a different line so we'll expand by
+ // word. First ensure the user has at least entered the next word.
+ int midPoint = Math.min((end - start) / 2, 2);
+ if (offset >= start + midPoint || currLine > mLine) {
+ offset = end;
+ } else {
+ offset = mPrevOffset;
+ }
+ }
+ mPrevOffset = offset;
+ mTouchWordOffset = Math.max(offset - trueOffset, 0);
+ mInWord = !isEndBoundary(offset);
+ positionCursor = true;
+ } else if (offset + mTouchWordOffset < mPrevOffset) {
+ // User is shrinking the selection.
+ if (currLine > mLine) {
+ // We're on a different line, so we'll snap to word boundaries.
+ offset = getWordStart(offset);
+ }
+ offset += mTouchWordOffset;
+ mPrevOffset = offset;
+ positionCursor = true;
+ mInWord = !isStartBoundary(offset);
}
- positionAtCursorOffset(offset, false);
+ if (positionCursor) {
+ final int selectionStart = mTextView.getSelectionStart();
+ if (offset <= selectionStart) {
+ // We can't cross the handles so let's just constrain the Y value.
+ int alteredOffset = mTextView.getOffsetAtCoordinate(mLine, x);
+ int length = mTextView.getText().length();
+ if (alteredOffset <= selectionStart) {
+ // Can't pass the other drag handle.
+ offset = Math.min(selectionStart + 1, length);
+ } else {
+ offset = Math.min(alteredOffset, length);
+ }
+ }
+ positionAtCursorOffset(offset, false);
+ }
}
- public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) {
- mActionPopupWindow = actionPopupWindow;
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean superResult = super.onTouchEvent(event);
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ // Reset the touch word offset when the user has lifted their finger.
+ mTouchWordOffset = 0;
+ }
+ return superResult;
}
}
@@ -3776,10 +4031,6 @@ public class Editor {
getHandle().show();
}
- public void showWithActionPopup() {
- getHandle().showWithActionPopup();
- }
-
public void hide() {
if (mHandle != null) {
mHandle.hide();
@@ -3825,6 +4076,11 @@ public class Editor {
private float mDownPositionX, mDownPositionY;
private boolean mGestureStayedInTapRegion;
+ // Where the user first starts the drag motion.
+ private int mStartOffset = -1;
+ // Indicates whether the user is selecting text and using the drag accelerator.
+ private boolean mDragAcceleratorActive;
+
SelectionModifierCursorController() {
resetTouchOffsets();
}
@@ -3861,11 +4117,6 @@ public class Editor {
mStartHandle.show();
mEndHandle.show();
- // Make sure both left and right handles share the same ActionPopupWindow (so that
- // moving any of the handles hides the action popup).
- mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION);
- mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow());
-
hideInsertionPointCursorController();
}
@@ -3874,6 +4125,22 @@ public class Editor {
if (mEndHandle != null) mEndHandle.hide();
}
+ public void enterDrag() {
+ // Just need to init the handles / hide insertion cursor.
+ show();
+ mDragAcceleratorActive = true;
+ // Start location of selection.
+ mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX,
+ mLastDownPositionY);
+ // Don't show the handles until user has lifted finger.
+ hide();
+
+ // This stops scrolling parents from intercepting the touch event, allowing
+ // the user to continue dragging across the screen to select text; TextView will
+ // scroll as necessary.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+
public void onTouchEvent(MotionEvent event) {
// This is done even when the View does not have focus, so that long presses can start
// selection and tap can move cursor from this tap position.
@@ -3882,7 +4149,7 @@ public class Editor {
final float x = event.getX();
final float y = event.getY();
- // Remember finger down position, to be able to start selection from there
+ // Remember finger down position, to be able to start selection from there.
mMinTouchOffset = mMaxTouchOffset = mTextView.getOffsetForPosition(x, y);
// Double tap detection
@@ -3899,7 +4166,7 @@ public class Editor {
boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop;
if (stayedInArea && isPositionOnText(x, y)) {
- startSelectionActionMode();
+ startSelectionActionModeWithSelection();
mDiscardNextActionUp = true;
}
}
@@ -3921,23 +4188,112 @@ public class Editor {
break;
case MotionEvent.ACTION_MOVE:
+ final ViewConfiguration viewConfiguration = ViewConfiguration.get(
+ mTextView.getContext());
+
if (mGestureStayedInTapRegion) {
final float deltaX = event.getX() - mDownPositionX;
final float deltaY = event.getY() - mDownPositionY;
final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(
- mTextView.getContext());
int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop();
if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) {
mGestureStayedInTapRegion = false;
}
}
+
+ if (mStartHandle != null && mStartHandle.isShowing()) {
+ // Don't do the drag if the handles are showing already.
+ break;
+ }
+
+ if (mStartOffset != -1) {
+ final int rawOffset = mTextView.getOffsetForPosition(event.getX(),
+ event.getY());
+ int offset = rawOffset;
+
+ // We don't start "dragging" until the user is past the initial word that
+ // gets selected on long press.
+ int firstWordStart = getWordStart(mStartOffset);
+ int firstWordEnd = getWordEnd(mStartOffset, false);
+ if (offset > firstWordEnd || offset < firstWordStart) {
+
+ // Basically the goal in the below code is to have the highlight be
+ // offset so that your finger isn't covering the end point.
+ int fingerOffset = viewConfiguration.getScaledTouchSlop();
+ float mx = event.getX();
+ float my = event.getY();
+ if (mx > fingerOffset) mx -= fingerOffset;
+ if (my > fingerOffset) my -= fingerOffset;
+ offset = mTextView.getOffsetForPosition(mx, my);
+
+ // Perform the check for closeness at edge of view, if we're very close
+ // don't adjust the offset to be in front of the finger - otherwise the
+ // user can't select words at the edge.
+ if (mTextView.getWidth() - fingerOffset > mx) {
+ // We're going by word, so we need to make sure that the offset
+ // that we get is within this, so we'll get the previous boundary.
+ final WordIterator wordIterator = getWordIteratorWithText();
+
+ final int precedingOffset = wordIterator.preceding(offset);
+ if (mStartOffset < offset) {
+ // Expanding with bottom handle, in this case the selection end
+ // is before the finger.
+ offset = Math.max(precedingOffset - 1, 0);
+ } else {
+ // Expand with the start handle, in this case the selection
+ // start is before the finger.
+ if (precedingOffset == WordIterator.DONE) {
+ offset = 0;
+ } else {
+ offset = wordIterator.preceding(precedingOffset);
+ }
+ }
+ }
+ if (offset == WordIterator.DONE)
+ offset = rawOffset;
+
+ // Need to adjust start offset based on direction of movement.
+ int newStart = mStartOffset < offset ? getWordStart(mStartOffset)
+ : getWordEnd(mStartOffset, true);
+ Selection.setSelection((Spannable) mTextView.getText(), newStart,
+ offset);
+ }
+ }
break;
case MotionEvent.ACTION_UP:
mPreviousTapUpTime = SystemClock.uptimeMillis();
+ if (mDragAcceleratorActive) {
+ // No longer dragging to select text, let the parent intercept events.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(false);
+
+ show();
+ int startOffset = mTextView.getSelectionStart();
+ int endOffset = mTextView.getSelectionEnd();
+
+ // Since we don't let drag handles pass once they're visible, we need to
+ // make sure the start / end locations are correct because the user *can*
+ // switch directions during the initial drag.
+ if (endOffset < startOffset) {
+ int tmp = endOffset;
+ endOffset = startOffset;
+ startOffset = tmp;
+
+ // Also update the selection with the right offsets in this case.
+ Selection.setSelection((Spannable) mTextView.getText(),
+ startOffset, endOffset);
+ }
+
+ // Need to do this to display the handles.
+ mStartHandle.showAtLocation(startOffset);
+ mEndHandle.showAtLocation(endOffset);
+
+ // No longer the first dragging motion, reset.
+ mDragAcceleratorActive = false;
+ mStartOffset = -1;
+ }
break;
}
}
@@ -3964,6 +4320,8 @@ public class Editor {
public void resetTouchOffsets() {
mMinTouchOffset = mMaxTouchOffset = -1;
+ mStartOffset = -1;
+ mDragAcceleratorActive = false;
}
/**
@@ -3973,6 +4331,13 @@ public class Editor {
return mStartHandle != null && mStartHandle.isDragging();
}
+ /**
+ * @return true if the user is selecting text using the drag accelerator.
+ */
+ public boolean isDragAcceleratorActive() {
+ return mDragAcceleratorActive;
+ }
+
public void onTouchModeChanged(boolean isInTouchMode) {
if (!isInTouchMode) {
hide();
@@ -4157,103 +4522,279 @@ public class Editor {
int mChangedStart, mChangedEnd, mChangedDelta;
}
+ /**
+ * @return True iff (start, end) is a valid range within the text.
+ */
+ private static boolean isValidRange(CharSequence text, int start, int end) {
+ return 0 <= start && start <= end && end <= text.length();
+ }
+
+ /**
+ * An InputFilter that monitors text input to maintain undo history. It does not modify the
+ * text being typed (and hence always returns null from the filter() method).
+ */
public static class UndoInputFilter implements InputFilter {
- final Editor mEditor;
+ private final Editor mEditor;
+
+ // Whether the current filter pass is directly caused by an end-user text edit.
+ private boolean mIsUserEdit;
+
+ // Whether the text field is handling an IME composition. Must be parceled in case the user
+ // rotates the screen during composition.
+ private boolean mHasComposition;
public UndoInputFilter(Editor editor) {
mEditor = editor;
}
+ public void saveInstanceState(Parcel parcel) {
+ parcel.writeInt(mIsUserEdit ? 1 : 0);
+ parcel.writeInt(mHasComposition ? 1 : 0);
+ }
+
+ public void restoreInstanceState(Parcel parcel) {
+ mIsUserEdit = parcel.readInt() != 0;
+ mHasComposition = parcel.readInt() != 0;
+ }
+
+ /**
+ * Signals that a user-triggered edit is starting.
+ */
+ public void beginBatchEdit() {
+ if (DEBUG_UNDO) Log.d(TAG, "beginBatchEdit");
+ mIsUserEdit = true;
+ }
+
+ public void endBatchEdit() {
+ if (DEBUG_UNDO) Log.d(TAG, "endBatchEdit");
+ mIsUserEdit = false;
+ }
+
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
if (DEBUG_UNDO) {
- Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ")");
- Log.d(TAG, "filter: dest=" + dest + " (" + dstart + "-" + dend + ")");
+ Log.d(TAG, "filter: source=" + source + " (" + start + "-" + end + ") " +
+ "dest=" + dest + " (" + dstart + "-" + dend + ")");
}
- final UndoManager um = mEditor.mUndoManager;
- if (um.isInUndo()) {
- if (DEBUG_UNDO) Log.d(TAG, "*** skipping, currently performing undo/redo");
+
+ // Check to see if this edit should be tracked for undo.
+ if (!canUndoEdit(source, start, end, dest, dstart, dend)) {
return null;
}
- um.beginUpdate("Edit text");
- TextModifyOperation op = um.getLastOperation(
- TextModifyOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE);
- if (op != null) {
- if (DEBUG_UNDO) Log.d(TAG, "Last op: range=(" + op.mRangeStart + "-" + op.mRangeEnd
- + "), oldText=" + op.mOldText);
- // See if we can continue modifying this operation.
- if (op.mOldText == null) {
- // The current operation is an add... are we adding more? We are adding
- // more if we are either appending new text to the end of the last edit or
- // completely replacing some or all of the last edit.
- if (start < end && ((dstart >= op.mRangeStart && dend <= op.mRangeEnd)
- || (dstart == op.mRangeEnd && dend == op.mRangeEnd))) {
- op.mRangeEnd = dstart + (end-start);
- um.endUpdate();
- if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, mRangeEnd="
- + op.mRangeEnd);
- return null;
- }
- } else {
- // The current operation is a delete... can we delete more?
- if (start == end && dend == op.mRangeStart-1) {
- SpannableStringBuilder str;
- if (op.mOldText instanceof SpannableString) {
- str = (SpannableStringBuilder)op.mOldText;
- } else {
- str = new SpannableStringBuilder(op.mOldText);
- }
- str.insert(0, dest, dstart, dend);
- op.mRangeStart = dstart;
- op.mOldText = str;
- um.endUpdate();
- if (DEBUG_UNDO) Log.d(TAG, "*** merging with last op, range=("
- + op.mRangeStart + "-" + op.mRangeEnd
- + "), oldText=" + op.mOldText);
- return null;
- }
+ // Check for and handle IME composition edits.
+ if (handleCompositionEdit(source, start, end, dstart)) {
+ return null;
+ }
+
+ // Handle keyboard edits.
+ handleKeyboardEdit(source, start, end, dest, dstart, dend);
+ return null;
+ }
+
+ /**
+ * Returns true iff the edit was handled, either because it should be ignored or because
+ * this function created an undo operation for it.
+ */
+ private boolean handleCompositionEdit(CharSequence source, int start, int end, int dstart) {
+ // Ignore edits while the user is composing.
+ if (isComposition(source)) {
+ mHasComposition = true;
+ return true;
+ }
+ final boolean hadComposition = mHasComposition;
+ mHasComposition = false;
+
+ // Check for the transition out of the composing state.
+ if (hadComposition) {
+ // If there was no text the user canceled composition. Ignore the edit.
+ if (start == end) {
+ return true;
}
- // Couldn't add to the current undo operation, need to start a new
- // undo state for a new undo operation.
- um.commitState(null);
- um.setUndoLabel("Edit text");
+ // Otherwise the user inserted the composition.
+ String newText = TextUtils.substring(source, start, end);
+ EditOperation edit = new EditOperation(mEditor, false, "", dstart, newText);
+ recordEdit(edit);
+ return true;
}
- // Create a new undo state reflecting the operation being performed.
- op = new TextModifyOperation(mEditor.mUndoOwner);
- op.mRangeStart = dstart;
- if (start < end) {
- op.mRangeEnd = dstart + (end-start);
+ // This was neither a composition event nor a transition out of composing.
+ return false;
+ }
+
+ private void handleKeyboardEdit(CharSequence source, int start, int end,
+ Spanned dest, int dstart, int dend) {
+ // An application may install a TextWatcher to provide additional modifications after
+ // the initial input filters run (e.g. a credit card formatter that adds spaces to a
+ // string). This results in multiple filter() calls for what the user considers to be
+ // a single operation. Always undo the whole set of changes in one step.
+ final boolean forceMerge = isInTextWatcher();
+
+ // Build a new operation with all the information from this edit.
+ String newText = TextUtils.substring(source, start, end);
+ String oldText = TextUtils.substring(dest, dstart, dend);
+ EditOperation edit = new EditOperation(mEditor, forceMerge, oldText, dstart, newText);
+ recordEdit(edit);
+ }
+
+ private void recordEdit(EditOperation edit) {
+ // Fetch the last edit operation and attempt to merge in the new edit.
+ final UndoManager um = mEditor.mUndoManager;
+ um.beginUpdate("Edit text");
+ EditOperation lastEdit = um.getLastOperation(
+ EditOperation.class, mEditor.mUndoOwner, UndoManager.MERGE_MODE_UNIQUE);
+ if (lastEdit == null) {
+ // Add this as the first edit.
+ if (DEBUG_UNDO) Log.d(TAG, "filter: adding first op " + edit);
+ um.addOperation(edit, UndoManager.MERGE_MODE_NONE);
+ } else if (!mIsUserEdit) {
+ // An application directly modified the Editable outside of a text edit. Treat this
+ // as a new change and don't attempt to merge.
+ if (DEBUG_UNDO) Log.d(TAG, "non-user edit, new op " + edit);
+ um.commitState(mEditor.mUndoOwner);
+ um.addOperation(edit, UndoManager.MERGE_MODE_NONE);
+ } else if (lastEdit.mergeWith(edit)) {
+ // Merge succeeded, nothing else to do.
+ if (DEBUG_UNDO) Log.d(TAG, "filter: merge succeeded, created " + lastEdit);
} else {
- op.mRangeEnd = dstart;
- }
- if (dstart < dend) {
- op.mOldText = dest.subSequence(dstart, dend);
+ // Could not merge with the last edit, so commit the last edit and add this edit.
+ if (DEBUG_UNDO) Log.d(TAG, "filter: merge failed, adding " + edit);
+ um.commitState(mEditor.mUndoOwner);
+ um.addOperation(edit, UndoManager.MERGE_MODE_NONE);
}
- if (DEBUG_UNDO) Log.d(TAG, "*** adding new op, range=(" + op.mRangeStart
- + "-" + op.mRangeEnd + "), oldText=" + op.mOldText);
- um.addOperation(op, UndoManager.MERGE_MODE_NONE);
um.endUpdate();
- return null;
+ }
+
+ private boolean canUndoEdit(CharSequence source, int start, int end,
+ Spanned dest, int dstart, int dend) {
+ if (!mEditor.mAllowUndo) {
+ if (DEBUG_UNDO) Log.d(TAG, "filter: undo is disabled");
+ return false;
+ }
+
+ if (mEditor.mUndoManager.isInUndo()) {
+ if (DEBUG_UNDO) Log.d(TAG, "filter: skipping, currently performing undo/redo");
+ return false;
+ }
+
+ // Text filters run before input operations are applied. However, some input operations
+ // are invalid and will throw exceptions when applied. This is common in tests. Don't
+ // attempt to undo invalid operations.
+ if (!isValidRange(source, start, end) || !isValidRange(dest, dstart, dend)) {
+ if (DEBUG_UNDO) Log.d(TAG, "filter: invalid op");
+ return false;
+ }
+
+ // Earlier filters can rewrite input to be a no-op, for example due to a length limit
+ // on an input field. Skip no-op changes.
+ if (start == end && dstart == dend) {
+ if (DEBUG_UNDO) Log.d(TAG, "filter: skipping no-op");
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isComposition(CharSequence source) {
+ if (!(source instanceof Spannable)) {
+ return false;
+ }
+ // This is a composition edit if the source has a non-zero-length composing span.
+ Spannable text = (Spannable) source;
+ int composeBegin = EditableInputConnection.getComposingSpanStart(text);
+ int composeEnd = EditableInputConnection.getComposingSpanEnd(text);
+ return composeBegin < composeEnd;
+ }
+
+ private boolean isInTextWatcher() {
+ CharSequence text = mEditor.mTextView.getText();
+ return (text instanceof SpannableStringBuilder)
+ && ((SpannableStringBuilder) text).getTextWatcherDepth() > 0;
}
}
- public static class TextModifyOperation extends UndoOperation<TextView> {
- int mRangeStart, mRangeEnd;
- CharSequence mOldText;
+ /**
+ * An operation to undo a single "edit" to a text view.
+ */
+ public static class EditOperation extends UndoOperation<Editor> {
+ private static final int TYPE_INSERT = 0;
+ private static final int TYPE_DELETE = 1;
+ private static final int TYPE_REPLACE = 2;
+
+ private int mType;
+ private boolean mForceMerge;
+ private String mOldText;
+ private int mOldTextStart;
+ private String mNewText;
+ private int mNewTextStart;
+
+ private int mOldCursorPos;
+ private int mNewCursorPos;
+
+ /**
+ * Constructs an edit operation from a text input operation on editor that replaces the
+ * oldText starting at dstart with newText. If forceMerge is true then always forcibly
+ * merge this operation with any previous one.
+ */
+ public EditOperation(Editor editor, boolean forceMerge, String oldText, int dstart,
+ String newText) {
+ super(editor.mUndoOwner);
+ mForceMerge = forceMerge;
+ mOldText = oldText;
+ mNewText = newText;
+
+ // Determine the type of the edit and store where it occurred. Avoid storing
+ // irrevelant data (e.g. mNewTextStart for a delete) because that makes the
+ // merging logic more complex (e.g. merging deletes could lead to mNewTextStart being
+ // outside the bounds of the final text).
+ if (mNewText.length() > 0 && mOldText.length() == 0) {
+ mType = TYPE_INSERT;
+ mNewTextStart = dstart;
+ } else if (mNewText.length() == 0 && mOldText.length() > 0) {
+ mType = TYPE_DELETE;
+ mOldTextStart = dstart;
+ } else {
+ mType = TYPE_REPLACE;
+ mOldTextStart = mNewTextStart = dstart;
+ }
- public TextModifyOperation(UndoOwner owner) {
- super(owner);
+ // Store cursor data.
+ mOldCursorPos = editor.mTextView.getSelectionStart();
+ mNewCursorPos = dstart + mNewText.length();
}
- public TextModifyOperation(Parcel src, ClassLoader loader) {
+ public EditOperation(Parcel src, ClassLoader loader) {
super(src, loader);
- mRangeStart = src.readInt();
- mRangeEnd = src.readInt();
- mOldText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(src);
+ mType = src.readInt();
+ mForceMerge = src.readInt() != 0;
+ mOldText = src.readString();
+ mOldTextStart = src.readInt();
+ mNewText = src.readString();
+ mNewTextStart = src.readInt();
+ mOldCursorPos = src.readInt();
+ mNewCursorPos = src.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mForceMerge ? 1 : 0);
+ dest.writeString(mOldText);
+ dest.writeInt(mOldTextStart);
+ dest.writeString(mNewText);
+ dest.writeInt(mNewTextStart);
+ dest.writeInt(mOldCursorPos);
+ dest.writeInt(mNewCursorPos);
+ }
+
+ private int getNewTextEnd() {
+ return mNewTextStart + mNewText.length();
+ }
+
+ private int getOldTextEnd() {
+ return mOldTextStart + mOldText.length();
}
@Override
@@ -4262,59 +4803,185 @@ public class Editor {
@Override
public void undo() {
- swapText();
+ if (DEBUG_UNDO) Log.d(TAG, "undo");
+ // Remove the new text and insert the old.
+ Editor editor = getOwnerData();
+ Editable text = (Editable) editor.mTextView.getText();
+ modifyText(text, mNewTextStart, getNewTextEnd(), mOldText, mOldTextStart,
+ mOldCursorPos);
}
@Override
public void redo() {
- swapText();
+ if (DEBUG_UNDO) Log.d(TAG, "redo");
+ // Remove the old text and insert the new.
+ Editor editor = getOwnerData();
+ Editable text = (Editable) editor.mTextView.getText();
+ modifyText(text, mOldTextStart, getOldTextEnd(), mNewText, mNewTextStart,
+ mNewCursorPos);
}
- private void swapText() {
- // Both undo and redo involves swapping the contents of the range
- // in the text view with our local text.
- TextView tv = getOwnerData();
- Editable editable = (Editable)tv.getText();
- CharSequence curText;
- if (mRangeStart >= mRangeEnd) {
- curText = null;
- } else {
- curText = editable.subSequence(mRangeStart, mRangeEnd);
- }
+ /**
+ * Attempts to merge this existing operation with a new edit.
+ * @param edit The new edit operation.
+ * @return If the merge succeeded, returns true. Otherwise returns false and leaves this
+ * object unchanged.
+ */
+ private boolean mergeWith(EditOperation edit) {
if (DEBUG_UNDO) {
- Log.d(TAG, "Swap: range=(" + mRangeStart + "-" + mRangeEnd
- + "), oldText=" + mOldText);
- Log.d(TAG, "Swap: curText=" + curText);
+ Log.d(TAG, "mergeWith old " + this);
+ Log.d(TAG, "mergeWith new " + edit);
}
- if (mOldText == null) {
- editable.delete(mRangeStart, mRangeEnd);
- mRangeEnd = mRangeStart;
- } else {
- editable.replace(mRangeStart, mRangeEnd, mOldText);
- mRangeEnd = mRangeStart + mOldText.length();
+ if (edit.mForceMerge) {
+ forceMergeWith(edit);
+ return true;
+ }
+ switch (mType) {
+ case TYPE_INSERT:
+ return mergeInsertWith(edit);
+ case TYPE_DELETE:
+ return mergeDeleteWith(edit);
+ case TYPE_REPLACE:
+ return mergeReplaceWith(edit);
+ default:
+ return false;
}
- mOldText = curText;
}
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mRangeStart);
- dest.writeInt(mRangeEnd);
- TextUtils.writeToParcel(mOldText, dest, flags);
+ private boolean mergeInsertWith(EditOperation edit) {
+ // Only merge continuous insertions.
+ if (edit.mType != TYPE_INSERT) {
+ return false;
+ }
+ // Only merge insertions that are contiguous.
+ if (getNewTextEnd() != edit.mNewTextStart) {
+ return false;
+ }
+ mNewText += edit.mNewText;
+ mNewCursorPos = edit.mNewCursorPos;
+ return true;
}
- public static final Parcelable.ClassLoaderCreator<TextModifyOperation> CREATOR
- = new Parcelable.ClassLoaderCreator<TextModifyOperation>() {
- public TextModifyOperation createFromParcel(Parcel in) {
- return new TextModifyOperation(in, null);
+ // TODO: Support forward delete.
+ private boolean mergeDeleteWith(EditOperation edit) {
+ // Only merge continuous deletes.
+ if (edit.mType != TYPE_DELETE) {
+ return false;
+ }
+ // Only merge deletions that are contiguous.
+ if (mOldTextStart != edit.getOldTextEnd()) {
+ return false;
}
+ mOldTextStart = edit.mOldTextStart;
+ mOldText = edit.mOldText + mOldText;
+ mNewCursorPos = edit.mNewCursorPos;
+ return true;
+ }
- public TextModifyOperation createFromParcel(Parcel in, ClassLoader loader) {
- return new TextModifyOperation(in, loader);
+ private boolean mergeReplaceWith(EditOperation edit) {
+ // Replacements can merge only with adjacent inserts.
+ if (edit.mType != TYPE_INSERT || getNewTextEnd() != edit.mNewTextStart) {
+ return false;
}
+ mOldText += edit.mOldText;
+ mNewText += edit.mNewText;
+ mNewCursorPos = edit.mNewCursorPos;
+ return true;
+ }
- public TextModifyOperation[] newArray(int size) {
- return new TextModifyOperation[size];
+ /**
+ * Forcibly creates a single merged edit operation by simulating the entire text
+ * contents being replaced.
+ */
+ private void forceMergeWith(EditOperation edit) {
+ if (DEBUG_UNDO) Log.d(TAG, "forceMerge");
+ Editor editor = getOwnerData();
+
+ // Copy the text of the current field.
+ // NOTE: Using StringBuilder instead of SpannableStringBuilder would be somewhat faster,
+ // but would require two parallel implementations of modifyText() because Editable and
+ // StringBuilder do not share an interface for replace/delete/insert.
+ Editable editable = (Editable) editor.mTextView.getText();
+ Editable originalText = new SpannableStringBuilder(editable.toString());
+
+ // Roll back the last operation.
+ modifyText(originalText, mNewTextStart, getNewTextEnd(), mOldText, mOldTextStart,
+ mOldCursorPos);
+
+ // Clone the text again and apply the new operation.
+ Editable finalText = new SpannableStringBuilder(editable.toString());
+ modifyText(finalText, edit.mOldTextStart, edit.getOldTextEnd(), edit.mNewText,
+ edit.mNewTextStart, edit.mNewCursorPos);
+
+ // Convert this operation into a non-mergeable replacement of the entire string.
+ mType = TYPE_REPLACE;
+ mNewText = finalText.toString();
+ mNewTextStart = 0;
+ mOldText = originalText.toString();
+ mOldTextStart = 0;
+ mNewCursorPos = edit.mNewCursorPos;
+ // mOldCursorPos is unchanged.
+ }
+
+ private static void modifyText(Editable text, int deleteFrom, int deleteTo,
+ CharSequence newText, int newTextInsertAt, int newCursorPos) {
+ // Apply the edit if it is still valid.
+ if (isValidRange(text, deleteFrom, deleteTo) &&
+ newTextInsertAt <= text.length() - (deleteTo - deleteFrom)) {
+ if (deleteFrom != deleteTo) {
+ text.delete(deleteFrom, deleteTo);
+ }
+ if (newText.length() != 0) {
+ text.insert(newTextInsertAt, newText);
+ }
+ }
+ // Restore the cursor position. If there wasn't an old cursor (newCursorPos == -1) then
+ // don't explicitly set it and rely on SpannableStringBuilder to position it.
+ // TODO: Select all the text that was undone.
+ if (0 <= newCursorPos && newCursorPos <= text.length()) {
+ Selection.setSelection(text, newCursorPos);
+ }
+ }
+
+ private String getTypeString() {
+ switch (mType) {
+ case TYPE_INSERT:
+ return "insert";
+ case TYPE_DELETE:
+ return "delete";
+ case TYPE_REPLACE:
+ return "replace";
+ default:
+ return "";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "[mType=" + getTypeString() + ", " +
+ "mOldText=" + mOldText + ", " +
+ "mOldTextStart=" + mOldTextStart + ", " +
+ "mNewText=" + mNewText + ", " +
+ "mNewTextStart=" + mNewTextStart + ", " +
+ "mOldCursorPos=" + mOldCursorPos + ", " +
+ "mNewCursorPos=" + mNewCursorPos + "]";
+ }
+
+ public static final Parcelable.ClassLoaderCreator<EditOperation> CREATOR
+ = new Parcelable.ClassLoaderCreator<EditOperation>() {
+ @Override
+ public EditOperation createFromParcel(Parcel in) {
+ return new EditOperation(in, null);
+ }
+
+ @Override
+ public EditOperation createFromParcel(Parcel in, ClassLoader loader) {
+ return new EditOperation(in, loader);
+ }
+
+ @Override
+ public EditOperation[] newArray(int size) {
+ return new EditOperation[size];
}
};
}
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 70089e0..fac36c5 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -30,8 +30,6 @@ import android.view.ContextMenu;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ExpandableListConnector.PositionMetadata;
import java.util.ArrayList;
@@ -1342,14 +1340,7 @@ public class ExpandableListView extends ListView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ExpandableListView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ExpandableListView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ExpandableListView.class.getName();
}
}
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index fe143de..21213ac 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -22,6 +22,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.annotation.StyleRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -326,7 +327,7 @@ class FastScroller {
refreshDrawablePressedState();
}
- public void setStyle(int resId) {
+ public void setStyle(@StyleRes int resId) {
final Context context = mList.getContext();
final TypedArray ta = context.obtainStyledAttributes(null,
com.android.internal.R.styleable.FastScroll, android.R.attr.fastScrollStyle, resId);
@@ -752,13 +753,13 @@ class FastScroller {
final View track = mTrackImage;
final View thumb = mThumbImage;
final Rect container = mContainerRect;
- final int containerWidth = container.width();
- final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(containerWidth, MeasureSpec.AT_MOST);
+ final int maxWidth = container.width();
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST);
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
track.measure(widthMeasureSpec, heightMeasureSpec);
final int trackWidth = track.getMeasuredWidth();
- final int thumbHalfHeight = thumb == null ? 0 : thumb.getHeight() / 2;
+ final int thumbHalfHeight = thumb.getHeight() / 2;
final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
final int right = left + trackWidth;
final int top = container.top + thumbHalfHeight;
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index d974c29..f6d198b 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -18,6 +18,7 @@ package android.widget;
import java.util.ArrayList;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -29,12 +30,9 @@ import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
@@ -103,15 +101,16 @@ public class FrameLayout extends ViewGroup {
super(context);
}
- public FrameLayout(Context context, AttributeSet attrs) {
+ public FrameLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public FrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ public FrameLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public FrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public FrameLayout(
+ Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
@@ -203,11 +202,15 @@ public class FrameLayout extends ViewGroup {
}
@Override
- @RemotableViewMethod
- public void setVisibility(@Visibility int visibility) {
- super.setVisibility(visibility);
- if (mForeground != null) {
- mForeground.setVisible(visibility == VISIBLE, false);
+ protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ final Drawable dr = mForeground;
+ if (dr != null) {
+ final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE;
+ if (visible != dr.isVisible()) {
+ dr.setVisible(visible, false);
+ }
}
}
@@ -703,17 +706,9 @@ public class FrameLayout extends ViewGroup {
return new LayoutParams(p);
}
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(FrameLayout.class.getName());
- }
-
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(FrameLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return FrameLayout.class.getName();
}
/**
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index f7c839f..af5a8bf 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -33,7 +33,6 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Transformation;
@@ -1374,15 +1373,14 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Gallery.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Gallery.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Gallery.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setScrollable(mItemCount > 1);
if (isEnabled()) {
if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
@@ -1394,9 +1392,10 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
}
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
switch (action) {
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 161ae7e..6cc4bda 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -31,8 +31,6 @@ import android.util.Printer;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
@@ -143,7 +141,9 @@ import static java.lang.Math.min;
* view was alone in a column, that column would itself collapse to zero width if and only if
* no gravity was defined on the view. If gravity was defined, then the gone-marked
* view has no effect on the layout and the container should be laid out as if the view
- * had never been added to it.
+ * had never been added to it. GONE views are taken to have zero weight during excess space
+ * distribution.
+ * <p>
* These statements apply equally to rows as well as columns, and to groups of rows or columns.
*
* <p>
@@ -1012,12 +1012,10 @@ public class GridLayout extends ViewGroup {
LayoutParams lp = getLayoutParams(c);
if (firstPass) {
measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
- mHorizontalAxis.recordOriginalMeasurement(i);
- mVerticalAxis.recordOriginalMeasurement(i);
} else {
boolean horizontal = (mOrientation == HORIZONTAL);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- if (spec.alignment == FILL) {
+ if (spec.getAbsoluteAlignment(horizontal) == FILL) {
Interval span = spec.span;
Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
int[] locations = axis.getLocations();
@@ -1093,11 +1091,6 @@ public class GridLayout extends ViewGroup {
invalidateValues();
}
- final Alignment getAlignment(Alignment alignment, boolean horizontal) {
- return (alignment != UNDEFINED_ALIGNMENT) ? alignment :
- (horizontal ? START : BASELINE);
- }
-
// Layout container
/**
@@ -1152,8 +1145,8 @@ public class GridLayout extends ViewGroup {
int pWidth = getMeasurement(c, true);
int pHeight = getMeasurement(c, false);
- Alignment hAlign = getAlignment(columnSpec.alignment, true);
- Alignment vAlign = getAlignment(rowSpec.alignment, false);
+ Alignment hAlign = columnSpec.getAbsoluteAlignment(true);
+ Alignment vAlign = rowSpec.getAbsoluteAlignment(false);
Bounds boundsX = mHorizontalAxis.getGroupBounds().getValue(i);
Bounds boundsY = mVerticalAxis.getGroupBounds().getValue(i);
@@ -1191,15 +1184,8 @@ public class GridLayout extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(GridLayout.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(GridLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return GridLayout.class.getName();
}
// Inner classes
@@ -1243,7 +1229,6 @@ public class GridLayout extends ViewGroup {
public boolean hasWeights;
public boolean hasWeightsValid = false;
- public int[] originalMeasurements;
public int[] deltas;
boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
@@ -1306,7 +1291,7 @@ public class GridLayout extends ViewGroup {
// we must include views that are GONE here, see introductory javadoc
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- Bounds bounds = getAlignment(spec.alignment, horizontal).getBounds();
+ Bounds bounds = spec.getAbsoluteAlignment(horizontal).getBounds();
assoc.put(spec, bounds);
}
return assoc.pack();
@@ -1322,9 +1307,8 @@ public class GridLayout extends ViewGroup {
// we must include views that are GONE here, see introductory javadoc
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- int size = (spec.weight == 0) ?
- getMeasurementIncludingMargin(c, horizontal) :
- getOriginalMeasurements()[i] + getDeltas()[i];
+ int size = getMeasurementIncludingMargin(c, horizontal) +
+ ((spec.weight == 0) ? 0 : getDeltas()[i]);
groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size);
}
}
@@ -1712,7 +1696,11 @@ public class GridLayout extends ViewGroup {
private boolean computeHasWeights() {
for (int i = 0, N = getChildCount(); i < N; i++) {
- LayoutParams lp = getLayoutParams(getChildAt(i));
+ final View child = getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ LayoutParams lp = getLayoutParams(child);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
if (spec.weight != 0) {
return true;
@@ -1729,19 +1717,6 @@ public class GridLayout extends ViewGroup {
return hasWeights;
}
- public int[] getOriginalMeasurements() {
- if (originalMeasurements == null) {
- originalMeasurements = new int[getChildCount()];
- }
- return originalMeasurements;
- }
-
- private void recordOriginalMeasurement(int i) {
- if (hasWeights()) {
- getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal);
- }
- }
-
public int[] getDeltas() {
if (deltas == null) {
deltas = new int[getChildCount()];
@@ -1752,7 +1727,10 @@ public class GridLayout extends ViewGroup {
private void shareOutDelta(int totalDelta, float totalWeight) {
Arrays.fill(deltas, 0);
for (int i = 0, N = getChildCount(); i < N; i++) {
- View c = getChildAt(i);
+ final View c = getChildAt(i);
+ if (c.getVisibility() == View.GONE) {
+ continue;
+ }
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
float weight = spec.weight;
@@ -1805,6 +1783,9 @@ public class GridLayout extends ViewGroup {
float totalWeight = 0f;
for (int i = 0, N = getChildCount(); i < N; i++) {
View c = getChildAt(i);
+ if (c.getVisibility() == View.GONE) {
+ continue;
+ }
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
totalWeight += spec.weight;
@@ -1900,7 +1881,6 @@ public class GridLayout extends ViewGroup {
locations = null;
- originalMeasurements = null;
deltas = null;
hasWeightsValid = false;
@@ -2020,7 +2000,6 @@ public class GridLayout extends ViewGroup {
R.styleable.ViewGroup_MarginLayout_layout_marginRight;
private static final int BOTTOM_MARGIN =
R.styleable.ViewGroup_MarginLayout_layout_marginBottom;
-
private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
@@ -2414,7 +2393,7 @@ public class GridLayout extends ViewGroup {
protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) {
this.flexibility &= spec.getFlexibility();
boolean horizontal = axis.horizontal;
- Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
+ Alignment alignment = spec.getAbsoluteAlignment(axis.horizontal);
// todo test this works correctly when the returned value is UNDEFINED
int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode());
include(before, size - before);
@@ -2565,6 +2544,16 @@ public class GridLayout extends ViewGroup {
this(startDefined, new Interval(start, start + size), alignment, weight);
}
+ private Alignment getAbsoluteAlignment(boolean horizontal) {
+ if (alignment != UNDEFINED_ALIGNMENT) {
+ return alignment;
+ }
+ if (weight == 0f) {
+ return horizontal ? START : BASELINE;
+ }
+ return FILL;
+ }
+
final Spec copyWriteSpan(Interval span) {
return new Spec(startDefined, span, alignment, weight);
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index efd6fc0..c6e3dc8 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -31,7 +31,6 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
@@ -2342,15 +2341,14 @@ public class GridView extends AbsListView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(GridView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return GridView.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(GridView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
final int columnsCount = getNumColumns();
final int rowsCount = getCount() / columnsCount;
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 1b93b97..324c2aa 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -20,7 +20,6 @@ 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;
@@ -762,9 +761,10 @@ public class HorizontalScrollView extends FrameLayout {
awakenScrollBars();
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
switch (action) {
@@ -795,9 +795,14 @@ public class HorizontalScrollView extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(HorizontalScrollView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return HorizontalScrollView.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
final int scrollRange = getScrollRange();
if (scrollRange > 0) {
info.setScrollable(true);
@@ -810,10 +815,10 @@ public class HorizontalScrollView extends FrameLayout {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(HorizontalScrollView.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setScrollable(getScrollRange() > 0);
event.setScrollX(mScrollX);
event.setScrollY(mScrollY);
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index 3a20628..332b158 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -18,8 +18,6 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
/**
@@ -93,14 +91,7 @@ public class ImageButton extends ImageView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ImageButton.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ImageButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ImageButton.class.getName();
}
}
diff --git a/core/java/android/widget/ImageSwitcher.java b/core/java/android/widget/ImageSwitcher.java
index c048970..08f21a2 100644
--- a/core/java/android/widget/ImageSwitcher.java
+++ b/core/java/android/widget/ImageSwitcher.java
@@ -16,13 +16,11 @@
package android.widget;
+import android.annotation.DrawableRes;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
public class ImageSwitcher extends ViewSwitcher
{
@@ -35,7 +33,7 @@ public class ImageSwitcher extends ViewSwitcher
super(context, attrs);
}
- public void setImageResource(int resid)
+ public void setImageResource(@DrawableRes int resid)
{
ImageView image = (ImageView)this.getNextView();
image.setImageResource(resid);
@@ -57,14 +55,7 @@ public class ImageSwitcher extends ViewSwitcher
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ImageSwitcher.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ImageSwitcher.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ImageSwitcher.class.getName();
}
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index c68bfca..041796b 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
@@ -43,7 +44,6 @@ import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
@@ -127,15 +127,16 @@ public class ImageView extends View {
initImageView();
}
- public ImageView(Context context, AttributeSet attrs) {
+ public ImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public ImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public ImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initImageView();
@@ -239,9 +240,10 @@ public class ImageView extends View {
return (getBackground() != null && getBackground().getCurrent() != null);
}
+ /** @hide */
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
CharSequence contentDescription = getContentDescription();
if (!TextUtils.isEmpty(contentDescription)) {
event.getText().add(contentDescription);
@@ -385,7 +387,7 @@ public class ImageView extends View {
* @attr ref android.R.styleable#ImageView_src
*/
@android.view.RemotableViewMethod
- public void setImageResource(int resId) {
+ public void setImageResource(@DrawableRes int resId) {
// 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;
@@ -1419,14 +1421,7 @@ public class ImageView extends View {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ImageView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ImageView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ImageView.class.getName();
}
}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 6476cdc..da15302 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -19,6 +19,7 @@ package android.widget;
import com.android.internal.R;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -28,8 +29,6 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
import java.lang.annotation.Retention;
@@ -188,11 +187,11 @@ public class LinearLayout extends ViewGroup {
this(context, null);
}
- public LinearLayout(Context context, AttributeSet attrs) {
+ public LinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ public LinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@@ -1807,15 +1806,8 @@ public class LinearLayout extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(LinearLayout.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(LinearLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return LinearLayout.class.getName();
}
/**
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index ba6f061..ee2c055 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -21,6 +21,7 @@ import com.android.internal.R;
import com.android.internal.util.Predicate;
import com.google.android.collect.Lists;
+import android.annotation.IdRes;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -40,7 +41,6 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewRootImpl;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
@@ -3630,7 +3630,7 @@ public class ListView extends AbsListView {
* First look in our children, then in any header and footer views that may be scrolled off.
*/
@Override
- protected View findViewTraversal(int id) {
+ protected View findViewTraversal(@IdRes int id) {
View v;
v = super.findViewTraversal(id);
if (v == null) {
@@ -3879,15 +3879,14 @@ public class ListView extends AbsListView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ListView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ListView.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ListView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
final int rowsCount = getCount();
final int selectionMode = getSelectionModeForAccessibility();
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index f1aaa4d..2375089 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -28,16 +28,13 @@ import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.PhoneWindow;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar.OnSeekBarChangeListener;
-import com.android.internal.policy.PolicyManager;
-
import java.util.Formatter;
import java.util.Locale;
@@ -128,7 +125,7 @@ public class MediaController extends FrameLayout {
private void initFloatingWindow() {
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
- mWindow = PolicyManager.makeNewWindow(mContext);
+ mWindow = new PhoneWindow(mContext);
mWindow.setWindowManager(mWindowManager, null, null);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mDecor = mWindow.getDecorView();
@@ -626,15 +623,8 @@ public class MediaController extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(MediaController.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(MediaController.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return MediaController.class.getName();
}
private View.OnClickListener mRewListener = new View.OnClickListener() {
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index cbd01b0..2152e43 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -23,8 +23,6 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.QwertyKeyListener;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* An editable text view, extending {@link AutoCompleteTextView}, that
@@ -203,15 +201,8 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(MultiAutoCompleteTextView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(MultiAutoCompleteTextView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return MultiAutoCompleteTextView.class.getName();
}
public static interface Tokenizer {
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index ee17b78..16dc26d 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -1558,9 +1558,10 @@ public class NumberPicker extends LinearLayout {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(NumberPicker.class.getName());
event.setScrollable(true);
event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 2708398..1507dfb 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -22,6 +22,7 @@ import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.SubMenuBuilder;
+import android.annotation.MenuRes;
import android.content.Context;
import android.view.Gravity;
import android.view.Menu;
@@ -115,6 +116,29 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
/**
+ * Sets the gravity used to align the popup window to its anchor view.
+ * <p>
+ * If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown.
+ *
+ * @param gravity the gravity used to align the popup window
+ *
+ * @see #getGravity()
+ */
+ public void setGravity(int gravity) {
+ mPopup.setGravity(gravity);
+ }
+
+ /**
+ * @return the gravity used to align the popup window to its anchor view
+ *
+ * @see #setGravity(int)
+ */
+ public int getGravity() {
+ return mPopup.getGravity();
+ }
+
+ /**
* Returns an {@link OnTouchListener} that can be added to the anchor view
* to implement drag-to-open behavior.
* <p>
@@ -182,7 +206,7 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
* popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
* @param menuRes Menu resource to inflate
*/
- public void inflate(int menuRes) {
+ public void inflate(@MenuRes int menuRes) {
getMenuInflater().inflate(menuRes, mMenu);
}
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 5419ab6..f676254 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -18,17 +18,22 @@ package android.widget;
import com.android.internal.R;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.os.IBinder;
+import android.transition.Transition;
+import android.transition.Transition.EpicenterCallback;
+import android.transition.Transition.TransitionListener;
+import android.transition.Transition.TransitionListenerAdapter;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -36,7 +41,9 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowManager;
@@ -46,7 +53,7 @@ import java.lang.ref.WeakReference;
* <p>A popup window that can be used to display an arbitrary view. The popup
* window is a floating container that appears on top of the current
* activity.</p>
- *
+ *
* @see android.widget.AutoCompleteTextView
* @see android.widget.Spinner
*/
@@ -58,7 +65,7 @@ public class PopupWindow {
* it doesn't.
*/
public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
-
+
/**
* Mode for {@link #setInputMethodMode(int)}: this popup always needs to
* work with an input method, regardless of whether it is focusable. This
@@ -66,7 +73,7 @@ public class PopupWindow {
* the input method while it is shown.
*/
public static final int INPUT_METHOD_NEEDED = 1;
-
+
/**
* Mode for {@link #setInputMethodMode(int)}: this popup never needs to
* work with an input method, regardless of whether it is focusable. This
@@ -77,14 +84,30 @@ public class PopupWindow {
private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START;
+ /**
+ * Default animation style indicating that separate animations should be
+ * used for top/bottom anchoring states.
+ */
+ private static final int ANIMATION_STYLE_DEFAULT = -1;
+
+ private final int[] mDrawingLocation = new int[2];
+ private final int[] mScreenLocation = new int[2];
+ private final Rect mTempRect = new Rect();
+ private final Rect mAnchorBounds = new Rect();
+
private Context mContext;
private WindowManager mWindowManager;
-
+
private boolean mIsShowing;
+ private boolean mIsTransitioningToDismiss;
private boolean mIsDropdown;
+ /** View that handles event dispatch and content transitions. */
+ private PopupDecorView mDecorView;
+
+ /** The contents of the popup. */
private View mContentView;
- private View mPopupView;
+
private boolean mFocusable;
private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
@@ -114,49 +137,67 @@ public class PopupWindow {
private float mElevation;
- private int[] mDrawingLocation = new int[2];
- private int[] mScreenLocation = new int[2];
- private Rect mTempRect = new Rect();
-
private Drawable mBackground;
private Drawable mAboveAnchorBackgroundDrawable;
private Drawable mBelowAnchorBackgroundDrawable;
- // Temporary animation centers. Should be moved into window params?
- private int mAnchorRelativeX;
- private int mAnchorRelativeY;
+ private Transition mEnterTransition;
+ private Transition mExitTransition;
private boolean mAboveAnchor;
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-
+
private OnDismissListener mOnDismissListener;
private boolean mIgnoreCheekPress = false;
- private int mAnimationStyle = -1;
-
+ private int mAnimationStyle = ANIMATION_STYLE_DEFAULT;
+
private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
com.android.internal.R.attr.state_above_anchor
};
private WeakReference<View> mAnchor;
- private final OnScrollChangedListener mOnScrollChangedListener =
- new OnScrollChangedListener() {
- @Override
- public void onScrollChanged() {
- final View anchor = mAnchor != null ? mAnchor.get() : null;
- if (anchor != null && mPopupView != null) {
- final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mPopupView.getLayoutParams();
-
- updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
- mAnchoredGravity));
- update(p.x, p.y, -1, -1, true);
- }
+ private final EpicenterCallback mEpicenterCallback = new EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ final View anchor = mAnchor != null ? mAnchor.get() : null;
+ final View decor = mDecorView;
+ if (anchor == null || decor == null) {
+ return null;
+ }
+
+ final Rect anchorBounds = mAnchorBounds;
+ final int[] anchorLocation = anchor.getLocationOnScreen();
+ final int[] popupLocation = mDecorView.getLocationOnScreen();
+
+ // Compute the position of the anchor relative to the popup.
+ anchorBounds.set(0, 0, anchor.getWidth(), anchor.getHeight());
+ anchorBounds.offset(anchorLocation[0] - popupLocation[0],
+ anchorLocation[1] - popupLocation[1]);
+
+ return anchorBounds;
+ }
+ };
+
+ private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ final View anchor = mAnchor != null ? mAnchor.get() : null;
+ if (anchor != null && mDecorView != null) {
+ final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+ mDecorView.getLayoutParams();
+
+ updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
+ mAnchoredGravity));
+ update(p.x, p.y, -1, -1, true);
}
- };
+ }
+ };
- private int mAnchorXoff, mAnchorYoff, mAnchoredGravity;
+ private int mAnchorXoff;
+ private int mAnchorYoff;
+ private int mAnchoredGravity;
private boolean mOverlapAnchor;
private boolean mPopupViewInitialLayoutDirectionInherited;
@@ -187,15 +228,15 @@ public class PopupWindow {
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
-
+
/**
* <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
- *
+ *
* <p>The popup does not provide a background.</p>
*/
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
- mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
@@ -203,11 +244,34 @@ public class PopupWindow {
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 == R.style.Animation_PopupWindow ? -1 : animStyle;
+ // Preserve default behavior from Gingerbread. If the animation is
+ // undefined or explicitly specifies the Gingerbread animation style,
+ // use a sentinel value.
+ if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
+ final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
+ if (animStyle == R.style.Animation_PopupWindow) {
+ mAnimationStyle = ANIMATION_STYLE_DEFAULT;
+ } else {
+ mAnimationStyle = animStyle;
+ }
+ } else {
+ mAnimationStyle = ANIMATION_STYLE_DEFAULT;
+ }
+
+ final Transition enterTransition = getTransition(a.getResourceId(
+ R.styleable.PopupWindow_popupEnterTransition, 0));
+ final Transition exitTransition;
+ if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
+ exitTransition = getTransition(a.getResourceId(
+ R.styleable.PopupWindow_popupExitTransition, 0));
+ } else {
+ exitTransition = enterTransition == null ? null : enterTransition.clone();
+ }
a.recycle();
+ setEnterTransition(enterTransition);
+ setExitTransition(exitTransition);
setBackgroundDrawable(bg);
}
@@ -288,6 +352,37 @@ public class PopupWindow {
setFocusable(focusable);
}
+ public void setEnterTransition(Transition enterTransition) {
+ mEnterTransition = enterTransition;
+
+ if (mEnterTransition != null) {
+ mEnterTransition.setEpicenterCallback(mEpicenterCallback);
+ }
+ }
+
+ public void setExitTransition(Transition exitTransition) {
+ mExitTransition = exitTransition;
+
+ if (mExitTransition != null) {
+ mExitTransition.setEpicenterCallback(mEpicenterCallback);
+ }
+ }
+
+ private Transition getTransition(int resId) {
+ if (resId != 0 && resId != R.transition.no_transition) {
+ final TransitionInflater inflater = TransitionInflater.from(mContext);
+ final Transition transition = inflater.inflateTransition(resId);
+ if (transition != null) {
+ final boolean isEmpty = transition instanceof TransitionSet
+ && ((TransitionSet) transition).getTransitionCount() == 0;
+ if (!isEmpty) {
+ return transition;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Return the drawable used as the popup window's background.
*
@@ -381,7 +476,7 @@ public class PopupWindow {
* Set the flag on popup to ignore cheek press events; by default this flag
* is set to false
* which means the popup will not ignore cheek press dispatch events.
- *
+ *
* <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>
@@ -391,7 +486,7 @@ public class PopupWindow {
public void setIgnoreCheekPress() {
mIgnoreCheekPress = true;
}
-
+
/**
* <p>Change the animation style resource for this popup.</p>
@@ -403,13 +498,13 @@ public class PopupWindow {
* @param animationStyle animation style to use when the popup appears
* and disappears. Set to -1 for the default animation, 0 for no
* animation, or a resource identifier for an explicit animation.
- *
+ *
* @see #update()
*/
public void setAnimationStyle(int animationStyle) {
mAnimationStyle = animationStyle;
}
-
+
/**
* <p>Return the view used as the content of the popup window.</p>
*
@@ -493,7 +588,7 @@ public class PopupWindow {
* @param focusable true if the popup should grab focus, false otherwise.
*
* @see #isFocusable()
- * @see #isShowing()
+ * @see #isShowing()
* @see #update()
*/
public void setFocusable(boolean focusable) {
@@ -502,23 +597,23 @@ public class PopupWindow {
/**
* Return the current value in {@link #setInputMethodMode(int)}.
- *
+ *
* @see #setInputMethodMode(int)
*/
public int getInputMethodMode() {
return mInputMethodMode;
-
+
}
-
+
/**
* Control how the popup operates with an input method: one of
* {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
* or {@link #INPUT_METHOD_NOT_NEEDED}.
- *
+ *
* <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 #getInputMethodMode()
* @see #update()
*/
@@ -549,12 +644,12 @@ public class PopupWindow {
public int getSoftInputMode() {
return mSoftInputMode;
}
-
+
/**
* <p>Indicates whether the popup window receives touch events.</p>
- *
+ *
* @return true if the popup is touchable, false otherwise
- *
+ *
* @see #setTouchable(boolean)
*/
public boolean isTouchable() {
@@ -573,7 +668,7 @@ public class PopupWindow {
* @param touchable true if the popup should receive touch events, false otherwise
*
* @see #isTouchable()
- * @see #isShowing()
+ * @see #isShowing()
* @see #update()
*/
public void setTouchable(boolean touchable) {
@@ -583,9 +678,9 @@ public class PopupWindow {
/**
* <p>Indicates whether the popup window will be informed of touch events
* outside of its window.</p>
- *
+ *
* @return true if the popup is outside touchable, false otherwise
- *
+ *
* @see #setOutsideTouchable(boolean)
*/
public boolean isOutsideTouchable() {
@@ -606,7 +701,7 @@ public class PopupWindow {
* touch events, false otherwise
*
* @see #isOutsideTouchable()
- * @see #isShowing()
+ * @see #isShowing()
* @see #update()
*/
public void setOutsideTouchable(boolean touchable) {
@@ -615,9 +710,9 @@ public class PopupWindow {
/**
* <p>Indicates whether clipping of the popup window is enabled.</p>
- *
+ *
* @return true if the clipping is enabled, false otherwise
- *
+ *
* @see #setClippingEnabled(boolean)
*/
public boolean isClippingEnabled() {
@@ -628,13 +723,13 @@ public class PopupWindow {
* <p>Allows the popup window to extend beyond the bounds of the screen. By default the
* window is clipped to the screen boundaries. Setting this to false will allow windows to be
* accurately positioned.</p>
- *
+ *
* <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>
*
* @param enabled false if the window should be allowed to extend outside of the screen
- * @see #isShowing()
+ * @see #isShowing()
* @see #isClippingEnabled()
* @see #update()
*/
@@ -662,12 +757,12 @@ public class PopupWindow {
void setAllowScrollingAnchorParent(boolean enabled) {
mAllowScrollingAnchorParent = enabled;
}
-
+
/**
* <p>Indicates whether the popup window supports splitting touches.</p>
- *
+ *
* @return true if the touch splitting is enabled, false otherwise
- *
+ *
* @see #setSplitTouchEnabled(boolean)
*/
public boolean isSplitTouchEnabled() {
@@ -796,7 +891,7 @@ public class PopupWindow {
* window manager by the popup. By default these are 0, meaning that
* the current width or height is requested as an explicit size from
* the window manager. You can supply
- * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
* {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
* spec supplied instead, replacing the absolute width and height that
* has been set in the popup.</p>
@@ -817,7 +912,7 @@ public class PopupWindow {
mWidthMode = widthSpec;
mHeightMode = heightSpec;
}
-
+
/**
* <p>Return this popup's height MeasureSpec</p>
*
@@ -838,7 +933,7 @@ public class PopupWindow {
* @param height the height MeasureSpec of the popup
*
* @see #getHeight()
- * @see #isShowing()
+ * @see #isShowing()
*/
public void setHeight(int height) {
mHeight = height;
@@ -849,7 +944,7 @@ public class PopupWindow {
*
* @return the width MeasureSpec of the popup
*
- * @see #setWidth(int)
+ * @see #setWidth(int)
*/
public int getWidth() {
return mWidth;
@@ -871,6 +966,34 @@ public class PopupWindow {
}
/**
+ * Sets whether the popup window should overlap its anchor view when
+ * displayed as a drop-down.
+ * <p>
+ * If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown.
+ *
+ * @param overlapAnchor Whether the popup should overlap its anchor.
+ *
+ * @see #getOverlapAnchor()
+ * @see #isShowing()
+ */
+ public void setOverlapAnchor(boolean overlapAnchor) {
+ mOverlapAnchor = overlapAnchor;
+ }
+
+ /**
+ * Returns whether the popup window should overlap its anchor view when
+ * displayed as a drop-down.
+ *
+ * @return Whether the popup should overlap its anchor.
+ *
+ * @see #setOverlapAnchor(boolean)
+ */
+ public boolean getOverlapAnchor() {
+ return mOverlapAnchor;
+ }
+
+ /**
* <p>Indicate whether this popup window is showing on screen.</p>
*
* @return true if the popup is showing, false otherwise
@@ -887,7 +1010,7 @@ public class PopupWindow {
* a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
* <code>Gravity.LEFT | Gravity.TOP</code>.
* </p>
- *
+ *
* @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
* @param gravity the gravity which controls the placement of the popup window
* @param x the popup's x location offset
@@ -913,32 +1036,34 @@ public class PopupWindow {
return;
}
+ TransitionManager.endTransitions(mDecorView);
+
unregisterForScrollChanged();
mIsShowing = true;
mIsDropdown = false;
- WindowManager.LayoutParams p = createPopupLayout(token);
- p.windowAnimations = computeAnimationResource();
-
+ final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
- if (gravity == Gravity.NO_GRAVITY) {
- gravity = Gravity.TOP | Gravity.START;
+
+ // Only override the default if some gravity was specified.
+ if (gravity != Gravity.NO_GRAVITY) {
+ p.gravity = gravity;
}
- p.gravity = gravity;
+
p.x = x;
p.y = y;
- if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
- if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
+
invokePopup(p);
}
/**
- * <p>Display the content view in a popup window anchored to the bottom-left
+ * Display the content view in a popup window anchored to the bottom-left
* corner of the anchor view. If there is not enough room on screen to show
* the popup in its entirety, this method tries to find a parent scroll
- * view to scroll. If no parent scroll view can be scrolled, the bottom-left
- * corner of the popup is pinned at the top left corner of the anchor view.</p>
+ * view to scroll. If no parent scroll view can be scrolled, the
+ * bottom-left corner of the popup is pinned at the top left corner of the
+ * anchor view.
*
* @param anchor the view on which to pin the popup window
*
@@ -949,14 +1074,15 @@ public class PopupWindow {
}
/**
- * <p>Display the content view in a popup window anchored to the bottom-left
+ * Display the content view in a popup window anchored to the bottom-left
* corner of the anchor view offset by the specified x and y coordinates.
- * If there is not enough room on screen to show
- * the popup in its entirety, this method tries to find a parent scroll
- * view to scroll. If no parent scroll view can be scrolled, the bottom-left
- * corner of the popup is pinned at the top left corner of the anchor view.</p>
- * <p>If the view later scrolls to move <code>anchor</code> to a different
- * location, the popup will be moved correspondingly.</p>
+ * If there is not enough room on screen to show the popup in its entirety,
+ * this method tries to find a parent scroll view to scroll. If no parent
+ * scroll view can be scrolled, the bottom-left corner of the popup is
+ * pinned at the top left corner of the anchor view.
+ * <p>
+ * If the view later scrolls to move <code>anchor</code> to a different
+ * location, the popup will be moved correspondingly.
*
* @param anchor the view on which to pin the popup window
* @param xoff A horizontal offset from the anchor in pixels
@@ -969,14 +1095,17 @@ public class PopupWindow {
}
/**
- * <p>Display the content view in a popup window anchored to the bottom-left
- * corner of the anchor view offset by the specified x and y coordinates.
- * If there is not enough room on screen to show
- * the popup in its entirety, this method tries to find a parent scroll
- * view to scroll. If no parent scroll view can be scrolled, the bottom-left
- * corner of the popup is pinned at the top left corner of the anchor view.</p>
- * <p>If the view later scrolls to move <code>anchor</code> to a different
- * location, the popup will be moved correspondingly.</p>
+ * Displays the content view in a popup window anchored to the corner of
+ * another view. The window is positioned according to the specified
+ * gravity and offset by the specified x and y coordinates.
+ * <p>
+ * If there is not enough room on screen to show the popup in its entirety,
+ * this method tries to find a parent scroll view to scroll. If no parent
+ * view can be scrolled, the specified vertical gravity will be ignored and
+ * the popup will anchor itself such that it is visible.
+ * <p>
+ * If the view later scrolls to move <code>anchor</code> to a different
+ * location, the popup will be moved correspondingly.
*
* @param anchor the view on which to pin the popup window
* @param xoff A horizontal offset from the anchor in pixels
@@ -990,20 +1119,18 @@ public class PopupWindow {
return;
}
+ TransitionManager.endTransitions(mDecorView);
+
registerForScrollChanged(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
- WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
+ final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
preparePopup(p);
- updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
-
- if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
- if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
-
- p.windowAnimations = computeAnimationResource();
+ final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);
+ updateAboveAnchor(aboveAnchor);
invokePopup(p);
}
@@ -1018,12 +1145,12 @@ public class PopupWindow {
// do the job.
if (mAboveAnchorBackgroundDrawable != null) {
if (mAboveAnchor) {
- mPopupView.setBackground(mAboveAnchorBackgroundDrawable);
+ mDecorView.setBackground(mAboveAnchorBackgroundDrawable);
} else {
- mPopupView.setBackground(mBelowAnchorBackgroundDrawable);
+ mDecorView.setBackground(mBelowAnchorBackgroundDrawable);
}
} else {
- mPopupView.refreshDrawableState();
+ mDecorView.refreshDrawableState();
}
}
}
@@ -1045,10 +1172,9 @@ public class PopupWindow {
}
/**
- * <p>Prepare the popup by embedding in into a new ViewGroup if the
- * background drawable is not null. If embedding is required, the layout
- * parameters' height is modified to take into account the background's
- * padding.</p>
+ * Prepare the popup by embedding it into a new ViewGroup if the background
+ * drawable is not null. If embedding is required, the layout parameters'
+ * height is modified to take into account the background's padding.
*
* @param p the layout parameters of the popup's content view
*/
@@ -1058,36 +1184,86 @@ public class PopupWindow {
+ "calling setContentView() before attempting to show the popup.");
}
- if (mBackground != null) {
- final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
- int height = ViewGroup.LayoutParams.MATCH_PARENT;
- if (layoutParams != null &&
- layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
- height = ViewGroup.LayoutParams.WRAP_CONTENT;
- }
-
- // when a background is available, we embed the content view
- // within another view that owns the background drawable
- PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
- PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, height
- );
- popupViewContainer.setBackground(mBackground);
- popupViewContainer.addView(mContentView, listParams);
+ // The old decor view may be transitioning out. Make sure it finishes
+ // and cleans up before we try to create another one.
+ if (mDecorView != null) {
+ mDecorView.cancelTransitions();
+ }
- mPopupView = popupViewContainer;
+ // When a background is available, we embed the content view within
+ // another view that owns the background drawable.
+ final View backgroundView;
+ if (mBackground != null) {
+ backgroundView = createBackgroundView(mContentView);
+ backgroundView.setBackground(mBackground);
} else {
- mPopupView = mContentView;
+ backgroundView = mContentView;
}
- mPopupView.setElevation(mElevation);
+ mDecorView = createDecorView(backgroundView);
+
+ // The background owner should be elevated so that it casts a shadow.
+ backgroundView.setElevation(mElevation);
+
+ // We may wrap that in another view, so we'll need to manually specify
+ // the surface insets.
+ final int surfaceInset = (int) Math.ceil(backgroundView.getZ() * 2);
+ p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+ p.hasManualSurfaceInsets = true;
+
mPopupViewInitialLayoutDirectionInherited =
- (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
+ (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth = p.width;
mPopupHeight = p.height;
}
/**
+ * Wraps a content view in a PopupViewContainer.
+ *
+ * @param contentView the content view to wrap
+ * @return a PopupViewContainer that wraps the content view
+ */
+ private PopupBackgroundView createBackgroundView(View contentView) {
+ final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
+ final int height;
+ if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ height = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext);
+ final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, height);
+ backgroundView.addView(contentView, listParams);
+
+ return backgroundView;
+ }
+
+ /**
+ * Wraps a content view in a FrameLayout.
+ *
+ * @param contentView the content view to wrap
+ * @return a FrameLayout that wraps the content view
+ */
+ private PopupDecorView createDecorView(View contentView) {
+ final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
+ final int height;
+ if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ height = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ final PopupDecorView decorView = new PopupDecorView(mContext);
+ decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height);
+ decorView.setClipChildren(false);
+ decorView.setClipToPadding(false);
+
+ return decorView;
+ }
+
+ /**
* <p>Invoke the popup window by adding the content view to the window
* manager.</p>
*
@@ -1099,16 +1275,21 @@ public class PopupWindow {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
- mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
+
+ final PopupDecorView decorView = mDecorView;
+ decorView.setFitsSystemWindows(mLayoutInsetDecor);
+ decorView.requestEnterTransition(mEnterTransition);
+
setLayoutDirectionFromAnchor();
- mWindowManager.addView(mPopupView, p);
+
+ mWindowManager.addView(decorView, p);
}
private void setLayoutDirectionFromAnchor() {
if (mAnchor != null) {
View anchor = mAnchor.get();
if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
- mPopupView.setLayoutDirection(anchor.getLayoutDirection());
+ mDecorView.setLayoutDirection(anchor.getLayoutDirection());
}
}
}
@@ -1120,26 +1301,39 @@ public class PopupWindow {
*
* @return the layout parameters to pass to the window manager
*/
- private WindowManager.LayoutParams createPopupLayout(IBinder token) {
- // generates the layout parameters for the drop down
- // we want a fixed size view located at the bottom left of the anchor
- WindowManager.LayoutParams p = new WindowManager.LayoutParams();
- // these gravity settings put the view at the top left corner of the
- // screen. The view is then positioned to the appropriate location
- // by setting the x and y offsets to match the anchor's bottom
- // left corner
+ private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
+ final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+ // These gravity settings put the view at the top left corner of the
+ // screen. The view is then positioned to the appropriate location by
+ // setting the x and y offsets to match the anchor's bottom-left
+ // corner.
p.gravity = Gravity.START | Gravity.TOP;
- p.width = mLastWidth = mWidth;
- p.height = mLastHeight = mHeight;
+ p.flags = computeFlags(p.flags);
+ p.type = mWindowLayoutType;
+ p.token = token;
+ p.softInputMode = mSoftInputMode;
+ p.windowAnimations = computeAnimationResource();
+
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
- p.flags = computeFlags(p.flags);
- p.type = mWindowLayoutType;
- p.token = token;
- p.softInputMode = mSoftInputMode;
+
+ if (mHeightMode < 0) {
+ p.height = mLastHeight = mHeightMode;
+ } else {
+ p.height = mLastHeight = mHeight;
+ }
+
+ if (mWidthMode < 0) {
+ p.width = mLastWidth = mWidthMode;
+ } else {
+ p.width = mLastWidth = mWidth;
+ }
+
+ // Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
@@ -1193,7 +1387,7 @@ public class PopupWindow {
}
private int computeAnimationResource() {
- if (mAnimationStyle == -1) {
+ if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) {
if (mIsDropdown) {
return mAboveAnchor
? com.android.internal.R.style.Animation_DropDownUp
@@ -1212,7 +1406,7 @@ public class PopupWindow {
* <p>
* The height must have been set on the layout parameters prior to calling
* this method.
- *
+ *
* @param anchor the view on which the popup window must be anchored
* @param p the layout parameters used to display the drop down
* @param xoff horizontal offset used to adjust for background padding
@@ -1310,19 +1504,15 @@ public class PopupWindow {
p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
- // Compute the position of the anchor relative to the popup.
- mAnchorRelativeX = mDrawingLocation[0] - p.x + anchorHeight / 2;
- mAnchorRelativeY = mDrawingLocation[1] - p.y + anchorWidth / 2;
-
return onTop;
}
-
+
/**
* Returns the maximum height that is available for the popup to be
* completely shown. It is recommended that this height be the maximum for
* the popup's height, otherwise it is possible that the popup will be
* clipped.
- *
+ *
* @param anchor The view on which the popup window must be anchored.
* @return The maximum available height for the popup to be completely
* shown.
@@ -1345,14 +1535,14 @@ public class PopupWindow {
public int getMaxAvailableHeight(View anchor, int yOffset) {
return getMaxAvailableHeight(anchor, yOffset, false);
}
-
+
/**
* Returns the maximum height that is available for the popup to be
* completely shown, optionally ignoring any bottom decorations such as
* the input method. It is recommended that this height be the maximum for
* the popup's height, otherwise it is possible that the popup will be
* clipped.
- *
+ *
* @param anchor The view on which the popup window must be anchored.
* @param yOffset y offset from the view's bottom edge
* @param ignoreBottomDecorations if true, the height returned will be
@@ -1360,7 +1550,7 @@ public class PopupWindow {
* bottom decorations
* @return The maximum available height for the popup to be completely
* shown.
- *
+ *
* @hide Pending API council approval.
*/
public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
@@ -1369,7 +1559,7 @@ public class PopupWindow {
final int[] anchorPos = mDrawingLocation;
anchor.getLocationOnScreen(anchorPos);
-
+
int bottomEdge = displayFrame.bottom;
if (ignoreBottomDecorations) {
Resources res = anchor.getContext().getResources();
@@ -1382,49 +1572,90 @@ public class PopupWindow {
int returnedHeight = Math.max(distanceToBottom, distanceToTop);
if (mBackground != null) {
mBackground.getPadding(mTempRect);
- returnedHeight -= mTempRect.top + mTempRect.bottom;
+ returnedHeight -= mTempRect.top + mTempRect.bottom;
}
-
+
return returnedHeight;
}
-
+
/**
- * <p>Dispose of the popup window. This method can be invoked only after
- * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling
- * this method will have no effect.</p>
+ * Disposes of the popup window. This method can be invoked only after
+ * {@link #showAsDropDown(android.view.View)} has been executed. Failing
+ * that, calling this method will have no effect.
*
- * @see #showAsDropDown(android.view.View)
+ * @see #showAsDropDown(android.view.View)
*/
public void dismiss() {
- if (isShowing() && mPopupView != null) {
- mIsShowing = false;
+ if (!isShowing() || mIsTransitioningToDismiss) {
+ return;
+ }
- unregisterForScrollChanged();
+ final PopupDecorView decorView = mDecorView;
+ final View contentView = mContentView;
- try {
- mWindowManager.removeViewImmediate(mPopupView);
- } finally {
- if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
- ((ViewGroup) mPopupView).removeView(mContentView);
- }
- mPopupView = null;
+ final ViewGroup contentHolder;
+ final ViewParent contentParent = contentView.getParent();
+ if (contentParent instanceof ViewGroup) {
+ contentHolder = ((ViewGroup) contentParent);
+ } else {
+ contentHolder = null;
+ }
- if (mOnDismissListener != null) {
- mOnDismissListener.onDismiss();
+ // Ensure any ongoing or pending transitions are canceled.
+ decorView.cancelTransitions();
+
+ unregisterForScrollChanged();
+
+ mIsShowing = false;
+ mIsTransitioningToDismiss = true;
+
+ if (mExitTransition != null && decorView.isLaidOut()) {
+ decorView.startExitTransition(mExitTransition, new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ dismissImmediate(decorView, contentHolder, contentView);
}
- }
+ });
+ } else {
+ dismissImmediate(decorView, contentHolder, contentView);
+ }
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
}
}
/**
+ * Removes the popup from the window manager and tears down the supporting
+ * view hierarchy, if necessary.
+ */
+ private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
+ // If this method gets called and the decor view doesn't have a parent,
+ // then it was either never added or was already removed. That should
+ // never happen, but it's worth checking to avoid potential crashes.
+ if (decorView.getParent() != null) {
+ mWindowManager.removeViewImmediate(decorView);
+ }
+
+ if (contentHolder != null) {
+ contentHolder.removeView(contentView);
+ }
+
+ // This needs to stay until after all transitions have ended since we
+ // need the reference to cancel transitions in preparePopup().
+ mDecorView = null;
+ mIsTransitioningToDismiss = false;
+ }
+
+ /**
* Sets the listener to be called when the window is dismissed.
- *
+ *
* @param onDismissListener The listener.
*/
public void setOnDismissListener(OnDismissListener onDismissListener) {
mOnDismissListener = onDismissListener;
}
-
+
/**
* Updates the state of the popup window, if it is currently being displayed,
* from the currently set state. This includes:
@@ -1436,12 +1667,12 @@ public class PopupWindow {
if (!isShowing() || mContentView == null) {
return;
}
-
- WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mPopupView.getLayoutParams();
-
+
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+
boolean update = false;
-
+
final int newAnim = computeAnimationResource();
if (newAnim != p.windowAnimations) {
p.windowAnimations = newAnim;
@@ -1456,7 +1687,7 @@ public class PopupWindow {
if (update) {
setLayoutDirectionFromAnchor();
- mWindowManager.updateViewLayout(mPopupView, p);
+ mWindowManager.updateViewLayout(mDecorView, p);
}
}
@@ -1469,11 +1700,11 @@ public class PopupWindow {
* @param height the new height
*/
public void update(int width, int height) {
- WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mPopupView.getLayoutParams();
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
update(p.x, p.y, width, height, false);
}
-
+
/**
* <p>Updates the position and the dimension of the popup window. Width and
* height can be set to -1 to update location only. Calling this function
@@ -1517,7 +1748,8 @@ public class PopupWindow {
return;
}
- WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
boolean update = force;
@@ -1557,7 +1789,7 @@ public class PopupWindow {
if (update) {
setLayoutDirectionFromAnchor();
- mWindowManager.updateViewLayout(mPopupView, p);
+ mWindowManager.updateViewLayout(mDecorView, p);
}
}
@@ -1571,7 +1803,7 @@ public class PopupWindow {
* @param height the new height, can be -1 to ignore
*/
public void update(View anchor, int width, int height) {
- update(anchor, false, 0, 0, true, width, height, mAnchoredGravity);
+ update(anchor, false, 0, 0, true, width, height);
}
/**
@@ -1590,30 +1822,26 @@ public class PopupWindow {
* @param height the new height, can be -1 to ignore
*/
public void update(View anchor, int xoff, int yoff, int width, int height) {
- update(anchor, true, xoff, yoff, true, width, height, mAnchoredGravity);
+ update(anchor, true, xoff, yoff, true, width, height);
}
private void update(View anchor, boolean updateLocation, int xoff, int yoff,
- boolean updateDimension, int width, int height, int gravity) {
+ boolean updateDimension, int width, int height) {
if (!isShowing() || mContentView == null) {
return;
}
- WeakReference<View> oldAnchor = mAnchor;
- final boolean needsUpdate = updateLocation
- && (mAnchorXoff != xoff || mAnchorYoff != yoff);
+ final WeakReference<View> oldAnchor = mAnchor;
+ final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
- registerForScrollChanged(anchor, xoff, yoff, gravity);
+ registerForScrollChanged(anchor, xoff, yoff, mAnchoredGravity);
} else if (needsUpdate) {
// No need to register again if this is a DropDown, showAsDropDown already did.
mAnchorXoff = xoff;
mAnchorYoff = yoff;
- mAnchoredGravity = gravity;
}
- WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
-
if (updateDimension) {
if (width == -1) {
width = mPopupWidth;
@@ -1627,11 +1855,12 @@ public class PopupWindow {
}
}
- int x = p.x;
- int y = p.y;
-
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ final int x = p.x;
+ final int y = p.y;
if (updateLocation) {
- updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
+ updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, mAnchoredGravity));
} else {
updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
mAnchoredGravity));
@@ -1651,23 +1880,22 @@ public class PopupWindow {
}
private void unregisterForScrollChanged() {
- WeakReference<View> anchorRef = mAnchor;
- View anchor = null;
- if (anchorRef != null) {
- anchor = anchorRef.get();
- }
+ final WeakReference<View> anchorRef = mAnchor;
+ final View anchor = anchorRef == null ? null : anchorRef.get();
if (anchor != null) {
- ViewTreeObserver vto = anchor.getViewTreeObserver();
+ final ViewTreeObserver vto = anchor.getViewTreeObserver();
vto.removeOnScrollChangedListener(mOnScrollChangedListener);
}
+
mAnchor = null;
}
private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
unregisterForScrollChanged();
- mAnchor = new WeakReference<View>(anchor);
- ViewTreeObserver vto = anchor.getViewTreeObserver();
+ mAnchor = new WeakReference<>(anchor);
+
+ final ViewTreeObserver vto = anchor.getViewTreeObserver();
if (vto != null) {
vto.addOnScrollChangedListener(mOnScrollChangedListener);
}
@@ -1677,41 +1905,28 @@ public class PopupWindow {
mAnchoredGravity = gravity;
}
- private class PopupViewContainer extends FrameLayout {
- private static final String TAG = "PopupWindow.PopupViewContainer";
+ private class PopupDecorView extends FrameLayout {
+ private TransitionListenerAdapter mPendingExitListener;
- public PopupViewContainer(Context context) {
+ public PopupDecorView(Context context) {
super(context);
}
@Override
- protected int[] onCreateDrawableState(int extraSpace) {
- if (mAboveAnchor) {
- // 1 more needed for the above anchor state
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
- View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
- return drawableState;
- } else {
- return super.onCreateDrawableState(extraSpace);
- }
- }
-
- @Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (getKeyDispatcherState() == null) {
return super.dispatchKeyEvent(event);
}
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
+ final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null && state.isTracking(event) && !event.isCanceled()) {
dismiss();
return true;
@@ -1735,7 +1950,7 @@ public class PopupWindow {
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
-
+
if ((event.getAction() == MotionEvent.ACTION_DOWN)
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
@@ -1748,15 +1963,115 @@ public class PopupWindow {
}
}
+ /**
+ * Requests that an enter transition run after the next layout pass.
+ */
+ public void requestEnterTransition(Transition transition) {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null && transition != null) {
+ final Transition enterTransition = transition.clone();
+
+ // Postpone the enter transition after the first layout pass.
+ observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ if (observer != null) {
+ observer.removeOnGlobalLayoutListener(this);
+ }
+
+ startEnterTransition(enterTransition);
+ }
+ });
+ }
+ }
+
+ /**
+ * Starts the pending enter transition, if one is set.
+ */
+ private void startEnterTransition(Transition enterTransition) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ enterTransition.addTarget(child);
+ child.setVisibility(View.INVISIBLE);
+ }
+
+ TransitionManager.beginDelayedTransition(this, enterTransition);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Starts an exit transition immediately.
+ * <p>
+ * <strong>Note:</strong> The transition listener is guaranteed to have
+ * its {@code onTransitionEnd} method called even if the transition
+ * never starts; however, it may be called with a {@code null} argument.
+ */
+ public void startExitTransition(Transition transition, final TransitionListener listener) {
+ if (transition == null) {
+ return;
+ }
+
+ // The exit listener MUST be called for cleanup, even if the
+ // transition never starts or ends. Stash it for later.
+ mPendingExitListener = new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ listener.onTransitionEnd(transition);
+
+ // The listener was called. Our job here is done.
+ mPendingExitListener = null;
+ }
+ };
+
+ final Transition exitTransition = transition.clone();
+ exitTransition.addListener(mPendingExitListener);
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ exitTransition.addTarget(child);
+ }
+
+ TransitionManager.beginDelayedTransition(this, exitTransition);
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ child.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ /**
+ * Cancels all pending or current transitions.
+ */
+ public void cancelTransitions() {
+ TransitionManager.endTransitions(this);
+
+ if (mPendingExitListener != null) {
+ mPendingExitListener.onTransitionEnd(null);
+ }
+ }
+ }
+
+ private class PopupBackgroundView extends FrameLayout {
+ public PopupBackgroundView(Context context) {
+ super(context);
+ }
+
@Override
- public void sendAccessibilityEvent(int eventType) {
- // clinets are interested in the content not the container, make it event source
- if (mContentView != null) {
- mContentView.sendAccessibilityEvent(eventType);
+ protected int[] onCreateDrawableState(int extraSpace) {
+ if (mAboveAnchor) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
+ return drawableState;
} else {
- super.sendAccessibilityEvent(eventType);
+ return super.onCreateDrawableState(extraSpace);
}
}
}
-
}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index de1bbc7..50d701a 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -18,15 +18,16 @@ package android.widget;
import android.annotation.Nullable;
import android.graphics.PorterDuff;
+
import com.android.internal.R;
+import android.annotation.InterpolatorRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.Animatable;
@@ -49,7 +50,6 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -64,8 +64,8 @@ import java.util.ArrayList;
/**
* <p>
* Visual indicator of progress in some operation. Displays a bar to the user
- * representing how far the operation has progressed; the application can
- * change the amount of progress (modifying the length of the bar) as it moves
+ * representing how far the operation has progressed; the application can
+ * change the amount of progress (modifying the length of the bar) as it moves
* forward. There is also a secondary progress displayable on a progress bar
* which is useful for displaying intermediate progress, such as the buffer
* level during a streaming playback progress bar.
@@ -81,7 +81,7 @@ import java.util.ArrayList;
* <p>The following code example shows how a progress bar can be used from
* a worker thread to update the user interface to notify the user of progress:
* </p>
- *
+ *
* <pre>
* public class MyActivity extends Activity {
* private static final int PROGRESS = 0x1;
@@ -169,13 +169,13 @@ import java.util.ArrayList;
* </ul>
* <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
* if your application uses a light colored theme (a white background).</p>
- *
- * <p><strong>XML attributes</b></strong>
- * <p>
- * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
+ *
+ * <p><strong>XML attributes</b></strong>
+ * <p>
+ * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
* {@link android.R.styleable#View View Attributes}
* </p>
- *
+ *
* @attr ref android.R.styleable#ProgressBar_animationResolution
* @attr ref android.R.styleable#ProgressBar_indeterminate
* @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
@@ -244,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);
}
@@ -261,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
@@ -282,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));
@@ -399,36 +399,49 @@ public class ProgressBar extends View {
* traverse layer and state list drawables.
*/
private Drawable tileify(Drawable drawable, boolean clip) {
-
+ // TODO: This is a terrible idea that potentially destroys any drawable
+ // that extends any of these classes. We *really* need to remove this.
+
if (drawable instanceof LayerDrawable) {
- LayerDrawable background = (LayerDrawable) drawable;
- final int N = background.getNumberOfLayers();
- Drawable[] outDrawables = new Drawable[N];
-
+ final LayerDrawable orig = (LayerDrawable) drawable;
+ final int N = orig.getNumberOfLayers();
+ final Drawable[] outDrawables = new Drawable[N];
+
for (int i = 0; i < N; i++) {
- int id = background.getId(i);
- outDrawables[i] = tileify(background.getDrawable(i),
+ final int id = orig.getId(i);
+ outDrawables[i] = tileify(orig.getDrawable(i),
(id == R.id.progress || id == R.id.secondaryProgress));
}
- LayerDrawable newBg = new LayerDrawable(outDrawables);
-
+ final LayerDrawable clone = new LayerDrawable(outDrawables);
for (int i = 0; i < N; i++) {
- newBg.setId(i, background.getId(i));
+ clone.setId(i, orig.getId(i));
+ clone.setLayerGravity(i, orig.getLayerGravity(i));
+ clone.setLayerWidth(i, orig.getLayerWidth(i));
+ clone.setLayerHeight(i, orig.getLayerHeight(i));
+ clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i));
+ clone.setLayerInsetRight(i, orig.getLayerInsetRight(i));
+ clone.setLayerInsetTop(i, orig.getLayerInsetTop(i));
+ clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i));
+ clone.setLayerInsetStart(i, orig.getLayerInsetStart(i));
+ clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i));
}
-
- return newBg;
-
- } else if (drawable instanceof StateListDrawable) {
- StateListDrawable in = (StateListDrawable) drawable;
- StateListDrawable out = new StateListDrawable();
- int numStates = in.getStateCount();
- for (int i = 0; i < numStates; i++) {
+
+ return clone;
+ }
+
+ if (drawable instanceof StateListDrawable) {
+ final StateListDrawable in = (StateListDrawable) drawable;
+ final StateListDrawable out = new StateListDrawable();
+ final int N = in.getStateCount();
+ for (int i = 0; i < N; i++) {
out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
}
+
return out;
-
- } else if (drawable instanceof BitmapDrawable) {
+ }
+
+ if (drawable instanceof BitmapDrawable) {
final BitmapDrawable bitmap = (BitmapDrawable) drawable;
final Bitmap tileBitmap = bitmap.getBitmap();
if (mSampleTile == null) {
@@ -448,7 +461,7 @@ public class ProgressBar extends View {
return clip ? new ClipDrawable(
shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable;
}
-
+
return drawable;
}
@@ -456,7 +469,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
@@ -468,7 +481,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);
@@ -479,7 +492,7 @@ public class ProgressBar extends View {
}
return drawable;
}
-
+
/**
* <p>
* Initialize the progress bar's default values:
@@ -520,7 +533,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.
*
@@ -699,7 +712,7 @@ public class ProgressBar extends View {
setIndeterminateDrawable(d);
}
-
+
/**
* <p>Get the drawable used to draw the progress bar in
* progress mode.</p>
@@ -1135,7 +1148,7 @@ public class ProgressBar extends View {
setProgressDrawable(d);
}
-
+
/**
* @return The drawable currently used to draw the progress bar
*/
@@ -1214,30 +1227,12 @@ public class ProgressBar extends View {
rd.fromUser = fromUser;
return rd;
}
-
+
public void recycle() {
sPool.release(this);
}
}
- private void setDrawableTint(int id, ColorStateList tint, Mode tintMode, boolean fallback) {
- Drawable layer = null;
-
- // We expect a layer drawable, so try to find the target ID.
- final Drawable d = mCurrentDrawable;
- if (d instanceof LayerDrawable) {
- layer = ((LayerDrawable) d).findDrawableByLayerId(id);
- }
-
- if (fallback && layer == null) {
- layer = d;
- }
-
- layer.mutate();
- layer.setTintList(tint);
- layer.setTintMode(tintMode);
- }
-
private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
boolean callBackToApp) {
float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
@@ -1257,13 +1252,13 @@ public class ProgressBar extends View {
} else {
invalidate();
}
-
+
if (callBackToApp && id == R.id.progress) {
- onProgressRefresh(scale, fromUser);
+ onProgressRefresh(scale, fromUser, progress);
}
}
- void onProgressRefresh(float scale, boolean fromUser) {
+ void onProgressRefresh(float scale, boolean fromUser, int progress) {
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
scheduleAccessibilityEventSender();
}
@@ -1285,7 +1280,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>
@@ -1295,13 +1290,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) {
@@ -1327,7 +1322,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()
@@ -1408,8 +1403,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) {
@@ -1426,13 +1421,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);
@@ -1443,7 +1438,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);
@@ -1466,13 +1461,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 {
@@ -1507,7 +1502,7 @@ public class ProgressBar extends View {
* @param context The application environment
* @param resID The resource identifier of the interpolator to load
*/
- public void setInterpolator(Context context, int resID) {
+ public void setInterpolator(Context context, @InterpolatorRes int resID) {
setInterpolator(AnimationUtils.loadInterpolator(context, resID));
}
@@ -1623,7 +1618,7 @@ public class ProgressBar extends View {
}
mIndeterminateDrawable.setBounds(left, top, right, bottom);
}
-
+
if (mProgressDrawable != null) {
mProgressDrawable.setBounds(0, 0, right, bottom);
}
@@ -1646,7 +1641,7 @@ public class ProgressBar extends View {
// rotates properly in its animation
final int saveCount = canvas.save();
- if(isLayoutRtl() && mMirrorForRtl) {
+ if (isLayoutRtl() && mMirrorForRtl) {
canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
canvas.scale(-1.0f, 1.0f);
} else {
@@ -1678,35 +1673,38 @@ public class ProgressBar extends View {
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- Drawable d = mCurrentDrawable;
-
int dw = 0;
int dh = 0;
+
+ final Drawable d = mCurrentDrawable;
if (d != null) {
dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
}
+
updateDrawableState();
+
dw += mPaddingLeft + mPaddingRight;
dh += mPaddingTop + mPaddingBottom;
- setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
- resolveSizeAndState(dh, heightMeasureSpec, 0));
+ final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0);
+ final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0);
+ setMeasuredDimension(measuredWidth, measuredHeight);
}
-
+
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
updateDrawableState();
}
-
+
private void updateDrawableState() {
- int[] state = getDrawableState();
-
+ final int[] state = getDrawableState();
+
if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
mProgressDrawable.setState(state);
}
-
+
if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
mIndeterminateDrawable.setState(state);
}
@@ -1728,14 +1726,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}
*/
@@ -1769,10 +1767,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;
}
@@ -1780,7 +1778,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);
}
@@ -1826,17 +1824,16 @@ public class ProgressBar extends View {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ProgressBar.class.getName());
- event.setItemCount(mMax);
- event.setCurrentItemIndex(mProgress);
+ public CharSequence getAccessibilityClassName() {
+ return ProgressBar.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ProgressBar.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
+ event.setItemCount(mMax);
+ event.setCurrentItemIndex(mProgress);
}
/**
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 23fa402..25b301f 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -37,8 +37,6 @@ import android.provider.ContactsContract.RawContacts;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* Widget used to show an image with the standard QuickContact badge
@@ -52,6 +50,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
private QueryHandler mQueryHandler;
private Drawable mDefaultAvatar;
private Bundle mExtras = null;
+ private String mPrioritizedMimeType;
protected String[] mExcludeMimes = null;
@@ -126,6 +125,15 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
public void setMode(int size) {
}
+ /**
+ * Set which mimetype should be prioritized in the QuickContacts UI. For example, passing the
+ * value {@link Email#CONTENT_ITEM_TYPE} can cause emails to be displayed more prominently in
+ * QuickContacts.
+ */
+ public void setPrioritizedMimeType(String prioritizedMimeType) {
+ mPrioritizedMimeType = prioritizedMimeType;
+ }
+
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -287,7 +295,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
final Bundle extras = (mExtras == null) ? new Bundle() : mExtras;
if (mContactUri != null) {
QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
- QuickContact.MODE_LARGE, mExcludeMimes);
+ mExcludeMimes, mPrioritizedMimeType);
} else if (mContactEmail != null && mQueryHandler != null) {
extras.putString(EXTRA_URI_CONTENT, mContactEmail);
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, extras,
@@ -305,15 +313,8 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(QuickContactBadge.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(QuickContactBadge.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return QuickContactBadge.class.getName();
}
/**
@@ -377,10 +378,10 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
mContactUri = lookupUri;
onContactUriChanged();
- if (trigger && lookupUri != null) {
+ if (trigger && mContactUri != null) {
// Found contact, so trigger QuickContact
- QuickContact.showQuickContact(getContext(), QuickContactBadge.this, lookupUri,
- QuickContact.MODE_LARGE, mExcludeMimes);
+ QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
+ mExcludeMimes, mPrioritizedMimeType);
} else if (createUri != null) {
// Prompt user to add this person to contacts
final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri);
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 11fda2c..28b4db2 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -23,23 +23,26 @@ import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
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.Path;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.Typeface;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
+import android.util.StateSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -56,14 +59,8 @@ import java.util.Locale;
*
* @hide
*/
-public class RadialTimePickerView extends View implements View.OnTouchListener {
- private static final String TAG = "ClockView";
-
- private static final boolean DEBUG = false;
-
- private static final int DEBUG_COLOR = 0x20FF0000;
- private static final int DEBUG_TEXT_COLOR = 0x60FF0000;
- private static final int DEBUG_STROKE_WIDTH = 2;
+public class RadialTimePickerView extends View {
+ private static final String TAG = "RadialTimePickerView";
private static final int HOURS = 0;
private static final int MINUTES = 1;
@@ -82,12 +79,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Transparent alpha level
private static final int ALPHA_TRANSPARENT = 0;
- // Alpha level of color for selector.
- private static final int ALPHA_SELECTOR = 60; // was 51
-
- private static final float COSINE_30_DEGREES = ((float) Math.sqrt(3)) * 0.5f;
- private static final float SINE_30_DEGREES = 0.5f;
-
private static final int DEGREES_FOR_ONE_HOUR = 30;
private static final int DEGREES_FOR_ONE_MINUTE = 6;
@@ -95,9 +86,27 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
- private static final int CENTER_RADIUS = 2;
+ private static final int FADE_OUT_DURATION = 500;
+ private static final int FADE_IN_DURATION = 500;
+
+ private static final int[] SNAP_PREFER_30S_MAP = new int[361];
+
+ private static final int NUM_POSITIONS = 12;
+ private static final float[] COS_30 = new float[NUM_POSITIONS];
+ private static final float[] SIN_30 = new float[NUM_POSITIONS];
+
+ static {
+ // Prepare mapping to snap touchable degrees to selectable degrees.
+ preparePrefer30sMap();
- private static int[] sSnapPrefer30sMap = new int[361];
+ final double increment = 2.0 * Math.PI / NUM_POSITIONS;
+ double angle = Math.PI / 2.0;
+ for (int i = 0; i < NUM_POSITIONS; i++) {
+ COS_30[i] = (float) Math.cos(angle);
+ SIN_30[i] = (float) Math.sin(angle);
+ angle += increment;
+ }
+ }
private final InvalidateUpdateListener mInvalidateUpdateListener =
new InvalidateUpdateListener();
@@ -108,7 +117,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private final String[] mMinutesTexts = new String[12];
private final Paint[] mPaint = new Paint[2];
- private final int[] mColor = new int[2];
private final IntHolder[] mAlpha = new IntHolder[2];
private final Paint mPaintCenter = new Paint();
@@ -118,42 +126,27 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private final IntHolder[][] mAlphaSelector = new IntHolder[2][3];
private final Paint mPaintBackground = new Paint();
- private final Paint mPaintDebug = new Paint();
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 ColorStateList[] mTextColor = new ColorStateList[3];
+ private final int[] mTextSize = new int[3];
+ private final int[] mTextInset = new int[3];
- private final float[] mTextSizeMultiplier = new float[3];
+ private final float[][] mOuterTextX = new float[2][12];
+ private final float[][] mOuterTextY = new float[2][12];
- private final float[] mAnimationRadiusMultiplier = new float[3];
-
- private final float mTransitionMidRadiusMultiplier;
- private final float mTransitionEndRadiusMultiplier;
+ private final float[] mInnerTextX = new float[12];
+ private final float[] mInnerTextY = new float[12];
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 ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<>();
+ private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<>();
private final RadialPickerTouchHelper mTouchHelper;
- private float mInnerTextSize;
-
private boolean mIs24HourMode;
private boolean mShowHours;
@@ -163,8 +156,14 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
*/
private boolean mIsOnInnerCircle;
+ private int mSelectorRadius;
+ private int mSelectorStroke;
+ private int mSelectorDotRadius;
+ private int mCenterDotRadius;
+
private int mXCenter;
private int mYCenter;
+ private int mCircleRadius;
private int mMinHypotenuseForInnerNumber;
private int mMaxHypotenuseForOuterNumber;
@@ -176,7 +175,8 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private AnimatorSet mTransition;
private int mAmOrPm;
- private int mDisabledAlpha;
+
+ private float mDisabledAlpha;
private OnValueSelectedListener mListener;
@@ -186,11 +186,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
}
- static {
- // Prepare mapping to snap touchable degrees to selectable degrees.
- preparePrefer30sMap();
- }
-
/**
* Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
* selectable area to each of the 12 visible values, such that the ratio of space apportioned
@@ -225,7 +220,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Iterate through the input.
for (int degrees = 0; degrees < 361; degrees++) {
// Save the input-output mapping.
- sSnapPrefer30sMap[degrees] = snappedOutputDegrees;
+ SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees;
// If this is the last input for the specified output, calculate the next output and
// the next expected count.
if (count == expectedCount) {
@@ -252,10 +247,10 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
* mapping.
*/
private static int snapPrefer30s(int degrees) {
- if (sSnapPrefer30sMap == null) {
+ if (SNAP_PREFER_30S_MAP == null) {
return -1;
}
- return sSnapPrefer30sMap[degrees];
+ return SNAP_PREFER_30S_MAP[degrees];
}
/**
@@ -308,7 +303,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Pull disabled alpha from theme.
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
- mDisabledAlpha = (int) (outValue.getFloat() * 255 + 0.5f);
+ mDisabledAlpha = outValue.getFloat();
// process style attributes
final Resources res = getResources();
@@ -327,72 +322,73 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
}
- final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor,
- res.getColor(R.color.timepicker_default_text_color_material));
+ mTextColor[HOURS] = a.getColorStateList(R.styleable.TimePicker_numbersTextColor);
+ mTextColor[HOURS_INNER] = a.getColorStateList(R.styleable.TimePicker_numbersInnerTextColor);
+ mTextColor[MINUTES] = mTextColor[HOURS];
mPaint[HOURS] = new Paint();
mPaint[HOURS].setAntiAlias(true);
mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
- mColor[HOURS] = numbersTextColor;
mPaint[MINUTES] = new Paint();
mPaint[MINUTES].setAntiAlias(true);
mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
- mColor[MINUTES] = numbersTextColor;
- mPaintCenter.setColor(numbersTextColor);
+ final ColorStateList selectorColors = a.getColorStateList(
+ R.styleable.TimePicker_numbersSelectorColor);
+ final int selectorActivatedColor = selectorColors.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
+
+ mPaintCenter.setColor(selectorActivatedColor);
mPaintCenter.setAntiAlias(true);
- mPaintCenter.setTextAlign(Paint.Align.CENTER);
+
+ final int[] activatedStateSet = StateSet.get(
+ StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
- mColorSelector[HOURS][SELECTOR_CIRCLE] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[HOURS][SELECTOR_CIRCLE] = selectorActivatedColor;
mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
- mColorSelector[HOURS][SELECTOR_DOT] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[HOURS][SELECTOR_DOT] =
+ mTextColor[HOURS].getColorForState(activatedStateSet, 0);
mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
- mColorSelector[HOURS][SELECTOR_LINE] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[HOURS][SELECTOR_LINE] = selectorActivatedColor;
mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
- mColorSelector[MINUTES][SELECTOR_CIRCLE] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[MINUTES][SELECTOR_CIRCLE] = selectorActivatedColor;
mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
- mColorSelector[MINUTES][SELECTOR_DOT] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[MINUTES][SELECTOR_DOT] =
+ mTextColor[MINUTES].getColorForState(activatedStateSet, 0);
mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
- mColorSelector[MINUTES][SELECTOR_LINE] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[MINUTES][SELECTOR_LINE] = selectorActivatedColor;
mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
- res.getColor(R.color.timepicker_default_numbers_background_color_material)));
+ context.getColor(R.color.timepicker_default_numbers_background_color_material)));
mPaintBackground.setAntiAlias(true);
- if (DEBUG) {
- mPaintDebug.setColor(DEBUG_COLOR);
- mPaintDebug.setAntiAlias(true);
- mPaintDebug.setStrokeWidth(DEBUG_STROKE_WIDTH);
- mPaintDebug.setStyle(Paint.Style.STROKE);
- mPaintDebug.setTextAlign(Paint.Align.CENTER);
- }
+ mSelectorRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_radius);
+ mSelectorStroke = res.getDimensionPixelSize(R.dimen.timepicker_selector_stroke);
+ mSelectorDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_selector_dot_radius);
+ mCenterDotRadius = res.getDimensionPixelSize(R.dimen.timepicker_center_dot_radius);
+
+ mTextSize[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
+ mTextSize[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_normal);
+ mTextSize[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_size_inner);
+
+ mTextInset[HOURS] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
+ mTextInset[MINUTES] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_normal);
+ mTextInset[HOURS_INNER] = res.getDimensionPixelSize(R.dimen.timepicker_text_inset_inner);
mShowHours = true;
mIs24HourMode = false;
@@ -409,22 +405,8 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
initHoursAndMinutesText();
initData();
- mTransitionMidRadiusMultiplier = Float.parseFloat(
- res.getString(R.string.timepicker_transition_mid_radius_multiplier));
- mTransitionEndRadiusMultiplier = Float.parseFloat(
- res.getString(R.string.timepicker_transition_end_radius_multiplier));
-
- mTextGridHeights[HOURS] = new float[7];
- mTextGridHeights[MINUTES] = new float[7];
-
- mSelectionRadiusMultiplier = Float.parseFloat(
- res.getString(R.string.timepicker_selection_radius_multiplier));
-
a.recycle();
- setOnTouchListener(this);
- setClickable(true);
-
// Initial values
final Calendar calendar = Calendar.getInstance(Locale.getDefault());
final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
@@ -436,21 +418,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
setHapticFeedbackEnabled(true);
}
- /**
- * Measure the view to end up as a square, based on the minimum of the height and width.
- */
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int minDimension = Math.min(measuredWidth, measuredHeight);
-
- super.onMeasure(MeasureSpec.makeMeasureSpec(minDimension, widthMode),
- MeasureSpec.makeMeasureSpec(minDimension, heightMode));
- }
-
public void initialize(int hour, int minute, boolean is24HourMode) {
if (mIs24HourMode != is24HourMode) {
mIs24HourMode = is24HourMode;
@@ -512,7 +479,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mIsOnInnerCircle = isOnInnerCircle;
initData();
- updateLayoutData();
mTouchHelper.invalidateRoot();
}
@@ -601,24 +567,32 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
public void showHours(boolean animate) {
- if (mShowHours) return;
+ if (mShowHours) {
+ return;
+ }
+
mShowHours = true;
+
if (animate) {
startMinutesToHoursAnimation();
}
+
initData();
- updateLayoutData();
invalidate();
}
public void showMinutes(boolean animate) {
- if (!mShowHours) return;
+ if (!mShowHours) {
+ return;
+ }
+
mShowHours = false;
+
if (animate) {
startHoursToMinutesAnimation();
}
+
initData();
- updateLayoutData();
invalidate();
}
@@ -643,162 +617,117 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mOuterTextMinutes = mMinutesTexts;
- final Resources res = getResources();
-
- if (mShowHours) {
- if (mIs24HourMode) {
- mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_circle_radius_multiplier_24HourMode));
- mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_numbers_radius_multiplier_outer));
- mTextSizeMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_text_size_multiplier_outer));
-
- mNumbersRadiusMultiplier[HOURS_INNER] = Float.parseFloat(
- res.getString(R.string.timepicker_numbers_radius_multiplier_inner));
- mTextSizeMultiplier[HOURS_INNER] = Float.parseFloat(
- res.getString(R.string.timepicker_text_size_multiplier_inner));
- } else {
- mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_circle_radius_multiplier));
- mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
- mTextSizeMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_text_size_multiplier_normal));
- }
- } else {
- mCircleRadiusMultiplier[MINUTES] = Float.parseFloat(
- res.getString(R.string.timepicker_circle_radius_multiplier));
- mNumbersRadiusMultiplier[MINUTES] = Float.parseFloat(
- res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
- mTextSizeMultiplier[MINUTES] = Float.parseFloat(
- res.getString(R.string.timepicker_text_size_multiplier_normal));
- }
-
- mAnimationRadiusMultiplier[HOURS] = 1;
- mAnimationRadiusMultiplier[HOURS_INNER] = 1;
- mAnimationRadiusMultiplier[MINUTES] = 1;
-
- mAlpha[HOURS].setValue(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
- mAlpha[MINUTES].setValue(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
-
- mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(
- mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
- mAlphaSelector[HOURS][SELECTOR_DOT].setValue(
- mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
- mAlphaSelector[HOURS][SELECTOR_LINE].setValue(
- mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
-
- mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(
- mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
- mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(
- mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
- mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(
- mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
+ final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT;
+ mAlpha[HOURS].setValue(hoursAlpha);
+ mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(hoursAlpha);
+ mAlphaSelector[HOURS][SELECTOR_DOT].setValue(hoursAlpha);
+ mAlphaSelector[HOURS][SELECTOR_LINE].setValue(hoursAlpha);
+
+ final int minutesAlpha = mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE;
+ mAlpha[MINUTES].setValue(minutesAlpha);
+ mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(minutesAlpha);
+ mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(minutesAlpha);
+ mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(minutesAlpha);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- updateLayoutData();
- }
+ if (!changed) {
+ return;
+ }
- private void updateLayoutData() {
mXCenter = getWidth() / 2;
mYCenter = getHeight() / 2;
+ mCircleRadius = Math.min(mXCenter, mYCenter);
- final int min = Math.min(mXCenter, mYCenter);
+ mMinHypotenuseForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius;
+ mMaxHypotenuseForOuterNumber = mCircleRadius - mTextInset[HOURS] - mSelectorRadius;
+ mHalfwayHypotenusePoint = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2;
- mCircleRadius[HOURS] = min * mCircleRadiusMultiplier[HOURS];
- mCircleRadius[HOURS_INNER] = min * mCircleRadiusMultiplier[HOURS];
- mCircleRadius[MINUTES] = min * mCircleRadiusMultiplier[MINUTES];
-
- mMinHypotenuseForInnerNumber = (int) (mCircleRadius[HOURS]
- * mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS];
- mMaxHypotenuseForOuterNumber = (int) (mCircleRadius[HOURS]
- * mNumbersRadiusMultiplier[HOURS]) + mSelectionRadius[HOURS];
- mHalfwayHypotenusePoint = (int) (mCircleRadius[HOURS]
- * ((mNumbersRadiusMultiplier[HOURS] + mNumbersRadiusMultiplier[HOURS_INNER]) / 2));
-
- mTextSize[HOURS] = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS];
- mTextSize[MINUTES] = mCircleRadius[MINUTES] * mTextSizeMultiplier[MINUTES];
-
- if (mIs24HourMode) {
- mInnerTextSize = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS_INNER];
- }
-
- calculateGridSizesHours();
- calculateGridSizesMinutes();
-
- mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier);
- mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
- mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier);
+ calculatePositionsHours();
+ calculatePositionsMinutes();
mTouchHelper.invalidateRoot();
}
@Override
public void onDraw(Canvas canvas) {
- if (!mInputEnabled) {
- canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), mDisabledAlpha);
- } else {
- canvas.save();
- }
-
- calculateGridSizesHours();
- calculateGridSizesMinutes();
+ final float alphaMod = mInputEnabled ? 1 : mDisabledAlpha;
drawCircleBackground(canvas);
- drawSelector(canvas);
-
- drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours,
- mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS],
- mColor[HOURS], mAlpha[HOURS].getValue());
-
- if (mIs24HourMode && mInnerTextHours != null) {
- drawTextElements(canvas, mInnerTextSize, mTypeface, mInnerTextHours,
- mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS],
- mColor[HOURS], mAlpha[HOURS].getValue());
- }
-
- drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
- mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES],
- mColor[MINUTES], mAlpha[MINUTES].getValue());
-
- drawCenter(canvas);
-
- if (DEBUG) {
- drawDebug(canvas);
- }
-
- canvas.restore();
+ drawHours(canvas, alphaMod);
+ drawMinutes(canvas, alphaMod);
+ drawCenter(canvas, alphaMod);
}
private void drawCircleBackground(Canvas canvas) {
- canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintBackground);
+ canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground);
+ }
+
+ private void drawHours(Canvas canvas, float alphaMod) {
+ final int hoursAlpha = (int) (mAlpha[HOURS].getValue() * alphaMod + 0.5f);
+ if (hoursAlpha > 0) {
+ // Draw the hour selector under the elements.
+ drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS, null, alphaMod);
+
+ // Draw outer hours.
+ drawTextElements(canvas, mTextSize[HOURS], mTypeface, mTextColor[HOURS],
+ mOuterTextHours, mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS],
+ hoursAlpha, !mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
+
+ // Draw inner hours (12-23) for 24-hour time.
+ if (mIs24HourMode && mInnerTextHours != null) {
+ drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER],
+ mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
+ mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
+ }
+ }
}
- private void drawCenter(Canvas canvas) {
- canvas.drawCircle(mXCenter, mYCenter, CENTER_RADIUS, mPaintCenter);
+ private void drawMinutes(Canvas canvas, float alphaMod) {
+ final int minutesAlpha = (int) (mAlpha[MINUTES].getValue() * alphaMod + 0.5f);
+ if (minutesAlpha > 0) {
+ drawSelector(canvas, MINUTES, mSelectorPath, alphaMod);
+
+ // Exclude the selector region, then draw minutes with no
+ // activated states.
+ canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE);
+ drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES],
+ mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES],
+ minutesAlpha, false, 0, false);
+ canvas.restore();
+
+ // Intersect the selector region, then draw minutes with only
+ // activated states.
+ canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipPath(mSelectorPath, Region.Op.INTERSECT);
+ drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES],
+ mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES],
+ minutesAlpha, true, mSelectionDegrees[MINUTES], true);
+ canvas.restore();
+ }
}
- private void drawSelector(Canvas canvas) {
- drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS);
- drawSelector(canvas, MINUTES);
+ private void drawCenter(Canvas canvas, float alphaMod) {
+ mPaintCenter.setAlpha((int) (255 * alphaMod + 0.5f));
+ canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter);
}
private int getMultipliedAlpha(int argb, int alpha) {
return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
}
- private void drawSelector(Canvas canvas, int index) {
+ private final Path mSelectorPath = new Path();
+
+ private void drawSelector(Canvas canvas, int index, Path selectorPath, float alphaMod) {
// Calculate the current radius at which to place the selection circle.
- mLineLength[index] = (int) (mCircleRadius[index]
- * mNumbersRadiusMultiplier[index] * mAnimationRadiusMultiplier[index]);
+ mLineLength[index] = mCircleRadius - mTextInset[index];
- double selectionRadians = Math.toRadians(mSelectionDegrees[index]);
+ final double selectionRadians = Math.toRadians(mSelectionDegrees[index]);
- int pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
- int pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
+ float pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
+ float pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
int color;
int alpha;
@@ -806,267 +735,146 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Draw the selection circle
color = mColorSelector[index % 2][SELECTOR_CIRCLE];
- alpha = mAlphaSelector[index % 2][SELECTOR_CIRCLE].getValue();
+ alpha = (int) (mAlphaSelector[index % 2][SELECTOR_CIRCLE].getValue() * alphaMod + 0.5f);
paint = mPaintSelector[index % 2][SELECTOR_CIRCLE];
paint.setColor(color);
paint.setAlpha(getMultipliedAlpha(color, alpha));
- canvas.drawCircle(pointX, pointY, mSelectionRadius[index], paint);
+ canvas.drawCircle(pointX, pointY, mSelectorRadius, paint);
- // Draw the dot if needed
- if (mSelectionDegrees[index] % 30 != 0) {
+ // If needed, set up the clip path for later.
+ if (selectorPath != null) {
+ mSelectorPath.reset();
+ mSelectorPath.addCircle(pointX, pointY, mSelectorRadius, Path.Direction.CCW);
+ }
+
+ // Draw the dot if needed.
+ final boolean shouldDrawDot = mSelectionDegrees[index] % 30 != 0;
+ if (shouldDrawDot) {
// We're not on a direct tick
color = mColorSelector[index % 2][SELECTOR_DOT];
- alpha = mAlphaSelector[index % 2][SELECTOR_DOT].getValue();
+ alpha = (int) (mAlphaSelector[index % 2][SELECTOR_DOT].getValue() * alphaMod + 0.5f);
paint = mPaintSelector[index % 2][SELECTOR_DOT];
paint.setColor(color);
paint.setAlpha(getMultipliedAlpha(color, alpha));
- canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7), paint);
- } else {
- // We're not drawing the dot, so shorten the line to only go as far as the edge of the
- // selection circle
- int lineLength = mLineLength[index] - mSelectionRadius[index];
- pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians));
- pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians));
+ canvas.drawCircle(pointX, pointY, mSelectorDotRadius, paint);
}
+ // Shorten the line to only go from the edge of the center dot to the
+ // edge of the selection circle.
+ final double sin = Math.sin(selectionRadians);
+ final double cos = Math.cos(selectionRadians);
+ final int lineLength = mLineLength[index] - mSelectorRadius;
+ final int centerX = mXCenter + (int) (mCenterDotRadius * sin);
+ final int centerY = mYCenter - (int) (mCenterDotRadius * cos);
+ pointX = centerX + (int) (lineLength * sin);
+ pointY = centerY - (int) (lineLength * cos);
+
// Draw the line
color = mColorSelector[index % 2][SELECTOR_LINE];
- alpha = mAlphaSelector[index % 2][SELECTOR_LINE].getValue();
+ alpha = (int) (mAlphaSelector[index % 2][SELECTOR_LINE].getValue() * alphaMod + 0.5f);
paint = mPaintSelector[index % 2][SELECTOR_LINE];
paint.setColor(color);
+ paint.setStrokeWidth(mSelectorStroke);
paint.setAlpha(getMultipliedAlpha(color, alpha));
canvas.drawLine(mXCenter, mYCenter, pointX, pointY, paint);
}
- private void drawDebug(Canvas canvas) {
- // Draw outer numbers circle
- final float outerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
- canvas.drawCircle(mXCenter, mYCenter, outerRadius, mPaintDebug);
-
- // Draw inner numbers circle
- final float innerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS_INNER];
- canvas.drawCircle(mXCenter, mYCenter, innerRadius, mPaintDebug);
-
- // Draw outer background circle
- canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintDebug);
-
- // Draw outer rectangle for circles
- float left = mXCenter - outerRadius;
- float top = mYCenter - outerRadius;
- float right = mXCenter + outerRadius;
- float bottom = mYCenter + outerRadius;
- 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];
- canvas.drawRect(left, top, right, bottom, mPaintDebug);
-
- // Draw outer view rectangle
- canvas.drawRect(0, 0, getWidth(), getHeight(), mPaintDebug);
-
- // Draw selected time
- final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute());
-
- ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- TextView tv = new TextView(getContext());
- tv.setLayoutParams(lp);
- tv.setText(selected);
- tv.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- Paint paint = tv.getPaint();
- paint.setColor(DEBUG_TEXT_COLOR);
-
- final int width = tv.getMeasuredWidth();
-
- float height = paint.descent() - paint.ascent();
- float x = mXCenter - width / 2;
- float y = mYCenter + 1.5f * height;
-
- canvas.drawText(selected, x, y, paint);
- }
-
- private void calculateGridSizesHours() {
+ private void calculatePositionsHours() {
// Calculate the text positions
- float numbersRadius = mCircleRadius[HOURS]
- * mNumbersRadiusMultiplier[HOURS] * mAnimationRadiusMultiplier[HOURS];
+ final float numbersRadius = mCircleRadius - mTextInset[HOURS];
// Calculate the positions for the 12 numbers in the main circle.
- calculateGridSizes(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
- mTextSize[HOURS], mTextGridHeights[HOURS], mTextGridWidths[HOURS]);
+ calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
+ mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]);
// If we have an inner circle, calculate those positions too.
if (mIs24HourMode) {
- float innerNumbersRadius = mCircleRadius[HOURS_INNER]
- * mNumbersRadiusMultiplier[HOURS_INNER]
- * mAnimationRadiusMultiplier[HOURS_INNER];
-
- calculateGridSizes(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
- mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths);
+ final int innerNumbersRadius = mCircleRadius - mTextInset[HOURS_INNER];
+ calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
+ mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY);
}
}
- private void calculateGridSizesMinutes() {
+ private void calculatePositionsMinutes() {
// Calculate the text positions
- float numbersRadius = mCircleRadius[MINUTES]
- * mNumbersRadiusMultiplier[MINUTES] * mAnimationRadiusMultiplier[MINUTES];
+ final float numbersRadius = mCircleRadius - mTextInset[MINUTES];
// Calculate the positions for the 12 numbers in the main circle.
- calculateGridSizes(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
- mTextSize[MINUTES], mTextGridHeights[MINUTES], mTextGridWidths[MINUTES]);
+ calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
+ mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]);
}
-
/**
* Using the trigonometric Unit Circle, calculate the positions that the text will need to be
* drawn at based on the specified circle radius. Place the values in the textGridHeights and
* textGridWidths parameters.
*/
- private static void calculateGridSizes(Paint paint, float numbersRadius, float xCenter,
- float yCenter, float textSize, float[] textGridHeights, float[] textGridWidths) {
- /*
- * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle.
- */
- final float offset1 = numbersRadius;
- // cos(30) = a / r => r * cos(30)
- final float offset2 = numbersRadius * COSINE_30_DEGREES;
- // sin(30) = o / r => r * sin(30)
- final float offset3 = numbersRadius * SINE_30_DEGREES;
-
+ private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter,
+ float textSize, float[] x, float[] y) {
+ // Adjust yCenter to account for the text's baseline.
paint.setTextSize(textSize);
- // We'll need yTextBase to be slightly lower to account for the text's baseline.
yCenter -= (paint.descent() + paint.ascent()) / 2;
- textGridHeights[0] = yCenter - offset1;
- textGridWidths[0] = xCenter - offset1;
- textGridHeights[1] = yCenter - offset2;
- textGridWidths[1] = xCenter - offset2;
- textGridHeights[2] = yCenter - offset3;
- textGridWidths[2] = xCenter - offset3;
- textGridHeights[3] = yCenter;
- textGridWidths[3] = xCenter;
- textGridHeights[4] = yCenter + offset3;
- textGridWidths[4] = xCenter + offset3;
- textGridHeights[5] = yCenter + offset2;
- textGridWidths[5] = xCenter + offset2;
- textGridHeights[6] = yCenter + offset1;
- textGridWidths[6] = xCenter + offset1;
+ for (int i = 0; i < NUM_POSITIONS; i++) {
+ x[i] = xCenter - radius * COS_30[i];
+ y[i] = yCenter - radius * SIN_30[i];
+ }
}
/**
* Draw the 12 text values at the positions specified by the textGrid parameters.
*/
- private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, String[] texts,
- float[] textGridWidths, float[] textGridHeights, Paint paint, int color, int alpha) {
+ private void drawTextElements(Canvas canvas, float textSize, Typeface typeface,
+ ColorStateList textColor, String[] texts, float[] textX, float[] textY, Paint paint,
+ int alpha, boolean showActivated, int activatedDegrees, boolean activatedOnly) {
paint.setTextSize(textSize);
paint.setTypeface(typeface);
- paint.setColor(color);
- paint.setAlpha(getMultipliedAlpha(color, alpha));
- canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], paint);
- canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], paint);
- canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], paint);
- canvas.drawText(texts[3], textGridWidths[6], textGridHeights[3], paint);
- canvas.drawText(texts[4], textGridWidths[5], textGridHeights[4], paint);
- canvas.drawText(texts[5], textGridWidths[4], textGridHeights[5], paint);
- canvas.drawText(texts[6], textGridWidths[3], textGridHeights[6], paint);
- canvas.drawText(texts[7], textGridWidths[2], textGridHeights[5], paint);
- canvas.drawText(texts[8], textGridWidths[1], textGridHeights[4], paint);
- canvas.drawText(texts[9], textGridWidths[0], textGridHeights[3], paint);
- canvas.drawText(texts[10], textGridWidths[1], textGridHeights[2], paint);
- canvas.drawText(texts[11], textGridWidths[2], textGridHeights[1], paint);
- }
- // Used for animating the hours by changing their radius
- @SuppressWarnings("unused")
- private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) {
- mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier;
- mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier;
- }
+ // The activated index can touch a range of elements.
+ final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS);
+ final int activatedFloor = (int) activatedIndex;
+ final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS;
- // Used for animating the minutes by changing their radius
- @SuppressWarnings("unused")
- private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) {
- mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier;
- }
+ for (int i = 0; i < 12; i++) {
+ final boolean activated = (activatedFloor == i || activatedCeil == i);
+ if (activatedOnly && !activated) {
+ continue;
+ }
- private static ObjectAnimator getRadiusDisappearAnimator(Object target,
- String radiusPropertyName, InvalidateUpdateListener updateListener,
- float midRadiusMultiplier, float endRadiusMultiplier) {
- Keyframe kf0, kf1, kf2;
- float midwayPoint = 0.2f;
- int duration = 500;
-
- kf0 = Keyframe.ofFloat(0f, 1);
- kf1 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
- kf2 = Keyframe.ofFloat(1f, endRadiusMultiplier);
- PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
- radiusPropertyName, kf0, kf1, kf2);
-
- ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
- target, radiusDisappear).setDuration(duration);
- animator.addUpdateListener(updateListener);
- return animator;
- }
+ final int stateMask = StateSet.VIEW_STATE_ENABLED
+ | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0);
+ final int color = textColor.getColorForState(StateSet.get(stateMask), 0);
+ paint.setColor(color);
+ paint.setAlpha(getMultipliedAlpha(color, alpha));
- private static ObjectAnimator getRadiusReappearAnimator(Object target,
- String radiusPropertyName, InvalidateUpdateListener updateListener,
- float midRadiusMultiplier, float endRadiusMultiplier) {
- Keyframe kf0, kf1, kf2, kf3;
- float midwayPoint = 0.2f;
- int duration = 500;
-
- // Set up animator for reappearing.
- float delayMultiplier = 0.25f;
- float transitionDurationMultiplier = 1f;
- float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
- int totalDuration = (int) (duration * totalDurationMultiplier);
- float delayPoint = (delayMultiplier * duration) / totalDuration;
- midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
-
- kf0 = Keyframe.ofFloat(0f, endRadiusMultiplier);
- kf1 = Keyframe.ofFloat(delayPoint, endRadiusMultiplier);
- kf2 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
- kf3 = Keyframe.ofFloat(1f, 1);
- PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
- radiusPropertyName, kf0, kf1, kf2, kf3);
-
- ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
- target, radiusReappear).setDuration(totalDuration);
- animator.addUpdateListener(updateListener);
- return animator;
+ canvas.drawText(texts[i], textX[i], textY[i], paint);
+ }
}
private static ObjectAnimator getFadeOutAnimator(IntHolder target, int startAlpha, int endAlpha,
InvalidateUpdateListener updateListener) {
- int duration = 500;
- ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
- animator.setDuration(duration);
+ final ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
+ animator.setDuration(FADE_OUT_DURATION);
animator.addUpdateListener(updateListener);
-
return animator;
}
private static ObjectAnimator getFadeInAnimator(IntHolder target, int startAlpha, int endAlpha,
InvalidateUpdateListener updateListener) {
- Keyframe kf0, kf1, kf2;
- int duration = 500;
-
- // Set up animator for reappearing.
- float delayMultiplier = 0.25f;
- float transitionDurationMultiplier = 1f;
- float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
- int totalDuration = (int) (duration * totalDurationMultiplier);
- float delayPoint = (delayMultiplier * duration) / totalDuration;
+ final float delayMultiplier = 0.25f;
+ final float transitionDurationMultiplier = 1f;
+ final float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
+ final int totalDuration = (int) (FADE_IN_DURATION * totalDurationMultiplier);
+ final float delayPoint = (delayMultiplier * FADE_IN_DURATION) / totalDuration;
+ final Keyframe kf0, kf1, kf2;
kf0 = Keyframe.ofInt(0f, startAlpha);
kf1 = Keyframe.ofInt(delayPoint, startAlpha);
kf2 = Keyframe.ofInt(1f, endAlpha);
- PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);
+ final PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);
- ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
- target, fadeIn).setDuration(totalDuration);
+ final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(target, fadeIn);
+ animator.setDuration(totalDuration);
animator.addUpdateListener(updateListener);
return animator;
}
@@ -1080,29 +888,23 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private void startHoursToMinutesAnimation() {
if (mHoursToMinutesAnims.size() == 0) {
- mHoursToMinutesAnims.add(getRadiusDisappearAnimator(this,
- "animationRadiusMultiplierHours", mInvalidateUpdateListener,
- mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
- ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
- ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mHoursToMinutesAnims.add(getRadiusReappearAnimator(this,
- "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
- mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
- ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
- ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
}
if (mTransition != null && mTransition.isRunning()) {
@@ -1115,29 +917,23 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private void startMinutesToHoursAnimation() {
if (mMinuteToHoursAnims.size() == 0) {
- mMinuteToHoursAnims.add(getRadiusDisappearAnimator(this,
- "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
- mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
- ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
- ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mMinuteToHoursAnims.add(getRadiusReappearAnimator(this,
- "animationRadiusMultiplierHours", mInvalidateUpdateListener,
- mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
- ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
- ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
}
if (mTransition != null && mTransition.isRunning()) {
@@ -1148,20 +944,21 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mTransition.start();
}
- private int getDegreesFromXY(float x, float y) {
+ private int getDegreesFromXY(float x, float y, boolean constrainOutside) {
final double hypotenuse = Math.sqrt(
(y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter));
// Basic check if we're outside the range of the disk
- if (hypotenuse > mCircleRadius[HOURS]) {
+ if (constrainOutside && hypotenuse > mCircleRadius) {
return -1;
}
+
// Check
if (mIs24HourMode && mShowHours) {
if (hypotenuse >= mMinHypotenuseForInnerNumber
&& hypotenuse <= mHalfwayHypotenusePoint) {
mIsOnInnerCircle = true;
- } else if (hypotenuse <= mMaxHypotenuseForOuterNumber
+ } else if ((hypotenuse <= mMaxHypotenuseForOuterNumber || !constrainOutside)
&& hypotenuse >= mHalfwayHypotenusePoint) {
mIsOnInnerCircle = false;
} else {
@@ -1169,11 +966,11 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
} else {
final int index = (mShowHours) ? HOURS : MINUTES;
- final float length = (mCircleRadius[index] * mNumbersRadiusMultiplier[index]);
- final int distanceToNumber = (int) Math.abs(hypotenuse - length);
- final int maxAllowedDistance =
- (int) (mCircleRadius[index] * (1 - mNumbersRadiusMultiplier[index]));
- if (distanceToNumber > maxAllowedDistance) {
+ final float length = (mCircleRadius - mTextInset[index]);
+ final int distanceToNumber = (int) (hypotenuse - length);
+ final int maxAllowedDistance = mTextInset[index];
+ if (distanceToNumber < -maxAllowedDistance
+ || (constrainOutside && distanceToNumber > maxAllowedDistance)) {
return -1;
}
}
@@ -1203,7 +1000,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
boolean mChangedDuringTouch = false;
@Override
- public boolean onTouch(View v, MotionEvent event) {
+ public boolean onTouchEvent(MotionEvent event) {
if (!mInputEnabled) {
return true;
}
@@ -1240,7 +1037,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// 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);
+ final int degrees = getDegreesFromXY(x, y, false);
if (degrees == -1) {
return false;
}
@@ -1385,7 +1182,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// 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 int degrees = getDegreesFromXY(x, y, true);
final boolean isOnInnerCircle = mIsOnInnerCircle;
mIsOnInnerCircle = wasOnInnerCircle;
@@ -1541,18 +1338,18 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
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];
+ centerRadius = mCircleRadius - mTextInset[HOURS_INNER];
+ radius = mSelectorRadius;
} else {
- centerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
- radius = mSelectionRadius[HOURS];
+ centerRadius = mCircleRadius - mTextInset[HOURS];
+ radius = mSelectorRadius;
}
degrees = getDegreesForHour(value);
} else if (type == TYPE_MINUTE) {
- centerRadius = mCircleRadius[MINUTES] * mNumbersRadiusMultiplier[MINUTES];
+ centerRadius = mCircleRadius - mTextInset[MINUTES];
degrees = getDegreesForMinute(value);
- radius = mSelectionRadius[MINUTES];
+ radius = mSelectorRadius;
} else {
// This should never happen.
centerRadius = 0;
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index afc4830..d44fbd7 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -18,8 +18,6 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
@@ -80,14 +78,7 @@ public class RadioButton extends CompoundButton {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(RadioButton.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(RadioButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return RadioButton.class.getName();
}
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 78d05b0..065feb8 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -18,13 +18,12 @@ package android.widget;
import com.android.internal.R;
+import android.annotation.IdRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
@@ -151,7 +150,7 @@ public class RadioGroup extends LinearLayout {
* @see #getCheckedRadioButtonId()
* @see #clearCheck()
*/
- public void check(int id) {
+ public void check(@IdRes int id) {
// don't even bother
if (id != -1 && (id == mCheckedId)) {
return;
@@ -168,7 +167,7 @@ public class RadioGroup extends LinearLayout {
setCheckedId(id);
}
- private void setCheckedId(int id) {
+ private void setCheckedId(@IdRes int id) {
mCheckedId = id;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
@@ -193,6 +192,7 @@ public class RadioGroup extends LinearLayout {
*
* @attr ref android.R.styleable#RadioGroup_checkedButton
*/
+ @IdRes
public int getCheckedRadioButtonId() {
return mCheckedId;
}
@@ -241,15 +241,8 @@ public class RadioGroup extends LinearLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(RadioGroup.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(RadioGroup.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return RadioGroup.class.getName();
}
/**
@@ -338,7 +331,7 @@ public class RadioGroup extends LinearLayout {
* @param group the group in which the checked radio button has changed
* @param checkedId the unique identifier of the newly checked radio button
*/
- public void onCheckedChanged(RadioGroup group, int checkedId);
+ public void onCheckedChanged(RadioGroup group, @IdRes int checkedId);
}
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 82b490e..b538334 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -21,9 +21,6 @@ import android.content.res.TypedArray;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.Shape;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
import com.android.internal.R;
/**
@@ -43,7 +40,7 @@ import com.android.internal.R;
* <p>
* The secondary progress should not be modified by the client as it is used
* internally as the background for a fractionally filled star.
- *
+ *
* @attr ref android.R.styleable#RatingBar_numStars
* @attr ref android.R.styleable#RatingBar_rating
* @attr ref android.R.styleable#RatingBar_stepSize
@@ -58,14 +55,14 @@ public class RatingBar extends AbsSeekBar {
* programmatically.
*/
public interface OnRatingBarChangeListener {
-
+
/**
* Notification that the rating has changed. Clients can use the
* fromUser parameter to distinguish user-initiated changes from those
* that occurred programmatically. This will not be called continuously
* while the user is dragging, only when the user finalizes a rating by
* lifting the touch.
- *
+ *
* @param ratingBar The RatingBar whose rating has changed.
* @param rating The current rating. This will be in the range
* 0..numStars.
@@ -79,9 +76,9 @@ public class RatingBar extends AbsSeekBar {
private int mNumStars = 5;
private int mProgressOnStartTracking;
-
+
private OnRatingBarChangeListener mOnRatingBarChangeListener;
-
+
public RatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@@ -98,19 +95,19 @@ public class RatingBar extends AbsSeekBar {
a.recycle();
if (numStars > 0 && numStars != mNumStars) {
- setNumStars(numStars);
+ setNumStars(numStars);
}
-
+
if (stepSize >= 0) {
setStepSize(stepSize);
} else {
setStepSize(0.5f);
}
-
+
if (rating >= 0) {
setRating(rating);
}
-
+
// A touch inside a star fill up to that fractional area (slightly more
// than 1 so boundaries round up).
mTouchProgressOffset = 1.1f;
@@ -123,16 +120,16 @@ public class RatingBar extends AbsSeekBar {
public RatingBar(Context context) {
this(context, null);
}
-
+
/**
* Sets the listener to be called when the rating changes.
- *
+ *
* @param listener The listener.
*/
public void setOnRatingBarChangeListener(OnRatingBarChangeListener listener) {
mOnRatingBarChangeListener = listener;
}
-
+
/**
* @return The listener (may be null) that is listening for rating change
* events.
@@ -144,7 +141,7 @@ public class RatingBar extends AbsSeekBar {
/**
* Whether this rating bar should only be an indicator (thus non-changeable
* by the user).
- *
+ *
* @param isIndicator Whether it should be an indicator.
*
* @attr ref android.R.styleable#RatingBar_isIndicator
@@ -153,7 +150,7 @@ public class RatingBar extends AbsSeekBar {
mIsUserSeekable = !isIndicator;
setFocusable(!isIndicator);
}
-
+
/**
* @return Whether this rating bar is only an indicator.
*
@@ -162,21 +159,21 @@ public class RatingBar extends AbsSeekBar {
public boolean isIndicator() {
return !mIsUserSeekable;
}
-
+
/**
* Sets the number of stars to show. In order for these to be shown
* properly, it is recommended the layout width of this widget be wrap
* content.
- *
+ *
* @param numStars The number of stars.
*/
public void setNumStars(final int numStars) {
if (numStars <= 0) {
return;
}
-
+
mNumStars = numStars;
-
+
// This causes the width to change, so re-layout
requestLayout();
}
@@ -188,10 +185,10 @@ public class RatingBar extends AbsSeekBar {
public int getNumStars() {
return mNumStars;
}
-
+
/**
* Sets the rating (the number of stars filled).
- *
+ *
* @param rating The rating to set.
*/
public void setRating(float rating) {
@@ -200,16 +197,16 @@ public class RatingBar extends AbsSeekBar {
/**
* Gets the current rating (number of stars filled).
- *
+ *
* @return The current rating.
*/
public float getRating() {
- return getProgress() / getProgressPerStar();
+ return getProgress() / getProgressPerStar();
}
/**
* Sets the step size (granularity) of this rating bar.
- *
+ *
* @param stepSize The step size of this rating bar. For example, if
* half-star granularity is wanted, this would be 0.5.
*/
@@ -217,7 +214,7 @@ public class RatingBar extends AbsSeekBar {
if (stepSize <= 0) {
return;
}
-
+
final float newMax = mNumStars / stepSize;
final int newProgress = (int) (newMax / getMax() * getProgress());
setMax((int) newMax);
@@ -226,13 +223,13 @@ public class RatingBar extends AbsSeekBar {
/**
* Gets the step size of this rating bar.
- *
+ *
* @return The step size.
*/
public float getStepSize() {
return (float) getNumStars() / getMax();
}
-
+
/**
* @return The amount of progress that fits into a star
*/
@@ -251,12 +248,12 @@ public class RatingBar extends AbsSeekBar {
}
@Override
- void onProgressRefresh(float scale, boolean fromUser) {
- super.onProgressRefresh(scale, fromUser);
+ void onProgressRefresh(float scale, boolean fromUser, int progress) {
+ super.onProgressRefresh(scale, fromUser, progress);
// Keep secondary progress in sync with primary
- updateSecondaryProgress(getProgress());
-
+ updateSecondaryProgress(progress);
+
if (!fromUser) {
// Callback for non-user rating changes
dispatchRatingChange(false);
@@ -267,7 +264,7 @@ public class RatingBar extends AbsSeekBar {
* The secondary progress is used to differentiate the background of a
* partially filled star. This method keeps the secondary progress in sync
* with the progress.
- *
+ *
* @param progress The primary progress level.
*/
private void updateSecondaryProgress(int progress) {
@@ -278,11 +275,11 @@ public class RatingBar extends AbsSeekBar {
setSecondaryProgress(secondaryProgress);
}
}
-
+
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
+
if (mSampleTile != null) {
// TODO: Once ProgressBar's TODOs are gone, this can be done more
// cleanly than mSampleTile
@@ -295,7 +292,7 @@ public class RatingBar extends AbsSeekBar {
@Override
void onStartTrackingTouch() {
mProgressOnStartTracking = getProgress();
-
+
super.onStartTrackingTouch();
}
@@ -327,19 +324,12 @@ public class RatingBar extends AbsSeekBar {
if (max <= 0) {
return;
}
-
+
super.setMax(max);
}
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(RatingBar.class.getName());
- }
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(RatingBar.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return RatingBar.class.getName();
}
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 5b604cd..d12739f 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -37,7 +37,6 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
@@ -201,7 +200,6 @@ public class RelativeLayout extends ViewGroup {
private static final int VALUE_NOT_SET = Integer.MIN_VALUE;
private View mBaselineView = null;
- private boolean mHasBaselineAlignedChild;
private int mGravity = Gravity.START | Gravity.TOP;
private final Rect mContentBounds = new Rect();
@@ -417,8 +415,6 @@ public class RelativeLayout extends ViewGroup {
height = myHeight;
}
- mHasBaselineAlignedChild = false;
-
View ignore = null;
int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
@@ -473,11 +469,11 @@ public class RelativeLayout extends ViewGroup {
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
for (int i = 0; i < count; i++) {
- View child = views[i];
+ final View child = views[i];
if (child.getVisibility() != GONE) {
- LayoutParams params = (LayoutParams) child.getLayoutParams();
-
- applyVerticalSizeRules(params, myHeight);
+ final LayoutParams params = (LayoutParams) child.getLayoutParams();
+
+ applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
@@ -519,25 +515,22 @@ public class RelativeLayout extends ViewGroup {
}
}
- if (mHasBaselineAlignedChild) {
- for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- LayoutParams params = (LayoutParams) child.getLayoutParams();
- alignBaseline(child, params);
-
- if (child != ignore || verticalGravity) {
- left = Math.min(left, params.mLeft - params.leftMargin);
- top = Math.min(top, params.mTop - params.topMargin);
- }
-
- if (child != ignore || horizontalGravity) {
- right = Math.max(right, params.mRight + params.rightMargin);
- bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
- }
+ // Use the top-start-most laid out view as the baseline. RTL offsets are
+ // applied later, so we can use the left-most edge as the starting edge.
+ View baselineView = null;
+ LayoutParams baselineParams = null;
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
+ if (baselineView == null || baselineParams == null
+ || compareLayoutPosition(childParams, baselineParams) < 0) {
+ baselineView = child;
+ baselineParams = childParams;
}
}
}
+ mBaselineView = baselineView;
if (isWrapContentWidth) {
// Width already has left padding in it since it was calculated by looking at
@@ -638,39 +631,23 @@ public class RelativeLayout extends ViewGroup {
params.mRight -= offsetWidth;
}
}
-
}
setMeasuredDimension(width, height);
}
- private void alignBaseline(View child, LayoutParams params) {
- final int layoutDirection = getLayoutDirection();
- int[] rules = params.getRules(layoutDirection);
- int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE);
-
- if (anchorBaseline != -1) {
- LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE);
- if (anchorParams != null) {
- int offset = anchorParams.mTop + anchorBaseline;
- int baseline = child.getBaseline();
- if (baseline != -1) {
- offset -= baseline;
- }
- int height = params.mBottom - params.mTop;
- params.mTop = offset;
- params.mBottom = params.mTop + height;
- }
- }
-
- if (mBaselineView == null) {
- mBaselineView = child;
- } else {
- LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams();
- if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) {
- mBaselineView = child;
- }
+ /**
+ * @return a negative number if the top of {@code p1} is above the top of
+ * {@code p2} or if they have identical top values and the left of
+ * {@code p1} is to the left of {@code p2}, or a positive number
+ * otherwise
+ */
+ private int compareLayoutPosition(LayoutParams p1, LayoutParams p2) {
+ final int topDiff = p1.mTop - p2.mTop;
+ if (topDiff != 0) {
+ return topDiff;
}
+ return p1.mLeft - p2.mLeft;
}
/**
@@ -950,8 +927,20 @@ public class RelativeLayout extends ViewGroup {
}
}
- private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) {
- int[] rules = childParams.getRules();
+ private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) {
+ final int[] rules = childParams.getRules();
+
+ // Baseline alignment overrides any explicitly specified top or bottom.
+ int baselineOffset = getRelatedViewBaselineOffset(rules);
+ if (baselineOffset != -1) {
+ if (myBaseline != -1) {
+ baselineOffset -= myBaseline;
+ }
+ childParams.mTop = baselineOffset;
+ childParams.mBottom = VALUE_NOT_SET;
+ return;
+ }
+
RelativeLayout.LayoutParams anchorParams;
childParams.mTop = VALUE_NOT_SET;
@@ -1000,10 +989,6 @@ public class RelativeLayout extends ViewGroup {
childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
}
}
-
- if (rules[ALIGN_BASELINE] != 0) {
- mHasBaselineAlignedChild = true;
- }
}
private View getRelatedView(int[] rules, int relation) {
@@ -1038,10 +1023,17 @@ public class RelativeLayout extends ViewGroup {
return null;
}
- private int getRelatedViewBaseline(int[] rules, int relation) {
- View v = getRelatedView(rules, relation);
+ private int getRelatedViewBaselineOffset(int[] rules) {
+ final View v = getRelatedView(rules, ALIGN_BASELINE);
if (v != null) {
- return v.getBaseline();
+ final int baseline = v.getBaseline();
+ if (baseline != -1) {
+ final ViewGroup.LayoutParams params = v.getLayoutParams();
+ if (params instanceof LayoutParams) {
+ final LayoutParams anchorParams = (LayoutParams) v.getLayoutParams();
+ return anchorParams.mTop + baseline;
+ }
+ }
}
return -1;
}
@@ -1104,8 +1096,9 @@ public class RelativeLayout extends ViewGroup {
return new LayoutParams(p);
}
+ /** @hide */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if (mTopToBottomLeftToRightSet == null) {
mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
}
@@ -1128,15 +1121,8 @@ public class RelativeLayout extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(RelativeLayout.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(RelativeLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return RelativeLayout.class.getName();
}
/**
@@ -1387,6 +1373,7 @@ public class RelativeLayout extends ViewGroup {
* {@link android.widget.RelativeLayout RelativeLayout}, such as
* ALIGN_WITH_PARENT_LEFT.
* @see #addRule(int, int)
+ * @see #getRule(int)
*/
public void addRule(int verb) {
mRules[verb] = TRUE;
@@ -1407,6 +1394,7 @@ public class RelativeLayout extends ViewGroup {
* for true or 0 for false). For verbs that don't refer to another sibling
* (for example, ALIGN_WITH_PARENT_BOTTOM) just use -1.
* @see #addRule(int)
+ * @see #getRule(int)
*/
public void addRule(int verb, int anchor) {
mRules[verb] = anchor;
@@ -1422,6 +1410,7 @@ public class RelativeLayout extends ViewGroup {
* ALIGN_WITH_PARENT_LEFT.
* @see #addRule(int)
* @see #addRule(int, int)
+ * @see #getRule(int)
*/
public void removeRule(int verb) {
mRules[verb] = 0;
@@ -1429,6 +1418,22 @@ public class RelativeLayout extends ViewGroup {
mRulesChanged = true;
}
+ /**
+ * Returns the layout rule associated with a specific verb.
+ *
+ * @param verb one of the verbs defined by {@link RelativeLayout}, such
+ * as ALIGN_WITH_PARENT_LEFT
+ * @return the id of another view to use as an anchor, a boolean value
+ * (represented as {@link RelativeLayout#TRUE} for true
+ * or 0 for false), or -1 for verbs that don't refer to another
+ * sibling (for example, ALIGN_WITH_PARENT_BOTTOM)
+ * @see #addRule(int)
+ * @see #addRule(int, int)
+ */
+ public int getRule(int verb) {
+ return mRules[verb];
+ }
+
private boolean hasRelativeRules() {
return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 ||
mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 ||
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index dd7fa18..a10be11 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.ColorInt;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
@@ -2263,7 +2264,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param color Sets the text color for all the states (normal, selected,
* focused) to be this color.
*/
- public void setTextColor(int viewId, int color) {
+ public void setTextColor(int viewId, @ColorInt int color) {
setInt(viewId, "setTextColor", color);
}
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 56bdb9b..349f3f0 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -26,14 +26,12 @@ import android.Manifest;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index 7341c2c..100f919 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -17,7 +17,9 @@
package android.widget;
import android.content.Context;
+import android.content.res.Resources;
import android.database.Cursor;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
@@ -31,9 +33,10 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
private int mLayout;
private int mDropDownLayout;
-
+
private LayoutInflater mInflater;
-
+ private LayoutInflater mDropDownInflater;
+
/**
* Constructor the enables auto-requery.
*
@@ -52,8 +55,9 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
super(context, c);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDropDownInflater = mInflater;
}
-
+
/**
* Constructor with default behavior as per
* {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
@@ -74,6 +78,7 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
super(context, c, autoRequery);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDropDownInflater = mInflater;
}
/**
@@ -91,11 +96,37 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
super(context, c, flags);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDropDownInflater = mInflater;
+ }
+
+ /**
+ * Sets the {@link android.content.res.Resources.Theme} against which drop-down views are
+ * inflated.
+ * <p>
+ * By default, drop-down views are inflated against the theme of the
+ * {@link Context} passed to the adapter's constructor.
+ *
+ * @param theme the theme against which to inflate drop-down views or
+ * {@code null} to use the theme from the adapter's context
+ * @see #newDropDownView(Context, Cursor, ViewGroup)
+ */
+ @Override
+ public void setDropDownViewTheme(Resources.Theme theme) {
+ super.setDropDownViewTheme(theme);
+
+ if (theme == null) {
+ mDropDownInflater = null;
+ } else if (theme == mInflater.getContext().getTheme()) {
+ mDropDownInflater = mInflater;
+ } else {
+ final Context context = new ContextThemeWrapper(mContext, theme);
+ mDropDownInflater = LayoutInflater.from(context);
+ }
}
/**
* Inflates view(s) from the specified XML file.
- *
+ *
* @see android.widget.CursorAdapter#newView(android.content.Context,
* android.database.Cursor, ViewGroup)
*/
@@ -106,7 +137,7 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
@Override
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
- return mInflater.inflate(mDropDownLayout, parent, false);
+ return mDropDownInflater.inflate(mDropDownLayout, parent, false);
}
/**
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
index 8eff1aa..91d6232 100644
--- a/core/java/android/widget/ScrollBarDrawable.java
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -23,63 +23,74 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
/**
- * This is only used by View for displaying its scroll bars. It should probably
+ * This is only used by View for displaying its scroll bars. It should probably
* be moved in to the view package since it is used in that lower-level layer.
* For now, we'll hide it so it can be cleaned up later.
+ *
* {@hide}
*/
-public class ScrollBarDrawable extends Drawable {
- private static final int[] STATE_ENABLED = new int[] { android.R.attr.state_enabled };
-
+public class ScrollBarDrawable extends Drawable implements Drawable.Callback {
private Drawable mVerticalTrack;
private Drawable mHorizontalTrack;
private Drawable mVerticalThumb;
private Drawable mHorizontalThumb;
+
private int mRange;
private int mOffset;
private int mExtent;
+
private boolean mVertical;
- private boolean mChanged;
+ private boolean mBoundsChanged;
private boolean mRangeChanged;
- private final Rect mTempBounds = new Rect();
private boolean mAlwaysDrawHorizontalTrack;
private boolean mAlwaysDrawVerticalTrack;
private boolean mMutated;
- public ScrollBarDrawable() {
- }
+ private int mAlpha = 255;
+ private boolean mHasSetAlpha;
+
+ private ColorFilter mColorFilter;
+ private boolean mHasSetColorFilter;
/**
- * Indicate whether the horizontal scrollbar track should always be drawn regardless of the
- * extent. Defaults to false.
+ * Indicate whether the horizontal scrollbar track should always be drawn
+ * regardless of the extent. Defaults to false.
+ *
+ * @param alwaysDrawTrack Whether the track should always be drawn
*
- * @param alwaysDrawTrack Set to true if the track should always be drawn
+ * @see #getAlwaysDrawHorizontalTrack()
*/
public void setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack) {
mAlwaysDrawHorizontalTrack = alwaysDrawTrack;
}
/**
- * Indicate whether the vertical scrollbar track should always be drawn regardless of the
- * extent. Defaults to false.
+ * Indicate whether the vertical scrollbar track should always be drawn
+ * regardless of the extent. Defaults to false.
+ *
+ * @param alwaysDrawTrack Whether the track should always be drawn
*
- * @param alwaysDrawTrack Set to true if the track should always be drawn
+ * @see #getAlwaysDrawVerticalTrack()
*/
public void setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack) {
mAlwaysDrawVerticalTrack = alwaysDrawTrack;
}
/**
- * Indicates whether the vertical scrollbar track should always be drawn regardless of the
- * extent.
+ * @return whether the vertical scrollbar track should always be drawn
+ * regardless of the extent.
+ *
+ * @see #setAlwaysDrawVerticalTrack(boolean)
*/
public boolean getAlwaysDrawVerticalTrack() {
return mAlwaysDrawVerticalTrack;
}
/**
- * Indicates whether the horizontal scrollbar track should always be drawn regardless of the
- * extent.
+ * @return whether the horizontal scrollbar track should always be drawn
+ * regardless of the extent.
+ *
+ * @see #setAlwaysDrawHorizontalTrack(boolean)
*/
public boolean getAlwaysDrawHorizontalTrack() {
return mAlwaysDrawHorizontalTrack;
@@ -87,17 +98,18 @@ public class ScrollBarDrawable extends Drawable {
public void setParameters(int range, int offset, int extent, boolean vertical) {
if (mVertical != vertical) {
- mChanged = true;
+ mVertical = vertical;
+
+ mBoundsChanged = true;
}
if (mRange != range || mOffset != offset || mExtent != extent) {
+ mRange = range;
+ mOffset = offset;
+ mExtent = extent;
+
mRangeChanged = true;
}
-
- mRange = range;
- mOffset = offset;
- mExtent = extent;
- mVertical = vertical;
}
@Override
@@ -113,27 +125,29 @@ public class ScrollBarDrawable extends Drawable {
drawThumb = false;
}
- Rect r = getBounds();
+ final Rect r = getBounds();
if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) {
return;
}
+
if (drawTrack) {
drawTrack(canvas, r, vertical);
}
if (drawThumb) {
- int size = vertical ? r.height() : r.width();
- int thickness = vertical ? r.width() : r.height();
- int length = Math.round((float) size * extent / range);
- int offset = Math.round((float) (size - length) * mOffset / (range - extent));
+ final int size = vertical ? r.height() : r.width();
+ final int thickness = vertical ? r.width() : r.height();
+ final int minLength = thickness * 2;
- // avoid the tiny thumb
- int minLength = thickness * 2;
+ // Avoid the tiny thumb.
+ int length = Math.round((float) size * extent / range);
if (length < minLength) {
length = minLength;
}
- // avoid the too-big thumb
- if (offset + length > size) {
+
+ // Avoid the too-big thumb.
+ int offset = Math.round((float) (size - length) * mOffset / (range - extent));
+ if (offset > size - length) {
offset = size - length;
}
@@ -144,90 +158,130 @@ public class ScrollBarDrawable extends Drawable {
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
- mChanged = true;
+ mBoundsChanged = true;
+ }
+
+ @Override
+ public boolean isStateful() {
+ return (mVerticalTrack != null && mVerticalTrack.isStateful())
+ || (mVerticalThumb != null && mVerticalThumb.isStateful())
+ || (mHorizontalTrack != null && mHorizontalTrack.isStateful())
+ || (mHorizontalThumb != null && mHorizontalThumb.isStateful())
+ || super.isStateful();
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ boolean changed = super.onStateChange(state);
+ if (mVerticalTrack != null) {
+ changed |= mVerticalTrack.setState(state);
+ }
+ if (mVerticalThumb != null) {
+ changed |= mVerticalThumb.setState(state);
+ }
+ if (mHorizontalTrack != null) {
+ changed |= mHorizontalTrack.setState(state);
+ }
+ if (mHorizontalThumb != null) {
+ changed |= mHorizontalThumb.setState(state);
+ }
+ return changed;
}
- protected void drawTrack(Canvas canvas, Rect bounds, boolean vertical) {
- Drawable track;
+ private void drawTrack(Canvas canvas, Rect bounds, boolean vertical) {
+ final Drawable track;
if (vertical) {
track = mVerticalTrack;
} else {
track = mHorizontalTrack;
}
+
if (track != null) {
- if (mChanged) {
+ if (mBoundsChanged) {
track.setBounds(bounds);
}
track.draw(canvas);
}
}
- protected void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
- final Rect thumbRect = mTempBounds;
- final boolean changed = mRangeChanged || mChanged;
- if (changed) {
- if (vertical) {
- thumbRect.set(bounds.left, bounds.top + offset,
- bounds.right, bounds.top + offset + length);
- } else {
- thumbRect.set(bounds.left + offset, bounds.top,
- bounds.left + offset + length, bounds.bottom);
- }
- }
-
+ private void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
+ final boolean changed = mRangeChanged || mBoundsChanged;
if (vertical) {
if (mVerticalThumb != null) {
final Drawable thumb = mVerticalThumb;
- if (changed) thumb.setBounds(thumbRect);
+ if (changed) {
+ thumb.setBounds(bounds.left, bounds.top + offset,
+ bounds.right, bounds.top + offset + length);
+ }
+
thumb.draw(canvas);
}
} else {
if (mHorizontalThumb != null) {
final Drawable thumb = mHorizontalThumb;
- if (changed) thumb.setBounds(thumbRect);
+ if (changed) {
+ thumb.setBounds(bounds.left + offset, bounds.top,
+ bounds.left + offset + length, bounds.bottom);
+ }
+
thumb.draw(canvas);
}
}
}
public void setVerticalThumbDrawable(Drawable thumb) {
- if (thumb != null) {
- if (mMutated) {
- thumb.mutate();
- }
- thumb.setState(STATE_ENABLED);
- mVerticalThumb = thumb;
+ if (mVerticalThumb != null) {
+ mVerticalThumb.setCallback(null);
}
+
+ propagateCurrentState(thumb);
+ mVerticalThumb = thumb;
}
public void setVerticalTrackDrawable(Drawable track) {
- if (track != null) {
- if (mMutated) {
- track.mutate();
- }
- track.setState(STATE_ENABLED);
+ if (mVerticalTrack != null) {
+ mVerticalTrack.setCallback(null);
}
+
+ propagateCurrentState(track);
mVerticalTrack = track;
}
public void setHorizontalThumbDrawable(Drawable thumb) {
- if (thumb != null) {
- if (mMutated) {
- thumb.mutate();
- }
- thumb.setState(STATE_ENABLED);
- mHorizontalThumb = thumb;
+ if (mHorizontalThumb != null) {
+ mHorizontalThumb.setCallback(null);
}
+
+ propagateCurrentState(thumb);
+ mHorizontalThumb = thumb;
}
public void setHorizontalTrackDrawable(Drawable track) {
- if (track != null) {
+ if (mHorizontalTrack != null) {
+ mHorizontalTrack.setCallback(null);
+ }
+
+ propagateCurrentState(track);
+ mHorizontalTrack = track;
+ }
+
+ private void propagateCurrentState(Drawable d) {
+ if (d != null) {
if (mMutated) {
- track.mutate();
+ d.mutate();
+ }
+
+ d.setState(getState());
+ d.setCallback(this);
+
+ if (mHasSetAlpha) {
+ d.setAlpha(mAlpha);
+ }
+
+ if (mHasSetColorFilter) {
+ d.setColorFilter(mColorFilter);
}
- track.setState(STATE_ENABLED);
}
- mHorizontalTrack = track;
}
public int getSize(boolean vertical) {
@@ -262,6 +316,9 @@ public class ScrollBarDrawable extends Drawable {
@Override
public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ mHasSetAlpha = true;
+
if (mVerticalTrack != null) {
mVerticalTrack.setAlpha(alpha);
}
@@ -278,32 +335,54 @@ public class ScrollBarDrawable extends Drawable {
@Override
public int getAlpha() {
- // All elements should have same alpha, just return one of them
- return mVerticalThumb.getAlpha();
+ return mAlpha;
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
+ mColorFilter = colorFilter;
+ mHasSetColorFilter = true;
+
if (mVerticalTrack != null) {
- mVerticalTrack.setColorFilter(cf);
+ mVerticalTrack.setColorFilter(colorFilter);
}
if (mVerticalThumb != null) {
- mVerticalThumb.setColorFilter(cf);
+ mVerticalThumb.setColorFilter(colorFilter);
}
if (mHorizontalTrack != null) {
- mHorizontalTrack.setColorFilter(cf);
+ mHorizontalTrack.setColorFilter(colorFilter);
}
if (mHorizontalThumb != null) {
- mHorizontalThumb.setColorFilter(cf);
+ mHorizontalThumb.setColorFilter(colorFilter);
}
}
@Override
+ public ColorFilter getColorFilter() {
+ return mColorFilter;
+ }
+
+ @Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
+ public void invalidateDrawable(Drawable who) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ unscheduleSelf(what);
+ }
+
+ @Override
public String toString() {
return "ScrollBarDrawable: range=" + mRange + " offset=" + mOffset +
" extent=" + mExtent + (mVertical ? " V" : " H");
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index a90b392..b95c27d 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -809,9 +809,10 @@ public class ScrollView extends FrameLayout {
awakenScrollBars();
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
if (!isEnabled()) {
@@ -839,9 +840,14 @@ public class ScrollView extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ScrollView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ScrollView.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (isEnabled()) {
final int scrollRange = getScrollRange();
if (scrollRange > 0) {
@@ -856,10 +862,10 @@ public class ScrollView extends FrameLayout {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ScrollView.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
final boolean scrollable = getScrollRange() > 0;
event.setScrollable(scrollable);
event.setScrollX(mScrollX);
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 4ee6418..bbf120a 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -49,8 +49,6 @@ import android.view.CollapsibleActionView;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView.OnItemClickListener;
@@ -1326,15 +1324,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(SearchView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SearchView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return SearchView.class.getName();
}
private void adjustDropDownSizeAndPosition() {
diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java
index dc7c04c..d010122 100644
--- a/core/java/android/widget/SeekBar.java
+++ b/core/java/android/widget/SeekBar.java
@@ -18,15 +18,13 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* A SeekBar is an extension of ProgressBar that adds a draggable thumb. The user can touch
* the thumb and drag left or right to set the current progress level or use the arrow keys.
- * Placing focusable widgets to the left or right of a SeekBar is discouraged.
+ * Placing focusable widgets to the left or right of a SeekBar is discouraged.
* <p>
* Clients of the SeekBar can attach a {@link SeekBar.OnSeekBarChangeListener} to
* be notified of the user's actions.
@@ -42,39 +40,39 @@ public class SeekBar extends AbsSeekBar {
* programmatically.
*/
public interface OnSeekBarChangeListener {
-
+
/**
* Notification that the progress level has changed. Clients can use the fromUser parameter
* to distinguish user-initiated changes from those that occurred programmatically.
- *
+ *
* @param seekBar The SeekBar whose progress has changed
* @param progress The current progress level. This will be in the range 0..max where max
* was set by {@link ProgressBar#setMax(int)}. (The default value for max is 100.)
* @param fromUser True if the progress change was initiated by the user.
*/
void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser);
-
+
/**
* Notification that the user has started a touch gesture. Clients may want to use this
- * to disable advancing the seekbar.
+ * to disable advancing the seekbar.
* @param seekBar The SeekBar in which the touch gesture began
*/
void onStartTrackingTouch(SeekBar seekBar);
-
+
/**
* Notification that the user has finished a touch gesture. Clients may want to use this
- * to re-enable advancing the seekbar.
+ * to re-enable advancing the seekbar.
* @param seekBar The SeekBar in which the touch gesture began
*/
void onStopTrackingTouch(SeekBar seekBar);
}
private OnSeekBarChangeListener mOnSeekBarChangeListener;
-
+
public SeekBar(Context context) {
this(context, null);
}
-
+
public SeekBar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.seekBarStyle);
}
@@ -88,26 +86,26 @@ public class SeekBar extends AbsSeekBar {
}
@Override
- void onProgressRefresh(float scale, boolean fromUser) {
- super.onProgressRefresh(scale, fromUser);
+ void onProgressRefresh(float scale, boolean fromUser, int progress) {
+ super.onProgressRefresh(scale, fromUser, progress);
if (mOnSeekBarChangeListener != null) {
- mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser);
+ mOnSeekBarChangeListener.onProgressChanged(this, progress, fromUser);
}
}
/**
* Sets a listener to receive notifications of changes to the SeekBar's progress level. Also
* provides notifications of when the user starts and stops a touch gesture within the SeekBar.
- *
+ *
* @param l The seek bar notification listener
- *
+ *
* @see SeekBar.OnSeekBarChangeListener
*/
public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) {
mOnSeekBarChangeListener = l;
}
-
+
@Override
void onStartTrackingTouch() {
super.onStartTrackingTouch();
@@ -115,7 +113,7 @@ public class SeekBar extends AbsSeekBar {
mOnSeekBarChangeListener.onStartTrackingTouch(this);
}
}
-
+
@Override
void onStopTrackingTouch() {
super.onStopTrackingTouch();
@@ -125,14 +123,7 @@ public class SeekBar extends AbsSeekBar {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(SeekBar.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SeekBar.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return SeekBar.class.getName();
}
}
diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java
index 98bcfff..2008ba8 100644
--- a/core/java/android/widget/SimpleAdapter.java
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -16,7 +16,11 @@
package android.widget;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
import android.content.Context;
+import android.content.res.Resources;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
@@ -40,14 +44,14 @@ import java.util.Map;
* If the returned value is false, the following views are then tried in order:
* <ul>
* <li> A view that implements Checkable (e.g. CheckBox). The expected bind value is a boolean.
- * <li> TextView. The expected bind value is a string and {@link #setViewText(TextView, String)}
+ * <li> TextView. The expected bind value is a string and {@link #setViewText(TextView, String)}
* is invoked.
- * <li> ImageView. The expected bind value is a resource id or a string and
- * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked.
+ * <li> ImageView. The expected bind value is a resource id or a string and
+ * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked.
* </ul>
* If no appropriate binding can be found, an {@link IllegalStateException} is thrown.
*/
-public class SimpleAdapter extends BaseAdapter implements Filterable {
+public class SimpleAdapter extends BaseAdapter implements Filterable, Spinner.ThemedSpinnerAdapter {
private int[] mTo;
private String[] mFrom;
private ViewBinder mViewBinder;
@@ -58,12 +62,15 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
private int mDropDownResource;
private LayoutInflater mInflater;
+ /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */
+ private LayoutInflater mDropDownInflater;
+
private SimpleFilter mFilter;
private ArrayList<Map<String, ?>> mUnfilteredData;
/**
* Constructor
- *
+ *
* @param context The context where the View associated with this SimpleAdapter is running
* @param data A List of Maps. Each entry in the List corresponds to one row in the list. The
* Maps contain the data for each row, and should include all the entries specified in
@@ -77,7 +84,7 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
* in the from parameter.
*/
public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
- int resource, String[] from, int[] to) {
+ @LayoutRes int resource, String[] from, @IdRes int[] to) {
mData = data;
mResource = mDropDownResource = resource;
mFrom = from;
@@ -85,7 +92,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
-
/**
* @see android.widget.Adapter#getCount()
*/
@@ -111,14 +117,14 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
* @see android.widget.Adapter#getView(int, View, ViewGroup)
*/
public View getView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mResource);
+ return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
- private View createViewFromResource(int position, View convertView,
+ private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
ViewGroup parent, int resource) {
View v;
if (convertView == null) {
- v = mInflater.inflate(resource, parent, false);
+ v = inflater.inflate(resource, parent, false);
} else {
v = convertView;
}
@@ -135,12 +141,41 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
* @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
*/
public void setDropDownViewResource(int resource) {
- this.mDropDownResource = resource;
+ mDropDownResource = resource;
+ }
+
+ /**
+ * Sets the {@link android.content.res.Resources.Theme} against which drop-down views are
+ * inflated.
+ * <p>
+ * By default, drop-down views are inflated against the theme of the
+ * {@link Context} passed to the adapter's constructor.
+ *
+ * @param theme the theme against which to inflate drop-down views or
+ * {@code null} to use the theme from the adapter's context
+ * @see #getDropDownView(int, View, ViewGroup)
+ */
+ @Override
+ public void setDropDownViewTheme(Resources.Theme theme) {
+ if (theme == null) {
+ mDropDownInflater = null;
+ } else if (theme == mInflater.getContext().getTheme()) {
+ mDropDownInflater = mInflater;
+ } else {
+ final Context context = new ContextThemeWrapper(mInflater.getContext(), theme);
+ mDropDownInflater = LayoutInflater.from(context);
+ }
+ }
+
+ @Override
+ public Resources.Theme getDropDownViewTheme() {
+ return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme();
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mDropDownResource);
+ return createViewFromResource(
+ mDropDownInflater, position, convertView, parent, mDropDownResource);
}
private void bindView(int position, View view) {
diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java
index 24ebb2c..c807d56 100644
--- a/core/java/android/widget/SimpleMonthAdapter.java
+++ b/core/java/android/widget/SimpleMonthAdapter.java
@@ -39,6 +39,7 @@ class SimpleMonthAdapter extends BaseAdapter {
private Calendar mSelectedDay = Calendar.getInstance();
private ColorStateList mCalendarTextColors = ColorStateList.valueOf(Color.BLACK);
+ private ColorStateList mCalendarDayBackgroundColor = ColorStateList.valueOf(Color.MAGENTA);
private OnDaySelectedListener mOnDaySelectedListener;
private int mFirstDayOfWeek;
@@ -88,6 +89,10 @@ class SimpleMonthAdapter extends BaseAdapter {
mCalendarTextColors = colors;
}
+ void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) {
+ mCalendarDayBackgroundColor = dayBackgroundColor;
+ }
+
/**
* Sets the text color, size, style, hint color, and highlight color from
* the specified TextAppearance resource. This is mostly copied from
@@ -144,9 +149,11 @@ class SimpleMonthAdapter extends BaseAdapter {
v.setClickable(true);
v.setOnDayClickListener(mOnDayClickListener);
- if (mCalendarTextColors != null) {
- v.setTextColor(mCalendarTextColors);
- }
+ v.setMonthTextColor(mCalendarTextColors);
+ v.setDayOfWeekTextColor(mCalendarTextColors);
+ v.setDayTextColor(mCalendarTextColors);
+
+ v.setDayBackgroundColor(mCalendarDayBackgroundColor);
}
final int minMonth = mMinDate.get(Calendar.MONTH);
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index d2a37ac..58ad515 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -27,12 +27,13 @@ import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Bundle;
+import android.text.TextPaint;
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.util.StateSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
@@ -44,7 +45,6 @@ import com.android.internal.widget.ExploreByTouchHelper;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Formatter;
-import java.util.List;
import java.util.Locale;
/**
@@ -52,8 +52,7 @@ import java.util.Locale;
* within the specified month.
*/
class SimpleMonthView extends View {
- private static final int DEFAULT_HEIGHT = 32;
- private static final int MIN_HEIGHT = 10;
+ private static final int MIN_ROW_HEIGHT = 10;
private static final int DEFAULT_SELECTED_DAY = -1;
private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
@@ -61,18 +60,21 @@ class SimpleMonthView extends View {
private static final int DEFAULT_NUM_ROWS = 6;
private static final int MAX_NUM_ROWS = 6;
- private static final int SELECTED_CIRCLE_ALPHA = 60;
-
- 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;
+ private final int mMonthTextSize;
+ private final int mDayOfWeekTextSize;
+ private final int mDayTextSize;
+
+ /** Height of the header containing the month and day of week labels. */
+ private final int mMonthHeaderHeight;
+
+ private final TextPaint mMonthPaint = new TextPaint();
+ private final TextPaint mDayOfWeekPaint = new TextPaint();
+ private final TextPaint mDayPaint = new TextPaint();
+
+ private final Paint mDayBackgroundPaint = new Paint();
/** Single-letter (when available) formatter for the day of week label. */
private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault());
@@ -81,14 +83,7 @@ class SimpleMonthView extends View {
private int mPadding = 0;
private String mDayOfWeekTypeface;
- private String mMonthTitleTypeface;
-
- private Paint mDayNumberPaint;
- private Paint mDayNumberDisabledPaint;
- private Paint mDayNumberSelectedPaint;
-
- private Paint mMonthTitlePaint;
- private Paint mMonthDayLabelPaint;
+ private String mMonthTypeface;
private int mMonth;
private int mYear;
@@ -97,13 +92,13 @@ class SimpleMonthView extends View {
private int mWidth;
// The height this view should draw at in pixels, set by height param
- private int mRowHeight = DEFAULT_HEIGHT;
+ private final int mRowHeight;
// If this view contains the today
private boolean mHasToday = false;
// Which day is selected [0-6] or -1 if no day is selected
- private int mSelectedDay = -1;
+ private int mActivatedDay = -1;
// Which day is today [0-6] or -1 if no day is today
private int mToday = DEFAULT_SELECTED_DAY;
@@ -142,6 +137,8 @@ class SimpleMonthView extends View {
private int mDisabledTextColor;
private int mSelectedDayColor;
+ private ColorStateList mDayTextColor;
+
public SimpleMonthView(Context context) {
this(context, null);
}
@@ -159,22 +156,21 @@ class SimpleMonthView extends View {
final Resources res = context.getResources();
mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
- mMonthTitleTypeface = res.getString(R.string.sans_serif);
+ mMonthTypeface = res.getString(R.string.sans_serif);
mStringBuilder = new StringBuilder(50);
mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
- mMiniDayNumberTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size);
- mMonthLabelTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size);
- mMonthDayLabelTextSize = res.getDimensionPixelSize(
+ mDayTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size);
+ mMonthTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size);
+ mDayOfWeekTextSize = res.getDimensionPixelSize(
R.dimen.datepicker_month_day_label_text_size);
- mMonthHeaderSize = res.getDimensionPixelOffset(
+ mMonthHeaderHeight = res.getDimensionPixelOffset(
R.dimen.datepicker_month_list_item_header_height);
- mDaySelectedCircleSize = res.getDimensionPixelSize(
- R.dimen.datepicker_day_number_select_circle_radius);
- mRowHeight = (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height)
- - mMonthHeaderSize) / MAX_NUM_ROWS;
+ mRowHeight = Math.max(MIN_ROW_HEIGHT,
+ (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height)
+ - mMonthHeaderHeight) / MAX_NUM_ROWS);
// Set up accessibility components.
mTouchHelper = new MonthViewTouchHelper(this);
@@ -182,8 +178,32 @@ class SimpleMonthView extends View {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
mLockAccessibilityDelegate = true;
- // Sets up any standard paints that will be used
- initView();
+ initPaints();
+ }
+
+ /**
+ * Sets up the text and style properties for painting.
+ */
+ private void initPaints() {
+ mMonthPaint.setAntiAlias(true);
+ mMonthPaint.setTextSize(mMonthTextSize);
+ mMonthPaint.setTypeface(Typeface.create(mMonthTypeface, Typeface.BOLD));
+ mMonthPaint.setTextAlign(Align.CENTER);
+ mMonthPaint.setStyle(Style.FILL);
+
+ mDayOfWeekPaint.setAntiAlias(true);
+ mDayOfWeekPaint.setTextSize(mDayOfWeekTextSize);
+ mDayOfWeekPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.BOLD));
+ mDayOfWeekPaint.setTextAlign(Align.CENTER);
+ mDayOfWeekPaint.setStyle(Style.FILL);
+
+ mDayBackgroundPaint.setAntiAlias(true);
+ mDayBackgroundPaint.setStyle(Style.FILL);
+
+ mDayPaint.setAntiAlias(true);
+ mDayPaint.setTextSize(mDayTextSize);
+ mDayPaint.setTextAlign(Align.CENTER);
+ mDayPaint.setStyle(Style.FILL);
}
@Override
@@ -193,22 +213,28 @@ class SimpleMonthView extends View {
mDayFormatter = new SimpleDateFormat("EEEEE", newConfig.locale);
}
- void setTextColor(ColorStateList colors) {
- final Resources res = getContext().getResources();
+ void setMonthTextColor(ColorStateList monthTextColor) {
+ final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0);
+ mMonthPaint.setColor(enabledColor);
+ invalidate();
+ }
- mNormalTextColor = colors.getColorForState(ENABLED_STATE_SET,
- res.getColor(R.color.datepicker_default_normal_text_color_holo_light));
- mMonthTitlePaint.setColor(mNormalTextColor);
- mMonthDayLabelPaint.setColor(mNormalTextColor);
+ void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) {
+ final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0);
+ mDayOfWeekPaint.setColor(enabledColor);
+ invalidate();
+ }
- mDisabledTextColor = colors.getColorForState(EMPTY_STATE_SET,
- res.getColor(R.color.datepicker_default_disabled_text_color_holo_light));
- mDayNumberDisabledPaint.setColor(mDisabledTextColor);
+ void setDayTextColor(ColorStateList dayTextColor) {
+ mDayTextColor = dayTextColor;
+ invalidate();
+ }
- mSelectedDayColor = colors.getColorForState(ENABLED_SELECTED_STATE_SET,
- res.getColor(R.color.holo_blue_light));
- mDayNumberSelectedPaint.setColor(mSelectedDayColor);
- mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+ void setDayBackgroundColor(ColorStateList dayBackgroundColor) {
+ final int activatedColor = dayBackgroundColor.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
+ mDayBackgroundPaint.setColor(activatedColor);
+ invalidate();
}
@Override
@@ -246,52 +272,6 @@ class SimpleMonthView extends View {
return true;
}
- /**
- * Sets up the text and style properties for painting.
- */
- private void initView() {
- mMonthTitlePaint = new Paint();
- mMonthTitlePaint.setAntiAlias(true);
- mMonthTitlePaint.setColor(mNormalTextColor);
- mMonthTitlePaint.setTextSize(mMonthLabelTextSize);
- mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
- mMonthTitlePaint.setTextAlign(Align.CENTER);
- mMonthTitlePaint.setStyle(Style.FILL);
- mMonthTitlePaint.setFakeBoldText(true);
-
- mMonthDayLabelPaint = new Paint();
- mMonthDayLabelPaint.setAntiAlias(true);
- mMonthDayLabelPaint.setColor(mNormalTextColor);
- mMonthDayLabelPaint.setTextSize(mMonthDayLabelTextSize);
- mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
- mMonthDayLabelPaint.setTextAlign(Align.CENTER);
- mMonthDayLabelPaint.setStyle(Style.FILL);
- mMonthDayLabelPaint.setFakeBoldText(true);
-
- mDayNumberSelectedPaint = new Paint();
- mDayNumberSelectedPaint.setAntiAlias(true);
- mDayNumberSelectedPaint.setColor(mSelectedDayColor);
- mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA);
- mDayNumberSelectedPaint.setTextAlign(Align.CENTER);
- mDayNumberSelectedPaint.setStyle(Style.FILL);
- mDayNumberSelectedPaint.setFakeBoldText(true);
-
- mDayNumberPaint = new Paint();
- mDayNumberPaint.setAntiAlias(true);
- mDayNumberPaint.setTextSize(mMiniDayNumberTextSize);
- mDayNumberPaint.setTextAlign(Align.CENTER);
- mDayNumberPaint.setStyle(Style.FILL);
- mDayNumberPaint.setFakeBoldText(false);
-
- mDayNumberDisabledPaint = new Paint();
- mDayNumberDisabledPaint.setAntiAlias(true);
- mDayNumberDisabledPaint.setColor(mDisabledTextColor);
- mDayNumberDisabledPaint.setTextSize(mMiniDayNumberTextSize);
- mDayNumberDisabledPaint.setTextAlign(Align.CENTER);
- mDayNumberDisabledPaint.setStyle(Style.FILL);
- mDayNumberDisabledPaint.setFakeBoldText(false);
- }
-
@Override
protected void onDraw(Canvas canvas) {
drawMonthTitle(canvas);
@@ -323,11 +303,7 @@ class SimpleMonthView extends View {
*/
void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
int enabledDayEnd) {
- if (mRowHeight < MIN_HEIGHT) {
- mRowHeight = MIN_HEIGHT;
- }
-
- mSelectedDay = selectedDay;
+ mActivatedDay = selectedDay;
if (isValidMonth(month)) {
mMonth = month;
@@ -415,7 +391,7 @@ class SimpleMonthView extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
- + mMonthHeaderSize);
+ + mMonthHeaderHeight);
}
@Override
@@ -437,21 +413,28 @@ class SimpleMonthView extends View {
private void drawMonthTitle(Canvas canvas) {
final float x = (mWidth + 2 * mPadding) / 2f;
- final float y = (mMonthHeaderSize - mMonthDayLabelTextSize) / 2f;
- canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
+
+ // Centered on the upper half of the month header.
+ final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent();
+ final float y = mMonthHeaderHeight * 0.25f - lineHeight / 2f;
+
+ canvas.drawText(getMonthAndYearString(), x, y, mMonthPaint);
}
private void drawWeekDayLabels(Canvas canvas) {
- final int y = mMonthHeaderSize - (mMonthDayLabelTextSize / 2);
- final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+ final float dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+ // Centered on the lower half of the month header.
+ final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent();
+ final float y = mMonthHeaderHeight * 0.75f - lineHeight / 2f;
for (int i = 0; i < mNumDays; i++) {
final int calendarDay = (i + mWeekStart) % mNumDays;
mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
final String dayLabel = mDayFormatter.format(mDayLabelCalendar.getTime());
- final int x = (2 * i + 1) * dayWidthHalf + mPadding;
- canvas.drawText(dayLabel, x, y, mMonthDayLabelPaint);
+ final float x = (2 * i + 1) * dayWidthHalf + mPadding;
+ canvas.drawText(dayLabel, x, y, mDayOfWeekPaint);
}
}
@@ -459,26 +442,40 @@ class SimpleMonthView extends View {
* Draws the month days.
*/
private void drawDays(Canvas canvas) {
- int y = (((mRowHeight + mMiniDayNumberTextSize) / 2) - DAY_SEPARATOR_WIDTH)
- + mMonthHeaderSize;
- int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
- int j = findDayOffset();
- for (int day = 1; day <= mNumCells; day++) {
- int x = (2 * j + 1) * dayWidthHalf + mPadding;
- if (mSelectedDay == day) {
- canvas.drawCircle(x, y - (mMiniDayNumberTextSize / 3), mDaySelectedCircleSize,
- mDayNumberSelectedPaint);
+ final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+ // Centered within the row.
+ final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent();
+ float y = mMonthHeaderHeight + (mRowHeight - lineHeight) / 2f;
+
+ for (int day = 1, j = findDayOffset(); day <= mNumCells; day++) {
+ final int x = (2 * j + 1) * dayWidthHalf + mPadding;
+ int stateMask = 0;
+
+ if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
+ stateMask |= StateSet.VIEW_STATE_ENABLED;
}
- if (mHasToday && mToday == day) {
- mDayNumberPaint.setColor(mSelectedDayColor);
- } else {
- mDayNumberPaint.setColor(mNormalTextColor);
+ if (mActivatedDay == day) {
+ stateMask |= StateSet.VIEW_STATE_ACTIVATED;
+
+ // Adjust the circle to be centered the row.
+ final float rowCenterY = y + lineHeight / 2;
+ canvas.drawCircle(x, rowCenterY, mRowHeight / 2,
+ mDayBackgroundPaint);
}
- final Paint paint = (day < mEnabledDayStart || day > mEnabledDayEnd) ?
- mDayNumberDisabledPaint : mDayNumberPaint;
- canvas.drawText(String.format("%d", day), x, y, paint);
+
+ final int[] stateSet = StateSet.get(stateMask);
+ final int dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
+ mDayPaint.setColor(dayTextColor);
+
+ final boolean isDayToday = mHasToday && mToday == day;
+ mDayPaint.setFakeBoldText(isDayToday);
+
+ canvas.drawText(String.format("%d", day), x, y, mDayPaint);
+
j++;
+
if (j == mNumDays) {
j = 0;
y += mRowHeight;
@@ -504,7 +501,7 @@ class SimpleMonthView extends View {
return -1;
}
// Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
- int row = (int) (y - mMonthHeaderSize) / mRowHeight;
+ int row = (int) (y - mMonthHeaderHeight) / mRowHeight;
int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
int day = column - findDayOffset() + 1;
@@ -628,7 +625,7 @@ class SimpleMonthView extends View {
node.setBoundsInParent(mTempRect);
node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
- if (virtualViewId == mSelectedDay) {
+ if (virtualViewId == mActivatedDay) {
node.setSelected(true);
}
@@ -654,7 +651,7 @@ class SimpleMonthView extends View {
*/
private void getItemBounds(int day, Rect rect) {
final int offsetX = mPadding;
- final int offsetY = mMonthHeaderSize;
+ final int offsetY = mMonthHeaderHeight;
final int cellHeight = mRowHeight;
final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
final int index = ((day - 1) + findDayOffset());
@@ -679,7 +676,7 @@ class SimpleMonthView extends View {
final CharSequence date = DateFormat.format(DATE_FORMAT,
mTempCalendar.getTimeInMillis());
- if (day == mSelectedDay) {
+ if (day == mActivatedDay) {
return getContext().getString(R.string.item_is_selected, date);
}
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index ec06c02..9c44236 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -32,7 +32,6 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* SlidingDrawer hides content out of the screen and allows the user to drag a handle
@@ -838,15 +837,8 @@ public class SlidingDrawer extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(SlidingDrawer.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SlidingDrawer.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return SlidingDrawer.class.getName();
}
private void closeDrawer() {
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 3592687..e7031fe 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -727,14 +727,10 @@ public class SpellChecker implements SpellCheckerSessionListener {
}
}
- if (scheduleOtherSpellCheck && wordStart <= end) {
+ if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
// Update range span: start new spell check from last wordStart
setRangeSpan(editable, wordStart, end);
} else {
- if (DBG && scheduleOtherSpellCheck) {
- Log.w(TAG, "Trying to schedule spellcheck for invalid region, from "
- + wordStart + " to " + end);
- }
removeRangeSpan(editable);
}
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 0d76239..3746ec6 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -16,11 +16,16 @@
package android.widget;
+import com.android.internal.R;
+
+import android.annotation.DrawableRes;
+import android.annotation.Nullable;
import android.annotation.Widget;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
@@ -30,18 +35,17 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ListPopupWindow.ForwardingListener;
import android.widget.PopupWindow.OnDismissListener;
-
/**
* A view that displays one child at a time and lets the user pick among them.
* The items in the Spinner come from the {@link Adapter} associated with
@@ -74,17 +78,22 @@ public class Spinner extends AbsSpinner implements OnClickListener {
* Use a dropdown anchored to the Spinner for selecting spinner options.
*/
public static final int MODE_DROPDOWN = 1;
-
+
/**
* Use the theme-supplied value to select the dropdown mode.
*/
private static final int MODE_THEME = -1;
+ /** Context used to inflate the popup window or dialog. */
+ private Context mPopupContext;
+
/** Forwarding listener used to implement drag-to-open. */
private ForwardingListener mForwardingListener;
+ /** Temporary holder for setAdapter() calls from the super constructor. */
+ private SpinnerAdapter mTempAdapter;
+
private SpinnerPopup mPopup;
- private DropDownAdapter mTempAdapter;
int mDropDownWidth;
private int mGravity;
@@ -184,69 +193,120 @@ public class Spinner extends AbsSpinner implements OnClickListener {
* @see #MODE_DIALOG
* @see #MODE_DROPDOWN
*/
- public Spinner(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode) {
+ public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
+ int mode) {
+ this(context, attrs, defStyleAttr, defStyleRes, mode, null);
+ }
+
+ /**
+ * Constructs a new spinner with the given context's theme, the supplied
+ * attribute set, default styles, popup mode (one of {@link #MODE_DIALOG}
+ * or {@link #MODE_DROPDOWN}), and the context against which the popup
+ * should be inflated.
+ *
+ * @param context The context against which the view is inflated, which
+ * provides access to the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default
+ * values for the view. Can be 0 to not look for
+ * defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme.
+ * Can be 0 to not look for defaults.
+ * @param mode Constant describing how the user will select choices from
+ * the spinner.
+ * @param popupContext The context against which the dialog or dropdown
+ * popup will be inflated. Can be null to use the view
+ * context. If set, this will override any value
+ * specified by
+ * {@link android.R.styleable#Spinner_popupTheme}.
+ *
+ * @see #MODE_DIALOG
+ * @see #MODE_DROPDOWN
+ */
+ public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode,
+ Context popupContext) {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.Spinner, defStyleAttr, defStyleRes);
+ attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
- if (mode == MODE_THEME) {
- mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG);
- }
-
- switch (mode) {
- case MODE_DIALOG: {
- mPopup = new DialogPopup();
- break;
+ if (popupContext != null) {
+ mPopupContext = popupContext;
+ } else {
+ final int popupThemeResId = a.getResourceId(R.styleable.Spinner_popupTheme, 0);
+ if (popupThemeResId != 0) {
+ mPopupContext = new ContextThemeWrapper(context, popupThemeResId);
+ } else {
+ mPopupContext = context;
+ }
}
- case MODE_DROPDOWN: {
- final DropdownPopup popup = new DropdownPopup(context, attrs, defStyleAttr, defStyleRes);
+ if (mode == MODE_THEME) {
+ mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
+ }
- mDropDownWidth = a.getLayoutDimension(
- com.android.internal.R.styleable.Spinner_dropDownWidth,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- popup.setBackgroundDrawable(a.getDrawable(
- com.android.internal.R.styleable.Spinner_popupBackground));
+ switch (mode) {
+ case MODE_DIALOG: {
+ mPopup = new DialogPopup();
+ mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
+ break;
+ }
- mPopup = popup;
- mForwardingListener = new ForwardingListener(this) {
- @Override
- public ListPopupWindow getPopup() {
- return popup;
- }
+ case MODE_DROPDOWN: {
+ final DropdownPopup popup = new DropdownPopup(
+ mPopupContext, attrs, defStyleAttr, defStyleRes);
+ final TypedArray pa = mPopupContext.obtainStyledAttributes(
+ attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
+ mDropDownWidth = pa.getLayoutDimension(R.styleable.Spinner_dropDownWidth,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ popup.setBackgroundDrawable(pa.getDrawable(R.styleable.Spinner_popupBackground));
+ popup.setPromptText(a.getString(R.styleable.Spinner_prompt));
+ pa.recycle();
+
+ mPopup = popup;
+ mForwardingListener = new ForwardingListener(this) {
+ @Override
+ public ListPopupWindow getPopup() {
+ return popup;
+ }
- @Override
- public boolean onForwardingStarted() {
- if (!mPopup.isShowing()) {
- mPopup.show(getTextDirection(), getTextAlignment());
+ @Override
+ public boolean onForwardingStarted() {
+ if (!mPopup.isShowing()) {
+ mPopup.show(getTextDirection(), getTextAlignment());
+ }
+ return true;
}
- return true;
- }
- };
- break;
- }
+ };
+ break;
+ }
}
- mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER);
-
- mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
-
+ mGravity = a.getInt(R.styleable.Spinner_gravity, Gravity.CENTER);
mDisableChildrenWhenDisabled = a.getBoolean(
- com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false);
+ R.styleable.Spinner_disableChildrenWhenDisabled, false);
a.recycle();
// Base constructor can call setAdapter before we initialize mPopup.
// Finish setting things up if this happened.
if (mTempAdapter != null) {
- mPopup.setAdapter(mTempAdapter);
+ setAdapter(mTempAdapter);
mTempAdapter = null;
}
}
/**
+ * @return the context used to inflate the Spinner's popup or dialog window
+ */
+ public Context getPopupContext() {
+ return mPopupContext;
+ }
+
+ /**
* Set the background drawable for the spinner's popup window of choices.
* Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
*
@@ -259,7 +319,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
return;
}
- ((DropdownPopup) mPopup).setBackgroundDrawable(background);
+ mPopup.setBackgroundDrawable(background);
}
/**
@@ -270,8 +330,8 @@ public class Spinner extends AbsSpinner implements OnClickListener {
*
* @attr ref android.R.styleable#Spinner_popupBackground
*/
- public void setPopupBackgroundResource(int resId) {
- setPopupBackgroundDrawable(getContext().getDrawable(resId));
+ public void setPopupBackgroundResource(@DrawableRes int resId) {
+ setPopupBackgroundDrawable(getPopupContext().getDrawable(resId));
}
/**
@@ -410,9 +470,17 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
/**
- * Sets the Adapter used to provide the data which backs this Spinner.
+ * Sets the {@link SpinnerAdapter} used to provide the data which backs
+ * this Spinner.
* <p>
- * Note that Spinner overrides {@link Adapter#getViewTypeCount()} on the
+ * If this Spinner has a popup theme set in XML via the
+ * {@link android.R.styleable#Spinner_popupTheme popupTheme} attribute, the
+ * adapter should inflate drop-down views using the same theme. The easiest
+ * way to achieve this is by using {@link #getPopupContext()} to obtain a
+ * layout inflater for use in
+ * {@link SpinnerAdapter#getDropDownView(int, View, ViewGroup)}.
+ * <p>
+ * Spinner overrides {@link Adapter#getViewTypeCount()} on the
* Adapter associated with this view. Calling
* {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
* returned from {@link #getAdapter()} will always return 0. Calling
@@ -429,6 +497,13 @@ public class Spinner extends AbsSpinner implements OnClickListener {
*/
@Override
public void setAdapter(SpinnerAdapter adapter) {
+ // The super constructor may call setAdapter before we're prepared.
+ // Postpone doing anything until we've finished construction.
+ if (mPopup == null) {
+ mTempAdapter = adapter;
+ return;
+ }
+
super.setAdapter(adapter);
mRecycler.clear();
@@ -439,11 +514,8 @@ public class Spinner extends AbsSpinner implements OnClickListener {
throw new IllegalArgumentException("Spinner adapter view type count must be 1");
}
- if (mPopup != null) {
- mPopup.setAdapter(new DropDownAdapter(adapter));
- } else {
- mTempAdapter = new DropDownAdapter(adapter);
- }
+ final Context popupContext = mPopupContext == null ? mContext : mPopupContext;
+ mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
}
@Override
@@ -693,15 +765,14 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Spinner.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Spinner.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Spinner.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (mAdapter != null) {
info.setCanOpenPopup(true);
@@ -847,14 +918,26 @@ public class Spinner extends AbsSpinner implements OnClickListener {
private ListAdapter mListAdapter;
/**
- * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
+ * Creates a new ListAdapter wrapper for the specified adapter.
*
- * @param adapter the Adapter to transform into a ListAdapter
+ * @param adapter the SpinnerAdapter to transform into a ListAdapter
+ * @param dropDownTheme the theme against which to inflate drop-down
+ * views, may be {@null} to use default theme
*/
- public DropDownAdapter(SpinnerAdapter adapter) {
- this.mAdapter = adapter;
+ public DropDownAdapter(@Nullable SpinnerAdapter adapter,
+ @Nullable Resources.Theme dropDownTheme) {
+ mAdapter = adapter;
+
if (adapter instanceof ListAdapter) {
- this.mListAdapter = (ListAdapter) adapter;
+ mListAdapter = (ListAdapter) adapter;
+ }
+
+ if (dropDownTheme != null && adapter instanceof Spinner.ThemedSpinnerAdapter) {
+ final Spinner.ThemedSpinnerAdapter themedAdapter =
+ (Spinner.ThemedSpinnerAdapter) adapter;
+ if (themedAdapter.getDropDownViewTheme() == null) {
+ themedAdapter.setDropDownViewTheme(dropDownTheme);
+ }
}
}
@@ -970,7 +1053,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public int getVerticalOffset();
public int getHorizontalOffset();
}
-
+
private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
private AlertDialog mPopup;
private ListAdapter mListAdapter;
@@ -994,7 +1077,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public void setPromptText(CharSequence hintText) {
mPrompt = hintText;
}
-
+
public CharSequence getHintText() {
return mPrompt;
}
@@ -1003,7 +1086,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
if (mListAdapter == null) {
return;
}
- AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ AlertDialog.Builder builder = new AlertDialog.Builder(getPopupContext());
if (mPrompt != null) {
builder.setTitle(mPrompt);
}
@@ -1179,4 +1262,21 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
}
}
+
+ public interface ThemedSpinnerAdapter {
+ /**
+ * Sets the {@link Resources.Theme} against which drop-down views are
+ * inflated.
+ *
+ * @param theme the context against which to inflate drop-down views
+ * @see SpinnerAdapter#getDropDownView(int, View, ViewGroup)
+ */
+ public void setDropDownViewTheme(Resources.Theme theme);
+
+ /**
+ * @return The {@link Resources.Theme} against which drop-down views are
+ * inflated.
+ */
+ public Resources.Theme getDropDownViewTheme();
+ }
}
diff --git a/core/java/android/widget/SpinnerAdapter.java b/core/java/android/widget/SpinnerAdapter.java
index 91504cf..f99f45b 100644
--- a/core/java/android/widget/SpinnerAdapter.java
+++ b/core/java/android/widget/SpinnerAdapter.java
@@ -22,16 +22,17 @@ import android.view.ViewGroup;
/**
* Extended {@link Adapter} that is the bridge between a
* {@link android.widget.Spinner} and its data. A spinner adapter allows to
- * define two different views: one that shows the data in the spinner itself and
- * one that shows the data in the drop down list when the spinner is pressed.</p>
+ * define two different views: one that shows the data in the spinner itself
+ * and one that shows the data in the drop down list when the spinner is
+ * pressed.
*/
public interface SpinnerAdapter extends Adapter {
/**
- * <p>Get a {@link android.view.View} that displays in the drop down popup
- * the data at the specified position in the data set.</p>
+ * Gets a {@link android.view.View} that displays in the drop down popup
+ * the data at the specified position in the data set.
*
- * @param position index of the item whose view we want.
- * @param convertView the old view to reuse, if possible. Note: You should
+ * @param position index of the item whose view we want.
+ * @param convertView the old view to reuse, if possible. Note: You should
* check that this view is non-null and of an appropriate type before
* using. If it is not possible to convert this view to display the
* correct data, this method can create a new view.
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 9e168b8..2bd3143 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -41,7 +41,6 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.LinearInterpolator;
import android.widget.RemoteViews.RemoteView;
@@ -1225,15 +1224,14 @@ public class StackView extends AdapterViewAnimator {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(StackView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return StackView.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(StackView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setScrollable(getChildCount() > 1);
if (isEnabled()) {
if (getDisplayedChild() < getChildCount() - 1) {
@@ -1245,9 +1243,10 @@ public class StackView extends AdapterViewAnimator {
}
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
if (!isEnabled()) {
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index 6349347..aad0625 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -145,8 +145,9 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
* copied to the query text field.
* <p>
*
- * @param refine which queries to refine. Possible values are {@link #REFINE_NONE},
- * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}.
+ * @param refineWhat which queries to refine. Possible values are
+ * {@link #REFINE_NONE}, {@link #REFINE_BY_ENTRY}, and
+ * {@link #REFINE_ALL}.
*/
public void setQueryRefinement(int refineWhat) {
mQueryRefinement = refineWhat;
@@ -327,7 +328,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
// First check TEXT_2_URL
CharSequence text2 = getStringOrNull(cursor, mText2UrlCol);
if (text2 != null) {
- text2 = formatUrl(text2);
+ text2 = formatUrl(context, text2);
} else {
text2 = getStringOrNull(cursor, mText2Col);
}
@@ -372,12 +373,12 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
}
}
- private CharSequence formatUrl(CharSequence url) {
+ private CharSequence formatUrl(Context context, CharSequence url) {
if (mUrlColor == null) {
// Lazily get the URL color from the current theme.
TypedValue colorValue = new TypedValue();
- mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
- mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
+ context.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
+ mUrlColor = context.getColorStateList(colorValue.resourceId);
}
SpannableString text = new SpannableString(url);
@@ -502,6 +503,30 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
}
/**
+ * This method is overridden purely to provide a bit of protection against
+ * flaky content providers.
+ *
+ * @see android.widget.CursorAdapter#getDropDownView(int, View, ViewGroup)
+ */
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ try {
+ return super.getDropDownView(position, convertView, parent);
+ } catch (RuntimeException e) {
+ Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
+ // Put exception string in item title
+ final Context context = mDropDownContext == null ? mContext : mDropDownContext;
+ final View v = newDropDownView(context, mCursor, parent);
+ if (v != null) {
+ final ChildViewCache views = (ChildViewCache) v.getTag();
+ final TextView tv = views.mText1;
+ tv.setText(e.toString());
+ }
+ return v;
+ }
+ }
+
+ /**
* Gets a drawable given a value provided by a suggestion provider.
*
* This value could be just the string value of a resource id
@@ -570,7 +595,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
OpenResourceIdResult r =
mProviderContext.getContentResolver().getResourceId(uri);
try {
- return r.r.getDrawable(r.id, mContext.getTheme());
+ return r.r.getDrawable(r.id, mProviderContext.getTheme());
} catch (Resources.NotFoundException ex) {
throw new FileNotFoundException("Resource does not exist: " + uri);
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 7a22224..bb290e7 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -17,6 +17,9 @@
package android.widget;
import android.animation.ObjectAnimator;
+import android.annotation.DrawableRes;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -24,10 +27,12 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -41,6 +46,7 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
+import android.view.ViewAssistStructure;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -84,7 +90,17 @@ public class Switch extends CompoundButton {
private static final int MONOSPACE = 3;
private Drawable mThumbDrawable;
+ private ColorStateList mThumbTintList = null;
+ private PorterDuff.Mode mThumbTintMode = null;
+ private boolean mHasThumbTint = false;
+ private boolean mHasThumbTintMode = false;
+
private Drawable mTrackDrawable;
+ private ColorStateList mTrackTintList = null;
+ private PorterDuff.Mode mTrackTintMode = null;
+ private boolean mHasTrackTint = false;
+ private boolean mHasTrackTintMode = false;
+
private int mThumbTextPadding;
private int mSwitchMinWidth;
private int mSwitchPadding;
@@ -249,7 +265,7 @@ public class Switch extends CompoundButton {
*
* @attr ref android.R.styleable#Switch_switchTextAppearance
*/
- public void setSwitchTextAppearance(Context context, int resid) {
+ public void setSwitchTextAppearance(Context context, @StyleRes int resid) {
TypedArray appearance =
context.obtainStyledAttributes(resid,
com.android.internal.R.styleable.TextAppearance);
@@ -457,7 +473,7 @@ public class Switch extends CompoundButton {
*
* @attr ref android.R.styleable#Switch_track
*/
- public void setTrackResource(int resId) {
+ public void setTrackResource(@DrawableRes int resId) {
setTrackDrawable(getContext().getDrawable(resId));
}
@@ -473,6 +489,86 @@ public class Switch extends CompoundButton {
}
/**
+ * Applies a tint to the track drawable. Does not modify the current
+ * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setTrackDrawable(Drawable)} will
+ * automatically mutate the drawable and apply the specified tint and tint
+ * mode using {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#Switch_trackTint
+ * @see #getTrackTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setTrackTintList(@Nullable ColorStateList tint) {
+ mTrackTintList = tint;
+ mHasTrackTint = true;
+
+ applyTrackTint();
+ }
+
+ /**
+ * @return the tint applied to the track drawable
+ * @attr ref android.R.styleable#Switch_trackTint
+ * @see #setTrackTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getTrackTintList() {
+ return mTrackTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setTrackTintList(ColorStateList)}} to the track drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#Switch_trackTintMode
+ * @see #getTrackTintMode()
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) {
+ mTrackTintMode = tintMode;
+ mHasTrackTintMode = true;
+
+ applyTrackTint();
+ }
+
+ /**
+ * @return the blending mode used to apply the tint to the track
+ * drawable
+ * @attr ref android.R.styleable#Switch_trackTintMode
+ * @see #setTrackTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getTrackTintMode() {
+ return mTrackTintMode;
+ }
+
+ private void applyTrackTint() {
+ if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) {
+ mTrackDrawable = mTrackDrawable.mutate();
+
+ if (mHasTrackTint) {
+ mTrackDrawable.setTintList(mTrackTintList);
+ }
+
+ if (mHasTrackTintMode) {
+ mTrackDrawable.setTintMode(mTrackTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mTrackDrawable.isStateful()) {
+ mTrackDrawable.setState(getDrawableState());
+ }
+ }
+ }
+
+ /**
* Set the drawable used for the switch "thumb" - the piece that the user
* can physically touch and drag along the track.
*
@@ -499,7 +595,7 @@ public class Switch extends CompoundButton {
*
* @attr ref android.R.styleable#Switch_thumb
*/
- public void setThumbResource(int resId) {
+ public void setThumbResource(@DrawableRes int resId) {
setThumbDrawable(getContext().getDrawable(resId));
}
@@ -516,6 +612,86 @@ public class Switch extends CompoundButton {
}
/**
+ * Applies a tint to the thumb drawable. Does not modify the current
+ * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setThumbDrawable(Drawable)} will
+ * automatically mutate the drawable and apply the specified tint and tint
+ * mode using {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#Switch_thumbTint
+ * @see #getThumbTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setThumbTintList(@Nullable ColorStateList tint) {
+ mThumbTintList = tint;
+ mHasThumbTint = true;
+
+ applyThumbTint();
+ }
+
+ /**
+ * @return the tint applied to the thumb drawable
+ * @attr ref android.R.styleable#Switch_thumbTint
+ * @see #setThumbTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getThumbTintList() {
+ return mThumbTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#Switch_thumbTintMode
+ * @see #getThumbTintMode()
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
+ mThumbTintMode = tintMode;
+ mHasThumbTintMode = true;
+
+ applyThumbTint();
+ }
+
+ /**
+ * @return the blending mode used to apply the tint to the thumb
+ * drawable
+ * @attr ref android.R.styleable#Switch_thumbTintMode
+ * @see #setThumbTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getThumbTintMode() {
+ return mThumbTintMode;
+ }
+
+ private void applyThumbTint() {
+ if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) {
+ mThumbDrawable = mThumbDrawable.mutate();
+
+ if (mHasThumbTint) {
+ mThumbDrawable.setTintList(mThumbTintList);
+ }
+
+ if (mHasThumbTintMode) {
+ mThumbDrawable.setTintMode(mThumbTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mThumbDrawable.isStateful()) {
+ mThumbDrawable.setState(getDrawableState());
+ }
+ }
+ }
+
+ /**
* Specifies whether the track should be split by the thumb. When true,
* the thumb's optical bounds will be clipped out of the track drawable,
* then the thumb will be drawn into the resulting gap.
@@ -665,9 +841,10 @@ public class Switch extends CompoundButton {
}
}
+ /** @hide */
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
final CharSequence text = isChecked() ? mTextOn : mTextOff;
if (text != null) {
@@ -1181,15 +1358,31 @@ public class Switch extends CompoundButton {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Switch.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Switch.class.getName();
+ }
+
+ @Override
+ public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) {
+ super.onProvideAssistStructure(structure, extras);
+ CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+ if (!TextUtils.isEmpty(switchText)) {
+ CharSequence oldText = structure.getText();
+ if (TextUtils.isEmpty(oldText)) {
+ structure.setText(switchText);
+ } else {
+ StringBuilder newText = new StringBuilder();
+ newText.append(oldText).append(' ').append(switchText);
+ structure.setText(newText);
+ }
+ structure.setTextPaint(mTextPaint);
+ }
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Switch.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
CharSequence switchText = isChecked() ? mTextOn : mTextOff;
if (!TextUtils.isEmpty(switchText)) {
CharSequence oldText = info.getText();
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 89df51a..c521f72 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -33,9 +33,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
import java.util.ArrayList;
import java.util.List;
@@ -173,8 +170,9 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
}
+ /** @hide */
@Override
- public void sendAccessibilityEvent(int eventType) {
+ public void sendAccessibilityEventInternal(int eventType) {
/* avoid super class behavior - TabWidget sends the right events */
}
@@ -384,15 +382,8 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TabHost.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TabHost.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TabHost.class.getName();
}
public void setCurrentTab(int index) {
@@ -612,7 +603,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
// Donut apps get old color scheme
tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
- tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));
+ tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
}
return tabIndicator;
@@ -657,7 +648,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
// Donut apps get old color scheme
tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4);
- tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4));
+ tv.setTextColor(context.getColorStateList(R.color.tab_indicator_text_v4));
}
return tabIndicator;
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 47a5449..9496e62 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -17,8 +17,8 @@
package android.widget;
import android.R;
+import android.annotation.DrawableRes;
import android.content.Context;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -29,7 +29,6 @@ import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
*
@@ -244,7 +243,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
* @param resId the resource identifier of the drawable to use as a
* divider.
*/
- public void setDividerDrawable(int resId) {
+ public void setDividerDrawable(@DrawableRes int resId) {
setDividerDrawable(mContext.getDrawable(resId));
}
@@ -265,7 +264,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
* @param resId the resource identifier of the drawable to use as the
* left strip drawable
*/
- public void setLeftStripDrawable(int resId) {
+ public void setLeftStripDrawable(@DrawableRes int resId) {
setLeftStripDrawable(mContext.getDrawable(resId));
}
@@ -286,7 +285,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
* @param resId the resource identifier of the drawable to use as the
* right strip drawable
*/
- public void setRightStripDrawable(int resId) {
+ public void setRightStripDrawable(@DrawableRes int resId) {
setRightStripDrawable(mContext.getDrawable(resId));
}
@@ -401,8 +400,9 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
}
}
+ /** @hide */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
// Dispatch only to the selected tab.
if (mSelectedTab != -1) {
@@ -415,28 +415,28 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TabWidget.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TabWidget.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setItemCount(getTabCount());
event.setCurrentItemIndex(mSelectedTab);
}
+ /** @hide */
@Override
- public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
+ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
// this class fires events only when tabs are focused or selected
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && isFocused()) {
event.recycle();
return;
}
- super.sendAccessibilityEventUnchecked(event);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TabWidget.class.getName());
+ super.sendAccessibilityEventUncheckedInternal(event);
}
/**
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index f4b2ce0..093bdcf 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -24,9 +24,6 @@ import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
import java.util.regex.Pattern;
/**
@@ -667,15 +664,8 @@ public class TableLayout extends LinearLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TableLayout.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TableLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TableLayout.class.getName();
}
/**
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index fe3631a..faf5b84 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -24,8 +24,6 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
@@ -380,15 +378,8 @@ public class TableRow extends LinearLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TableRow.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TableRow.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TableRow.class.getName();
}
/**
diff --git a/core/java/android/widget/TextSwitcher.java b/core/java/android/widget/TextSwitcher.java
index 1aefd2b..ecd9a8c 100644
--- a/core/java/android/widget/TextSwitcher.java
+++ b/core/java/android/widget/TextSwitcher.java
@@ -21,8 +21,6 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* Specialized {@link android.widget.ViewSwitcher} that contains
@@ -92,14 +90,7 @@ public class TextSwitcher extends ViewSwitcher {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TextSwitcher.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TextSwitcher.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TextSwitcher.class.getName();
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dd8280b..718ef93 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -17,14 +17,20 @@
package android.widget;
import android.R;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
+import android.annotation.XmlRes;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.UndoManager;
import android.content.res.ColorStateList;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -32,6 +38,7 @@ import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
@@ -41,6 +48,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.ParcelableParcel;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -103,10 +111,9 @@ import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewAssistStructure;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup.LayoutParams;
@@ -214,6 +221,8 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
* @attr ref android.R.styleable#TextView_drawableStart
* @attr ref android.R.styleable#TextView_drawableEnd
* @attr ref android.R.styleable#TextView_drawablePadding
+ * @attr ref android.R.styleable#TextView_drawableTint
+ * @attr ref android.R.styleable#TextView_drawableTintMode
* @attr ref android.R.styleable#TextView_lineSpacingExtra
* @attr ref android.R.styleable#TextView_lineSpacingMultiplier
* @attr ref android.R.styleable#TextView_marqueeRepeatLimit
@@ -298,7 +307,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private float mShadowRadius, mShadowDx, mShadowDy;
private int mShadowColor;
-
private boolean mPreDrawRegistered;
private boolean mPreDrawListenerDetached;
@@ -312,16 +320,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private TextUtils.TruncateAt mEllipsize;
static class Drawables {
- final static int DRAWABLE_NONE = -1;
- final static int DRAWABLE_RIGHT = 0;
- final static int DRAWABLE_LEFT = 1;
+ static final int LEFT = 0;
+ static final int TOP = 1;
+ static final int RIGHT = 2;
+ static final int BOTTOM = 3;
+
+ static final int DRAWABLE_NONE = -1;
+ static final int DRAWABLE_RIGHT = 0;
+ static final int DRAWABLE_LEFT = 1;
final Rect mCompoundRect = new Rect();
- Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
- mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
+ final Drawable[] mShowing = new Drawable[4];
+
+ ColorStateList mTintList;
+ PorterDuff.Mode mTintMode;
+ boolean mHasTint;
+ boolean mHasTintMode;
+ Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
Drawable mDrawableLeftInitial, mDrawableRightInitial;
+
boolean mIsRtlCompatibilityMode;
boolean mOverride;
@@ -344,19 +363,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void resolveWithLayoutDirection(int layoutDirection) {
// First reset "left" and "right" drawables to their initial values
- mDrawableLeft = mDrawableLeftInitial;
- mDrawableRight = mDrawableRightInitial;
+ mShowing[Drawables.LEFT] = mDrawableLeftInitial;
+ mShowing[Drawables.RIGHT] = mDrawableRightInitial;
if (mIsRtlCompatibilityMode) {
// Use "start" drawable as "left" drawable if the "left" drawable was not defined
- if (mDrawableStart != null && mDrawableLeft == null) {
- mDrawableLeft = mDrawableStart;
+ if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
+ mShowing[Drawables.LEFT] = mDrawableStart;
mDrawableSizeLeft = mDrawableSizeStart;
mDrawableHeightLeft = mDrawableHeightStart;
}
// Use "end" drawable as "right" drawable if the "right" drawable was not defined
- if (mDrawableEnd != null && mDrawableRight == null) {
- mDrawableRight = mDrawableEnd;
+ if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
+ mShowing[Drawables.RIGHT] = mDrawableEnd;
mDrawableSizeRight = mDrawableSizeEnd;
mDrawableHeightRight = mDrawableHeightEnd;
}
@@ -366,11 +385,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch(layoutDirection) {
case LAYOUT_DIRECTION_RTL:
if (mOverride) {
- mDrawableRight = mDrawableStart;
+ mShowing[Drawables.RIGHT] = mDrawableStart;
mDrawableSizeRight = mDrawableSizeStart;
mDrawableHeightRight = mDrawableHeightStart;
- mDrawableLeft = mDrawableEnd;
+ mShowing[Drawables.LEFT] = mDrawableEnd;
mDrawableSizeLeft = mDrawableSizeEnd;
mDrawableHeightLeft = mDrawableHeightEnd;
}
@@ -379,11 +398,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case LAYOUT_DIRECTION_LTR:
default:
if (mOverride) {
- mDrawableLeft = mDrawableStart;
+ mShowing[Drawables.LEFT] = mDrawableStart;
mDrawableSizeLeft = mDrawableSizeStart;
mDrawableHeightLeft = mDrawableHeightStart;
- mDrawableRight = mDrawableEnd;
+ mShowing[Drawables.RIGHT] = mDrawableEnd;
mDrawableSizeRight = mDrawableSizeEnd;
mDrawableHeightRight = mDrawableHeightEnd;
}
@@ -395,17 +414,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void updateDrawablesLayoutDirection(int layoutDirection) {
- if (mDrawableLeft != null) {
- mDrawableLeft.setLayoutDirection(layoutDirection);
- }
- if (mDrawableRight != null) {
- mDrawableRight.setLayoutDirection(layoutDirection);
- }
- if (mDrawableTop != null) {
- mDrawableTop.setLayoutDirection(layoutDirection);
- }
- if (mDrawableBottom != null) {
- mDrawableBottom.setLayoutDirection(layoutDirection);
+ for (Drawable dr : mShowing) {
+ if (dr != null) {
+ dr.setLayoutDirection(layoutDirection);
+ }
}
}
@@ -415,10 +427,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
mDrawableError = dr;
- final Rect compoundRect = mCompoundRect;
- int[] state = tv.getDrawableState();
-
if (mDrawableError != null) {
+ final Rect compoundRect = mCompoundRect;
+ final int[] state = tv.getDrawableState();
+
mDrawableError.setState(state);
mDrawableError.copyBounds(compoundRect);
mDrawableError.setCallback(tv);
@@ -433,12 +445,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// first restore the initial state if needed
switch (mDrawableSaved) {
case DRAWABLE_LEFT:
- mDrawableLeft = mDrawableTemp;
+ mShowing[Drawables.LEFT] = mDrawableTemp;
mDrawableSizeLeft = mDrawableSizeTemp;
mDrawableHeightLeft = mDrawableHeightTemp;
break;
case DRAWABLE_RIGHT:
- mDrawableRight = mDrawableTemp;
+ mShowing[Drawables.RIGHT] = mDrawableTemp;
mDrawableSizeRight = mDrawableSizeTemp;
mDrawableHeightRight = mDrawableHeightTemp;
break;
@@ -451,11 +463,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case LAYOUT_DIRECTION_RTL:
mDrawableSaved = DRAWABLE_LEFT;
- mDrawableTemp = mDrawableLeft;
+ mDrawableTemp = mShowing[Drawables.LEFT];
mDrawableSizeTemp = mDrawableSizeLeft;
mDrawableHeightTemp = mDrawableHeightLeft;
- mDrawableLeft = mDrawableError;
+ mShowing[Drawables.LEFT] = mDrawableError;
mDrawableSizeLeft = mDrawableSizeError;
mDrawableHeightLeft = mDrawableHeightError;
break;
@@ -463,11 +475,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
default:
mDrawableSaved = DRAWABLE_RIGHT;
- mDrawableTemp = mDrawableRight;
+ mDrawableTemp = mShowing[Drawables.RIGHT];
mDrawableSizeTemp = mDrawableSizeRight;
mDrawableHeightTemp = mDrawableHeightRight;
- mDrawableRight = mDrawableError;
+ mShowing[Drawables.RIGHT] = mDrawableError;
mDrawableSizeRight = mDrawableSizeError;
mDrawableHeightRight = mDrawableHeightError;
break;
@@ -520,6 +532,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private final TextPaint mTextPaint;
private boolean mUserSetTextScaleX;
private Layout mLayout;
+ private boolean mLocaleChanged = false;
private int mGravity = Gravity.TOP | Gravity.START;
private boolean mHorizontallyScrolling;
@@ -623,16 +636,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
this(context, null);
}
- public TextView(Context context, AttributeSet attrs) {
+ public TextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
- public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@SuppressWarnings("deprecation")
- public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public TextView(
+ Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mText = "";
@@ -771,6 +785,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean selectallonfocus = false;
Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
drawableBottom = null, drawableStart = null, drawableEnd = null;
+ ColorStateList drawableTint = null;
+ PorterDuff.Mode drawableTintMode = null;
int drawablePadding = 0;
int ellipsize = -1;
boolean singleLine = false;
@@ -856,6 +872,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
drawableEnd = a.getDrawable(attr);
break;
+ case com.android.internal.R.styleable.TextView_drawableTint:
+ drawableTint = a.getColorStateList(attr);
+ break;
+
+ case com.android.internal.R.styleable.TextView_drawableTintMode:
+ drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
+ break;
+
case com.android.internal.R.styleable.TextView_drawablePadding:
drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
break;
@@ -1031,6 +1055,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
break;
+ case com.android.internal.R.styleable.TextView_allowUndo:
+ createEditorIfNeeded();
+ mEditor.mAllowUndo = a.getBoolean(attr, true);
+ break;
+
case com.android.internal.R.styleable.TextView_imeOptions:
createEditorIfNeeded();
mEditor.createInputContentTypeIfNeeded();
@@ -1240,6 +1269,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
bufferType = BufferType.SPANNABLE;
}
+ // Set up the tint (if needed) before setting the drawables so that it
+ // gets applied correctly.
+ if (drawableTint != null || drawableTintMode != null) {
+ if (mDrawables == null) {
+ mDrawables = new Drawables(context);
+ }
+ if (drawableTint != null) {
+ mDrawables.mTintList = drawableTint;
+ mDrawables.mHasTint = true;
+ }
+ if (drawableTintMode != null) {
+ mDrawables.mTintMode = drawableTintMode;
+ mDrawables.mHasTintMode = true;
+ }
+ }
+
// This call will save the initial left/right drawables
setCompoundDrawablesWithIntrinsicBounds(
drawableLeft, drawableTop, drawableRight, drawableBottom);
@@ -1425,6 +1470,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
resetResolvedDrawables();
resolveDrawables();
+ applyCompoundDrawableTint();
}
}
@@ -1572,7 +1618,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @hide
*/
public final UndoManager getUndoManager() {
- return mEditor == null ? null : mEditor.mUndoManager;
+ // TODO: Consider supporting a global undo manager.
+ throw new UnsupportedOperationException("not implemented");
}
/**
@@ -1590,22 +1637,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @hide
*/
public final void setUndoManager(UndoManager undoManager, String tag) {
- if (undoManager != null) {
- createEditorIfNeeded();
- mEditor.mUndoManager = undoManager;
- mEditor.mUndoOwner = undoManager.getOwner(tag, this);
- mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor);
- if (!(mText instanceof Editable)) {
- setText(mText, BufferType.EDITABLE);
- }
-
- setFilters((Editable) mText, mFilters);
- } else if (mEditor != null) {
- // XXX need to destroy all associated state.
- mEditor.mUndoManager = null;
- mEditor.mUndoOwner = null;
- mEditor.mUndoInputFilter = null;
- }
+ // TODO: Consider supporting a global undo manager. An implementation will need to:
+ // * createEditorIfNeeded()
+ // * Promote to BufferType.EDITABLE if needed.
+ // * Update the UndoManager and UndoOwner.
+ // Likewise it will need to be able to restore the default UndoManager.
+ throw new UnsupportedOperationException("not implemented");
}
/**
@@ -1783,7 +1820,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public int getCompoundPaddingTop() {
final Drawables dr = mDrawables;
- if (dr == null || dr.mDrawableTop == null) {
+ if (dr == null || dr.mShowing[Drawables.TOP] == null) {
return mPaddingTop;
} else {
return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
@@ -1796,7 +1833,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public int getCompoundPaddingBottom() {
final Drawables dr = mDrawables;
- if (dr == null || dr.mDrawableBottom == null) {
+ if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
return mPaddingBottom;
} else {
return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
@@ -1809,7 +1846,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public int getCompoundPaddingLeft() {
final Drawables dr = mDrawables;
- if (dr == null || dr.mDrawableLeft == null) {
+ if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
return mPaddingLeft;
} else {
return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
@@ -1822,7 +1859,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public int getCompoundPaddingRight() {
final Drawables dr = mDrawables;
- if (dr == null || dr.mDrawableRight == null) {
+ if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
return mPaddingRight;
} else {
return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
@@ -2020,14 +2057,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
// We need to retain the last set padding, so just clear
// out all of the fields in the existing structure.
- if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
- dr.mDrawableLeft = null;
- if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
- dr.mDrawableTop = null;
- if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
- dr.mDrawableRight = null;
- if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
- dr.mDrawableBottom = null;
+ for (int i = dr.mShowing.length - 1; i >= 0; i--) {
+ if (dr.mShowing[i] != null) {
+ dr.mShowing[i].setCallback(null);
+ }
+ dr.mShowing[i] = null;
+ }
dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
@@ -2041,25 +2076,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mDrawables.mOverride = false;
- if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
- dr.mDrawableLeft.setCallback(null);
+ if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
+ dr.mShowing[Drawables.LEFT].setCallback(null);
}
- dr.mDrawableLeft = left;
+ dr.mShowing[Drawables.LEFT] = left;
- if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
- dr.mDrawableTop.setCallback(null);
+ if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
+ dr.mShowing[Drawables.TOP].setCallback(null);
}
- dr.mDrawableTop = top;
+ dr.mShowing[Drawables.TOP] = top;
- if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
- dr.mDrawableRight.setCallback(null);
+ if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
+ dr.mShowing[Drawables.RIGHT].setCallback(null);
}
- dr.mDrawableRight = right;
+ dr.mShowing[Drawables.RIGHT] = right;
- if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
- dr.mDrawableBottom.setCallback(null);
+ if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
+ dr.mShowing[Drawables.BOTTOM].setCallback(null);
}
- dr.mDrawableBottom = bottom;
+ dr.mShowing[Drawables.BOTTOM] = bottom;
final Rect compoundRect = dr.mCompoundRect;
int[] state;
@@ -2115,6 +2150,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
resetResolvedDrawables();
resolveDrawables();
+ applyCompoundDrawableTint();
invalidate();
requestLayout();
}
@@ -2138,7 +2174,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_drawableBottom
*/
@android.view.RemotableViewMethod
- public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
+ public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
+ @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
final Context context = getContext();
setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
top != 0 ? context.getDrawable(top) : null,
@@ -2198,10 +2235,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// We're switching to relative, discard absolute.
if (dr != null) {
- if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
- dr.mDrawableLeft = dr.mDrawableLeftInitial = null;
- if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
- dr.mDrawableRight = dr.mDrawableRightInitial = null;
+ if (dr.mShowing[Drawables.LEFT] != null) {
+ dr.mShowing[Drawables.LEFT].setCallback(null);
+ }
+ dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
+ if (dr.mShowing[Drawables.RIGHT] != null) {
+ dr.mShowing[Drawables.RIGHT].setCallback(null);
+ }
+ dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
}
@@ -2219,12 +2260,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// out all of the fields in the existing structure.
if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
dr.mDrawableStart = null;
- if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
- dr.mDrawableTop = null;
- if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
+ if (dr.mShowing[Drawables.TOP] != null) {
+ dr.mShowing[Drawables.TOP].setCallback(null);
+ }
+ dr.mShowing[Drawables.TOP] = null;
+ if (dr.mDrawableEnd != null) {
+ dr.mDrawableEnd.setCallback(null);
+ }
dr.mDrawableEnd = null;
- if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
- dr.mDrawableBottom = null;
+ if (dr.mShowing[Drawables.BOTTOM] != null) {
+ dr.mShowing[Drawables.BOTTOM].setCallback(null);
+ }
+ dr.mShowing[Drawables.BOTTOM] = null;
dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
@@ -2243,20 +2290,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
dr.mDrawableStart = start;
- if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
- dr.mDrawableTop.setCallback(null);
+ if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
+ dr.mShowing[Drawables.TOP].setCallback(null);
}
- dr.mDrawableTop = top;
+ dr.mShowing[Drawables.TOP] = top;
if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
dr.mDrawableEnd.setCallback(null);
}
dr.mDrawableEnd = end;
- if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
- dr.mDrawableBottom.setCallback(null);
+ if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
+ dr.mShowing[Drawables.BOTTOM].setCallback(null);
}
- dr.mDrawableBottom = bottom;
+ dr.mShowing[Drawables.BOTTOM] = bottom;
final Rect compoundRect = dr.mCompoundRect;
int[] state;
@@ -2329,8 +2376,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_drawableBottom
*/
@android.view.RemotableViewMethod
- public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
- int bottom) {
+ public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
+ @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
final Context context = getContext();
setCompoundDrawablesRelativeWithIntrinsicBounds(
start != 0 ? context.getDrawable(start) : null,
@@ -2382,9 +2429,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public Drawable[] getCompoundDrawables() {
final Drawables dr = mDrawables;
if (dr != null) {
- return new Drawable[] {
- dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
- };
+ return dr.mShowing.clone();
} else {
return new Drawable[] { null, null, null, null };
}
@@ -2403,7 +2448,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final Drawables dr = mDrawables;
if (dr != null) {
return new Drawable[] {
- dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
+ dr.mDrawableStart, dr.mShowing[Drawables.TOP],
+ dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
};
} else {
return new Drawable[] { null, null, null, null };
@@ -2444,6 +2490,118 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return dr != null ? dr.mDrawablePadding : 0;
}
+ /**
+ * Applies a tint to the compound drawables. Does not modify the
+ * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to
+ * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
+ * and related methods will automatically mutate the drawables and apply
+ * the specified tint and tint mode using
+ * {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#TextView_drawableTint
+ * @see #getCompoundDrawableTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
+ if (mDrawables == null) {
+ mDrawables = new Drawables(getContext());
+ }
+ mDrawables.mTintList = tint;
+ mDrawables.mHasTint = true;
+
+ applyCompoundDrawableTint();
+ }
+
+ /**
+ * @return the tint applied to the compound drawables
+ * @attr ref android.R.styleable#TextView_drawableTint
+ * @see #setCompoundDrawableTintList(ColorStateList)
+ */
+ public ColorStateList getCompoundDrawableTintList() {
+ return mDrawables != null ? mDrawables.mTintList : null;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
+ * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#TextView_drawableTintMode
+ * @see #setCompoundDrawableTintList(ColorStateList)
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
+ if (mDrawables == null) {
+ mDrawables = new Drawables(getContext());
+ }
+ mDrawables.mTintMode = tintMode;
+ mDrawables.mHasTintMode = true;
+
+ applyCompoundDrawableTint();
+ }
+
+ /**
+ * Returns the blending mode used to apply the tint to the compound
+ * drawables, if specified.
+ *
+ * @return the blending mode used to apply the tint to the compound
+ * drawables
+ * @attr ref android.R.styleable#TextView_drawableTintMode
+ * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
+ */
+ public PorterDuff.Mode getCompoundDrawableTintMode() {
+ return mDrawables != null ? mDrawables.mTintMode : null;
+ }
+
+ private void applyCompoundDrawableTint() {
+ if (mDrawables == null) {
+ return;
+ }
+
+ if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
+ final ColorStateList tintList = mDrawables.mTintList;
+ final PorterDuff.Mode tintMode = mDrawables.mTintMode;
+ final boolean hasTint = mDrawables.mHasTint;
+ final boolean hasTintMode = mDrawables.mHasTintMode;
+ final int[] state = getDrawableState();
+
+ for (Drawable dr : mDrawables.mShowing) {
+ if (dr == null) {
+ continue;
+ }
+
+ if (dr == mDrawables.mDrawableError) {
+ // From a developer's perspective, the error drawable isn't
+ // a compound drawable. Don't apply the generic compound
+ // drawable tint to it.
+ continue;
+ }
+
+ dr.mutate();
+
+ if (hasTint) {
+ dr.setTintList(tintList);
+ }
+
+ if (hasTintMode) {
+ dr.setTintMode(tintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (dr.isStateful()) {
+ dr.setState(state);
+ }
+ }
+ }
+ }
+
@Override
public void setPadding(int left, int top, int right, int bottom) {
if (left != mPaddingLeft ||
@@ -2484,94 +2642,92 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Sets the text appearance from the specified style resource.
+ * <p>
+ * Use a framework-defined {@code TextAppearance} style like
+ * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
+ * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
+ * set of attributes that can be used in a custom style.
+ *
+ * @param resId the resource identifier of the style to apply
+ * @attr ref android.R.styleable#TextView_textAppearance
+ */
+ @SuppressWarnings("deprecation")
+ public void setTextAppearance(@StyleRes int resId) {
+ setTextAppearance(mContext, resId);
+ }
+
+ /**
* Sets the text color, size, style, hint color, and highlight color
* from the specified TextAppearance resource.
+ *
+ * @deprecated Use {@link #setTextAppearance(int)} instead.
*/
- public void setTextAppearance(Context context, int resid) {
- TypedArray appearance =
- context.obtainStyledAttributes(resid,
- com.android.internal.R.styleable.TextAppearance);
-
- int color;
- ColorStateList colors;
- int ts;
+ @Deprecated
+ public void setTextAppearance(Context context, @StyleRes int resId) {
+ final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
- color = appearance.getColor(
- com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
- if (color != 0) {
- setHighlightColor(color);
+ final int textColorHighlight = ta.getColor(
+ R.styleable.TextAppearance_textColorHighlight, 0);
+ if (textColorHighlight != 0) {
+ setHighlightColor(textColorHighlight);
}
- colors = appearance.getColorStateList(com.android.internal.R.styleable.
- TextAppearance_textColor);
- if (colors != null) {
- setTextColor(colors);
+ final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
+ if (textColor != null) {
+ setTextColor(textColor);
}
- ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
- TextAppearance_textSize, 0);
- if (ts != 0) {
- setRawTextSize(ts);
+ final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
+ if (textSize != 0) {
+ setRawTextSize(textSize);
}
- colors = appearance.getColorStateList(com.android.internal.R.styleable.
- TextAppearance_textColorHint);
- if (colors != null) {
- setHintTextColor(colors);
+ final ColorStateList textColorHint = ta.getColorStateList(
+ R.styleable.TextAppearance_textColorHint);
+ if (textColorHint != null) {
+ setHintTextColor(textColorHint);
}
- colors = appearance.getColorStateList(com.android.internal.R.styleable.
- TextAppearance_textColorLink);
- if (colors != null) {
- setLinkTextColor(colors);
+ final ColorStateList textColorLink = ta.getColorStateList(
+ R.styleable.TextAppearance_textColorLink);
+ if (textColorLink != null) {
+ setLinkTextColor(textColorLink);
}
- String familyName;
- int typefaceIndex, styleIndex;
-
- familyName = appearance.getString(com.android.internal.R.styleable.
- TextAppearance_fontFamily);
- typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
- TextAppearance_typeface, -1);
- styleIndex = appearance.getInt(com.android.internal.R.styleable.
- TextAppearance_textStyle, -1);
-
- setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
-
- final int shadowcolor = appearance.getInt(
- com.android.internal.R.styleable.TextAppearance_shadowColor, 0);
- if (shadowcolor != 0) {
- final float dx = appearance.getFloat(
- com.android.internal.R.styleable.TextAppearance_shadowDx, 0);
- final float dy = appearance.getFloat(
- com.android.internal.R.styleable.TextAppearance_shadowDy, 0);
- final float r = appearance.getFloat(
- com.android.internal.R.styleable.TextAppearance_shadowRadius, 0);
+ final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
+ final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
+ final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
+ setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
- setShadowLayer(r, dx, dy, shadowcolor);
+ final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
+ if (shadowColor != 0) {
+ final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
+ final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
+ final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
+ setShadowLayer(r, dx, dy, shadowColor);
}
- if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
- false)) {
+ if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
}
- if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_elegantTextHeight)) {
- setElegantTextHeight(appearance.getBoolean(
- com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false));
+ if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
+ setElegantTextHeight(ta.getBoolean(
+ R.styleable.TextAppearance_elegantTextHeight, false));
}
- if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_letterSpacing)) {
- setLetterSpacing(appearance.getFloat(
- com.android.internal.R.styleable.TextAppearance_letterSpacing, 0));
+ if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
+ setLetterSpacing(ta.getFloat(
+ R.styleable.TextAppearance_letterSpacing, 0));
}
- if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)) {
- setFontFeatureSettings(appearance.getString(
- com.android.internal.R.styleable.TextAppearance_fontFeatureSettings));
+ if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
+ setFontFeatureSettings(ta.getString(
+ R.styleable.TextAppearance_fontFeatureSettings));
}
- appearance.recycle();
+ ta.recycle();
}
/**
@@ -2592,9 +2748,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see Paint#setTextLocale
*/
public void setTextLocale(Locale locale) {
+ mLocaleChanged = true;
mTextPaint.setTextLocale(locale);
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (!mLocaleChanged) {
+ mTextPaint.setTextLocale(Locale.getDefault());
+ }
+ }
+
/**
* @return the size (in pixels) of the default text size in this TextView.
*/
@@ -2829,7 +2994,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_textColor
*/
@android.view.RemotableViewMethod
- public void setTextColor(int color) {
+ public void setTextColor(@ColorInt int color) {
mTextColor = ColorStateList.valueOf(color);
updateTextColors();
}
@@ -2870,6 +3035,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @return Returns the current text color.
*/
+ @ColorInt
public final int getCurrentTextColor() {
return mCurTextColor;
}
@@ -2880,7 +3046,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_textColorHighlight
*/
@android.view.RemotableViewMethod
- public void setHighlightColor(int color) {
+ public void setHighlightColor(@ColorInt int color) {
if (mHighlightColor != color) {
mHighlightColor = color;
invalidate();
@@ -2894,6 +3060,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_textColorHighlight
*/
+ @ColorInt
public int getHighlightColor() {
return mHighlightColor;
}
@@ -2988,6 +3155,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_shadowColor
*/
+ @ColorInt
public int getShadowColor() {
return mShadowColor;
}
@@ -3063,7 +3231,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_textColorHint
*/
@android.view.RemotableViewMethod
- public final void setHintTextColor(int color) {
+ public final void setHintTextColor(@ColorInt int color) {
mHintTextColor = ColorStateList.valueOf(color);
updateTextColors();
}
@@ -3102,6 +3270,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @return Returns the current hint text color.
*/
+ @ColorInt
public final int getCurrentHintTextColor() {
return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
}
@@ -3115,7 +3284,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_textColorLink
*/
@android.view.RemotableViewMethod
- public final void setLinkTextColor(int color) {
+ public final void setLinkTextColor(@ColorInt int color) {
mLinkTextColor = ColorStateList.valueOf(color);
updateTextColors();
}
@@ -3662,26 +3831,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
updateTextColors();
}
- final Drawables dr = mDrawables;
- if (dr != null) {
- int[] state = getDrawableState();
- if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
- dr.mDrawableTop.setState(state);
- }
- if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
- dr.mDrawableBottom.setState(state);
- }
- if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
- dr.mDrawableLeft.setState(state);
- }
- if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
- dr.mDrawableRight.setState(state);
- }
- if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
- dr.mDrawableStart.setState(state);
- }
- if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
- dr.mDrawableEnd.setState(state);
+ if (mDrawables != null) {
+ final int[] state = getDrawableState();
+ for (Drawable dr : mDrawables.mShowing) {
+ if (dr != null && dr.isStateful()) {
+ dr.setState(state);
+ }
}
}
}
@@ -3690,25 +3845,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
- final Drawables dr = mDrawables;
- if (dr != null) {
- if (dr.mDrawableTop != null) {
- dr.mDrawableTop.setHotspot(x, y);
- }
- if (dr.mDrawableBottom != null) {
- dr.mDrawableBottom.setHotspot(x, y);
- }
- if (dr.mDrawableLeft != null) {
- dr.mDrawableLeft.setHotspot(x, y);
- }
- if (dr.mDrawableRight != null) {
- dr.mDrawableRight.setHotspot(x, y);
- }
- if (dr.mDrawableStart != null) {
- dr.mDrawableStart.setHotspot(x, y);
- }
- if (dr.mDrawableEnd != null) {
- dr.mDrawableEnd.setHotspot(x, y);
+ if (mDrawables != null) {
+ final int[] state = getDrawableState();
+ for (Drawable dr : mDrawables.mShowing) {
+ if (dr != null && dr.isStateful()) {
+ dr.setHotspot(x, y);
+ }
}
}
}
@@ -3756,6 +3898,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ss.error = getError();
+ if (mEditor != null) {
+ ss.editorState = mEditor.saveInstanceState();
+ }
return ss;
}
@@ -3825,6 +3970,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
});
}
+
+ if (ss.editorState != null) {
+ createEditorIfNeeded();
+ mEditor.restoreInstanceState(ss.editorState);
+ }
}
/**
@@ -3971,6 +4121,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (type == BufferType.EDITABLE || getKeyListener() != null ||
needEditableForNotification) {
createEditorIfNeeded();
+ mEditor.forgetUndoRedo();
Editable t = mEditableFactory.newEditable(text);
text = t;
setFilters(t, mFilters);
@@ -4129,11 +4280,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@android.view.RemotableViewMethod
- public final void setText(int resid) {
+ public final void setText(@StringRes int resid) {
setText(getContext().getResources().getText(resid));
}
- public final void setText(int resid, BufferType type) {
+ public final void setText(@StringRes int resid, BufferType type) {
setText(getContext().getResources().getText(resid), type);
}
@@ -4169,7 +4320,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_hint
*/
@android.view.RemotableViewMethod
- public final void setHint(int resid) {
+ public final void setHint(@StringRes int resid) {
setHint(getContext().getResources().getText(resid));
}
@@ -4573,7 +4724,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see EditorInfo#extras
* @attr ref android.R.styleable#TextView_editorExtras
*/
- public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
+ public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
createEditorIfNeeded();
XmlResourceParser parser = getResources().getXml(xmlResId);
mEditor.createInputContentTypeIfNeeded();
@@ -4951,7 +5102,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// - onFocusChanged cannot start it when focus is given to a view with selected text (after
// a screen rotation) since layout is not yet initialized at that point.
if (mEditor != null && mEditor.mCreatedWithASelection) {
- mEditor.startSelectionActionMode();
+ mEditor.startSelectionActionModeWithSelection();
mEditor.mCreatedWithASelection = false;
}
@@ -4959,7 +5110,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
// not be set. Do the test here instead.
if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
- mEditor.startSelectionActionMode();
+ mEditor.startSelectionActionModeWithSelection();
}
unregisterForPreDraw();
@@ -5039,9 +5190,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
protected boolean verifyDrawable(Drawable who) {
final boolean verified = super.verifyDrawable(who);
if (!verified && mDrawables != null) {
- return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
- who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
- who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
+ for (Drawable dr : mDrawables.mShowing) {
+ if (who == dr) {
+ return true;
+ }
+ }
}
return verified;
}
@@ -5050,23 +5203,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mDrawables != null) {
- if (mDrawables.mDrawableLeft != null) {
- mDrawables.mDrawableLeft.jumpToCurrentState();
- }
- if (mDrawables.mDrawableTop != null) {
- mDrawables.mDrawableTop.jumpToCurrentState();
- }
- if (mDrawables.mDrawableRight != null) {
- mDrawables.mDrawableRight.jumpToCurrentState();
- }
- if (mDrawables.mDrawableBottom != null) {
- mDrawables.mDrawableBottom.jumpToCurrentState();
- }
- if (mDrawables.mDrawableStart != null) {
- mDrawables.mDrawableStart.jumpToCurrentState();
- }
- if (mDrawables.mDrawableEnd != null) {
- mDrawables.mDrawableEnd.jumpToCurrentState();
+ for (Drawable dr : mDrawables.mShowing) {
+ if (dr != null) {
+ dr.jumpToCurrentState();
+ }
}
}
}
@@ -5085,7 +5225,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// accordingly.
final TextView.Drawables drawables = mDrawables;
if (drawables != null) {
- if (drawable == drawables.mDrawableLeft) {
+ if (drawable == drawables.mShowing[Drawables.LEFT]) {
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
@@ -5093,7 +5233,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += mPaddingLeft;
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
handled = true;
- } else if (drawable == drawables.mDrawableRight) {
+ } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
@@ -5101,7 +5241,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
handled = true;
- } else if (drawable == drawables.mDrawableTop) {
+ } else if (drawable == drawables.mShowing[Drawables.TOP]) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
@@ -5109,7 +5249,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
scrollY += mPaddingTop;
handled = true;
- } else if (drawable == drawables.mDrawableBottom) {
+ } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
@@ -5313,44 +5453,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableLeft != null) {
+ if (dr.mShowing[Drawables.LEFT] != null) {
canvas.save();
canvas.translate(scrollX + mPaddingLeft + leftOffset,
scrollY + compoundPaddingTop +
(vspace - dr.mDrawableHeightLeft) / 2);
- dr.mDrawableLeft.draw(canvas);
+ dr.mShowing[Drawables.LEFT].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableRight != null) {
+ if (dr.mShowing[Drawables.RIGHT] != null) {
canvas.save();
canvas.translate(scrollX + right - left - mPaddingRight
- dr.mDrawableSizeRight - rightOffset,
scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
- dr.mDrawableRight.draw(canvas);
+ dr.mShowing[Drawables.RIGHT].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableTop != null) {
+ if (dr.mShowing[Drawables.TOP] != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
(hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
- dr.mDrawableTop.draw(canvas);
+ dr.mShowing[Drawables.TOP].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableBottom != null) {
+ if (dr.mShowing[Drawables.BOTTOM] != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
(hspace - dr.mDrawableWidthBottom) / 2,
scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
- dr.mDrawableBottom.draw(canvas);
+ dr.mShowing[Drawables.BOTTOM].draw(canvas);
canvas.restore();
}
}
@@ -7961,7 +8101,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
- if (mEditor != null) mEditor.onTouchEvent(event);
+ if (mEditor != null) {
+ mEditor.onTouchEvent(event);
+
+ if (mEditor.mSelectionModifierCursorController != null &&
+ mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
+ return true;
+ }
+ }
final boolean superResult = super.onTouchEvent(event);
@@ -8243,14 +8390,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
- final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
- if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
+ if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
+ // Handle Ctrl-only shortcuts.
switch (keyCode) {
case KeyEvent.KEYCODE_A:
if (canSelectText()) {
return onTextContextMenuItem(ID_SELECT_ALL);
}
break;
+ case KeyEvent.KEYCODE_Z:
+ if (canUndo()) {
+ return onTextContextMenuItem(ID_UNDO);
+ }
+ break;
case KeyEvent.KEYCODE_X:
if (canCut()) {
return onTextContextMenuItem(ID_CUT);
@@ -8267,6 +8419,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
break;
}
+ } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
+ // Handle Ctrl-Shift shortcuts.
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_Z:
+ if (canRedo()) {
+ return onTextContextMenuItem(ID_REDO);
+ }
+ break;
+ case KeyEvent.KEYCODE_V:
+ if (canPaste()) {
+ return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
+ }
+ }
}
return super.onKeyShortcut(keyCode, event);
}
@@ -8381,9 +8546,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /** @hide */
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
final boolean isPassword = hasPasswordTransformationMethod();
if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
@@ -8405,10 +8571,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public CharSequence getAccessibilityClassName() {
+ return TextView.class.getName();
+ }
+
+ @Override
+ public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) {
+ super.onProvideAssistStructure(structure, extras);
+ final boolean isPassword = hasPasswordTransformationMethod();
+ if (!isPassword) {
+ structure.setText(getText(), getSelectionStart(), getSelectionEnd());
+ structure.setTextPaint(mTextPaint);
+ }
+ structure.setHint(getHint());
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
- event.setClassName(TextView.class.getName());
final boolean isPassword = hasPasswordTransformationMethod();
event.setPassword(isPassword);
@@ -8419,11 +8601,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
- info.setClassName(TextView.class.getName());
final boolean isPassword = hasPasswordTransformationMethod();
info.setPassword(isPassword);
@@ -8561,7 +8743,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Selection.setSelection((Spannable) text, start, end);
// Make sure selection mode is engaged.
if (mEditor != null) {
- mEditor.startSelectionActionMode();
+ mEditor.startSelectionActionModeWithSelection();
}
return true;
}
@@ -8579,15 +8761,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /** @hide */
@Override
- public void sendAccessibilityEvent(int eventType) {
+ public void sendAccessibilityEventInternal(int eventType) {
// Do not send scroll events since first they are not interesting for
// accessibility and second such events a generated too frequently.
// For details see the implementation of bringTextIntoView().
if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
return;
}
- super.sendAccessibilityEvent(eventType);
+ super.sendAccessibilityEventInternal(eventType);
}
/**
@@ -8626,9 +8809,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
static final int ID_SELECT_ALL = android.R.id.selectAll;
+ static final int ID_UNDO = android.R.id.undo;
+ static final int ID_REDO = android.R.id.redo;
static final int ID_CUT = android.R.id.cut;
static final int ID_COPY = android.R.id.copy;
static final int ID_PASTE = android.R.id.paste;
+ static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
+ static final int ID_REPLACE = android.R.id.replaceText;
/**
* Called when a context menu option for the text view is selected. Currently
@@ -8656,8 +8843,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
selectAllText();
return true;
+ case ID_UNDO:
+ if (mEditor != null) {
+ mEditor.undo();
+ }
+ return true; // Returns true even if nothing was undone.
+
+ case ID_REDO:
+ if (mEditor != null) {
+ mEditor.redo();
+ }
+ return true; // Returns true even if nothing was undone.
+
case ID_PASTE:
- paste(min, max);
+ paste(min, max, true /* withFormatting */);
+ return true;
+
+ case ID_PASTE_AS_PLAIN_TEXT:
+ paste(min, max, false /* withFormatting */);
return true;
case ID_CUT:
@@ -8752,9 +8955,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* A custom implementation can add new entries in the default menu in its
* {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
- * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
- * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
- * or {@link android.R.id#paste} ids as parameters.
+ * default actions can also be removed from the menu using
+ * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
+ * {@link android.R.id#cut}, {@link android.R.id#copy} or {@link android.R.id#paste} ids as
+ * parameters.
*
* Returning false from
* {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
@@ -8785,7 +8989,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @hide
*/
protected void stopSelectionActionMode() {
- mEditor.stopSelectionActionMode();
+ if (mEditor != null) {
+ mEditor.stopSelectionActionMode();
+ }
+ }
+
+ boolean canUndo() {
+ return mEditor != null && mEditor.canUndo();
+ }
+
+ boolean canRedo() {
+ return mEditor != null && mEditor.canRedo();
}
boolean canCut() {
@@ -8831,14 +9045,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Paste clipboard content between min and max positions.
*/
- private void paste(int min, int max) {
+ private void paste(int min, int max, boolean withFormatting) {
ClipboardManager clipboard =
(ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
if (clip != null) {
boolean didFirst = false;
for (int i=0; i<clip.getItemCount(); i++) {
- CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
+ final CharSequence paste;
+ if (withFormatting) {
+ paste = clip.getItemAt(i).coerceToStyledText(getContext());
+ } else {
+ // Get an item as text and remove all spans by toString().
+ final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
+ paste = (text instanceof Spanned) ? text.toString() : text;
+ }
if (paste != null) {
if (!didFirst) {
Selection.setSelection((Spannable) mText, max);
@@ -8896,7 +9117,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return getLayout().getLineForVertical((int) y);
}
- private int getOffsetAtCoordinate(int line, float x) {
+ int getOffsetAtCoordinate(int line, float x) {
x = convertToLocalHorizontalCoordinate(x);
return getLayout().getOffsetForHorizontal(line, x);
}
@@ -9155,6 +9376,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
CharSequence text;
boolean frozenWithFocus;
CharSequence error;
+ ParcelableParcel editorState; // Optional state from Editor.
SavedState(Parcelable superState) {
super(superState);
@@ -9174,6 +9396,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
out.writeInt(1);
TextUtils.writeToParcel(error, out, flags);
}
+
+ if (editorState == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(1);
+ editorState.writeToParcel(out, flags);
+ }
}
@Override
@@ -9209,6 +9438,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (in.readInt() != 0) {
error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
}
+
+ if (in.readInt() != 0) {
+ editorState = ParcelableParcel.CREATOR.createFromParcel(in);
+ }
}
}
@@ -9300,7 +9533,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// TODO: Add an option to configure this
private static final float MARQUEE_DELTA_MAX = 0.07f;
private static final int MARQUEE_DELAY = 1200;
- private static final int MARQUEE_RESTART_DELAY = 1200;
private static final int MARQUEE_DP_PER_SECOND = 30;
private static final byte MARQUEE_STOPPED = 0x0;
diff --git a/core/java/android/widget/TextViewWithCircularIndicator.java b/core/java/android/widget/TextViewWithCircularIndicator.java
index 43c0843..d3c786c 100644
--- a/core/java/android/widget/TextViewWithCircularIndicator.java
+++ b/core/java/android/widget/TextViewWithCircularIndicator.java
@@ -18,7 +18,6 @@ package android.widget;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
@@ -27,14 +26,8 @@ import android.util.AttributeSet;
import com.android.internal.R;
class TextViewWithCircularIndicator extends TextView {
-
- private static final int SELECTED_CIRCLE_ALPHA = 60;
-
private final Paint mCirclePaint = new Paint();
-
private final String mItemIsSelectedText;
- private int mCircleColor;
- private boolean mDrawIndicator;
public TextViewWithCircularIndicator(Context context) {
this(context, null);
@@ -50,22 +43,11 @@ class TextViewWithCircularIndicator extends TextView {
public TextViewWithCircularIndicator(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
- super(context, attrs);
-
-
- // Use Theme attributes if possible
- final TypedArray a = mContext.obtainStyledAttributes(attrs,
- R.styleable.DatePicker, defStyleAttr, defStyleRes);
- final int resId = a.getResourceId(R.styleable.DatePicker_yearListItemTextAppearance, -1);
- if (resId != -1) {
- setTextAppearance(context, resId);
- }
+ super(context, attrs, defStyleAttr, defStyleRes);
final Resources res = context.getResources();
mItemIsSelectedText = res.getString(R.string.item_is_selected);
- a.recycle();
-
init();
}
@@ -77,33 +59,26 @@ class TextViewWithCircularIndicator extends TextView {
}
public void setCircleColor(int color) {
- if (color != mCircleColor) {
- mCircleColor = color;
- mCirclePaint.setColor(mCircleColor);
- mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
- requestLayout();
- }
- }
-
- public void setDrawIndicator(boolean drawIndicator) {
- mDrawIndicator = drawIndicator;
+ mCirclePaint.setColor(color);
+ invalidate();
}
@Override
public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (mDrawIndicator) {
+ if (isActivated()) {
final int width = getWidth();
final int height = getHeight();
- int radius = Math.min(width, height) / 2;
+ final int radius = Math.min(width, height) / 2;
canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint);
}
+
+ super.onDraw(canvas);
}
@Override
public CharSequence getContentDescription() {
- CharSequence itemText = getText();
- if (mDrawIndicator) {
+ final CharSequence itemText = getText();
+ if (isActivated()) {
return String.format(mItemIsSelectedText, itemText);
} else {
return itemText;
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 26e02f8..944b491 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -24,8 +24,6 @@ import android.content.res.TypedArray;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-
import com.android.internal.R;
import java.util.Locale;
@@ -196,26 +194,14 @@ public class TimePicker extends FrameLayout {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- return mDelegate.dispatchPopulateAccessibilityEvent(event);
- }
-
- @Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
- mDelegate.onPopulateAccessibilityEvent(event);
+ public CharSequence getAccessibilityClassName() {
+ return TimePicker.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- mDelegate.onInitializeAccessibilityEvent(event);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- mDelegate.onInitializeAccessibilityNodeInfo(info);
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ return mDelegate.dispatchPopulateAccessibilityEvent(event);
}
/**
@@ -248,8 +234,6 @@ public class TimePicker extends FrameLayout {
boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
void onPopulateAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
/**
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 1534429..9fdd718 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -17,7 +17,6 @@
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;
@@ -34,7 +33,6 @@ 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.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -91,6 +89,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
private int mInitialHourOfDay;
private int mInitialMinute;
private boolean mIs24HourView;
+ private boolean mIsAmPmAtStart;
// For hardware IME input.
private char mPlaceholderText;
@@ -131,19 +130,19 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
mPmText = amPmStrings[1];
final int layoutResourceId = a.getResourceId(R.styleable.TimePicker_internalLayout,
- R.layout.time_picker_holo);
+ R.layout.time_picker_material);
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 = (TextView) mainView.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);
+ mSeparatorView = (TextView) mainView.findViewById(R.id.separator);
+ mMinuteView = (TextView) mainView.findViewById(R.id.minutes);
mMinuteView.setOnClickListener(mClickListener);
mMinuteView.setAccessibilityDelegate(
new ClickActionDelegate(context, R.string.select_minutes));
@@ -161,17 +160,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
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);
+ mAmPmLayout = mainView.findViewById(R.id.ampm_layout);
mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label);
mAmLabel.setText(amPmStrings[0]);
mAmLabel.setOnClickListener(mClickListener);
@@ -284,24 +274,40 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
}
private void updateHeaderAmPm() {
+
if (mIs24HourView) {
mAmPmLayout.setVisibility(View.GONE);
} else {
// 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);
- }
+ final boolean isAmPmAtStart = dateTimePattern.startsWith("a");
+ setAmPmAtStart(isAmPmAtStart);
updateAmPmLabelStates(mInitialHourOfDay < 12 ? AM : PM);
}
}
+ private void setAmPmAtStart(boolean isAmPmAtStart) {
+ if (mIsAmPmAtStart != isAmPmAtStart) {
+ mIsAmPmAtStart = isAmPmAtStart;
+
+ final RelativeLayout.LayoutParams params =
+ (RelativeLayout.LayoutParams) mAmPmLayout.getLayoutParams();
+ if (params.getRule(RelativeLayout.RIGHT_OF) != 0 ||
+ params.getRule(RelativeLayout.LEFT_OF) != 0) {
+ if (isAmPmAtStart) {
+ params.removeRule(RelativeLayout.RIGHT_OF);
+ params.addRule(RelativeLayout.LEFT_OF, mHourView.getId());
+ } else {
+ params.removeRule(RelativeLayout.LEFT_OF);
+ params.addRule(RelativeLayout.RIGHT_OF, mMinuteView.getId());
+ }
+ }
+
+ mAmPmLayout.setLayoutParams(params);
+ }
+ }
+
/**
* Set the current hour.
*/
@@ -466,16 +472,6 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
event.getText().add(selectedDate);
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(TimePicker.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(TimePicker.class.getName());
- }
-
/**
* Set whether in keyboard mode or not.
*
@@ -609,11 +605,11 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
private void updateAmPmLabelStates(int amOrPm) {
final boolean isAm = amOrPm == AM;
mAmLabel.setChecked(isAm);
- mAmLabel.setAlpha(isAm ? 1 : mDisabledAlpha);
+ mAmLabel.setSelected(isAm);
final boolean isPm = amOrPm == PM;
mPmLabel.setChecked(isPm);
- mPmLabel.setAlpha(isPm ? 1 : mDisabledAlpha);
+ mPmLabel.setSelected(isPm);
}
/**
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index e162f4a..513c55b 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -28,12 +28,10 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
-import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.Locale;
@@ -427,16 +425,6 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
event.getText().add(selectedDateUtterance);
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(TimePicker.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(TimePicker.class.getName());
- }
-
private void updateInputState() {
// Make sure that if the user changes the value and the IME is active
// for one of the inputs if this widget, the IME is closed. If the user
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index be4cdc1..207f675 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.IntDef;
+import android.annotation.StringRes;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.content.Context;
@@ -280,7 +281,7 @@ public class Toast {
*
* @throws Resources.NotFoundException if the resource can't be found.
*/
- public static Toast makeText(Context context, int resId, @Duration int duration)
+ public static Toast makeText(Context context, @StringRes int resId, @Duration int duration)
throws Resources.NotFoundException {
return makeText(context, context.getResources().getText(resId), duration);
}
@@ -289,7 +290,7 @@ public class Toast {
* Update the text in a Toast that was previously created using one of the makeText() methods.
* @param resId The new text for the Toast.
*/
- public void setText(int resId) {
+ public void setText(@StringRes int resId) {
setText(mContext.getText(resId));
}
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index 28519d1..6a8449e 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -21,8 +21,6 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* Displays checked/unchecked states as a button
@@ -154,14 +152,7 @@ public class ToggleButton extends CompoundButton {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ToggleButton.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ToggleButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ToggleButton.class.getName();
}
}
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index c5325c4..087406a 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -16,12 +16,18 @@
package android.widget;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.MenuRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
import android.app.ActionBar;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
-import android.graphics.RectF;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -104,6 +110,9 @@ public class Toolbar extends ViewGroup {
private ImageButton mNavButtonView;
private ImageView mLogoView;
+ private TintInfo mOverflowTintInfo;
+ private TintInfo mNavTintInfo;
+
private Drawable mCollapseIcon;
private CharSequence mCollapseDescription;
private ImageButton mCollapseButtonView;
@@ -266,6 +275,21 @@ public class Toolbar extends ViewGroup {
if (!TextUtils.isEmpty(navDesc)) {
setNavigationContentDescription(navDesc);
}
+
+ if (a.hasValue(R.styleable.Toolbar_overflowTint)) {
+ setOverflowTintList(a.getColorStateList(R.styleable.Toolbar_overflowTint));
+ }
+ if (a.hasValue(R.styleable.Toolbar_overflowTintMode)) {
+ setOverflowTintMode(Drawable.parseTintMode(
+ a.getInt(R.styleable.Toolbar_overflowTintMode, -1), null));
+ }
+ if (a.hasValue(R.styleable.Toolbar_navigationTint)) {
+ setNavigationTintList(a.getColorStateList(R.styleable.Toolbar_navigationTint));
+ }
+ if (a.hasValue(R.styleable.Toolbar_navigationTintMode)) {
+ setNavigationTintMode(Drawable.parseTintMode(
+ a.getInt(R.styleable.Toolbar_navigationTintMode, -1), null));
+ }
a.recycle();
}
@@ -276,7 +300,7 @@ public class Toolbar extends ViewGroup {
* @param resId theme used to inflate popup menus
* @see #getPopupTheme()
*/
- public void setPopupTheme(int resId) {
+ public void setPopupTheme(@StyleRes int resId) {
if (mPopupTheme != resId) {
mPopupTheme = resId;
if (resId == 0) {
@@ -311,7 +335,7 @@ public class Toolbar extends ViewGroup {
*
* @param resId ID of a drawable resource
*/
- public void setLogo(int resId) {
+ public void setLogo(@DrawableRes int resId) {
setLogo(getContext().getDrawable(resId));
}
@@ -461,7 +485,7 @@ public class Toolbar extends ViewGroup {
*
* @param resId String resource id
*/
- public void setLogoDescription(int resId) {
+ public void setLogoDescription(@StringRes int resId) {
setLogoDescription(getContext().getText(resId));
}
@@ -546,7 +570,7 @@ public class Toolbar extends ViewGroup {
*
* @param resId Resource ID of a string to set as the title
*/
- public void setTitle(int resId) {
+ public void setTitle(@StringRes int resId) {
setTitle(getContext().getText(resId));
}
@@ -601,7 +625,7 @@ public class Toolbar extends ViewGroup {
*
* @param resId String resource ID
*/
- public void setSubtitle(int resId) {
+ public void setSubtitle(@StringRes int resId) {
setSubtitle(getContext().getText(resId));
}
@@ -643,7 +667,7 @@ public class Toolbar extends ViewGroup {
* Sets the text color, size, style, hint color, and highlight color
* from the specified TextAppearance resource.
*/
- public void setTitleTextAppearance(Context context, int resId) {
+ public void setTitleTextAppearance(Context context, @StyleRes int resId) {
mTitleTextAppearance = resId;
if (mTitleTextView != null) {
mTitleTextView.setTextAppearance(context, resId);
@@ -654,7 +678,7 @@ public class Toolbar extends ViewGroup {
* Sets the text color, size, style, hint color, and highlight color
* from the specified TextAppearance resource.
*/
- public void setSubtitleTextAppearance(Context context, int resId) {
+ public void setSubtitleTextAppearance(Context context, @StyleRes int resId) {
mSubtitleTextAppearance = resId;
if (mSubtitleTextView != null) {
mSubtitleTextView.setTextAppearance(context, resId);
@@ -666,7 +690,7 @@ public class Toolbar extends ViewGroup {
*
* @param color The new text color in 0xAARRGGBB format
*/
- public void setTitleTextColor(int color) {
+ public void setTitleTextColor(@ColorInt int color) {
mTitleTextColor = color;
if (mTitleTextView != null) {
mTitleTextView.setTextColor(color);
@@ -678,7 +702,7 @@ public class Toolbar extends ViewGroup {
*
* @param color The new text color in 0xAARRGGBB format
*/
- public void setSubtitleTextColor(int color) {
+ public void setSubtitleTextColor(@ColorInt int color) {
mSubtitleTextColor = color;
if (mSubtitleTextView != null) {
mSubtitleTextView.setTextColor(color);
@@ -709,7 +733,7 @@ public class Toolbar extends ViewGroup {
*
* @attr ref android.R.styleable#Toolbar_navigationContentDescription
*/
- public void setNavigationContentDescription(int resId) {
+ public void setNavigationContentDescription(@StringRes int resId) {
setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null);
}
@@ -746,7 +770,7 @@ public class Toolbar extends ViewGroup {
*
* @attr ref android.R.styleable#Toolbar_navigationIcon
*/
- public void setNavigationIcon(int resId) {
+ public void setNavigationIcon(@DrawableRes int resId) {
setNavigationIcon(getContext().getDrawable(resId));
}
@@ -806,6 +830,91 @@ public class Toolbar extends ViewGroup {
}
/**
+ * Applies a tint to the icon drawable. Does not modify the current tint
+ * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setNavigationIcon(Drawable)} will automatically mutate
+ * the drawable and apply the specified tint and tint mode.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#Toolbar_navigationTint
+ */
+ public void setNavigationTintList(ColorStateList tint) {
+ if (mNavTintInfo == null) {
+ mNavTintInfo = new TintInfo();
+ }
+ mNavTintInfo.mTintList = tint;
+ mNavTintInfo.mHasTintList = true;
+
+ applyNavigationTint();
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by {@link
+ * #setNavigationTintList(ColorStateList)} to the navigation drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#Toolbar_navigationTintMode
+ */
+ public void setNavigationTintMode(PorterDuff.Mode tintMode) {
+ if (mNavTintInfo == null) {
+ mNavTintInfo = new TintInfo();
+ }
+ mNavTintInfo.mTintMode = tintMode;
+ mNavTintInfo.mHasTintMode = true;
+
+ applyNavigationTint();
+ }
+
+ /**
+ * Applies a tint to the overflow drawable. Does not modify the current tint
+ * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#Toolbar_overflowTint
+ */
+ public void setOverflowTintList(ColorStateList tint) {
+ if (mMenuView != null) {
+ // If the menu view is available, directly set the tint
+ mMenuView.setOverflowTintList(tint);
+ } else {
+ // Otherwise we will record the value
+ if (mOverflowTintInfo == null) {
+ mOverflowTintInfo = new TintInfo();
+ }
+ mOverflowTintInfo.mTintList = tint;
+ mOverflowTintInfo.mHasTintList = true;
+ }
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by {@link
+ * #setOverflowTintList(ColorStateList)} to the overflow drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#Toolbar_overflowTintMode
+ */
+ public void setOverflowTintMode(PorterDuff.Mode tintMode) {
+ if (mMenuView != null) {
+ // If the menu view is available, directly set the tint mode
+ mMenuView.setOverflowTintMode(tintMode);
+ } else {
+ // Otherwise we will record the value
+ if (mOverflowTintInfo == null) {
+ mOverflowTintInfo = new TintInfo();
+ }
+ mOverflowTintInfo.mTintMode = tintMode;
+ mOverflowTintInfo.mHasTintMode = true;
+ }
+ }
+
+ /**
* Return the Menu shown in the toolbar.
*
* <p>Applications that wish to populate the toolbar's menu can do so from here. To use
@@ -841,6 +950,17 @@ public class Toolbar extends ViewGroup {
lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
mMenuView.setLayoutParams(lp);
addSystemView(mMenuView);
+
+ if (mOverflowTintInfo != null) {
+ // If we have tint info for the overflow, set it on the menu view now
+ if (mOverflowTintInfo.mHasTintList) {
+ mMenuView.setOverflowTintList(mOverflowTintInfo.mTintList);
+ }
+ if (mOverflowTintInfo.mHasTintMode) {
+ mMenuView.setOverflowTintMode(mOverflowTintInfo.mTintMode);
+ }
+ mOverflowTintInfo = null;
+ }
}
}
@@ -856,7 +976,7 @@ public class Toolbar extends ViewGroup {
*
* @param resId ID of a menu resource to inflate
*/
- public void inflateMenu(int resId) {
+ public void inflateMenu(@MenuRes int resId) {
getMenuInflater().inflate(resId, getMenu());
}
@@ -994,6 +1114,7 @@ public class Toolbar extends ViewGroup {
final LayoutParams lp = generateDefaultLayoutParams();
lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
mNavButtonView.setLayoutParams(lp);
+ applyNavigationTint();
}
}
@@ -1012,6 +1133,7 @@ public class Toolbar extends ViewGroup {
collapseActionView();
}
});
+ applyNavigationTint();
}
}
@@ -1763,6 +1885,30 @@ public class Toolbar extends ViewGroup {
return mPopupContext;
}
+ private void applyNavigationTint() {
+ final TintInfo tintInfo = mNavTintInfo;
+ if (tintInfo != null && (tintInfo.mHasTintList || tintInfo.mHasTintMode)) {
+ if (mNavButtonView != null) {
+ if (tintInfo.mHasTintList) {
+ mNavButtonView.setImageTintList(tintInfo.mTintList);
+ }
+ if (tintInfo.mHasTintMode) {
+ mNavButtonView.setImageTintMode(tintInfo.mTintMode);
+ }
+ }
+
+ if (mCollapseButtonView != null) {
+ // We will use the same tint for the collapse button
+ if (tintInfo.mHasTintList) {
+ mCollapseButtonView.setImageTintList(tintInfo.mTintList);
+ }
+ if (tintInfo.mHasTintMode) {
+ mCollapseButtonView.setImageTintMode(tintInfo.mTintMode);
+ }
+ }
+ }
+ }
+
/**
* Interface responsible for receiving menu item click events if the items themselves
* do not have individual item click listeners.
@@ -1990,4 +2136,11 @@ public class Toolbar extends ViewGroup {
public void onRestoreInstanceState(Parcelable state) {
}
}
+
+ private static class TintInfo {
+ ColorStateList mTintList;
+ PorterDuff.Mode mTintMode;
+ boolean mHasTintMode;
+ boolean mHasTintList;
+ }
}
diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java
index 5606c60..69ff488 100644
--- a/core/java/android/widget/TwoLineListItem.java
+++ b/core/java/android/widget/TwoLineListItem.java
@@ -20,8 +20,6 @@ import android.annotation.Widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RelativeLayout;
/**
@@ -94,14 +92,7 @@ public class TwoLineListItem extends RelativeLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TwoLineListItem.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TwoLineListItem.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TwoLineListItem.class.getName();
}
}
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 47644f9..2671739 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -43,8 +43,6 @@ import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.MediaController.MediaPlayerControl;
import java.io.IOException;
@@ -202,15 +200,8 @@ public class VideoView extends SurfaceView
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(VideoView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(VideoView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return VideoView.class.getName();
}
public int resolveAdjustedSize(int desiredSize, int measureSpec) {
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
index eee914e..1580f51 100644
--- a/core/java/android/widget/ViewAnimator.java
+++ b/core/java/android/widget/ViewAnimator.java
@@ -17,13 +17,12 @@
package android.widget;
+import android.annotation.AnimRes;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -311,7 +310,7 @@ public class ViewAnimator extends FrameLayout {
* @see #getInAnimation()
* @see #setInAnimation(android.view.animation.Animation)
*/
- public void setInAnimation(Context context, int resourceID) {
+ public void setInAnimation(Context context, @AnimRes int resourceID) {
setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
}
@@ -324,7 +323,7 @@ public class ViewAnimator extends FrameLayout {
* @see #getOutAnimation()
* @see #setOutAnimation(android.view.animation.Animation)
*/
- public void setOutAnimation(Context context, int resourceID) {
+ public void setOutAnimation(Context context, @AnimRes int resourceID) {
setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
}
@@ -358,14 +357,7 @@ public class ViewAnimator extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ViewAnimator.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ViewAnimator.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ViewAnimator.class.getName();
}
}
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index cf1f554..94e7ba1 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -24,8 +24,6 @@ import android.content.res.TypedArray;
import android.os.*;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
/**
@@ -150,15 +148,8 @@ public class ViewFlipper extends ViewAnimator {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ViewFlipper.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ViewFlipper.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ViewFlipper.class.getName();
}
/**
diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java
index 0376918..0d5627e 100644
--- a/core/java/android/widget/ViewSwitcher.java
+++ b/core/java/android/widget/ViewSwitcher.java
@@ -20,8 +20,6 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
/**
* {@link ViewAnimator} that switches between two views, and has a factory
@@ -69,15 +67,8 @@ public class ViewSwitcher extends ViewAnimator {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ViewSwitcher.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ViewSwitcher.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ViewSwitcher.class.getName();
}
/**
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
index 24ed7ce..6f0465f 100644
--- a/core/java/android/widget/YearPickerView.java
+++ b/core/java/android/widget/YearPickerView.java
@@ -17,8 +17,10 @@
package android.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.util.AttributeSet;
+import android.util.StateSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
@@ -42,7 +44,7 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
private DatePickerController mController;
private int mSelectedPosition = -1;
- private int mYearSelectedCircleColor;
+ private int mYearActivatedColor;
public YearPickerView(Context context) {
this(context, null);
@@ -97,15 +99,14 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
onDateChanged();
}
- public void setYearSelectedCircleColor(int color) {
- if (color != mYearSelectedCircleColor) {
- mYearSelectedCircleColor = color;
- }
- requestLayout();
+ public void setYearBackgroundColor(ColorStateList yearBackgroundColor) {
+ mYearActivatedColor = yearBackgroundColor.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
+ invalidate();
}
- public int getYearSelectedCircleColor() {
- return mYearSelectedCircleColor;
+ public void setYearTextAppearance(int resId) {
+ mAdapter.setItemTextAppearance(resId);
}
private void updateAdapterData() {
@@ -127,12 +128,8 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
mController.onYearSelected(mAdapter.getItem(position));
}
- void setItemTextAppearance(int resId) {
- mAdapter.setItemTextAppearance(resId);
- }
-
private class YearAdapter extends ArrayAdapter<Integer> {
- int mItemTextAppearanceResId;
+ private int mItemTextAppearanceResId;
public YearAdapter(Context context, int resource) {
super(context, resource);
@@ -140,16 +137,15 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- TextViewWithCircularIndicator v = (TextViewWithCircularIndicator)
+ final TextViewWithCircularIndicator v = (TextViewWithCircularIndicator)
super.getView(position, convertView, parent);
- v.setTextAppearance(getContext(), mItemTextAppearanceResId);
- v.requestLayout();
- int year = getItem(position);
- boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year;
- v.setDrawIndicator(selected);
- if (selected) {
- v.setCircleColor(mYearSelectedCircleColor);
- }
+ v.setTextAppearance(v.getContext(), mItemTextAppearanceResId);
+ v.setCircleColor(mYearActivatedColor);
+
+ final int year = getItem(position);
+ final boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year;
+ v.setActivated(selected);
+
return v;
}
@@ -189,9 +185,10 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
mController.getSelectedDay().get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR));
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
event.setFromIndex(0);
diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java
index 715e868..0255bdb 100644
--- a/core/java/android/widget/ZoomButton.java
+++ b/core/java/android/widget/ZoomButton.java
@@ -23,8 +23,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
public class ZoomButton extends ImageButton implements OnLongClickListener {
@@ -104,14 +102,7 @@ public class ZoomButton extends ImageButton implements OnLongClickListener {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ZoomButton.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ZoomButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ZoomButton.class.getName();
}
}
diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java
index 8897875..66c052b 100644
--- a/core/java/android/widget/ZoomControls.java
+++ b/core/java/android/widget/ZoomControls.java
@@ -22,8 +22,6 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
import com.android.internal.R;
@@ -110,14 +108,7 @@ public class ZoomControls extends LinearLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ZoomControls.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ZoomControls.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ZoomControls.class.getName();
}
}
diff --git a/core/java/com/android/internal/alsa/AlsaCardsParser.java b/core/java/com/android/internal/alsa/AlsaCardsParser.java
new file mode 100644
index 0000000..5c0a888
--- /dev/null
+++ b/core/java/com/android/internal/alsa/AlsaCardsParser.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.alsa;
+
+import android.util.Slog;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * @hide Retrieves information from an ALSA "cards" file.
+ */
+public class AlsaCardsParser {
+ private static final String TAG = "AlsaCardsParser";
+ protected static final boolean DEBUG = true;
+
+ private static final String kCardsFilePath = "/proc/asound/cards";
+
+ private static LineTokenizer mTokenizer = new LineTokenizer(" :[]");
+
+ private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>();
+
+ public class AlsaCardRecord {
+ private static final String TAG = "AlsaCardRecord";
+ private static final String kUsbCardKeyStr = "at usb-";
+
+ public int mCardNum = -1;
+ public String mField1 = "";
+ public String mCardName = "";
+ public String mCardDescription = "";
+ public boolean mIsUsb = false;
+
+ public AlsaCardRecord() {}
+
+ public boolean parse(String line, int lineIndex) {
+ int tokenIndex = 0;
+ int delimIndex = 0;
+
+ if (lineIndex == 0) {
+ // line # (skip)
+ tokenIndex = mTokenizer.nextToken(line, tokenIndex);
+ delimIndex = mTokenizer.nextDelimiter(line, tokenIndex);
+
+ try {
+ // mCardNum
+ mCardNum = Integer.parseInt(line.substring(tokenIndex, delimIndex));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Failed to parse line " + lineIndex + " of " + kCardsFilePath
+ + ": " + line.substring(tokenIndex, delimIndex));
+ return false;
+ }
+
+ // mField1
+ tokenIndex = mTokenizer.nextToken(line, delimIndex);
+ delimIndex = mTokenizer.nextDelimiter(line, tokenIndex);
+ mField1 = line.substring(tokenIndex, delimIndex);
+
+ // mCardName
+ tokenIndex = mTokenizer.nextToken(line, delimIndex);
+ mCardName = line.substring(tokenIndex);
+
+ // done
+ } else if (lineIndex == 1) {
+ tokenIndex = mTokenizer.nextToken(line, 0);
+ if (tokenIndex != -1) {
+ int keyIndex = line.indexOf(kUsbCardKeyStr);
+ mIsUsb = keyIndex != -1;
+ if (mIsUsb) {
+ mCardDescription = line.substring(tokenIndex, keyIndex - 1);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public String textFormat() {
+ return mCardName + " : " + mCardDescription;
+ }
+ }
+
+ public AlsaCardsParser() {}
+
+ public void scan() {
+ if (DEBUG) {
+ Slog.i(TAG, "AlsaCardsParser.scan()");
+ }
+ mCardRecords = new ArrayList<AlsaCardRecord>();
+
+ File cardsFile = new File(kCardsFilePath);
+ try {
+ FileReader reader = new FileReader(cardsFile);
+ BufferedReader bufferedReader = new BufferedReader(reader);
+ String line = "";
+ while ((line = bufferedReader.readLine()) != null) {
+ AlsaCardRecord cardRecord = new AlsaCardRecord();
+ if (DEBUG) {
+ Slog.i(TAG, " " + line);
+ }
+ cardRecord.parse(line, 0);
+
+ line = bufferedReader.readLine();
+ if (line == null) {
+ break;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, " " + line);
+ }
+ cardRecord.parse(line, 1);
+
+ mCardRecords.add(cardRecord);
+ }
+ reader.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public ArrayList<AlsaCardRecord> getScanRecords() {
+ return mCardRecords;
+ }
+
+ public AlsaCardRecord getCardRecordAt(int index) {
+ return mCardRecords.get(index);
+ }
+
+ public AlsaCardRecord getCardRecordFor(int cardNum) {
+ for (AlsaCardRecord rec : mCardRecords) {
+ if (rec.mCardNum == cardNum) {
+ return rec;
+ }
+ }
+
+ return null;
+ }
+
+ public int getNumCardRecords() {
+ return mCardRecords.size();
+ }
+
+ public boolean isCardUsb(int cardNum) {
+ for (AlsaCardRecord rec : mCardRecords) {
+ if (rec.mCardNum == cardNum) {
+ return rec.mIsUsb;
+ }
+ }
+
+ return false;
+ }
+
+ // return -1 if none found
+ public int getDefaultUsbCard() {
+ // Choose the most-recently added EXTERNAL card
+ // or return the first added EXTERNAL card?
+ for (AlsaCardRecord rec : mCardRecords) {
+ if (rec.mIsUsb) {
+ return rec.mCardNum;
+ }
+ }
+
+ return -1;
+ }
+
+ public int getDefaultCard() {
+ // return an external card if possible
+ int card = getDefaultUsbCard();
+
+ if (card < 0 && getNumCardRecords() > 0) {
+ // otherwise return the (internal) card with the highest number
+ card = getCardRecordAt(getNumCardRecords() - 1).mCardNum;
+ }
+ return card;
+ }
+
+ static public boolean hasCardNumber(ArrayList<AlsaCardRecord> recs, int cardNum) {
+ for (AlsaCardRecord cardRec : recs) {
+ if (cardRec.mCardNum == cardNum) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public ArrayList<AlsaCardRecord> getNewCardRecords(ArrayList<AlsaCardRecord> prevScanRecs) {
+ ArrayList<AlsaCardRecord> newRecs = new ArrayList<AlsaCardRecord>();
+ for (AlsaCardRecord rec : mCardRecords) {
+ // now scan to see if this card number is in the previous scan list
+ if (!hasCardNumber(prevScanRecs, rec.mCardNum)) {
+ newRecs.add(rec);
+ }
+ }
+ return newRecs;
+ }
+
+ //
+ // Logging
+ //
+ public void Log(String heading) {
+ if (DEBUG) {
+ Slog.i(TAG, heading);
+ for (AlsaCardRecord cardRec : mCardRecords) {
+ Slog.i(TAG, cardRec.textFormat());
+ }
+ }
+ }
+}
diff --git a/core/java/android/alsa/AlsaDevicesParser.java b/core/java/com/android/internal/alsa/AlsaDevicesParser.java
index 82cc1ae..81b7943 100644
--- a/core/java/android/alsa/AlsaDevicesParser.java
+++ b/core/java/com/android/internal/alsa/AlsaDevicesParser.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.alsa;
+package com.android.internal.alsa;
import android.util.Slog;
import java.io.BufferedReader;
@@ -21,7 +21,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
-import java.util.Vector;
+import java.util.ArrayList;
/**
* @hide
@@ -29,6 +29,9 @@ import java.util.Vector;
*/
public class AlsaDevicesParser {
private static final String TAG = "AlsaDevicesParser";
+ protected static final boolean DEBUG = false;
+
+ private static final String kDevicesFilePath = "/proc/asound/devices";
private static final int kIndex_CardDeviceField = 5;
private static final int kStartIndex_CardNum = 6;
@@ -58,8 +61,7 @@ public class AlsaDevicesParser {
int mDeviceType = kDeviceType_Unknown;
int mDeviceDir = kDeviceDir_Unknown;
- public AlsaDeviceRecord() {
- }
+ public AlsaDeviceRecord() {}
public boolean parse(String line) {
// "0123456789012345678901234567890"
@@ -89,51 +91,57 @@ public class AlsaDevicesParser {
}
String token = line.substring(tokenOffset, delimOffset);
- switch (tokenIndex) {
- case kToken_LineNum:
- // ignore
- break;
-
- case kToken_CardNum:
- mCardNum = Integer.parseInt(token);
- if (line.charAt(delimOffset) != '-') {
- tokenIndex++; // no device # in the token stream
- }
- break;
-
- case kToken_DeviceNum:
- mDeviceNum = Integer.parseInt(token);
- break;
-
- case kToken_Type0:
- if (token.equals("digital")) {
- // NOP
- } else if (token.equals("control")) {
- mDeviceType = kDeviceType_Control;
- } else if (token.equals("raw")) {
- // NOP
- }
- break;
-
- case kToken_Type1:
- if (token.equals("audio")) {
- mDeviceType = kDeviceType_Audio;
- } else if (token.equals("midi")) {
- mDeviceType = kDeviceType_MIDI;
- mHasMIDIDevices = true;
- }
- break;
-
- case kToken_Type2:
- if (token.equals("capture")) {
- mDeviceDir = kDeviceDir_Capture;
- mHasCaptureDevices = true;
- } else if (token.equals("playback")) {
- mDeviceDir = kDeviceDir_Playback;
- mHasPlaybackDevices = true;
- }
- break;
- } // switch (tokenIndex)
+ try {
+ switch (tokenIndex) {
+ case kToken_LineNum:
+ // ignore
+ break;
+
+ case kToken_CardNum:
+ mCardNum = Integer.parseInt(token);
+ if (line.charAt(delimOffset) != '-') {
+ tokenIndex++; // no device # in the token stream
+ }
+ break;
+
+ case kToken_DeviceNum:
+ mDeviceNum = Integer.parseInt(token);
+ break;
+
+ case kToken_Type0:
+ if (token.equals("digital")) {
+ // NOP
+ } else if (token.equals("control")) {
+ mDeviceType = kDeviceType_Control;
+ } else if (token.equals("raw")) {
+ // NOP
+ }
+ break;
+
+ case kToken_Type1:
+ if (token.equals("audio")) {
+ mDeviceType = kDeviceType_Audio;
+ } else if (token.equals("midi")) {
+ mDeviceType = kDeviceType_MIDI;
+ mHasMIDIDevices = true;
+ }
+ break;
+
+ case kToken_Type2:
+ if (token.equals("capture")) {
+ mDeviceDir = kDeviceDir_Capture;
+ mHasCaptureDevices = true;
+ } else if (token.equals("playback")) {
+ mDeviceDir = kDeviceDir_Playback;
+ mHasPlaybackDevices = true;
+ }
+ break;
+ } // switch (tokenIndex)
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Failed to parse token " + tokenIndex + " of " + kDevicesFilePath
+ + " token: " + token);
+ return false;
+ }
tokenIndex++;
} // while (true)
@@ -176,38 +184,27 @@ public class AlsaDevicesParser {
}
}
- private Vector<AlsaDeviceRecord>
- deviceRecords_ = new Vector<AlsaDeviceRecord>();
+ private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>();
- private boolean isLineDeviceRecord(String line) {
- return line.charAt(kIndex_CardDeviceField) == '[';
- }
+ public AlsaDevicesParser() {}
- public AlsaDevicesParser() {
+ //
+ // Access
+ //
+ public int getDefaultDeviceNum(int card) {
+ // TODO - This (obviously) isn't sufficient. Revisit.
+ return 0;
}
- public int getNumDeviceRecords() {
- return deviceRecords_.size();
- }
-
- public AlsaDeviceRecord getDeviceRecordAt(int index) {
- return deviceRecords_.get(index);
- }
-
- public void Log() {
- int numDevRecs = getNumDeviceRecords();
- for (int index = 0; index < numDevRecs; ++index) {
- Slog.w(TAG, "usb:" + getDeviceRecordAt(index).textFormat());
- }
- }
-
- public boolean hasPlaybackDevices() {
+ //
+ // Predicates
+ //
+ public boolean hasPlaybackDevices() {
return mHasPlaybackDevices;
}
public boolean hasPlaybackDevices(int card) {
- for (int index = 0; index < deviceRecords_.size(); index++) {
- AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) {
@@ -222,8 +219,7 @@ public class AlsaDevicesParser {
}
public boolean hasCaptureDevices(int card) {
- for (int index = 0; index < deviceRecords_.size(); index++) {
- AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) {
@@ -238,8 +234,7 @@ public class AlsaDevicesParser {
}
public boolean hasMIDIDevices(int card) {
- for (int index = 0; index < deviceRecords_.size(); index++) {
- AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) {
return true;
@@ -248,11 +243,17 @@ public class AlsaDevicesParser {
return false;
}
+ //
+ // Process
+ //
+ private boolean isLineDeviceRecord(String line) {
+ return line.charAt(kIndex_CardDeviceField) == '[';
+ }
+
public void scan() {
- deviceRecords_.clear();
+ mDeviceRecords.clear();
- final String devicesFilePath = "/proc/asound/devices";
- File devicesFile = new File(devicesFilePath);
+ File devicesFile = new File(kDevicesFilePath);
try {
FileReader reader = new FileReader(devicesFile);
BufferedReader bufferedReader = new BufferedReader(reader);
@@ -261,7 +262,7 @@ public class AlsaDevicesParser {
if (isLineDeviceRecord(line)) {
AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord();
deviceRecord.parse(line);
- deviceRecords_.add(deviceRecord);
+ mDeviceRecords.add(deviceRecord);
}
}
reader.close();
@@ -271,5 +272,17 @@ public class AlsaDevicesParser {
e.printStackTrace();
}
}
+
+ //
+ // Loging
+ //
+ public void Log(String heading) {
+ if (DEBUG) {
+ Slog.i(TAG, heading);
+ for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
+ Slog.i(TAG, deviceRecord.textFormat());
+ }
+ }
+ }
} // class AlsaDevicesParser
diff --git a/core/java/android/alsa/LineTokenizer.java b/core/java/com/android/internal/alsa/LineTokenizer.java
index 78c91b5..43047a9 100644
--- a/core/java/android/alsa/LineTokenizer.java
+++ b/core/java/com/android/internal/alsa/LineTokenizer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.alsa;
+package com.android.internal.alsa;
/**
* @hide
diff --git a/core/java/com/android/internal/app/AlertActivity.java b/core/java/com/android/internal/app/AlertActivity.java
index 5566aa6..ed48b0d 100644
--- a/core/java/com/android/internal/app/AlertActivity.java
+++ b/core/java/com/android/internal/app/AlertActivity.java
@@ -17,11 +17,9 @@
package com.android.internal.app;
import android.app.Activity;
-import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
-import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 20d209f..9dabb4e 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -20,6 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import com.android.internal.R;
+import android.annotation.Nullable;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -38,7 +39,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
-import android.view.ViewTreeObserver;
+import android.view.ViewStub;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -450,27 +451,107 @@ public class AlertController {
}
}
+ /**
+ * Resolves whether a custom or default panel should be used. Removes the
+ * default panel if a custom panel should be used. If the resolved panel is
+ * a view stub, inflates before returning.
+ *
+ * @param customPanel the custom panel
+ * @param defaultPanel the default panel
+ * @return the panel to use
+ */
+ @Nullable
+ private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
+ if (customPanel == null) {
+ // Inflate the default panel, if needed.
+ if (defaultPanel instanceof ViewStub) {
+ defaultPanel = ((ViewStub) defaultPanel).inflate();
+ }
+
+ return (ViewGroup) defaultPanel;
+ }
+
+ // Remove the default panel entirely.
+ if (defaultPanel != null) {
+ final ViewParent parent = defaultPanel.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(defaultPanel);
+ }
+ }
+
+ // Inflate the custom panel, if needed.
+ if (customPanel instanceof ViewStub) {
+ customPanel = ((ViewStub) customPanel).inflate();
+ }
+
+ return (ViewGroup) customPanel;
+ }
+
private void setupView() {
- final ViewGroup contentPanel = (ViewGroup) mWindow.findViewById(R.id.contentPanel);
- setupContent(contentPanel);
- final boolean hasButtons = setupButtons();
+ final View parentPanel = mWindow.findViewById(R.id.parentPanel);
+ final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
+ final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
+ final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
- final ViewGroup topPanel = (ViewGroup) mWindow.findViewById(R.id.topPanel);
- final TypedArray a = mContext.obtainStyledAttributes(
- null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
- final boolean hasTitle = setupTitle(topPanel);
+ // Install custom content before setting up the title or buttons so
+ // that we can handle panel overrides.
+ final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
+ setupCustomContent(customPanel);
- final View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
- if (!hasButtons) {
- buttonPanel.setVisibility(View.GONE);
- final View spacer = mWindow.findViewById(R.id.textSpacerNoButtons);
- if (spacer != null) {
- spacer.setVisibility(View.VISIBLE);
+ final View customTopPanel = customPanel.findViewById(R.id.topPanel);
+ final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
+ final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
+
+ // Resolve the correct panels and remove the defaults, if needed.
+ final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
+ final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
+ final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
+
+ setupContent(contentPanel);
+ setupButtons(buttonPanel);
+ setupTitle(topPanel);
+
+ final boolean hasCustomPanel = customPanel != null
+ && customPanel.getVisibility() != View.GONE;
+ final boolean hasTopPanel = topPanel != null
+ && topPanel.getVisibility() != View.GONE;
+ final boolean hasButtonPanel = buttonPanel != null
+ && buttonPanel.getVisibility() != View.GONE;
+
+ // Only display the text spacer if we don't have buttons.
+ if (!hasButtonPanel) {
+ if (contentPanel != null) {
+ final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
+ if (spacer != null) {
+ spacer.setVisibility(View.VISIBLE);
+ }
}
mWindow.setCloseOnTouchOutsideIfNotSet(true);
}
- final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
+ // Only display the divider if we have a title and a custom view or a
+ // message.
+ if (hasTopPanel) {
+ final View divider;
+ if (mMessage != null || hasCustomPanel || mListView != null) {
+ divider = topPanel.findViewById(R.id.titleDivider);
+ } else {
+ divider = topPanel.findViewById(R.id.titleDividerTop);
+ }
+
+ if (divider != null) {
+ divider.setVisibility(View.VISIBLE);
+ }
+ }
+
+ final TypedArray a = mContext.obtainStyledAttributes(
+ null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+ setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
+ hasTopPanel, hasCustomPanel, hasButtonPanel);
+ a.recycle();
+ }
+
+ private void setupCustomContent(ViewGroup customPanel) {
final View customView;
if (mView != null) {
customView = mView;
@@ -502,30 +583,9 @@ public class AlertController {
} else {
customPanel.setVisibility(View.GONE);
}
-
- // Only display the divider if we have a title and a custom view or a
- // message.
- if (hasTitle) {
- final View divider;
- if (mMessage != null || customView != null || mListView != null) {
- divider = mWindow.findViewById(R.id.titleDivider);
- } else {
- divider = mWindow.findViewById(R.id.titleDividerTop);
- }
-
- if (divider != null) {
- divider.setVisibility(View.VISIBLE);
- }
- }
-
- setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView,
- hasButtons);
- a.recycle();
}
- private boolean setupTitle(ViewGroup topPanel) {
- boolean hasTitle = true;
-
+ private void setupTitle(ViewGroup topPanel) {
if (mCustomTitleView != null) {
// Add the custom title view directly to the topPanel layout
LayoutParams lp = new LayoutParams(
@@ -567,18 +627,16 @@ public class AlertController {
titleTemplate.setVisibility(View.GONE);
mIconView.setVisibility(View.GONE);
topPanel.setVisibility(View.GONE);
- hasTitle = false;
}
}
- return hasTitle;
}
private void setupContent(ViewGroup contentPanel) {
- mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
+ mScrollView = (ScrollView) contentPanel.findViewById(R.id.scrollView);
mScrollView.setFocusable(false);
// Special case for users that only want to display a String
- mMessageView = (TextView) mWindow.findViewById(R.id.message);
+ mMessageView = (TextView) contentPanel.findViewById(R.id.message);
if (mMessageView == null) {
return;
}
@@ -601,8 +659,8 @@ public class AlertController {
}
// Set up scroll indicators (if present).
- final View indicatorUp = mWindow.findViewById(R.id.scrollIndicatorUp);
- final View indicatorDown = mWindow.findViewById(R.id.scrollIndicatorDown);
+ final View indicatorUp = contentPanel.findViewById(R.id.scrollIndicatorUp);
+ final View indicatorDown = contentPanel.findViewById(R.id.scrollIndicatorDown);
if (indicatorUp != null || indicatorDown != null) {
if (mMessage != null) {
// We're just showing the ScrollView, set up listener.
@@ -663,12 +721,12 @@ public class AlertController {
}
}
- private boolean setupButtons() {
+ private void setupButtons(ViewGroup buttonPanel) {
int BIT_BUTTON_POSITIVE = 1;
int BIT_BUTTON_NEGATIVE = 2;
int BIT_BUTTON_NEUTRAL = 4;
int whichButtons = 0;
- mButtonPositive = (Button) mWindow.findViewById(R.id.button1);
+ mButtonPositive = (Button) buttonPanel.findViewById(R.id.button1);
mButtonPositive.setOnClickListener(mButtonHandler);
if (TextUtils.isEmpty(mButtonPositiveText)) {
@@ -679,7 +737,7 @@ public class AlertController {
whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
}
- mButtonNegative = (Button) mWindow.findViewById(R.id.button2);
+ mButtonNegative = (Button) buttonPanel.findViewById(R.id.button2);
mButtonNegative.setOnClickListener(mButtonHandler);
if (TextUtils.isEmpty(mButtonNegativeText)) {
@@ -691,7 +749,7 @@ public class AlertController {
whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
}
- mButtonNeutral = (Button) mWindow.findViewById(R.id.button3);
+ mButtonNeutral = (Button) buttonPanel.findViewById(R.id.button3);
mButtonNeutral.setOnClickListener(mButtonHandler);
if (TextUtils.isEmpty(mButtonNeutralText)) {
@@ -717,7 +775,10 @@ public class AlertController {
}
}
- return whichButtons != 0;
+ final boolean hasButtons = whichButtons != 0;
+ if (!hasButtons) {
+ buttonPanel.setVisibility(View.GONE);
+ }
}
private void centerButton(Button button) {
diff --git a/core/java/com/android/internal/app/DumpHeapActivity.java b/core/java/com/android/internal/app/DumpHeapActivity.java
new file mode 100644
index 0000000..7e70b0c
--- /dev/null
+++ b/core/java/com/android/internal/app/DumpHeapActivity.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ClipData;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.DebugUtils;
+
+/**
+ * This activity is displayed when the system has collected a heap dump from
+ * a large process and the user has selected to share it.
+ */
+public class DumpHeapActivity extends Activity {
+ /** The process we are reporting */
+ public static final String KEY_PROCESS = "process";
+ /** The size limit the process reached */
+ public static final String KEY_SIZE = "size";
+
+ // Broadcast action to determine when to delete the current dump heap data.
+ public static final String ACTION_DELETE_DUMPHEAP = "com.android.server.am.DELETE_DUMPHEAP";
+
+ // Extra for above: delay delete of data, since the user is in the process of sharing it.
+ public static final String EXTRA_DELAY_DELETE = "delay_delete";
+
+ static final public Uri JAVA_URI = Uri.parse("content://com.android.server.heapdump/java");
+
+ String mProcess;
+ long mSize;
+ AlertDialog mDialog;
+ boolean mHandled = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mProcess = getIntent().getStringExtra(KEY_PROCESS);
+ mSize = getIntent().getLongExtra(KEY_SIZE, 0);
+ AlertDialog.Builder b = new AlertDialog.Builder(this,
+ android.R.style.Theme_Material_Light_Dialog_Alert);
+ b.setTitle(com.android.internal.R.string.dump_heap_title);
+ b.setMessage(getString(com.android.internal.R.string.dump_heap_text,
+ mProcess, DebugUtils.sizeValueToString(mSize, null)));
+ b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mHandled = true;
+ sendBroadcast(new Intent(ACTION_DELETE_DUMPHEAP));
+ finish();
+ }
+ });
+ b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mHandled = true;
+ Intent broadcast = new Intent(ACTION_DELETE_DUMPHEAP);
+ broadcast.putExtra(EXTRA_DELAY_DELETE, true);
+ sendBroadcast(broadcast);
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ ClipData clip = ClipData.newUri(getContentResolver(), "Heap Dump", JAVA_URI);
+ intent.setClipData(clip);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setType(clip.getDescription().getMimeType(0));
+ intent.putExtra(Intent.EXTRA_STREAM, JAVA_URI);
+ startActivity(Intent.createChooser(intent,
+ getText(com.android.internal.R.string.dump_heap_title)));
+ finish();
+ }
+ });
+ mDialog = b.show();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (!isChangingConfigurations()) {
+ if (!mHandled) {
+ sendBroadcast(new Intent(ACTION_DELETE_DUMPHEAP));
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDialog.dismiss();
+ }
+}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
new file mode 100644
index 0000000..a987a16
--- /dev/null
+++ b/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.graphics.Bitmap;
+
+/** @hide */
+oneway interface IAssistScreenshotReceiver {
+ void send(in Bitmap screenshot);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 5a10524..6450d52 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -26,10 +26,13 @@ import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
interface IVoiceInteractionManagerService {
- void startSession(IVoiceInteractionService service, in Bundle sessionArgs);
+ void showSession(IVoiceInteractionService service, in Bundle sessionArgs, int flags);
boolean deliverNewSession(IBinder token, IVoiceInteractionSession session,
IVoiceInteractor interactor);
+ boolean showSessionFromSession(IBinder token, in Bundle sessionArgs, int flags);
+ boolean hideSessionFromSession(IBinder token);
int startVoiceActivity(IBinder token, in Intent intent, String resolvedType);
+ void setKeepAwake(IBinder token, boolean keepAwake);
void finish(IBinder token);
/**
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
index 3e0b021..84e9cf0 100644
--- a/core/java/com/android/internal/app/IVoiceInteractor.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -16,6 +16,7 @@
package com.android.internal.app;
+import android.app.VoiceInteractor;
import android.os.Bundle;
import com.android.internal.app.IVoiceInteractorCallback;
@@ -27,6 +28,9 @@ import com.android.internal.app.IVoiceInteractorRequest;
interface IVoiceInteractor {
IVoiceInteractorRequest startConfirmation(String callingPackage,
IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras);
+ IVoiceInteractorRequest startPickOption(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence prompt,
+ in VoiceInteractor.PickOptionRequest.Option[] options, in Bundle extras);
IVoiceInteractorRequest startCompleteVoice(String callingPackage,
IVoiceInteractorCallback callback, CharSequence message, in Bundle extras);
IVoiceInteractorRequest startAbortVoice(String callingPackage,
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index dcd5759..1331e74 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -16,6 +16,7 @@
package com.android.internal.app;
+import android.app.VoiceInteractor;
import android.os.Bundle;
import com.android.internal.app.IVoiceInteractorRequest;
@@ -26,8 +27,10 @@ import com.android.internal.app.IVoiceInteractorRequest;
oneway interface IVoiceInteractorCallback {
void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
in Bundle result);
+ void deliverPickOptionResult(IVoiceInteractorRequest request, boolean finished,
+ in VoiceInteractor.PickOptionRequest.Option[] selections, in Bundle result);
void deliverCompleteVoiceResult(IVoiceInteractorRequest request, in Bundle result);
void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result);
- void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
+ void deliverCommandResult(IVoiceInteractorRequest request, boolean finished, in Bundle result);
void deliverCancel(IVoiceInteractorRequest request);
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 7c1308f..f598828 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -28,7 +28,6 @@ import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 70fb510..75beee9 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -24,6 +24,7 @@ import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -897,17 +898,17 @@ public final class ProcessStats implements Parcelable {
pw.print(STATE_NAMES[procStates[ip]]); pw.print(": ");
pw.print(count);
pw.print(" samples ");
- printSizeValue(pw, proc.getPssMinimum(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssMinimum(bucket) * 1024);
pw.print(" ");
- printSizeValue(pw, proc.getPssAverage(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssAverage(bucket) * 1024);
pw.print(" ");
- printSizeValue(pw, proc.getPssMaximum(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssMaximum(bucket) * 1024);
pw.print(" / ");
- printSizeValue(pw, proc.getPssUssMinimum(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssUssMinimum(bucket) * 1024);
pw.print(" ");
- printSizeValue(pw, proc.getPssUssAverage(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssUssAverage(bucket) * 1024);
pw.print(" ");
- printSizeValue(pw, proc.getPssUssMaximum(bucket) * 1024);
+ DebugUtils.printSizeValue(pw, proc.getPssUssMaximum(bucket) * 1024);
pw.println();
}
}
@@ -924,9 +925,9 @@ public final class ProcessStats implements Parcelable {
if (proc.mNumCachedKill != 0) {
pw.print(prefix); pw.print("Killed from cached state: ");
pw.print(proc.mNumCachedKill); pw.print(" times from pss ");
- printSizeValue(pw, proc.mMinCachedKillPss * 1024); pw.print("-");
- printSizeValue(pw, proc.mAvgCachedKillPss * 1024); pw.print("-");
- printSizeValue(pw, proc.mMaxCachedKillPss * 1024); pw.println();
+ DebugUtils.printSizeValue(pw, proc.mMinCachedKillPss * 1024); pw.print("-");
+ DebugUtils.printSizeValue(pw, proc.mAvgCachedKillPss * 1024); pw.print("-");
+ DebugUtils.printSizeValue(pw, proc.mMaxCachedKillPss * 1024); pw.println();
}
}
@@ -939,11 +940,11 @@ public final class ProcessStats implements Parcelable {
int bucket, int index) {
pw.print(prefix); pw.print(label);
pw.print(": ");
- printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
+ DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
pw.print(" min, ");
- printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
+ DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
pw.print(" avg, ");
- printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
+ DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
pw.println(" max");
}
@@ -1150,43 +1151,6 @@ public final class ProcessStats implements Parcelable {
pw.print("%");
}
- static void printSizeValue(PrintWriter pw, long number) {
- float result = number;
- String suffix = "";
- if (result > 900) {
- suffix = "KB";
- result = result / 1024;
- }
- if (result > 900) {
- suffix = "MB";
- result = result / 1024;
- }
- if (result > 900) {
- suffix = "GB";
- result = result / 1024;
- }
- if (result > 900) {
- suffix = "TB";
- result = result / 1024;
- }
- if (result > 900) {
- suffix = "PB";
- result = result / 1024;
- }
- String value;
- if (result < 1) {
- value = String.format("%.2f", result);
- } else if (result < 10) {
- value = String.format("%.1f", result);
- } else if (result < 100) {
- value = String.format("%.0f", result);
- } else {
- value = String.format("%.0f", result);
- }
- pw.print(value);
- pw.print(suffix);
- }
-
public static void dumpProcessListCsv(PrintWriter pw, ArrayList<ProcessState> procs,
boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates,
boolean sepProcStates, int[] procStates, long now) {
@@ -2437,7 +2401,7 @@ public final class ProcessStats implements Parcelable {
pw.print(prefix);
pw.print(label);
pw.print(": ");
- printSizeValue(pw, mem);
+ DebugUtils.printSizeValue(pw, mem);
pw.print(" (");
pw.print(samples);
pw.print(" samples)");
@@ -2475,7 +2439,7 @@ public final class ProcessStats implements Parcelable {
totalPss = printMemoryCategory(pw, " ", "Z-Ram ", totalMem.sysMemZRamWeight,
totalMem.totalTime, totalPss, totalMem.sysMemSamples);
pw.print(" TOTAL : ");
- printSizeValue(pw, totalPss);
+ DebugUtils.printSizeValue(pw, totalPss);
pw.println();
printMemoryCategory(pw, " ", STATE_NAMES[STATE_SERVICE_RESTARTING],
totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
@@ -3781,17 +3745,17 @@ public final class ProcessStats implements Parcelable {
printPercent(pw, (double) totalTime / (double) overallTime);
if (numPss > 0) {
pw.print(" (");
- printSizeValue(pw, minPss * 1024);
+ DebugUtils.printSizeValue(pw, minPss * 1024);
pw.print("-");
- printSizeValue(pw, avgPss * 1024);
+ DebugUtils.printSizeValue(pw, avgPss * 1024);
pw.print("-");
- printSizeValue(pw, maxPss * 1024);
+ DebugUtils.printSizeValue(pw, maxPss * 1024);
pw.print("/");
- printSizeValue(pw, minUss * 1024);
+ DebugUtils.printSizeValue(pw, minUss * 1024);
pw.print("-");
- printSizeValue(pw, avgUss * 1024);
+ DebugUtils.printSizeValue(pw, avgUss * 1024);
pw.print("-");
- printSizeValue(pw, maxUss * 1024);
+ DebugUtils.printSizeValue(pw, maxUss * 1024);
if (full) {
pw.print(" over ");
pw.print(numPss);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 649a59f..3ceea9d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -342,6 +342,9 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
return;
}
+ // Do not show the profile switch message anymore.
+ mProfileSwitchMessageId = -1;
+
final Intent intent = intentForDisplayResolveInfo(dri);
onIntentSelected(dri.ri, intent, false);
finish();
@@ -505,15 +508,6 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
// Header views don't count.
return;
}
- ResolveInfo resolveInfo = mAdapter.resolveInfoForPosition(position, true);
- if (mResolvingHome && hasManagedProfile()
- && !supportsManagedProfiles(resolveInfo)) {
- Toast.makeText(this, String.format(getResources().getString(
- com.android.internal.R.string.activity_resolver_work_profiles_support),
- resolveInfo.activityInfo.loadLabel(getPackageManager()).toString()),
- Toast.LENGTH_LONG).show();
- return;
- }
final int checkedPos = mListView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
@@ -579,7 +573,6 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
mListView.getCheckedItemPosition() : mAdapter.getFilteredPosition(),
id == R.id.button_always,
mAlwaysUseOption);
- dismiss();
}
void startSelected(int which, boolean always, boolean filtered) {
@@ -587,6 +580,14 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
return;
}
ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered);
+ if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
+ Toast.makeText(this, String.format(getResources().getString(
+ com.android.internal.R.string.activity_resolver_work_profiles_support),
+ ri.activityInfo.loadLabel(getPackageManager()).toString()),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
Intent intent = mAdapter.intentForPosition(which, filtered);
onIntentSelected(ri, intent, always);
finish();
@@ -841,6 +842,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic
Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
}
+ // Clear the value of mOtherProfile from previous call.
+ mOtherProfile = null;
mList.clear();
if (mBaseResolveList != null) {
currentResolveList = mOrigResolveList = mBaseResolveList;
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 061b535..7ae7d0f 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -20,6 +20,7 @@ import android.animation.ValueAnimator;
import android.content.res.TypedArray;
import android.view.ViewParent;
import android.widget.Toolbar;
+
import com.android.internal.R;
import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.MenuBuilder;
@@ -46,6 +47,7 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.ActionMode;
+import android.view.ActionMode.Callback;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -88,20 +90,20 @@ public class WindowDecorActionBar extends ActionBar implements
private TabImpl mSelectedTab;
private int mSavedTabPosition = INVALID_POSITION;
-
+
private boolean mDisplayHomeAsUpSet;
- ActionModeImpl mActionMode;
+ ActionMode mActionMode;
ActionMode mDeferredDestroyActionMode;
ActionMode.Callback mDeferredModeDestroyCallback;
-
+
private boolean mLastMenuVisibility;
private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
new ArrayList<OnMenuVisibilityListener>();
private static final int CONTEXT_DISPLAY_NORMAL = 0;
private static final int CONTEXT_DISPLAY_SPLIT = 1;
-
+
private static final int INVALID_POSITION = -1;
private int mContextDisplayMode;
@@ -942,11 +944,12 @@ public class WindowDecorActionBar extends ActionBar implements
private ActionMode.Callback mCallback;
private WeakReference<View> mCustomView;
- public ActionModeImpl(Context context, ActionMode.Callback callback) {
+ public ActionModeImpl(
+ Context context, ActionMode.Callback callback) {
mActionModeContext = context;
mCallback = callback;
mMenu = new MenuBuilder(context)
- .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
mMenu.setCallback(this);
}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 044383e..e32a3a2 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -44,11 +44,8 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
-
import static android.system.OsConstants.*;
/**
@@ -87,7 +84,6 @@ public class LocalTransport extends BackupTransport {
private File mRestoreSetDir;
private File mRestoreSetIncrementalDir;
private File mRestoreSetFullDir;
- private long mRestoreToken;
// Additional bookkeeping for full backup
private String mFullTargetPackage;
@@ -96,20 +92,22 @@ public class LocalTransport extends BackupTransport {
private BufferedOutputStream mFullBackupOutputStream;
private byte[] mFullBackupBuffer;
- private File mFullRestoreSetDir;
- private HashSet<String> mFullRestorePackages;
private FileInputStream mCurFullRestoreStream;
private FileOutputStream mFullRestoreSocketStream;
private byte[] mFullRestoreBuffer;
- public LocalTransport(Context context) {
- mContext = context;
+ private void makeDataDirs() {
mCurrentSetDir.mkdirs();
- mCurrentSetFullDir.mkdir();
- mCurrentSetIncrementalDir.mkdir();
if (!SELinux.restorecon(mCurrentSetDir)) {
Log.e(TAG, "SELinux restorecon failed for " + mCurrentSetDir);
}
+ mCurrentSetFullDir.mkdir();
+ mCurrentSetIncrementalDir.mkdir();
+ }
+
+ public LocalTransport(Context context) {
+ mContext = context;
+ makeDataDirs();
}
@Override
@@ -154,6 +152,7 @@ public class LocalTransport extends BackupTransport {
public int initializeDevice() {
if (DEBUG) Log.v(TAG, "wiping all data");
deleteContents(mCurrentSetDir);
+ makeDataDirs();
return TRANSPORT_OK;
}
@@ -372,6 +371,9 @@ public class LocalTransport extends BackupTransport {
return TRANSPORT_ERROR;
}
}
+ if (DEBUG) {
+ Log.v(TAG, " stored " + numBytes + " of data");
+ }
return TRANSPORT_OK;
}
@@ -425,7 +427,6 @@ public class LocalTransport extends BackupTransport {
+ " matching packages");
mRestorePackages = packages;
mRestorePackage = -1;
- mRestoreToken = token;
mRestoreSetDir = new File(mDataDir, Long.toString(token));
mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
@@ -434,6 +435,10 @@ public class LocalTransport extends BackupTransport {
@Override
public RestoreDescription nextRestorePackage() {
+ if (DEBUG) {
+ Log.v(TAG, "nextRestorePackage() : mRestorePackage=" + mRestorePackage
+ + " length=" + mRestorePackages.length);
+ }
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
boolean found = false;
@@ -444,7 +449,10 @@ public class LocalTransport extends BackupTransport {
// skip packages where we have a data dir but no actual contents
String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
if (contents != null && contents.length > 0) {
- if (DEBUG) Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) = " + name);
+ if (DEBUG) {
+ Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
+ + mRestorePackage + " = " + name);
+ }
mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
found = true;
}
@@ -453,7 +461,10 @@ public class LocalTransport extends BackupTransport {
// No key/value data; check for [non-empty] full data
File maybeFullData = new File(mRestoreSetFullDir, name);
if (maybeFullData.length() > 0) {
- if (DEBUG) Log.v(TAG, " nextRestorePackage(TYPE_FULL_STREAM) = " + name);
+ if (DEBUG) {
+ Log.v(TAG, " nextRestorePackage(TYPE_FULL_STREAM) @ "
+ + mRestorePackage + " = " + name);
+ }
mRestoreType = RestoreDescription.TYPE_FULL_STREAM;
mCurFullRestoreStream = null; // ensure starting from the ground state
found = true;
@@ -463,6 +474,11 @@ public class LocalTransport extends BackupTransport {
if (found) {
return new RestoreDescription(name, mRestoreType);
}
+
+ if (DEBUG) {
+ Log.v(TAG, " ... package @ " + mRestorePackage + " = " + name
+ + " has no data; skipping");
+ }
}
if (DEBUG) Log.v(TAG, " no more packages to restore");
diff --git a/core/java/com/android/internal/http/multipart/ByteArrayPartSource.java b/core/java/com/android/internal/http/multipart/ByteArrayPartSource.java
deleted file mode 100644
index faaac7f..0000000
--- a/core/java/com/android/internal/http/multipart/ByteArrayPartSource.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/ByteArrayPartSource.java,v 1.7 2004/04/18 23:51:37 jsdever Exp $
- * $Revision: 480424 $
- * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.internal.http.multipart;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
-/**
- * A PartSource that reads from a byte array. This class should be used when
- * the data to post is already loaded into memory.
- *
- * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
- *
- * @since 2.0
- */
-public class ByteArrayPartSource implements PartSource {
-
- /** Name of the source file. */
- private String fileName;
-
- /** Byte array of the source file. */
- private byte[] bytes;
-
- /**
- * Constructor for ByteArrayPartSource.
- *
- * @param fileName the name of the file these bytes represent
- * @param bytes the content of this part
- */
- public ByteArrayPartSource(String fileName, byte[] bytes) {
-
- this.fileName = fileName;
- this.bytes = bytes;
-
- }
-
- /**
- * @see PartSource#getLength()
- */
- public long getLength() {
- return bytes.length;
- }
-
- /**
- * @see PartSource#getFileName()
- */
- public String getFileName() {
- return fileName;
- }
-
- /**
- * @see PartSource#createInputStream()
- */
- public InputStream createInputStream() {
- return new ByteArrayInputStream(bytes);
- }
-
-}
diff --git a/core/java/com/android/internal/http/multipart/FilePart.java b/core/java/com/android/internal/http/multipart/FilePart.java
deleted file mode 100644
index 45e4be6..0000000
--- a/core/java/com/android/internal/http/multipart/FilePart.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePart.java,v 1.19 2004/04/18 23:51:37 jsdever Exp $
- * $Revision: 480424 $
- * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.internal.http.multipart;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import org.apache.http.util.EncodingUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * This class implements a part of a Multipart post object that
- * consists of a file.
- *
- * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
- * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
- * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
- * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
- * @author <a href="mailto:mdiggory@latte.harvard.edu">Mark Diggory</a>
- * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
- * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
- *
- * @since 2.0
- *
- * @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 class FilePart extends PartBase {
-
- /** Default content encoding of file attachments. */
- public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
-
- /** Default charset of file attachments. */
- public static final String DEFAULT_CHARSET = "ISO-8859-1";
-
- /** Default transfer encoding of file attachments. */
- public static final String DEFAULT_TRANSFER_ENCODING = "binary";
-
- /** Log object for this class. */
- private static final Log LOG = LogFactory.getLog(FilePart.class);
-
- /** Attachment's file name */
- protected static final String FILE_NAME = "; filename=";
-
- /** Attachment's file name as a byte array */
- private static final byte[] FILE_NAME_BYTES =
- EncodingUtils.getAsciiBytes(FILE_NAME);
-
- /** Source of the file part. */
- private PartSource source;
-
- /**
- * FilePart Constructor.
- *
- * @param name the name for this part
- * @param partSource the source for this part
- * @param contentType the content type for this part, if <code>null</code> the
- * {@link #DEFAULT_CONTENT_TYPE default} is used
- * @param charset the charset encoding for this part, if <code>null</code> the
- * {@link #DEFAULT_CHARSET default} is used
- */
- public FilePart(String name, PartSource partSource, String contentType, String charset) {
-
- super(
- name,
- contentType == null ? DEFAULT_CONTENT_TYPE : contentType,
- charset == null ? "ISO-8859-1" : charset,
- DEFAULT_TRANSFER_ENCODING
- );
-
- if (partSource == null) {
- throw new IllegalArgumentException("Source may not be null");
- }
- this.source = partSource;
- }
-
- /**
- * FilePart Constructor.
- *
- * @param name the name for this part
- * @param partSource the source for this part
- */
- public FilePart(String name, PartSource partSource) {
- this(name, partSource, null, null);
- }
-
- /**
- * FilePart Constructor.
- *
- * @param name the name of the file part
- * @param file the file to post
- *
- * @throws FileNotFoundException if the <i>file</i> is not a normal
- * file or if it is not readable.
- */
- public FilePart(String name, File file)
- throws FileNotFoundException {
- this(name, new FilePartSource(file), null, null);
- }
-
- /**
- * FilePart Constructor.
- *
- * @param name the name of the file part
- * @param file the file to post
- * @param contentType the content type for this part, if <code>null</code> the
- * {@link #DEFAULT_CONTENT_TYPE default} is used
- * @param charset the charset encoding for this part, if <code>null</code> the
- * {@link #DEFAULT_CHARSET default} is used
- *
- * @throws FileNotFoundException if the <i>file</i> is not a normal
- * file or if it is not readable.
- */
- public FilePart(String name, File file, String contentType, String charset)
- throws FileNotFoundException {
- this(name, new FilePartSource(file), contentType, charset);
- }
-
- /**
- * FilePart Constructor.
- *
- * @param name the name of the file part
- * @param fileName the file name
- * @param file the file to post
- *
- * @throws FileNotFoundException if the <i>file</i> is not a normal
- * file or if it is not readable.
- */
- public FilePart(String name, String fileName, File file)
- throws FileNotFoundException {
- this(name, new FilePartSource(fileName, file), null, null);
- }
-
- /**
- * FilePart Constructor.
- *
- * @param name the name of the file part
- * @param fileName the file name
- * @param file the file to post
- * @param contentType the content type for this part, if <code>null</code> the
- * {@link #DEFAULT_CONTENT_TYPE default} is used
- * @param charset the charset encoding for this part, if <code>null</code> the
- * {@link #DEFAULT_CHARSET default} is used
- *
- * @throws FileNotFoundException if the <i>file</i> is not a normal
- * file or if it is not readable.
- */
- public FilePart(String name, String fileName, File file, String contentType, String charset)
- throws FileNotFoundException {
- this(name, new FilePartSource(fileName, file), contentType, charset);
- }
-
- /**
- * Write the disposition header to the output stream
- * @param out The output stream
- * @throws IOException If an IO problem occurs
- * @see Part#sendDispositionHeader(OutputStream)
- */
- @Override
- protected void sendDispositionHeader(OutputStream out)
- throws IOException {
- LOG.trace("enter sendDispositionHeader(OutputStream out)");
- super.sendDispositionHeader(out);
- String filename = this.source.getFileName();
- if (filename != null) {
- out.write(FILE_NAME_BYTES);
- out.write(QUOTE_BYTES);
- out.write(EncodingUtils.getAsciiBytes(filename));
- out.write(QUOTE_BYTES);
- }
- }
-
- /**
- * Write the data in "source" to the specified stream.
- * @param out The output stream.
- * @throws IOException if an IO problem occurs.
- * @see Part#sendData(OutputStream)
- */
- @Override
- protected void sendData(OutputStream out) throws IOException {
- LOG.trace("enter sendData(OutputStream out)");
- if (lengthOfData() == 0) {
-
- // this file contains no data, so there is nothing to send.
- // we don't want to create a zero length buffer as this will
- // cause an infinite loop when reading.
- LOG.debug("No data to send.");
- return;
- }
-
- byte[] tmp = new byte[4096];
- InputStream instream = source.createInputStream();
- try {
- int len;
- while ((len = instream.read(tmp)) >= 0) {
- out.write(tmp, 0, len);
- }
- } finally {
- // we're done with the stream, close it
- instream.close();
- }
- }
-
- /**
- * Returns the source of the file part.
- *
- * @return The source.
- */
- protected PartSource getSource() {
- LOG.trace("enter getSource()");
- return this.source;
- }
-
- /**
- * Return the length of the data.
- * @return The length.
- * @see Part#lengthOfData()
- */
- @Override
- protected long lengthOfData() {
- LOG.trace("enter lengthOfData()");
- return source.getLength();
- }
-
-}
diff --git a/core/java/com/android/internal/http/multipart/FilePartSource.java b/core/java/com/android/internal/http/multipart/FilePartSource.java
deleted file mode 100644
index eb5cc0f..0000000
--- a/core/java/com/android/internal/http/multipart/FilePartSource.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/FilePartSource.java,v 1.10 2004/04/18 23:51:37 jsdever Exp $
- * $Revision: 480424 $
- * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.internal.http.multipart;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A PartSource that reads from a File.
- *
- * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
- * @author <a href="mailto:mdiggory@latte.harvard.edu">Mark Diggory</a>
- * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
- *
- * @since 2.0
- */
-public class FilePartSource implements PartSource {
-
- /** File part file. */
- private File file = null;
-
- /** File part file name. */
- private String fileName = null;
-
- /**
- * Constructor for FilePartSource.
- *
- * @param file the FilePart source File.
- *
- * @throws FileNotFoundException if the file does not exist or
- * cannot be read
- */
- public FilePartSource(File file) throws FileNotFoundException {
- this.file = file;
- if (file != null) {
- if (!file.isFile()) {
- throw new FileNotFoundException("File is not a normal file.");
- }
- if (!file.canRead()) {
- throw new FileNotFoundException("File is not readable.");
- }
- this.fileName = file.getName();
- }
- }
-
- /**
- * Constructor for FilePartSource.
- *
- * @param fileName the file name of the FilePart
- * @param file the source File for the FilePart
- *
- * @throws FileNotFoundException if the file does not exist or
- * cannot be read
- */
- public FilePartSource(String fileName, File file)
- throws FileNotFoundException {
- this(file);
- if (fileName != null) {
- this.fileName = fileName;
- }
- }
-
- /**
- * Return the length of the file
- * @return the length of the file.
- * @see PartSource#getLength()
- */
- public long getLength() {
- if (this.file != null) {
- return this.file.length();
- } else {
- return 0;
- }
- }
-
- /**
- * Return the current filename
- * @return the filename.
- * @see PartSource#getFileName()
- */
- public String getFileName() {
- return (fileName == null) ? "noname" : fileName;
- }
-
- /**
- * Return a new {@link FileInputStream} for the current filename.
- * @return the new input stream.
- * @throws IOException If an IO problem occurs.
- * @see PartSource#createInputStream()
- */
- public InputStream createInputStream() throws IOException {
- if (this.file != null) {
- return new FileInputStream(this.file);
- } else {
- return new ByteArrayInputStream(new byte[] {});
- }
- }
-
-}
diff --git a/core/java/com/android/internal/http/multipart/MultipartEntity.java b/core/java/com/android/internal/http/multipart/MultipartEntity.java
deleted file mode 100644
index 5319251..0000000
--- a/core/java/com/android/internal/http/multipart/MultipartEntity.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/MultipartRequestEntity.java,v 1.1 2004/10/06 03:39:59 mbecke Exp $
- * $Revision: 502647 $
- * $Date: 2007-02-02 17:22:54 +0100 (Fri, 02 Feb 2007) $
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.internal.http.multipart;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Random;
-
-import org.apache.http.Header;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HTTP;
-import org.apache.http.util.EncodingUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * Implements a request entity suitable for an HTTP multipart POST method.
- * <p>
- * The HTTP multipart POST method is defined in section 3.3 of
- * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC1867</a>:
- * <blockquote>
- * The media-type multipart/form-data follows the rules of all multipart
- * MIME data streams as outlined in RFC 1521. The multipart/form-data contains
- * a series of parts. Each part is expected to contain a content-disposition
- * header where the value is "form-data" and a name attribute specifies
- * the field name within the form, e.g., 'content-disposition: form-data;
- * name="xxxxx"', where xxxxx is the field name corresponding to that field.
- * Field names originally in non-ASCII character sets may be encoded using
- * the method outlined in RFC 1522.
- * </blockquote>
- * </p>
- * <p>This entity is designed to be used in conjunction with the
- * {@link org.apache.http.HttpRequest} to provide
- * multipart posts. Example usage:</p>
- * <pre>
- * File f = new File("/path/fileToUpload.txt");
- * HttpRequest request = new HttpRequest("http://host/some_path");
- * Part[] parts = {
- * new StringPart("param_name", "value"),
- * new FilePart(f.getName(), f)
- * };
- * filePost.setEntity(
- * new MultipartRequestEntity(parts, filePost.getParams())
- * );
- * HttpClient client = new HttpClient();
- * int status = client.executeMethod(filePost);
- * </pre>
- *
- * @since 3.0
- *
- * @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 class MultipartEntity extends AbstractHttpEntity {
-
- private static final Log log = LogFactory.getLog(MultipartEntity.class);
-
- /** The Content-Type for multipart/form-data. */
- private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data";
-
- /**
- * Sets the value to use as the multipart boundary.
- * <p>
- * This parameter expects a value if type {@link String}.
- * </p>
- */
- public static final String MULTIPART_BOUNDARY = "http.method.multipart.boundary";
-
- /**
- * The pool of ASCII chars to be used for generating a multipart boundary.
- */
- private static byte[] MULTIPART_CHARS = EncodingUtils.getAsciiBytes(
- "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
-
- /**
- * Generates a random multipart boundary string.
- */
- private static byte[] generateMultipartBoundary() {
- Random rand = new Random();
- byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to 40
- for (int i = 0; i < bytes.length; i++) {
- bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)];
- }
- return bytes;
- }
-
- /** The MIME parts as set by the constructor */
- protected Part[] parts;
-
- private byte[] multipartBoundary;
-
- private HttpParams params;
-
- private boolean contentConsumed = false;
-
- /**
- * Creates a new multipart entity containing the given parts.
- * @param parts The parts to include.
- * @param params The params of the HttpMethod using this entity.
- */
- public MultipartEntity(Part[] parts, HttpParams params) {
- if (parts == null) {
- throw new IllegalArgumentException("parts cannot be null");
- }
- if (params == null) {
- throw new IllegalArgumentException("params cannot be null");
- }
- this.parts = parts;
- this.params = params;
- }
-
- public MultipartEntity(Part[] parts) {
- setContentType(MULTIPART_FORM_CONTENT_TYPE);
- if (parts == null) {
- throw new IllegalArgumentException("parts cannot be null");
- }
- this.parts = parts;
- this.params = null;
- }
-
- /**
- * Returns the MIME boundary string that is used to demarcate boundaries of
- * this part. The first call to this method will implicitly create a new
- * boundary string. To create a boundary string first the
- * HttpMethodParams.MULTIPART_BOUNDARY parameter is considered. Otherwise
- * a random one is generated.
- *
- * @return The boundary string of this entity in ASCII encoding.
- */
- protected byte[] getMultipartBoundary() {
- if (multipartBoundary == null) {
- String temp = null;
- if (params != null) {
- temp = (String) params.getParameter(MULTIPART_BOUNDARY);
- }
- if (temp != null) {
- multipartBoundary = EncodingUtils.getAsciiBytes(temp);
- } else {
- multipartBoundary = generateMultipartBoundary();
- }
- }
- return multipartBoundary;
- }
-
- /**
- * Returns <code>true</code> if all parts are repeatable, <code>false</code> otherwise.
- */
- public boolean isRepeatable() {
- for (int i = 0; i < parts.length; i++) {
- if (!parts[i].isRepeatable()) {
- return false;
- }
- }
- return true;
- }
-
- /* (non-Javadoc)
- */
- public void writeTo(OutputStream out) throws IOException {
- Part.sendParts(out, parts, getMultipartBoundary());
- }
- /* (non-Javadoc)
- * @see org.apache.commons.http.AbstractHttpEntity.#getContentType()
- */
- @Override
- public Header getContentType() {
- StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
- buffer.append("; boundary=");
- buffer.append(EncodingUtils.getAsciiString(getMultipartBoundary()));
- return new BasicHeader(HTTP.CONTENT_TYPE, buffer.toString());
-
- }
-
- /* (non-Javadoc)
- */
- public long getContentLength() {
- try {
- return Part.getLengthOfParts(parts, getMultipartBoundary());
- } catch (Exception e) {
- log.error("An exception occurred while getting the length of the parts", e);
- return 0;
- }
- }
-
- public InputStream getContent() throws IOException, IllegalStateException {
- if(!isRepeatable() && this.contentConsumed ) {
- throw new IllegalStateException("Content has been consumed");
- }
- this.contentConsumed = true;
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- Part.sendParts(baos, this.parts, this.multipartBoundary);
- ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
- return bais;
- }
-
- public boolean isStreaming() {
- return false;
- }
-}
diff --git a/core/java/com/android/internal/http/multipart/Part.java b/core/java/com/android/internal/http/multipart/Part.java
deleted file mode 100644
index 1d66dc6..0000000
--- a/core/java/com/android/internal/http/multipart/Part.java
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v 1.16 2005/01/14 21:16:40 olegk Exp $
- * $Revision: 480424 $
- * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.internal.http.multipart;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-import org.apache.http.util.EncodingUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * Abstract class for one Part of a multipart post object.
- *
- * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
- * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
- * @author <a href="mailto:adrian@ephox.com">Adrian Sutton</a>
- * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
- * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
- *
- * @since 2.0
- *
- * @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 abstract class Part {
-
- /** Log object for this class. */
- private static final Log LOG = LogFactory.getLog(Part.class);
-
- /**
- * The boundary
- * @deprecated use {@link org.apache.http.client.methods.multipart#MULTIPART_BOUNDARY}
- */
- protected static final String BOUNDARY = "----------------314159265358979323846";
-
- /**
- * The boundary as a byte array.
- * @deprecated
- */
- protected static final byte[] BOUNDARY_BYTES = EncodingUtils.getAsciiBytes(BOUNDARY);
-
- /**
- * The default boundary to be used if {@link #setPartBoundary(byte[])} has not
- * been called.
- */
- private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;
-
- /** Carriage return/linefeed */
- protected static final String CRLF = "\r\n";
-
- /** Carriage return/linefeed as a byte array */
- protected static final byte[] CRLF_BYTES = EncodingUtils.getAsciiBytes(CRLF);
-
- /** Content dispostion characters */
- protected static final String QUOTE = "\"";
-
- /** Content dispostion as a byte array */
- protected static final byte[] QUOTE_BYTES =
- EncodingUtils.getAsciiBytes(QUOTE);
-
- /** Extra characters */
- protected static final String EXTRA = "--";
-
- /** Extra characters as a byte array */
- protected static final byte[] EXTRA_BYTES =
- EncodingUtils.getAsciiBytes(EXTRA);
-
- /** Content dispostion characters */
- protected static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=";
-
- /** Content dispostion as a byte array */
- protected static final byte[] CONTENT_DISPOSITION_BYTES =
- EncodingUtils.getAsciiBytes(CONTENT_DISPOSITION);
-
- /** Content type header */
- protected static final String CONTENT_TYPE = "Content-Type: ";
-
- /** Content type header as a byte array */
- protected static final byte[] CONTENT_TYPE_BYTES =
- EncodingUtils.getAsciiBytes(CONTENT_TYPE);
-
- /** Content charset */
- protected static final String CHARSET = "; charset=";
-
- /** Content charset as a byte array */
- protected static final byte[] CHARSET_BYTES =
- EncodingUtils.getAsciiBytes(CHARSET);
-
- /** Content type header */
- protected static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding: ";
-
- /** Content type header as a byte array */
- protected static final byte[] CONTENT_TRANSFER_ENCODING_BYTES =
- EncodingUtils.getAsciiBytes(CONTENT_TRANSFER_ENCODING);
-
- /**
- * Return the boundary string.
- * @return the boundary string
- * @deprecated uses a constant string. Rather use {@link #getPartBoundary}
- */
- public static String getBoundary() {
- return BOUNDARY;
- }
-
- /**
- * The ASCII bytes to use as the multipart boundary.
- */
- private byte[] boundaryBytes;
-
- /**
- * Return the name of this part.
- * @return The name.
- */
- public abstract String getName();
-
- /**
- * Returns the content type of this part.
- * @return the content type, or <code>null</code> to exclude the content type header
- */
- public abstract String getContentType();
-
- /**
- * Return the character encoding of this part.
- * @return the character encoding, or <code>null</code> to exclude the character
- * encoding header
- */
- public abstract String getCharSet();
-
- /**
- * Return the transfer encoding of this part.
- * @return the transfer encoding, or <code>null</code> to exclude the transfer encoding header
- */
- public abstract String getTransferEncoding();
-
- /**
- * Gets the part boundary to be used.
- * @return the part boundary as an array of bytes.
- *
- * @since 3.0
- */
- protected byte[] getPartBoundary() {
- if (boundaryBytes == null) {
- // custom boundary bytes have not been set, use the default.
- return DEFAULT_BOUNDARY_BYTES;
- } else {
- return boundaryBytes;
- }
- }
-
- /**
- * Sets the part boundary. Only meant to be used by
- * {@link Part#sendParts(OutputStream, Part[], byte[])}
- * and {@link Part#getLengthOfParts(Part[], byte[])}
- * @param boundaryBytes An array of ASCII bytes.
- * @since 3.0
- */
- void setPartBoundary(byte[] boundaryBytes) {
- this.boundaryBytes = boundaryBytes;
- }
-
- /**
- * Tests if this part can be sent more than once.
- * @return <code>true</code> if {@link #sendData(OutputStream)} can be successfully called
- * more than once.
- * @since 3.0
- */
- public boolean isRepeatable() {
- return true;
- }
-
- /**
- * Write the start to the specified output stream
- * @param out The output stream
- * @throws IOException If an IO problem occurs.
- */
- protected void sendStart(OutputStream out) throws IOException {
- LOG.trace("enter sendStart(OutputStream out)");
- out.write(EXTRA_BYTES);
- out.write(getPartBoundary());
- out.write(CRLF_BYTES);
- }
-
- /**
- * Write the content disposition header to the specified output stream
- *
- * @param out The output stream
- * @throws IOException If an IO problem occurs.
- */
- protected void sendDispositionHeader(OutputStream out) throws IOException {
- LOG.trace("enter sendDispositionHeader(OutputStream out)");
- out.write(CONTENT_DISPOSITION_BYTES);
- out.write(QUOTE_BYTES);
- out.write(EncodingUtils.getAsciiBytes(getName()));
- out.write(QUOTE_BYTES);
- }
-
- /**
- * Write the content type header to the specified output stream
- * @param out The output stream
- * @throws IOException If an IO problem occurs.
- */
- protected void sendContentTypeHeader(OutputStream out) throws IOException {
- LOG.trace("enter sendContentTypeHeader(OutputStream out)");
- String contentType = getContentType();
- if (contentType != null) {
- out.write(CRLF_BYTES);
- out.write(CONTENT_TYPE_BYTES);
- out.write(EncodingUtils.getAsciiBytes(contentType));
- String charSet = getCharSet();
- if (charSet != null) {
- out.write(CHARSET_BYTES);
- out.write(EncodingUtils.getAsciiBytes(charSet));
- }
- }
- }
-
- /**
- * Write the content transfer encoding header to the specified
- * output stream
- *
- * @param out The output stream
- * @throws IOException If an IO problem occurs.
- */
- protected void sendTransferEncodingHeader(OutputStream out) throws IOException {
- LOG.trace("enter sendTransferEncodingHeader(OutputStream out)");
- String transferEncoding = getTransferEncoding();
- if (transferEncoding != null) {
- out.write(CRLF_BYTES);
- out.write(CONTENT_TRANSFER_ENCODING_BYTES);
- out.write(EncodingUtils.getAsciiBytes(transferEncoding));
- }
- }
-
- /**
- * Write the end of the header to the output stream
- * @param out The output stream
- * @throws IOException If an IO problem occurs.
- */
- protected void sendEndOfHeader(OutputStream out) throws IOException {
- LOG.trace("enter sendEndOfHeader(OutputStream out)");
- out.write(CRLF_BYTES);
- out.write(CRLF_BYTES);
- }
-
- /**
- * Write the data to the specified output stream
- * @param out The output stream
- * @throws IOException If an IO problem occurs.
- */
- protected abstract void sendData(OutputStream out) throws IOException;
-
- /**
- * Return the length of the main content
- *
- * @return long The length.
- * @throws IOException If an IO problem occurs
- */
- protected abstract long lengthOfData() throws IOException;
-
- /**
- * Write the end data to the output stream.
- * @param out The output stream
- * @throws IOException If an IO problem occurs.
- */
- protected void sendEnd(OutputStream out) throws IOException {
- LOG.trace("enter sendEnd(OutputStream out)");
- out.write(CRLF_BYTES);
- }
-
- /**
- * Write all the data to the output stream.
- * If you override this method make sure to override
- * #length() as well
- *
- * @param out The output stream
- * @throws IOException If an IO problem occurs.
- */
- public void send(OutputStream out) throws IOException {
- LOG.trace("enter send(OutputStream out)");
- sendStart(out);
- sendDispositionHeader(out);
- sendContentTypeHeader(out);
- sendTransferEncodingHeader(out);
- sendEndOfHeader(out);
- sendData(out);
- sendEnd(out);
- }
-
-
- /**
- * Return the full length of all the data.
- * If you override this method make sure to override
- * #send(OutputStream) as well
- *
- * @return long The length.
- * @throws IOException If an IO problem occurs
- */
- public long length() throws IOException {
- LOG.trace("enter length()");
- if (lengthOfData() < 0) {
- return -1;
- }
- ByteArrayOutputStream overhead = new ByteArrayOutputStream();
- sendStart(overhead);
- sendDispositionHeader(overhead);
- sendContentTypeHeader(overhead);
- sendTransferEncodingHeader(overhead);
- sendEndOfHeader(overhead);
- sendEnd(overhead);
- return overhead.size() + lengthOfData();
- }
-
- /**
- * Return a string representation of this object.
- * @return A string representation of this object.
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- return this.getName();
- }
-
- /**
- * Write all parts and the last boundary to the specified output stream.
- *
- * @param out The stream to write to.
- * @param parts The parts to write.
- *
- * @throws IOException If an I/O error occurs while writing the parts.
- */
- public static void sendParts(OutputStream out, final Part[] parts)
- throws IOException {
- sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
- }
-
- /**
- * Write all parts and the last boundary to the specified output stream.
- *
- * @param out The stream to write to.
- * @param parts The parts to write.
- * @param partBoundary The ASCII bytes to use as the part boundary.
- *
- * @throws IOException If an I/O error occurs while writing the parts.
- *
- * @since 3.0
- */
- public static void sendParts(OutputStream out, Part[] parts, byte[] partBoundary)
- throws IOException {
-
- if (parts == null) {
- throw new IllegalArgumentException("Parts may not be null");
- }
- if (partBoundary == null || partBoundary.length == 0) {
- throw new IllegalArgumentException("partBoundary may not be empty");
- }
- for (int i = 0; i < parts.length; i++) {
- // set the part boundary before the part is sent
- parts[i].setPartBoundary(partBoundary);
- parts[i].send(out);
- }
- out.write(EXTRA_BYTES);
- out.write(partBoundary);
- out.write(EXTRA_BYTES);
- out.write(CRLF_BYTES);
- }
-
- /**
- * Return the total sum of all parts and that of the last boundary
- *
- * @param parts The parts.
- * @return The total length
- *
- * @throws IOException If an I/O error occurs while writing the parts.
- */
- public static long getLengthOfParts(Part[] parts)
- throws IOException {
- return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
- }
-
- /**
- * Gets the length of the multipart message including the given parts.
- *
- * @param parts The parts.
- * @param partBoundary The ASCII bytes to use as the part boundary.
- * @return The total length
- *
- * @throws IOException If an I/O error occurs while writing the parts.
- *
- * @since 3.0
- */
- public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws IOException {
- LOG.trace("getLengthOfParts(Parts[])");
- if (parts == null) {
- throw new IllegalArgumentException("Parts may not be null");
- }
- long total = 0;
- for (int i = 0; i < parts.length; i++) {
- // set the part boundary before we calculate the part's length
- parts[i].setPartBoundary(partBoundary);
- long l = parts[i].length();
- if (l < 0) {
- return -1;
- }
- total += l;
- }
- total += EXTRA_BYTES.length;
- total += partBoundary.length;
- total += EXTRA_BYTES.length;
- total += CRLF_BYTES.length;
- return total;
- }
-}
diff --git a/core/java/com/android/internal/http/multipart/PartBase.java b/core/java/com/android/internal/http/multipart/PartBase.java
deleted file mode 100644
index 876d15d..0000000
--- a/core/java/com/android/internal/http/multipart/PartBase.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/PartBase.java,v 1.5 2004/04/18 23:51:37 jsdever Exp $
- * $Revision: 480424 $
- * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.internal.http.multipart;
-
-
-/**
- * Provides setters and getters for the basic Part properties.
- *
- * @author Michael Becke
- */
-public abstract class PartBase extends Part {
-
- /** Name of the file part. */
- private String name;
-
- /** Content type of the file part. */
- private String contentType;
-
- /** Content encoding of the file part. */
- private String charSet;
-
- /** The transfer encoding. */
- private String transferEncoding;
-
- /**
- * Constructor.
- *
- * @param name The name of the part
- * @param contentType The content type, or <code>null</code>
- * @param charSet The character encoding, or <code>null</code>
- * @param transferEncoding The transfer encoding, or <code>null</code>
- */
- public PartBase(String name, String contentType, String charSet, String transferEncoding) {
-
- if (name == null) {
- throw new IllegalArgumentException("Name must not be null");
- }
- this.name = name;
- this.contentType = contentType;
- this.charSet = charSet;
- this.transferEncoding = transferEncoding;
- }
-
- /**
- * Returns the name.
- * @return The name.
- * @see Part#getName()
- */
- @Override
- public String getName() {
- return this.name;
- }
-
- /**
- * Returns the content type of this part.
- * @return String The name.
- */
- @Override
- public String getContentType() {
- return this.contentType;
- }
-
- /**
- * Return the character encoding of this part.
- * @return String The name.
- */
- @Override
- public String getCharSet() {
- return this.charSet;
- }
-
- /**
- * Returns the transfer encoding of this part.
- * @return String The name.
- */
- @Override
- public String getTransferEncoding() {
- return transferEncoding;
- }
-
- /**
- * Sets the character encoding.
- *
- * @param charSet the character encoding, or <code>null</code> to exclude the character
- * encoding header
- */
- public void setCharSet(String charSet) {
- this.charSet = charSet;
- }
-
- /**
- * Sets the content type.
- *
- * @param contentType the content type, or <code>null</code> to exclude the content type header
- */
- public void setContentType(String contentType) {
- this.contentType = contentType;
- }
-
- /**
- * Sets the part name.
- *
- * @param name
- */
- public void setName(String name) {
- if (name == null) {
- throw new IllegalArgumentException("Name must not be null");
- }
- this.name = name;
- }
-
- /**
- * Sets the transfer encoding.
- *
- * @param transferEncoding the transfer encoding, or <code>null</code> to exclude the
- * transfer encoding header
- */
- public void setTransferEncoding(String transferEncoding) {
- this.transferEncoding = transferEncoding;
- }
-
-}
diff --git a/core/java/com/android/internal/http/multipart/PartSource.java b/core/java/com/android/internal/http/multipart/PartSource.java
deleted file mode 100644
index 3740696..0000000
--- a/core/java/com/android/internal/http/multipart/PartSource.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/PartSource.java,v 1.6 2004/04/18 23:51:37 jsdever Exp $
- * $Revision: 480424 $
- * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.internal.http.multipart;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * An interface for providing access to data when posting MultiPart messages.
- *
- * @see FilePart
- *
- * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
- *
- * @since 2.0
- */
-public interface PartSource {
-
- /**
- * Gets the number of bytes contained in this source.
- *
- * @return a value >= 0
- */
- long getLength();
-
- /**
- * Gets the name of the file this source represents.
- *
- * @return the fileName used for posting a MultiPart file part
- */
- String getFileName();
-
- /**
- * Gets a new InputStream for reading this source. This method can be
- * called more than once and should therefore return a new stream every
- * time.
- *
- * @return a new InputStream
- *
- * @throws IOException if an error occurs when creating the InputStream
- */
- InputStream createInputStream() throws IOException;
-
-}
diff --git a/core/java/com/android/internal/http/multipart/StringPart.java b/core/java/com/android/internal/http/multipart/StringPart.java
deleted file mode 100644
index 73d0f90..0000000
--- a/core/java/com/android/internal/http/multipart/StringPart.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/multipart/StringPart.java,v 1.11 2004/04/18 23:51:37 jsdever Exp $
- * $Revision: 480424 $
- * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
- *
- * ====================================================================
- *
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package com.android.internal.http.multipart;
-
-import java.io.OutputStream;
-import java.io.IOException;
-
-import org.apache.http.util.EncodingUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * Simple string parameter for a multipart post
- *
- * @author <a href="mailto:mattalbright@yahoo.com">Matthew Albright</a>
- * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
- * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
- * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
- *
- * @since 2.0
- *
- * @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 class StringPart extends PartBase {
-
- /** Log object for this class. */
- private static final Log LOG = LogFactory.getLog(StringPart.class);
-
- /** Default content encoding of string parameters. */
- public static final String DEFAULT_CONTENT_TYPE = "text/plain";
-
- /** Default charset of string parameters*/
- public static final String DEFAULT_CHARSET = "US-ASCII";
-
- /** Default transfer encoding of string parameters*/
- public static final String DEFAULT_TRANSFER_ENCODING = "8bit";
-
- /** Contents of this StringPart. */
- private byte[] content;
-
- /** The String value of this part. */
- private String value;
-
- /**
- * Constructor.
- *
- * @param name The name of the part
- * @param value the string to post
- * @param charset the charset to be used to encode the string, if <code>null</code>
- * the {@link #DEFAULT_CHARSET default} is used
- */
- public StringPart(String name, String value, String charset) {
-
- super(
- name,
- DEFAULT_CONTENT_TYPE,
- charset == null ? DEFAULT_CHARSET : charset,
- DEFAULT_TRANSFER_ENCODING
- );
- if (value == null) {
- throw new IllegalArgumentException("Value may not be null");
- }
- if (value.indexOf(0) != -1) {
- // See RFC 2048, 2.8. "8bit Data"
- throw new IllegalArgumentException("NULs may not be present in string parts");
- }
- this.value = value;
- }
-
- /**
- * Constructor.
- *
- * @param name The name of the part
- * @param value the string to post
- */
- public StringPart(String name, String value) {
- this(name, value, null);
- }
-
- /**
- * Gets the content in bytes. Bytes are lazily created to allow the charset to be changed
- * after the part is created.
- *
- * @return the content in bytes
- */
- private byte[] getContent() {
- if (content == null) {
- content = EncodingUtils.getBytes(value, getCharSet());
- }
- return content;
- }
-
- /**
- * Writes the data to the given OutputStream.
- * @param out the OutputStream to write to
- * @throws IOException if there is a write error
- */
- @Override
- protected void sendData(OutputStream out) throws IOException {
- LOG.trace("enter sendData(OutputStream)");
- out.write(getContent());
- }
-
- /**
- * Return the length of the data.
- * @return The length of the data.
- * @see Part#lengthOfData()
- */
- @Override
- protected long lengthOfData() {
- LOG.trace("enter lengthOfData()");
- return getContent().length;
- }
-
- /* (non-Javadoc)
- * @see org.apache.commons.httpclient.methods.multipart.BasePart#setCharSet(java.lang.String)
- */
- @Override
- public void setCharSet(String charSet) {
- super.setCharSet(charSet);
- this.content = null;
- }
-
-}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 183527c..57fcf57 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -35,6 +35,8 @@ import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SpellCheckerInfo;
import android.view.textservice.TextServicesManager;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -118,19 +120,7 @@ public class InputMethodUtils {
& ApplicationInfo.FLAG_SYSTEM) != 0;
}
- /**
- * @deprecated Use {@link #isSystemImeThatHasSubtypeOf(InputMethodInfo, Context, boolean,
- * Locale, boolean, String)} instead.
- */
- @Deprecated
- public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
- if (!isSystemIme(imi)) {
- return false;
- }
- return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
- }
-
- private static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
+ public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
final Context context, final boolean checkDefaultAttribute,
@Nullable final Locale requiredLocale, final boolean checkCountry,
final String requiredSubtypeMode) {
@@ -380,33 +370,22 @@ public class InputMethodUtils {
.build();
}
- /**
- * @deprecated Use {@link #isSystemImeThatHasSubtypeOf(InputMethodInfo, Context, boolean,
- * Locale, boolean, String)} instead.
- */
- @Deprecated
- public static boolean isValidSystemDefaultIme(
- boolean isSystemReady, InputMethodInfo imi, Context context) {
- if (!isSystemReady) {
- return false;
- }
- if (!isSystemIme(imi)) {
- return false;
- }
- if (imi.getIsDefaultResourceId() != 0) {
- try {
- if (imi.isDefault(context) && containsSubtypeOf(
- imi, context.getResources().getConfiguration().locale.getLanguage(),
- SUBTYPE_MODE_ANY)) {
- return true;
- }
- } catch (Resources.NotFoundException ex) {
- }
+ public static Locale constructLocaleFromString(String localeStr) {
+ if (TextUtils.isEmpty(localeStr)) {
+ return null;
}
- if (imi.getSubtypeCount() == 0) {
- Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
+ // TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(languageTag)}.
+ String[] localeParams = localeStr.split("_", 3);
+ // The length of localeStr is guaranteed to always return a 1 <= value <= 3
+ // because localeStr is not empty.
+ if (localeParams.length == 1) {
+ return new Locale(localeParams[0]);
+ } else if (localeParams.length == 2) {
+ return new Locale(localeParams[0], localeParams[1]);
+ } else if (localeParams.length == 3) {
+ return new Locale(localeParams[0], localeParams[1], localeParams[2]);
}
- return false;
+ return null;
}
public static boolean containsSubtypeOf(final InputMethodInfo imi,
@@ -418,15 +397,16 @@ public class InputMethodUtils {
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
if (checkCountry) {
- // TODO: Use {@link Locale#toLanguageTag()} and
- // {@link Locale#forLanguageTag(languageTag)} instead.
- if (!TextUtils.equals(subtype.getLocale(), locale.toString())) {
+ final Locale subtypeLocale = constructLocaleFromString(subtype.getLocale());
+ if (subtypeLocale == null ||
+ !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) ||
+ !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
continue;
}
} else {
final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
subtype.getLocale()));
- if (!subtypeLocale.getLanguage().equals(locale.getLanguage())) {
+ if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) {
continue;
}
}
@@ -438,25 +418,6 @@ public class InputMethodUtils {
return false;
}
- /**
- * @deprecated Use {@link #containsSubtypeOf(InputMethodInfo, Locale, boolean, String)} instead.
- */
- @Deprecated
- public static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
- final int N = imi.getSubtypeCount();
- for (int i = 0; i < N; ++i) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(i);
- if (!subtype.getLocale().startsWith(language)) {
- continue;
- }
- if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
- mode.equalsIgnoreCase(subtype.getMode())) {
- return true;
- }
- }
- return false;
- }
-
public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
final int subtypeCount = imi.getSubtypeCount();
@@ -489,12 +450,15 @@ public class InputMethodUtils {
while (i > 0) {
i--;
final InputMethodInfo imi = enabledImes.get(i);
- if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi)
- && !imi.isAuxiliaryIme()) {
+ if (imi.isAuxiliaryIme()) {
+ continue;
+ }
+ if (InputMethodUtils.isSystemIme(imi)
+ && containsSubtypeOf(imi, ENGLISH_LOCALE, false /* checkCountry */,
+ SUBTYPE_MODE_KEYBOARD)) {
return imi;
}
- if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)
- && !imi.isAuxiliaryIme()) {
+ if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)) {
firstFoundSystemIme = i;
}
}
@@ -518,7 +482,8 @@ public class InputMethodUtils {
return NOT_A_SUBTYPE_ID;
}
- private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+ @VisibleForTesting
+ public static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
Resources res, InputMethodInfo imi) {
final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
final String systemLocale = res.getConfiguration().locale.toString();
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/com/android/internal/net/VpnInfo.aidl
new file mode 100644
index 0000000..6fc97be
--- /dev/null
+++ b/core/java/com/android/internal/net/VpnInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net;
+
+parcelable VpnInfo;
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java
new file mode 100644
index 0000000..a676dac
--- /dev/null
+++ b/core/java/com/android/internal/net/VpnInfo.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A lightweight container used to carry information of the ongoing VPN.
+ * Internal use only..
+ *
+ * @hide
+ */
+public class VpnInfo implements Parcelable {
+ public int ownerUid;
+ public String vpnIface;
+ public String primaryUnderlyingIface;
+
+ @Override
+ public String toString() {
+ return "VpnInfo{" +
+ "ownerUid=" + ownerUid +
+ ", vpnIface='" + vpnIface + '\'' +
+ ", primaryUnderlyingIface='" + primaryUnderlyingIface + '\'' +
+ '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(ownerUid);
+ dest.writeString(vpnIface);
+ dest.writeString(primaryUnderlyingIface);
+ }
+
+ public static final Parcelable.Creator<VpnInfo> CREATOR = new Parcelable.Creator<VpnInfo>() {
+ @Override
+ public VpnInfo createFromParcel(Parcel source) {
+ VpnInfo info = new VpnInfo();
+ info.ownerUid = source.readInt();
+ info.vpnIface = source.readString();
+ info.primaryUnderlyingIface = source.readString();
+ return info;
+ }
+
+ @Override
+ public VpnInfo[] newArray(int size) {
+ return new VpnInfo[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index cfeca08..4cd959f 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -26,12 +26,15 @@ public class BatterySipper implements Comparable<BatterySipper> {
public double value;
public double[] values;
public DrainType drainType;
+
+ // Measured in milliseconds.
public long usageTime;
public long cpuTime;
public long gpsTime;
public long wifiRunningTime;
public long cpuFgTime;
public long wakeLockTime;
+
public long mobileRxPackets;
public long mobileTxPackets;
public long mobileActive;
@@ -48,6 +51,14 @@ public class BatterySipper implements Comparable<BatterySipper> {
public String[] mPackages;
public String packageWithHighestDrain;
+ // Measured in mAh (milli-ampere per hour).
+ public double wifiPower;
+ public double cpuPower;
+ public double wakeLockPower;
+ public double mobileRadioPower;
+ public double gpsPower;
+ public double sensorPower;
+
public enum DrainType {
IDLE,
CELL,
@@ -107,4 +118,31 @@ public class BatterySipper implements Comparable<BatterySipper> {
}
return uidObj.getUid();
}
+
+ /**
+ * Add stats from other to this BatterySipper.
+ */
+ public void add(BatterySipper other) {
+ cpuTime += other.cpuTime;
+ gpsTime += other.gpsTime;
+ wifiRunningTime += other.wifiRunningTime;
+ cpuFgTime += other.cpuFgTime;
+ wakeLockTime += other.wakeLockTime;
+ mobileRxPackets += other.mobileRxPackets;
+ mobileTxPackets += other.mobileTxPackets;
+ mobileActive += other.mobileActive;
+ mobileActiveCount += other.mobileActiveCount;
+ wifiRxPackets += other.wifiRxPackets;
+ wifiTxPackets += other.wifiTxPackets;
+ mobileRxBytes += other.mobileRxBytes;
+ mobileTxBytes += other.mobileTxBytes;
+ wifiRxBytes += other.wifiRxBytes;
+ wifiTxBytes += other.wifiTxBytes;
+ wifiPower += other.wifiPower;
+ gpsPower += other.gpsPower;
+ cpuPower += other.cpuPower;
+ sensorPower += other.sensorPower;
+ mobileRadioPower += other.mobileRadioPower;
+ wakeLockPower += other.wakeLockPower;
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index eae4427..d3611bf 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -344,6 +344,7 @@ public final class BatteryStatsHelper {
mMobilemsppList.add(bs);
}
}
+
for (int i=0; i<mUserSippers.size(); i++) {
List<BatterySipper> user = mUserSippers.valueAt(i);
for (int j=0; j<user.size(); j++) {
@@ -389,8 +390,8 @@ public final class BatteryStatsHelper {
private void processAppUsage(SparseArray<UserHandle> asUsers) {
final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
- SensorManager sensorManager = (SensorManager) mContext.getSystemService(
- Context.SENSOR_SERVICE);
+ final SensorManager sensorManager =
+ (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
final int which = mStatsType;
final int speedSteps = mPowerProfile.getNumSpeedSteps();
final double[] powerCpuNormal = new double[speedSteps];
@@ -401,238 +402,317 @@ public final class BatteryStatsHelper {
final double mobilePowerPerPacket = getMobilePowerPerPacket();
final double mobilePowerPerMs = getMobilePowerPerMs();
final double wifiPowerPerPacket = getWifiPowerPerPacket();
- long appWakelockTimeUs = 0;
+ long totalAppWakelockTimeUs = 0;
BatterySipper osApp = null;
mStatsPeriod = mTypeBatteryRealtime;
- SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+
+ final ArrayList<BatterySipper> appList = new ArrayList<>();
+
+ // Max values used to normalize later.
+ double maxWifiPower = 0;
+ double maxCpuPower = 0;
+ double maxWakeLockPower = 0;
+ double maxMobileRadioPower = 0;
+ double maxGpsPower = 0;
+ double maxSensorPower = 0;
+
+ final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
final int NU = uidStats.size();
for (int iu = 0; iu < NU; iu++) {
- Uid u = uidStats.valueAt(iu);
- double p; // in mAs
- double power = 0; // in mAs
- double highestDrain = 0;
- String packageWithHighestDrain = null;
- Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
- long cpuTime = 0;
- long cpuFgTime = 0;
- long wakelockTime = 0;
- long gpsTime = 0;
+ final Uid u = uidStats.valueAt(iu);
+ final BatterySipper app = new BatterySipper(
+ BatterySipper.DrainType.APP, u, new double[]{0});
+
+ final Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
if (processStats.size() > 0) {
- // Process CPU time
+ // Process CPU time.
+
+ // Keep track of the package with highest drain.
+ double highestDrain = 0;
+
for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
: processStats.entrySet()) {
Uid.Proc ps = ent.getValue();
- final long userTime = ps.getUserTime(which);
- final long systemTime = ps.getSystemTime(which);
- final long foregroundTime = ps.getForegroundTime(which);
- cpuFgTime += foregroundTime * 10; // convert to millis
- final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
- int totalTimeAtSpeeds = 0;
- // Get the total first
+ app.cpuFgTime += ps.getForegroundTime(which);
+ final long totalCpuTime = ps.getUserTime(which) + ps.getSystemTime(which);
+ app.cpuTime += totalCpuTime;
+
+ // Calculate the total CPU time spent at the various speed steps.
+ long totalTimeAtSpeeds = 0;
for (int step = 0; step < speedSteps; step++) {
cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
totalTimeAtSpeeds += cpuSpeedStepTimes[step];
}
- if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
- // Then compute the ratio of time spent at each speed
- double processPower = 0;
+ totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);
+
+ // Then compute the ratio of time spent at each speed and figure out
+ // the total power consumption.
+ double cpuPower = 0;
for (int step = 0; step < speedSteps; step++) {
- double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
- if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
- + step + " ratio=" + makemAh(ratio) + " power="
- + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));
- processPower += ratio * tmpCpuTime * powerCpuNormal[step];
+ final double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
+ final double cpuSpeedStepPower =
+ ratio * totalCpuTime * powerCpuNormal[step];
+ if (DEBUG && ratio != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
+ + step + " ratio=" + makemAh(ratio) + " power="
+ + makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
+ }
+ cpuPower += cpuSpeedStepPower;
}
- cpuTime += tmpCpuTime;
- if (DEBUG && processPower != 0) {
+
+ if (DEBUG && cpuPower != 0) {
Log.d(TAG, String.format("process %s, cpu power=%s",
- ent.getKey(), makemAh(processPower / (60*60*1000))));
+ ent.getKey(), makemAh(cpuPower / (60 * 60 * 1000))));
}
- power += processPower;
- if (packageWithHighestDrain == null
- || packageWithHighestDrain.startsWith("*")) {
- highestDrain = processPower;
- packageWithHighestDrain = ent.getKey();
- } else if (highestDrain < processPower
- && !ent.getKey().startsWith("*")) {
- highestDrain = processPower;
- packageWithHighestDrain = ent.getKey();
+ app.cpuPower += cpuPower;
+
+ // Each App can have multiple packages and with multiple running processes.
+ // Keep track of the package who's process has the highest drain.
+ if (app.packageWithHighestDrain == null ||
+ app.packageWithHighestDrain.startsWith("*")) {
+ highestDrain = cpuPower;
+ app.packageWithHighestDrain = ent.getKey();
+ } else if (highestDrain < cpuPower && !ent.getKey().startsWith("*")) {
+ highestDrain = cpuPower;
+ app.packageWithHighestDrain = ent.getKey();
}
}
}
- if (cpuFgTime > cpuTime) {
- if (DEBUG && cpuFgTime > cpuTime + 10000) {
+
+ // Ensure that the CPU times make sense.
+ if (app.cpuFgTime > app.cpuTime) {
+ if (DEBUG && app.cpuFgTime > app.cpuTime + 10000) {
Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
}
- cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
+
+ // Statistics may not have been gathered yet.
+ app.cpuTime = app.cpuFgTime;
}
- power /= (60*60*1000);
+
+ // Convert the CPU power to mAh
+ app.cpuPower /= (60 * 60 * 1000);
+ maxCpuPower = Math.max(maxCpuPower, app.cpuPower);
// Process wake lock usage
- Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
+ final Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
+ u.getWakelockStats();
+ long wakeLockTimeUs = 0;
for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
: wakelockStats.entrySet()) {
- Uid.Wakelock wakelock = wakelockEntry.getValue();
+ final Uid.Wakelock wakelock = wakelockEntry.getValue();
+
// Only care about partial wake locks since full wake locks
// are canceled when the user turns the screen off.
BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
if (timer != null) {
- wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which);
+ wakeLockTimeUs += timer.getTotalTimeLocked(mRawRealtime, which);
}
}
- appWakelockTimeUs += wakelockTime;
- wakelockTime /= 1000; // convert to millis
-
- // Add cost of holding a wake lock
- p = (wakelockTime
- * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);
- if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "
- + wakelockTime + " power=" + makemAh(p));
- power += p;
+ app.wakeLockTime = wakeLockTimeUs / 1000; // convert to millis
+ totalAppWakelockTimeUs += wakeLockTimeUs;
+
+ // Add cost of holding a wake lock.
+ app.wakeLockPower = (app.wakeLockTime *
+ mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60 * 60 * 1000);
+ if (DEBUG && app.wakeLockPower != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": wake "
+ + app.wakeLockTime + " power=" + makemAh(app.wakeLockPower));
+ }
+ maxWakeLockPower = Math.max(maxWakeLockPower, app.wakeLockPower);
- // Add cost of mobile traffic
- final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
- final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
- final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
- final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
+ // Add cost of mobile traffic.
final long mobileActive = u.getMobileRadioActiveTime(mStatsType);
+ app.mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
+ app.mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
+ app.mobileActive = mobileActive / 1000;
+ app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
+ app.mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
+ app.mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
+
if (mobileActive > 0) {
// We are tracking when the radio is up, so can use the active time to
// determine power use.
mAppMobileActive += mobileActive;
- p = (mobilePowerPerMs * mobileActive) / 1000;
+ app.mobileRadioPower = (mobilePowerPerMs * mobileActive) / 1000;
} else {
// We are not tracking when the radio is up, so must approximate power use
// based on the number of packets.
- p = (mobileRx + mobileTx) * mobilePowerPerPacket;
+ app.mobileRadioPower = (app.mobileRxPackets + app.mobileTxPackets)
+ * mobilePowerPerPacket;
}
- if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
- + (mobileRx+mobileTx) + " active time " + mobileActive
- + " power=" + makemAh(p));
- power += p;
+ if (DEBUG && app.mobileRadioPower != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+ + (app.mobileRxPackets + app.mobileTxPackets)
+ + " active time " + mobileActive
+ + " power=" + makemAh(app.mobileRadioPower));
+ }
+ maxMobileRadioPower = Math.max(maxMobileRadioPower, app.mobileRadioPower);
// Add cost of wifi traffic
- final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
- final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
- final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
- final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
- p = (wifiRx + wifiTx) * wifiPowerPerPacket;
- if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
- + (mobileRx+mobileTx) + " power=" + makemAh(p));
- power += p;
+ app.wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
+ app.wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
+ app.wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
+ app.wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
+
+ final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets)
+ * wifiPowerPerPacket;
+ if (DEBUG && wifiPacketPower != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
+ + (app.wifiRxPackets + app.wifiTxPackets)
+ + " power=" + makemAh(wifiPacketPower));
+ }
// Add cost of keeping WIFI running.
- long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000;
- mAppWifiRunning += wifiRunningTimeMs;
- p = (wifiRunningTimeMs
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
- if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "
- + wifiRunningTimeMs + " power=" + makemAh(p));
- power += p;
+ app.wifiRunningTime = u.getWifiRunningTime(mRawRealtime, which) / 1000;
+ mAppWifiRunning += app.wifiRunningTime;
+
+ final double wifiLockPower = (app.wifiRunningTime
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60 * 60 * 1000);
+ if (DEBUG && wifiLockPower != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": wifi running "
+ + app.wifiRunningTime + " power=" + makemAh(wifiLockPower));
+ }
// Add cost of WIFI scans
- long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
- p = (wifiScanTimeMs
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);
- if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
- + " power=" + makemAh(p));
- power += p;
+ final long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
+ final double wifiScanPower = (wifiScanTimeMs
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN))
+ / (60 * 60 * 1000);
+ if (DEBUG && wifiScanPower != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
+ + " power=" + makemAh(wifiScanPower));
+ }
+
+ // Add cost of WIFI batch scans.
+ double wifiBatchScanPower = 0;
for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
- long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
- p = ((batchScanTimeMs
+ final long batchScanTimeMs =
+ u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
+ final double batchScanPower = ((batchScanTimeMs
* mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
- ) / (60*60*1000);
- if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
- + " time=" + batchScanTimeMs + " power=" + makemAh(p));
- power += p;
+ ) / (60 * 60 * 1000);
+ if (DEBUG && batchScanPower != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
+ + " time=" + batchScanTimeMs + " power=" + makemAh(batchScanPower));
+ }
+ wifiBatchScanPower += batchScanPower;
}
+ // Add up all the WiFi costs.
+ app.wifiPower = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower;
+ maxWifiPower = Math.max(maxWifiPower, app.wifiPower);
+
// Process Sensor usage
- SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
- int NSE = sensorStats.size();
- for (int ise=0; ise<NSE; ise++) {
- Uid.Sensor sensor = sensorStats.valueAt(ise);
- int sensorHandle = sensorStats.keyAt(ise);
- BatteryStats.Timer timer = sensor.getSensorTime();
- long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
- double multiplier = 0;
+ final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
+ final int NSE = sensorStats.size();
+ for (int ise = 0; ise < NSE; ise++) {
+ final Uid.Sensor sensor = sensorStats.valueAt(ise);
+ final int sensorHandle = sensorStats.keyAt(ise);
+ final BatteryStats.Timer timer = sensor.getSensorTime();
+ final long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
+ double sensorPower = 0;
switch (sensorHandle) {
case Uid.Sensor.GPS:
- multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
- gpsTime = sensorTime;
+ app.gpsTime = sensorTime;
+ app.gpsPower = (app.gpsTime
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON))
+ / (60 * 60 * 1000);
+ sensorPower = app.gpsPower;
+ maxGpsPower = Math.max(maxGpsPower, app.gpsPower);
break;
default:
List<Sensor> sensorList = sensorManager.getSensorList(
android.hardware.Sensor.TYPE_ALL);
for (android.hardware.Sensor s : sensorList) {
if (s.getHandle() == sensorHandle) {
- multiplier = s.getPower();
+ sensorPower = (sensorTime * s.getPower()) / (60 * 60 * 1000);
+ app.sensorPower += sensorPower;
break;
}
}
}
- p = (multiplier * sensorTime) / (60*60*1000);
- if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
- + " time=" + sensorTime + " power=" + makemAh(p));
- power += p;
+ if (DEBUG && sensorPower != 0) {
+ Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
+ + " time=" + sensorTime + " power=" + makemAh(sensorPower));
+ }
}
+ maxSensorPower = Math.max(maxSensorPower, app.sensorPower);
- if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",
- u.getUid(), makemAh(power)));
-
- // Add the app to the list if it is consuming power
- final int userId = UserHandle.getUserId(u.getUid());
- if (power != 0 || u.getUid() == 0) {
- BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,
- new double[] {power});
- app.cpuTime = cpuTime;
- app.gpsTime = gpsTime;
- app.wifiRunningTime = wifiRunningTimeMs;
- app.cpuFgTime = cpuFgTime;
- app.wakeLockTime = wakelockTime;
- app.mobileRxPackets = mobileRx;
- app.mobileTxPackets = mobileTx;
- app.mobileActive = mobileActive / 1000;
- app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
- app.wifiRxPackets = wifiRx;
- app.wifiTxPackets = wifiTx;
- app.mobileRxBytes = mobileRxB;
- app.mobileTxBytes = mobileTxB;
- app.wifiRxBytes = wifiRxB;
- app.wifiTxBytes = wifiTxB;
- app.packageWithHighestDrain = packageWithHighestDrain;
- if (u.getUid() == Process.WIFI_UID) {
- mWifiSippers.add(app);
- mWifiPower += power;
- } else if (u.getUid() == Process.BLUETOOTH_UID) {
- mBluetoothSippers.add(app);
- mBluetoothPower += power;
- } else if (!forAllUsers && asUsers.get(userId) == null
- && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
- List<BatterySipper> list = mUserSippers.get(userId);
- if (list == null) {
- list = new ArrayList<BatterySipper>();
- mUserSippers.put(userId, list);
- }
- list.add(app);
- if (power != 0) {
- Double userPower = mUserPower.get(userId);
- if (userPower == null) {
- userPower = power;
- } else {
- userPower += power;
- }
- mUserPower.put(userId, userPower);
- }
- } else {
- mUsageList.add(app);
- if (power > mMaxPower) mMaxPower = power;
- if (power > mMaxRealPower) mMaxRealPower = power;
- mComputedPower += power;
+ final double totalUnnormalizedPower = app.cpuPower + app.wifiPower + app.wakeLockPower
+ + app.mobileRadioPower + app.gpsPower + app.sensorPower;
+ if (DEBUG && totalUnnormalizedPower != 0) {
+ Log.d(TAG, String.format("UID %d: total power=%s",
+ u.getUid(), makemAh(totalUnnormalizedPower)));
+ }
+
+ // Add the app to the list if it is consuming power.
+ if (totalUnnormalizedPower != 0 || u.getUid() == 0) {
+ appList.add(app);
+ }
+ }
+
+ // Fetch real power consumption from hardware.
+ double actualTotalWifiPower = 0.0;
+ if (mStats.getWifiControllerActivity(BatteryStats.CONTROLLER_ENERGY, mStatsType) != 0) {
+ final double kDefaultVoltage = 3.36;
+ final long energy = mStats.getWifiControllerActivity(
+ BatteryStats.CONTROLLER_ENERGY, mStatsType);
+ final double voltage = mPowerProfile.getAveragePowerOrDefault(
+ PowerProfile.OPERATING_VOLTAGE_WIFI, kDefaultVoltage);
+ actualTotalWifiPower = energy / (voltage * 1000*60*60);
+ }
+
+ final int appCount = appList.size();
+ for (int i = 0; i < appCount; i++) {
+ // Normalize power where possible.
+ final BatterySipper app = appList.get(i);
+ if (actualTotalWifiPower != 0) {
+ app.wifiPower = (app.wifiPower / maxWifiPower) * actualTotalWifiPower;
+ }
+
+ // Assign the final power consumption here.
+ final double power = app.wifiPower + app.cpuPower + app.wakeLockPower
+ + app.mobileRadioPower + app.gpsPower + app.sensorPower;
+ app.values[0] = app.value = power;
+
+ //
+ // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
+ //
+
+ final int uid = app.getUid();
+ final int userId = UserHandle.getUserId(uid);
+ if (uid == Process.WIFI_UID) {
+ mWifiSippers.add(app);
+ mWifiPower += power;
+ } else if (uid == Process.BLUETOOTH_UID) {
+ mBluetoothSippers.add(app);
+ mBluetoothPower += power;
+ } else if (!forAllUsers && asUsers.get(userId) == null
+ && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+ // We are told to just report this user's apps as one large entry.
+ List<BatterySipper> list = mUserSippers.get(userId);
+ if (list == null) {
+ list = new ArrayList<>();
+ mUserSippers.put(userId, list);
}
- if (u.getUid() == 0) {
- osApp = app;
+ list.add(app);
+
+ Double userPower = mUserPower.get(userId);
+ if (userPower == null) {
+ userPower = power;
+ } else {
+ userPower += power;
}
+ mUserPower.put(userId, userPower);
+ } else {
+ mUsageList.add(app);
+ if (power > mMaxPower) mMaxPower = power;
+ if (power > mMaxRealPower) mMaxRealPower = power;
+ mComputedPower += power;
+ }
+
+ if (uid == 0) {
+ osApp = app;
}
}
@@ -641,7 +721,7 @@ public final class BatteryStatsHelper {
// this remainder to the OS, if possible.
if (osApp != null) {
long wakeTimeMillis = mBatteryUptime / 1000;
- wakeTimeMillis -= (appWakelockTimeUs / 1000)
+ wakeTimeMillis -= (totalAppWakelockTimeUs / 1000)
+ (mStats.getScreenOnTime(mRawRealtime, which) / 1000);
if (wakeTimeMillis > 0) {
double power = (wakeTimeMillis
@@ -741,46 +821,11 @@ public final class BatteryStatsHelper {
for (int i=0; i<from.size(); i++) {
BatterySipper wbs = from.get(i);
if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
- bs.cpuTime += wbs.cpuTime;
- bs.gpsTime += wbs.gpsTime;
- bs.wifiRunningTime += wbs.wifiRunningTime;
- bs.cpuFgTime += wbs.cpuFgTime;
- bs.wakeLockTime += wbs.wakeLockTime;
- bs.mobileRxPackets += wbs.mobileRxPackets;
- bs.mobileTxPackets += wbs.mobileTxPackets;
- bs.mobileActive += wbs.mobileActive;
- bs.mobileActiveCount += wbs.mobileActiveCount;
- bs.wifiRxPackets += wbs.wifiRxPackets;
- bs.wifiTxPackets += wbs.wifiTxPackets;
- bs.mobileRxBytes += wbs.mobileRxBytes;
- bs.mobileTxBytes += wbs.mobileTxBytes;
- bs.wifiRxBytes += wbs.wifiRxBytes;
- bs.wifiTxBytes += wbs.wifiTxBytes;
+ bs.add(wbs);
}
bs.computeMobilemspp();
}
- private void addWiFiUsage() {
- long onTimeMs = mStats.getWifiOnTime(mRawRealtime, mStatsType) / 1000;
- long runningTimeMs = mStats.getGlobalWifiRunningTime(mRawRealtime, mStatsType) / 1000;
- if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs
- + " app runningTime=" + mAppWifiRunning);
- runningTimeMs -= mAppWifiRunning;
- if (runningTimeMs < 0) runningTimeMs = 0;
- double wifiPower = (onTimeMs * 0 /* TODO */
- * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
- + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON))
- / (60*60*1000);
- if (DEBUG && wifiPower != 0) {
- Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower));
- }
- if ((wifiPower+mWifiPower) != 0) {
- BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs,
- wifiPower + mWifiPower);
- aggregateSippers(bs, mWifiSippers, "WIFI");
- }
- }
-
private void addIdleUsage() {
long idleTimeMs = (mTypeBatteryRealtime
- mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000;
@@ -794,24 +839,81 @@ public final class BatteryStatsHelper {
}
}
- private void addBluetoothUsage() {
- long btOnTimeMs = mStats.getBluetoothOnTime(mRawRealtime, mStatsType) / 1000;
- double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
- / (60*60*1000);
- if (DEBUG && btPower != 0) {
- Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower));
+ /**
+ * We do per-app blaming of WiFi activity. If energy info is reported from the controller,
+ * then only the WiFi process gets blamed here since we normalize power calculations and
+ * assign all the power drain to apps. If energy info is not reported, we attribute the
+ * difference between total running time of WiFi for all apps and the actual running time
+ * of WiFi to the WiFi subsystem.
+ */
+ private void addWiFiUsage() {
+ final long idleTimeMs = mStats.getWifiControllerActivity(
+ BatteryStats.CONTROLLER_IDLE_TIME, mStatsType);
+ final long txTimeMs = mStats.getWifiControllerActivity(
+ BatteryStats.CONTROLLER_TX_TIME, mStatsType);
+ final long rxTimeMs = mStats.getWifiControllerActivity(
+ BatteryStats.CONTROLLER_RX_TIME, mStatsType);
+ final long energy = mStats.getWifiControllerActivity(
+ BatteryStats.CONTROLLER_ENERGY, mStatsType);
+ final long totalTimeRunning = idleTimeMs + txTimeMs + rxTimeMs;
+
+ double powerDrain = 0;
+ if (energy == 0 && totalTimeRunning > 0) {
+ // Energy is not reported, which means we may have left over power drain not attributed
+ // to any app. Assign this power to the WiFi app.
+ // TODO(adamlesinski): This mimics the old behavior. However, mAppWifiRunningTime
+ // is the accumulation of the time each app kept the WiFi chip on. Multiple apps
+ // can do this at the same time, so these times do not add up to the total time
+ // the WiFi chip was on. Consider normalizing the time spent running and calculating
+ // power from that? Normalizing the times will assign a weight to each app which
+ // should better represent power usage.
+ powerDrain = ((totalTimeRunning - mAppWifiRunning)
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
}
- int btPingCount = mStats.getBluetoothPingCount();
- double pingPower = (btPingCount
- * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD))
- / (60*60*1000);
- if (DEBUG && pingPower != 0) {
- Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower));
+
+ if (DEBUG && powerDrain != 0) {
+ Log.d(TAG, "Wifi active: time=" + (txTimeMs + rxTimeMs)
+ + " power=" + makemAh(powerDrain));
+ }
+
+ // TODO(adamlesinski): mWifiPower is already added as a BatterySipper...
+ // Are we double counting here?
+ final double power = mWifiPower + powerDrain;
+ if (power > 0) {
+ BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, totalTimeRunning, power);
+ aggregateSippers(bs, mWifiSippers, "WIFI");
}
- btPower += pingPower;
- if ((btPower+mBluetoothPower) != 0) {
- BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs,
- btPower + mBluetoothPower);
+ }
+
+ /**
+ * Bluetooth usage is not attributed to any apps yet, so the entire blame goes to the
+ * Bluetooth Category.
+ */
+ private void addBluetoothUsage() {
+ final double kDefaultVoltage = 3.36;
+ final long idleTimeMs = mStats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_IDLE_TIME, mStatsType);
+ final long txTimeMs = mStats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_TX_TIME, mStatsType);
+ final long rxTimeMs = mStats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_RX_TIME, mStatsType);
+ final long energy = mStats.getBluetoothControllerActivity(
+ BatteryStats.CONTROLLER_ENERGY, mStatsType);
+ final double voltage = mPowerProfile.getAveragePowerOrDefault(
+ PowerProfile.OPERATING_VOLTAGE_BLUETOOTH, kDefaultVoltage);
+
+ // energy is measured in mA * V * ms, and we are interested in mAh
+ final double powerDrain = energy / (voltage * 60*60*1000);
+
+ if (DEBUG && powerDrain != 0) {
+ Log.d(TAG, "Bluetooth active: time=" + (txTimeMs + rxTimeMs)
+ + " power=" + makemAh(powerDrain));
+ }
+
+ final long totalTime = idleTimeMs + txTimeMs + rxTimeMs;
+ final double power = mBluetoothPower + powerDrain;
+ if (power > 0) {
+ BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, totalTime, power);
aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 20bb95e..f9b1ca1 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -20,11 +20,15 @@ import static android.net.NetworkStats.UID_ALL;
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
import android.app.ActivityManager;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiManager;
import android.os.BadParcelableException;
import android.os.BatteryManager;
@@ -38,6 +42,8 @@ import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.WorkSource;
@@ -56,20 +62,29 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
+import android.util.Xml;
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -94,7 +109,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 116 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 119 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -111,6 +126,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private final JournaledFile mFile;
public final AtomicFile mCheckinFile;
+ public final AtomicFile mDailyFile;
static final int MSG_UPDATE_WAKELOCKS = 1;
static final int MSG_REPORT_POWER_CHANGE = 2;
@@ -208,7 +224,7 @@ public final class BatteryStatsImpl extends BatteryStats {
final HistoryItem mHistoryLastLastWritten = new HistoryItem();
final HistoryItem mHistoryReadTmp = new HistoryItem();
final HistoryItem mHistoryAddTmp = new HistoryItem();
- final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<HistoryTag, Integer>();
+ final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap();
String[] mReadHistoryStrings;
int[] mReadHistoryUids;
int mReadHistoryChars;
@@ -227,6 +243,38 @@ public final class BatteryStatsImpl extends BatteryStats {
HistoryItem mHistoryLastEnd;
HistoryItem mHistoryCache;
+ // Used by computeHistoryStepDetails
+ HistoryStepDetails mLastHistoryStepDetails = null;
+ byte mLastHistoryStepLevel = 0;
+ final HistoryStepDetails mCurHistoryStepDetails = new HistoryStepDetails();
+ final HistoryStepDetails mReadHistoryStepDetails = new HistoryStepDetails();
+ final HistoryStepDetails mTmpHistoryStepDetails = new HistoryStepDetails();
+ /**
+ * Total time (in 1/100 sec) spent executing in user code.
+ */
+ long mLastStepCpuUserTime;
+ long mCurStepCpuUserTime;
+ /**
+ * Total time (in 1/100 sec) spent executing in kernel code.
+ */
+ long mLastStepCpuSystemTime;
+ long mCurStepCpuSystemTime;
+ /**
+ * Times from /proc/stat
+ */
+ long mLastStepStatUserTime;
+ long mLastStepStatSystemTime;
+ long mLastStepStatIOWaitTime;
+ long mLastStepStatIrqTime;
+ long mLastStepStatSoftIrqTime;
+ long mLastStepStatIdleTime;
+ long mCurStepStatUserTime;
+ long mCurStepStatSystemTime;
+ long mCurStepStatIOWaitTime;
+ long mCurStepStatIrqTime;
+ long mCurStepStatSoftIrqTime;
+ long mCurStepStatIdleTime;
+
private HistoryItem mHistoryIterator;
private boolean mReadOverflow;
private boolean mIteratingHistory;
@@ -290,6 +338,12 @@ public final class BatteryStatsImpl extends BatteryStats {
final LongSamplingCounter[] mNetworkPacketActivityCounters =
new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ final LongSamplingCounter[] mBluetoothActivityCounters =
+ new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES];
+
+ final LongSamplingCounter[] mWifiActivityCounters =
+ new LongSamplingCounter[NUM_CONTROLLER_ACTIVITY_TYPES];
+
boolean mWifiOn;
StopwatchTimer mWifiOnTimer;
@@ -354,16 +408,22 @@ public final class BatteryStatsImpl extends BatteryStats {
int mModStepMode = 0;
int mLastDischargeStepLevel;
- long mLastDischargeStepTime;
int mMinDischargeStepLevel;
- int mNumDischargeStepDurations;
- final long[] mDischargeStepDurations = new long[MAX_LEVEL_STEPS];
+ final LevelStepTracker mDischargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS);
+ final LevelStepTracker mDailyDischargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS*2);
int mLastChargeStepLevel;
- long mLastChargeStepTime;
int mMaxChargeStepLevel;
- int mNumChargeStepDurations;
- final long[] mChargeStepDurations = new long[MAX_LEVEL_STEPS];
+ final LevelStepTracker mChargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS);
+ final LevelStepTracker mDailyChargeStepTracker = new LevelStepTracker(MAX_LEVEL_STEPS*2);
+
+ static final int MAX_DAILY_ITEMS = 10;
+
+ long mDailyStartTime = 0;
+ long mNextMinDailyDeadline = 0;
+ long mNextMaxDailyDeadline = 0;
+
+ final ArrayList<DailyItem> mDailyItems = new ArrayList<>();
long mLastWriteTime = 0; // Milliseconds
@@ -446,6 +506,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public BatteryStatsImpl() {
mFile = null;
mCheckinFile = null;
+ mDailyFile = null;
mHandler = null;
clearHistoryLocked();
}
@@ -1938,6 +1999,10 @@ public final class BatteryStatsImpl extends BatteryStats {
static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
static final int STATE_BATTERY_PLUG_SHIFT = 24;
+ // We use the low bit of the battery state int to indicate that we have full details
+ // from a battery level change.
+ static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001;
+
public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
dest.writeInt(DELTA_TIME_ABS);
@@ -1958,7 +2023,11 @@ public final class BatteryStatsImpl extends BatteryStats {
deltaTimeToken = (int)deltaTime;
}
int firstToken = deltaTimeToken | (cur.states&DELTA_STATE_MASK);
- final int batteryLevelInt = buildBatteryLevelInt(cur);
+ final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
+ ? BATTERY_DELTA_LEVEL_FLAG : 0;
+ final boolean computeStepDetails = includeStepDetails != 0
+ || mLastHistoryStepDetails == null;
+ final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
if (batteryLevelIntChanged) {
firstToken |= DELTA_BATTERY_LEVEL_FLAG;
@@ -2040,12 +2109,26 @@ public final class BatteryStatsImpl extends BatteryStats {
+ cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ cur.eventTag.string);
}
+ if (computeStepDetails) {
+ computeHistoryStepDetails(mCurHistoryStepDetails, mLastHistoryStepDetails);
+ if (includeStepDetails != 0) {
+ mCurHistoryStepDetails.writeToParcel(dest);
+ }
+ cur.stepDetails = mCurHistoryStepDetails;
+ mLastHistoryStepDetails = mCurHistoryStepDetails;
+ } else {
+ cur.stepDetails = null;
+ }
+ if (mLastHistoryStepLevel < cur.batteryLevel) {
+ mLastHistoryStepDetails = null;
+ }
+ mLastHistoryStepLevel = cur.batteryLevel;
}
private int buildBatteryLevelInt(HistoryItem h) {
return ((((int)h.batteryLevel)<<25)&0xfe000000)
- | ((((int)h.batteryTemperature)<<14)&0x01ffc000)
- | (((int)h.batteryVoltage)&0x00003fff);
+ | ((((int)h.batteryTemperature)<<14)&0x01ff8000)
+ | ((((int)h.batteryVoltage)<<1)&0x00007fff);
}
private int buildStateInt(HistoryItem h) {
@@ -2063,6 +2146,98 @@ public final class BatteryStatsImpl extends BatteryStats {
| (h.states&(~DELTA_STATE_MASK));
}
+ private void computeHistoryStepDetails(final HistoryStepDetails out,
+ final HistoryStepDetails last) {
+ final HistoryStepDetails tmp = last != null ? mTmpHistoryStepDetails : out;
+
+ // Perform a CPU update right after we do this collection, so we have started
+ // collecting good data for the next step.
+ requestImmediateCpuUpdate();
+
+ if (last == null) {
+ // We are not generating a delta, so all we need to do is reset the stats
+ // we will later be doing a delta from.
+ final int NU = mUidStats.size();
+ for (int i=0; i<NU; i++) {
+ final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+ uid.mLastStepUserTime = uid.mCurStepUserTime;
+ uid.mLastStepSystemTime = uid.mCurStepSystemTime;
+ }
+ mLastStepCpuUserTime = mCurStepCpuUserTime;
+ mLastStepCpuSystemTime = mCurStepCpuSystemTime;
+ mLastStepStatUserTime = mCurStepStatUserTime;
+ mLastStepStatSystemTime = mCurStepStatSystemTime;
+ mLastStepStatIOWaitTime = mCurStepStatIOWaitTime;
+ mLastStepStatIrqTime = mCurStepStatIrqTime;
+ mLastStepStatSoftIrqTime = mCurStepStatSoftIrqTime;
+ mLastStepStatIdleTime = mCurStepStatIdleTime;
+ tmp.clear();
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTime + " sys="
+ + mLastStepStatSystemTime + " io=" + mLastStepStatIOWaitTime
+ + " irq=" + mLastStepStatIrqTime + " sirq="
+ + mLastStepStatSoftIrqTime + " idle=" + mLastStepStatIdleTime);
+ Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTime + " sys="
+ + mCurStepStatSystemTime + " io=" + mCurStepStatIOWaitTime
+ + " irq=" + mCurStepStatIrqTime + " sirq="
+ + mCurStepStatSoftIrqTime + " idle=" + mCurStepStatIdleTime);
+ }
+ out.userTime = (int)(mCurStepCpuUserTime - mLastStepCpuUserTime);
+ out.systemTime = (int)(mCurStepCpuSystemTime - mLastStepCpuSystemTime);
+ out.statUserTime = (int)(mCurStepStatUserTime - mLastStepStatUserTime);
+ out.statSystemTime = (int)(mCurStepStatSystemTime - mLastStepStatSystemTime);
+ out.statIOWaitTime = (int)(mCurStepStatIOWaitTime - mLastStepStatIOWaitTime);
+ out.statIrqTime = (int)(mCurStepStatIrqTime - mLastStepStatIrqTime);
+ out.statSoftIrqTime = (int)(mCurStepStatSoftIrqTime - mLastStepStatSoftIrqTime);
+ out.statIdlTime = (int)(mCurStepStatIdleTime - mLastStepStatIdleTime);
+ out.appCpuUid1 = out.appCpuUid2 = out.appCpuUid3 = -1;
+ out.appCpuUTime1 = out.appCpuUTime2 = out.appCpuUTime3 = 0;
+ out.appCpuSTime1 = out.appCpuSTime2 = out.appCpuSTime3 = 0;
+ final int NU = mUidStats.size();
+ for (int i=0; i<NU; i++) {
+ final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+ final int totalUTime = (int)(uid.mCurStepUserTime - uid.mLastStepUserTime);
+ final int totalSTime = (int)(uid.mCurStepSystemTime - uid.mLastStepSystemTime);
+ final int totalTime = totalUTime + totalSTime;
+ uid.mLastStepUserTime = uid.mCurStepUserTime;
+ uid.mLastStepSystemTime = uid.mCurStepSystemTime;
+ if (totalTime <= (out.appCpuUTime3+out.appCpuSTime3)) {
+ continue;
+ }
+ if (totalTime <= (out.appCpuUTime2+out.appCpuSTime2)) {
+ out.appCpuUid3 = uid.mUid;
+ out.appCpuUTime3 = totalUTime;
+ out.appCpuSTime3 = totalSTime;
+ } else {
+ out.appCpuUid3 = out.appCpuUid2;
+ out.appCpuUTime3 = out.appCpuUTime2;
+ out.appCpuSTime3 = out.appCpuSTime2;
+ if (totalTime <= (out.appCpuUTime1+out.appCpuSTime1)) {
+ out.appCpuUid2 = uid.mUid;
+ out.appCpuUTime2 = totalUTime;
+ out.appCpuSTime2 = totalSTime;
+ } else {
+ out.appCpuUid2 = out.appCpuUid1;
+ out.appCpuUTime2 = out.appCpuUTime1;
+ out.appCpuSTime2 = out.appCpuSTime1;
+ out.appCpuUid1 = uid.mUid;
+ out.appCpuUTime1 = totalUTime;
+ out.appCpuSTime1 = totalSTime;
+ }
+ }
+ }
+ mLastStepCpuUserTime = mCurStepCpuUserTime;
+ mLastStepCpuSystemTime = mCurStepCpuSystemTime;
+ mLastStepStatUserTime = mCurStepStatUserTime;
+ mLastStepStatSystemTime = mCurStepStatSystemTime;
+ mLastStepStatIOWaitTime = mCurStepStatIOWaitTime;
+ mLastStepStatIrqTime = mCurStepStatIrqTime;
+ mLastStepStatSoftIrqTime = mCurStepStatSoftIrqTime;
+ mLastStepStatIdleTime = mCurStepStatIdleTime;
+ }
+
public void readHistoryDelta(Parcel src, HistoryItem cur) {
int firstToken = src.readInt();
int deltaTimeToken = firstToken&DELTA_TIME_MASK;
@@ -2091,8 +2266,9 @@ public final class BatteryStatsImpl extends BatteryStats {
cur.numReadInts += 2;
}
+ final int batteryLevelInt;
if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
- int batteryLevelInt = src.readInt();
+ batteryLevelInt = src.readInt();
cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f);
cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21);
cur.batteryVoltage = (char)(batteryLevelInt&0x3fff);
@@ -2102,6 +2278,8 @@ public final class BatteryStatsImpl extends BatteryStats {
+ " batteryLevel=" + cur.batteryLevel
+ " batteryTemp=" + cur.batteryTemperature
+ " batteryVolt=" + (int)cur.batteryVoltage);
+ } else {
+ batteryLevelInt = 0;
}
if ((firstToken&DELTA_STATE_FLAG) != 0) {
@@ -2180,6 +2358,13 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
cur.eventCode = HistoryItem.EVENT_NONE;
}
+
+ if ((batteryLevelInt&BATTERY_DELTA_LEVEL_FLAG) != 0) {
+ cur.stepDetails = mReadHistoryStepDetails;
+ cur.stepDetails.readFromParcel(src);
+ } else {
+ cur.stepDetails = null;
+ }
}
@Override
@@ -2207,6 +2392,7 @@ public final class BatteryStatsImpl extends BatteryStats {
&& (diffStates2&lastDiffStates2) == 0
&& (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
&& (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
+ && mHistoryLastWritten.stepDetails == null
&& (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
|| cur.eventCode == HistoryItem.EVENT_NONE)
&& mHistoryLastWritten.batteryLevel == cur.batteryLevel
@@ -2632,6 +2818,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ private void requestImmediateCpuUpdate() {
+ mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
+ mHandler.sendEmptyMessage(MSG_UPDATE_WAKELOCKS);
+ }
+
public void setRecordAllHistoryLocked(boolean enabled) {
mRecordAllHistory = enabled;
if (!enabled) {
@@ -2823,6 +3014,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public int startAddingCpuLocked() {
mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
+ if (!mOnBatteryInternal) {
+ return -1;
+ }
+
final int N = mPartialTimers.size();
if (N == 0) {
mLastPartialTimers.clear();
@@ -2853,7 +3048,23 @@ public final class BatteryStatsImpl extends BatteryStats {
return 0;
}
- public void finishAddingCpuLocked(int perc, int utime, int stime, long[] cpuSpeedTimes) {
+ public void finishAddingCpuLocked(int perc, int remainUTime, int remainSTtime,
+ int totalUTime, int totalSTime, int statUserTime, int statSystemTime,
+ int statIOWaitTime, int statIrqTime, int statSoftIrqTime, int statIdleTime,
+ long[] cpuSpeedTimes) {
+ if (DEBUG) Slog.d(TAG, "Adding cpu: tuser=" + totalUTime + " tsys=" + totalSTime
+ + " user=" + statUserTime + " sys=" + statSystemTime
+ + " io=" + statIOWaitTime + " irq=" + statIrqTime
+ + " sirq=" + statSoftIrqTime + " idle=" + statIdleTime);
+ mCurStepCpuUserTime += totalUTime;
+ mCurStepCpuSystemTime += totalSTime;
+ mCurStepStatUserTime += statUserTime;
+ mCurStepStatSystemTime += statSystemTime;
+ mCurStepStatIOWaitTime += statIOWaitTime;
+ mCurStepStatIrqTime += statIrqTime;
+ mCurStepStatSoftIrqTime += statSoftIrqTime;
+ mCurStepStatIdleTime += statIdleTime;
+
final int N = mPartialTimers.size();
if (perc != 0) {
int num = 0;
@@ -2874,26 +3085,24 @@ public final class BatteryStatsImpl extends BatteryStats {
if (st.mInList) {
Uid uid = st.mUid;
if (uid != null && uid.mUid != Process.SYSTEM_UID) {
- int myUTime = utime/num;
- int mySTime = stime/num;
- utime -= myUTime;
- stime -= mySTime;
+ int myUTime = remainUTime/num;
+ int mySTime = remainSTtime/num;
+ remainUTime -= myUTime;
+ remainSTtime -= mySTime;
num--;
Uid.Proc proc = uid.getProcessStatsLocked("*wakelock*");
- proc.addCpuTimeLocked(myUTime, mySTime);
- proc.addSpeedStepTimes(cpuSpeedTimes);
+ proc.addCpuTimeLocked(myUTime, mySTime, cpuSpeedTimes);
}
}
}
}
// Just in case, collect any lost CPU time.
- if (utime != 0 || stime != 0) {
+ if (remainUTime != 0 || remainSTtime != 0) {
Uid uid = getUidStatsLocked(Process.SYSTEM_UID);
if (uid != null) {
Uid.Proc proc = uid.getProcessStatsLocked("*lost*");
- proc.addCpuTimeLocked(utime, stime);
- proc.addSpeedStepTimes(cpuSpeedTimes);
+ proc.addCpuTimeLocked(remainUTime, remainSTtime, cpuSpeedTimes);
}
}
}
@@ -3019,6 +3228,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteScreenStateLocked(int state) {
if (mScreenState != state) {
+ recordDailyStatsIfNeededLocked(true);
final int oldState = mScreenState;
mScreenState = state;
if (DEBUG) Slog.v(TAG, "Screen state: oldState=" + Display.stateToString(oldState)
@@ -4109,6 +4319,20 @@ public final class BatteryStatsImpl extends BatteryStats {
return mBluetoothStateTimer[bluetoothState].getCountLocked(which);
}
+ @Override public long getBluetoothControllerActivity(int type, int which) {
+ if (type >= 0 && type < mBluetoothActivityCounters.length) {
+ return mBluetoothActivityCounters[type].getCountLocked(which);
+ }
+ return 0;
+ }
+
+ @Override public long getWifiControllerActivity(int type, int which) {
+ if (type >= 0 && type < mWifiActivityCounters.length) {
+ return mWifiActivityCounters[type].getCountLocked(which);
+ }
+ return 0;
+ }
+
@Override public long getFlashlightOnTime(long elapsedRealtimeUs, int which) {
return mFlashlightOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@@ -4214,6 +4438,14 @@ public final class BatteryStatsImpl extends BatteryStats {
LongSamplingCounter mMobileRadioActiveCount;
/**
+ * The CPU times we had at the last history details update.
+ */
+ long mLastStepUserTime;
+ long mLastStepSystemTime;
+ long mCurStepUserTime;
+ long mCurStepSystemTime;
+
+ /**
* The statistics we have collected for this uid's wake locks.
*/
final OverflowArrayMap<Wakelock> mWakelockStats = new OverflowArrayMap<Wakelock>() {
@@ -4543,6 +4775,14 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public int getWifiScanCount(int which) {
+ if (mWifiScanTimer == null) {
+ return 0;
+ }
+ return mWifiScanTimer.getCountLocked(which);
+ }
+
+ @Override
public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
if (mWifiBatchedScanTimer[csphBin] == null) {
@@ -4552,6 +4792,15 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public int getWifiBatchedScanCount(int csphBin, int which) {
+ if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
+ if (mWifiBatchedScanTimer[csphBin] == null) {
+ return 0;
+ }
+ return mWifiBatchedScanTimer[csphBin].getCountLocked(which);
+ }
+
+ @Override
public long getWifiMulticastTime(long elapsedRealtimeUs, int which) {
if (mWifiMulticastTimer == null) {
return 0;
@@ -4876,6 +5125,9 @@ public final class BatteryStatsImpl extends BatteryStats {
mPackageStats.clear();
}
+ mLastStepUserTime = mLastStepSystemTime = 0;
+ mCurStepUserTime = mCurStepSystemTime = 0;
+
if (!active) {
if (mWifiRunningTimer != null) {
mWifiRunningTimer.detach();
@@ -5392,17 +5644,17 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mActive = true;
/**
- * Total time (in 1/100 sec) spent executing in user code.
+ * Total time (in ms) spent executing in user code.
*/
long mUserTime;
/**
- * Total time (in 1/100 sec) spent executing in kernel code.
+ * Total time (in ms) spent executing in kernel code.
*/
long mSystemTime;
/**
- * Amount of time the process was running in the foreground.
+ * Amount of time (in ms) the process was running in the foreground.
*/
long mForegroundTime;
@@ -5678,9 +5930,22 @@ public final class BatteryStatsImpl extends BatteryStats {
return BatteryStatsImpl.this;
}
- public void addCpuTimeLocked(int utime, int stime) {
+ public void addCpuTimeLocked(int utime, int stime, long[] speedStepBins) {
mUserTime += utime;
+ mCurStepUserTime += utime;
mSystemTime += stime;
+ mCurStepSystemTime += stime;
+
+ for (int i = 0; i < mSpeedBins.length && i < speedStepBins.length; i++) {
+ long amt = speedStepBins[i];
+ if (amt != 0) {
+ SamplingCounter c = mSpeedBins[i];
+ if (c == null) {
+ mSpeedBins[i] = c = new SamplingCounter(mOnBatteryTimeBase);
+ }
+ c.addCountAtomic(speedStepBins[i]);
+ }
+ }
}
public void addForegroundTimeLocked(long ttime) {
@@ -5770,20 +6035,6 @@ public final class BatteryStatsImpl extends BatteryStats {
return val;
}
- /* Called by ActivityManagerService when CPU times are updated. */
- public void addSpeedStepTimes(long[] values) {
- for (int i = 0; i < mSpeedBins.length && i < values.length; i++) {
- long amt = values[i];
- if (amt != 0) {
- SamplingCounter c = mSpeedBins[i];
- if (c == null) {
- mSpeedBins[i] = c = new SamplingCounter(mOnBatteryTimeBase);
- }
- c.addCountAtomic(values[i]);
- }
- }
- }
-
@Override
public long getTimeAtCpuSpeedStep(int speedStep, int which) {
if (speedStep < mSpeedBins.length) {
@@ -6404,6 +6655,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mFile = null;
}
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
+ mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
mHandler = new MyHandler(handler.getLooper());
mStartCount++;
mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase);
@@ -6426,6 +6678,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
}
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mBluetoothActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ mWifiActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ }
mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase);
mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase);
mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase);
@@ -6462,11 +6718,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mCurrentBatteryLevel = 0;
initDischarge();
clearHistoryLocked();
+ updateDailyDeadlineLocked();
}
public BatteryStatsImpl(Parcel p) {
mFile = null;
mCheckinFile = null;
+ mDailyFile = null;
mHandler = null;
clearHistoryLocked();
readFromParcel(p);
@@ -6486,6 +6744,286 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public void updateDailyDeadlineLocked() {
+ // Get the current time.
+ long currentTime = mDailyStartTime = System.currentTimeMillis();
+ Calendar calDeadline = Calendar.getInstance();
+ calDeadline.setTimeInMillis(currentTime);
+
+ // Move time up to the next day, ranging from 1am to 3pm.
+ calDeadline.set(Calendar.DAY_OF_YEAR, calDeadline.get(Calendar.DAY_OF_YEAR) + 1);
+ calDeadline.set(Calendar.MILLISECOND, 0);
+ calDeadline.set(Calendar.SECOND, 0);
+ calDeadline.set(Calendar.MINUTE, 0);
+ calDeadline.set(Calendar.HOUR_OF_DAY, 1);
+ mNextMinDailyDeadline = calDeadline.getTimeInMillis();
+ calDeadline.set(Calendar.HOUR_OF_DAY, 3);
+ mNextMaxDailyDeadline = calDeadline.getTimeInMillis();
+ }
+
+ public void recordDailyStatsIfNeededLocked(boolean settled) {
+ long currentTime = System.currentTimeMillis();
+ if (currentTime >= mNextMaxDailyDeadline) {
+ recordDailyStatsLocked();
+ } else if (settled && currentTime >= mNextMinDailyDeadline) {
+ recordDailyStatsLocked();
+ } else if (currentTime < (mDailyStartTime-(1000*60*60*24))) {
+ recordDailyStatsLocked();
+ }
+ }
+
+ public void recordDailyStatsLocked() {
+ DailyItem item = new DailyItem();
+ item.mStartTime = mDailyStartTime;
+ item.mEndTime = System.currentTimeMillis();
+ boolean hasData = false;
+ if (mDailyDischargeStepTracker.mNumStepDurations > 0) {
+ hasData = true;
+ item.mDischargeSteps = new LevelStepTracker(
+ mDailyDischargeStepTracker.mNumStepDurations,
+ mDailyDischargeStepTracker.mStepDurations);
+ }
+ if (mDailyChargeStepTracker.mNumStepDurations > 0) {
+ hasData = true;
+ item.mChargeSteps = new LevelStepTracker(
+ mDailyChargeStepTracker.mNumStepDurations,
+ mDailyChargeStepTracker.mStepDurations);
+ }
+ mDailyDischargeStepTracker.init();
+ mDailyChargeStepTracker.init();
+ updateDailyDeadlineLocked();
+
+ if (hasData) {
+ mDailyItems.add(item);
+ while (mDailyItems.size() > MAX_DAILY_ITEMS) {
+ mDailyItems.remove(0);
+ }
+ final ByteArrayOutputStream memStream = new ByteArrayOutputStream();
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(memStream, "utf-8");
+ writeDailyItemsLocked(out);
+ BackgroundThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mCheckinFile) {
+ FileOutputStream stream = null;
+ try {
+ stream = mDailyFile.startWrite();
+ memStream.writeTo(stream);
+ stream.flush();
+ FileUtils.sync(stream);
+ stream.close();
+ mDailyFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w("BatteryStats",
+ "Error writing battery daily items", e);
+ mDailyFile.failWrite(stream);
+ }
+ }
+ }
+ });
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ private void writeDailyItemsLocked(XmlSerializer out) throws IOException {
+ StringBuilder sb = new StringBuilder(64);
+ out.startDocument(null, true);
+ out.startTag(null, "daily-items");
+ for (int i=0; i<mDailyItems.size(); i++) {
+ final DailyItem dit = mDailyItems.get(i);
+ out.startTag(null, "item");
+ out.attribute(null, "start", Long.toString(dit.mStartTime));
+ out.attribute(null, "end", Long.toString(dit.mEndTime));
+ writeDailyLevelSteps(out, "dis", dit.mDischargeSteps, sb);
+ writeDailyLevelSteps(out, "chg", dit.mChargeSteps, sb);
+ out.endTag(null, "item");
+ }
+ out.endTag(null, "daily-items");
+ out.endDocument();
+ }
+
+ private void writeDailyLevelSteps(XmlSerializer out, String tag, LevelStepTracker steps,
+ StringBuilder tmpBuilder) throws IOException {
+ if (steps != null) {
+ out.startTag(null, tag);
+ out.attribute(null, "n", Integer.toString(steps.mNumStepDurations));
+ for (int i=0; i<steps.mNumStepDurations; i++) {
+ out.startTag(null, "s");
+ tmpBuilder.setLength(0);
+ steps.encodeEntryAt(i, tmpBuilder);
+ out.attribute(null, "v", tmpBuilder.toString());
+ out.endTag(null, "s");
+ }
+ out.endTag(null, tag);
+ }
+ }
+
+ public void readDailyStatsLocked() {
+ Slog.d(TAG, "Reading daily items from " + mDailyFile.getBaseFile());
+ mDailyItems.clear();
+ FileInputStream stream;
+ try {
+ stream = mDailyFile.openRead();
+ } catch (FileNotFoundException e) {
+ return;
+ }
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, null);
+ readDailyItemsLocked(parser);
+ } catch (XmlPullParserException e) {
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ private void readDailyItemsLocked(XmlPullParser parser) {
+ try {
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("item")) {
+ readDailyItemTagLocked(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <daily-items>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Failed parsing daily " + e);
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed parsing daily " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed parsing daily " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Failed parsing daily " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed parsing daily " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing daily " + e);
+ }
+ }
+
+ void readDailyItemTagLocked(XmlPullParser parser) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ DailyItem dit = new DailyItem();
+ String attr = parser.getAttributeValue(null, "start");
+ if (attr != null) {
+ dit.mStartTime = Long.parseLong(attr);
+ }
+ attr = parser.getAttributeValue(null, "end");
+ if (attr != null) {
+ dit.mEndTime = Long.parseLong(attr);
+ }
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("dis")) {
+ readDailyItemTagDetailsLocked(parser, dit, false, "dis");
+ } else if (tagName.equals("chg")) {
+ readDailyItemTagDetailsLocked(parser, dit, true, "chg");
+ } else {
+ Slog.w(TAG, "Unknown element under <item>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ mDailyItems.add(dit);
+ }
+
+ void readDailyItemTagDetailsLocked(XmlPullParser parser, DailyItem dit, boolean isCharge,
+ String tag)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ final String numAttr = parser.getAttributeValue(null, "n");
+ if (numAttr == null) {
+ Slog.w(TAG, "Missing 'n' attribute at " + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ return;
+ }
+ final int num = Integer.parseInt(numAttr);
+ LevelStepTracker steps = new LevelStepTracker(num);
+ if (isCharge) {
+ dit.mChargeSteps = steps;
+ } else {
+ dit.mDischargeSteps = steps;
+ }
+ int i = 0;
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if ("s".equals(tagName)) {
+ if (i < num) {
+ String valueAttr = parser.getAttributeValue(null, "v");
+ if (valueAttr != null) {
+ steps.decodeEntryAt(i, valueAttr);
+ i++;
+ }
+ }
+ } else {
+ Slog.w(TAG, "Unknown element under <" + tag + ">: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ steps.mNumStepDurations = i;
+ }
+
+ @Override
+ public DailyItem getDailyItemLocked(int daysAgo) {
+ int index = mDailyItems.size()-1-daysAgo;
+ return index >= 0 ? mDailyItems.get(index) : null;
+ }
+
+ @Override
+ public long getCurrentDailyStartTime() {
+ return mDailyStartTime;
+ }
+
+ @Override
+ public long getNextMinDailyDeadline() {
+ return mNextMinDailyDeadline;
+ }
+
+ @Override
+ public long getNextMaxDailyDeadline() {
+ return mNextMaxDailyDeadline;
+ }
+
@Override
public boolean startIteratingOldHistoryLocked() {
if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
@@ -6656,10 +7194,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOnSinceCharge = 0;
mDischargeAmountScreenOff = 0;
mDischargeAmountScreenOffSinceCharge = 0;
- mLastDischargeStepTime = -1;
- mNumDischargeStepDurations = 0;
- mLastChargeStepTime = -1;
- mNumChargeStepDurations = 0;
+ mDischargeStepTracker.init();
+ mChargeStepTracker.init();
}
public void resetAllStatsCmdLocked() {
@@ -6733,6 +7269,10 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
mBluetoothStateTimer[i].reset(false);
}
+ for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mBluetoothActivityCounters[i].reset(false);
+ mWifiActivityCounters[i].reset(false);
+ }
mNumConnectivityChange = mLoadedNumConnectivityChange = mUnpluggedNumConnectivityChange = 0;
for (int i=0; i<mUidStats.size(); i++) {
@@ -6756,6 +7296,18 @@ public final class BatteryStatsImpl extends BatteryStats {
mWakeupReasonStats.clear();
}
+ mLastHistoryStepDetails = null;
+ mLastStepCpuUserTime = mLastStepCpuSystemTime = 0;
+ mCurStepCpuUserTime = mCurStepCpuSystemTime = 0;
+ mLastStepCpuUserTime = mCurStepCpuUserTime = 0;
+ mLastStepCpuSystemTime = mCurStepCpuSystemTime = 0;
+ mLastStepStatUserTime = mCurStepStatUserTime = 0;
+ mLastStepStatSystemTime = mCurStepStatSystemTime = 0;
+ mLastStepStatIOWaitTime = mCurStepStatIOWaitTime = 0;
+ mLastStepStatIrqTime = mCurStepStatIrqTime = 0;
+ mLastStepStatSoftIrqTime = mCurStepStatSoftIrqTime = 0;
+ mLastStepStatIdleTime = mCurStepStatIdleTime = 0;
+
initDischarge();
clearHistoryLocked();
@@ -6807,6 +7359,9 @@ public final class BatteryStatsImpl extends BatteryStats {
public void pullPendingStateUpdatesLocked() {
updateKernelWakelocksLocked();
updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
+ // TODO(adamlesinski): enable when bluedroid stops deadlocking. b/19248786
+ // updateBluetoothControllerActivityLocked();
+ updateWifiControllerActivityLocked();
if (mOnBatteryInternal) {
final boolean screenOn = mScreenState == Display.STATE_ON;
updateDischargeScreenLevelsLocked(screenOn, screenOn);
@@ -6870,12 +7425,13 @@ public final class BatteryStatsImpl extends BatteryStats {
resetAllStatsLocked();
mDischargeStartLevel = level;
reset = true;
- mNumDischargeStepDurations = 0;
+ mDischargeStepTracker.init();
}
- mOnBattery = mOnBatteryInternal = onBattery;
+ mOnBattery = mOnBatteryInternal = true;
mLastDischargeStepLevel = level;
mMinDischargeStepLevel = level;
- mLastDischargeStepTime = -1;
+ mDischargeStepTracker.clearTime();
+ mDailyDischargeStepTracker.clearTime();
mInitStepMode = mCurStepMode;
mModStepMode = 0;
pullPendingStateUpdatesLocked();
@@ -6900,7 +7456,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOff = 0;
updateTimeBasesLocked(true, !screenOn, uptime, realtime);
} else {
- mOnBattery = mOnBatteryInternal = onBattery;
+ mOnBattery = mOnBatteryInternal = false;
pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
@@ -6914,10 +7470,9 @@ public final class BatteryStatsImpl extends BatteryStats {
}
updateDischargeScreenLevelsLocked(screenOn, screenOn);
updateTimeBasesLocked(false, !screenOn, uptime, realtime);
- mNumChargeStepDurations = 0;
+ mChargeStepTracker.init();
mLastChargeStepLevel = level;
mMaxChargeStepLevel = level;
- mLastChargeStepTime = -1;
mInitStepMode = mCurStepMode;
mModStepMode = 0;
}
@@ -6969,34 +7524,12 @@ public final class BatteryStatsImpl extends BatteryStats {
// This should probably be exposed in the API, though it's not critical
private static final int BATTERY_PLUGGED_NONE = 0;
- private static int addLevelSteps(long[] steps, int stepCount, long lastStepTime,
- int numStepLevels, long modeBits, long elapsedRealtime) {
- if (lastStepTime >= 0 && numStepLevels > 0) {
- long duration = elapsedRealtime - lastStepTime;
- for (int i=0; i<numStepLevels; i++) {
- System.arraycopy(steps, 0, steps, 1, steps.length-1);
- long thisDuration = duration / (numStepLevels-i);
- duration -= thisDuration;
- if (thisDuration > STEP_LEVEL_TIME_MASK) {
- thisDuration = STEP_LEVEL_TIME_MASK;
- }
- steps[0] = thisDuration | modeBits;
- }
- stepCount += numStepLevels;
- if (stepCount > steps.length) {
- stepCount = steps.length;
- }
- }
- return stepCount;
- }
-
public void setBatteryState(int status, int health, int plugType, int level,
int temp, int volt) {
synchronized(this) {
final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
final long uptime = SystemClock.uptimeMillis();
final long elapsedRealtime = SystemClock.elapsedRealtime();
- int oldStatus = mHistoryCur.batteryStatus;
if (!mHaveBatteryLevel) {
mHaveBatteryLevel = true;
// We start out assuming that the device is plugged in (not
@@ -7010,8 +7543,14 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
}
}
- oldStatus = status;
+ mHistoryCur.batteryStatus = (byte)status;
+ mHistoryCur.batteryLevel = (byte)level;
+ mMaxChargeStepLevel = mMinDischargeStepLevel =
+ mLastChargeStepLevel = mLastDischargeStepLevel = level;
+ } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
+ recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
}
+ int oldStatus = mHistoryCur.batteryStatus;
if (onBattery) {
mDischargeCurrentLevel = level;
if (!mRecordingHistory) {
@@ -7072,23 +7611,23 @@ public final class BatteryStatsImpl extends BatteryStats {
| (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
if (onBattery) {
if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
- mNumDischargeStepDurations = addLevelSteps(mDischargeStepDurations,
- mNumDischargeStepDurations, mLastDischargeStepTime,
- mLastDischargeStepLevel - level, modeBits, elapsedRealtime);
+ mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
+ modeBits, elapsedRealtime);
+ mDailyDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
+ modeBits, elapsedRealtime);
mLastDischargeStepLevel = level;
mMinDischargeStepLevel = level;
- mLastDischargeStepTime = elapsedRealtime;
mInitStepMode = mCurStepMode;
mModStepMode = 0;
}
} else {
if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
- mNumChargeStepDurations = addLevelSteps(mChargeStepDurations,
- mNumChargeStepDurations, mLastChargeStepTime,
- level - mLastChargeStepLevel, modeBits, elapsedRealtime);
+ mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
+ modeBits, elapsedRealtime);
+ mDailyChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
+ modeBits, elapsedRealtime);
mLastChargeStepLevel = level;
mMaxChargeStepLevel = level;
- mLastChargeStepTime = elapsedRealtime;
mInitStepMode = mCurStepMode;
mModStepMode = 0;
}
@@ -7259,6 +7798,65 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ private void updateBluetoothControllerActivityLocked() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter == null) {
+ return;
+ }
+
+ // We read the data even if we are not on battery. Each read clears
+ // the previous data, so we must always read to make sure the
+ // data is for the current interval.
+ BluetoothActivityEnergyInfo info = adapter.getControllerActivityEnergyInfo(
+ BluetoothAdapter.ACTIVITY_ENERGY_INFO_REFRESHED);
+ if (info == null || !info.isValid() || !mOnBatteryInternal) {
+ // Bad info or we are not on battery.
+ return;
+ }
+
+ mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
+ info.getControllerRxTimeMillis());
+ mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
+ info.getControllerTxTimeMillis());
+ mBluetoothActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
+ info.getControllerIdleTimeMillis());
+ mBluetoothActivityCounters[CONTROLLER_ENERGY].addCountLocked(
+ info.getControllerEnergyUsed());
+ }
+
+ private void updateWifiControllerActivityLocked() {
+ IWifiManager wifiManager = IWifiManager.Stub.asInterface(
+ ServiceManager.getService(Context.WIFI_SERVICE));
+ if (wifiManager == null) {
+ return;
+ }
+
+ WifiActivityEnergyInfo info;
+ try {
+ // We read the data even if we are not on battery. Each read clears
+ // the previous data, so we must always read to make sure the
+ // data is for the current interval.
+ info = wifiManager.reportActivityInfo();
+ } catch (RemoteException e) {
+ // Nothing to report, WiFi is dead.
+ return;
+ }
+
+ if (info == null || !info.isValid() || !mOnBatteryInternal) {
+ // Bad info or we are not on battery.
+ return;
+ }
+
+ mWifiActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
+ info.getControllerRxTimeMillis());
+ mWifiActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
+ info.getControllerTxTimeMillis());
+ mWifiActivityCounters[CONTROLLER_IDLE_TIME].addCountLocked(
+ info.getControllerIdleTimeMillis());
+ mWifiActivityCounters[CONTROLLER_ENERGY].addCountLocked(
+ info.getControllerEnergyUsed());
+ }
+
public long getAwakeTimeBattery() {
return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
}
@@ -7363,22 +7961,24 @@ public final class BatteryStatsImpl extends BatteryStats {
long usPerLevel = duration/discharge;
return usPerLevel * mCurrentBatteryLevel;
*/
- if (mNumDischargeStepDurations < 1) {
+ if (mDischargeStepTracker.mNumStepDurations < 1) {
return -1;
}
- long msPerLevel = computeTimePerLevel(mDischargeStepDurations, mNumDischargeStepDurations);
+ long msPerLevel = mDischargeStepTracker.computeTimePerLevel();
if (msPerLevel <= 0) {
return -1;
}
return (msPerLevel * mCurrentBatteryLevel) * 1000;
}
- public int getNumDischargeStepDurations() {
- return mNumDischargeStepDurations;
+ @Override
+ public LevelStepTracker getDischargeLevelStepTracker() {
+ return mDischargeStepTracker;
}
- public long[] getDischargeStepDurationsArray() {
- return mDischargeStepDurations;
+ @Override
+ public LevelStepTracker getDailyDischargeLevelStepTracker() {
+ return mDailyDischargeStepTracker;
}
@Override
@@ -7400,22 +8000,24 @@ public final class BatteryStatsImpl extends BatteryStats {
long usPerLevel = duration/(curLevel-plugLevel);
return usPerLevel * (100-curLevel);
*/
- if (mNumChargeStepDurations < 1) {
+ if (mChargeStepTracker.mNumStepDurations < 1) {
return -1;
}
- long msPerLevel = computeTimePerLevel(mChargeStepDurations, mNumChargeStepDurations);
+ long msPerLevel = mChargeStepTracker.computeTimePerLevel();
if (msPerLevel <= 0) {
return -1;
}
return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000;
}
- public int getNumChargeStepDurations() {
- return mNumChargeStepDurations;
+ @Override
+ public LevelStepTracker getChargeLevelStepTracker() {
+ return mChargeStepTracker;
}
- public long[] getChargeStepDurationsArray() {
- return mChargeStepDurations;
+ @Override
+ public LevelStepTracker getDailyChargeLevelStepTracker() {
+ return mDailyChargeStepTracker;
}
long getBatteryUptimeLocked() {
@@ -7713,6 +8315,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void readLocked() {
+ if (mDailyFile != null) {
+ readDailyStatsLocked();
+ }
+
if (mFile == null) {
Slog.w("BatteryStats", "readLocked: no file associated with this instance");
return;
@@ -7750,6 +8356,8 @@ public final class BatteryStatsImpl extends BatteryStats {
addHistoryBufferLocked(elapsedRealtime, uptime, HistoryItem.CMD_START, mHistoryCur);
startRecordingHistory(elapsedRealtime, uptime, false);
}
+
+ recordDailyStatsIfNeededLocked(false);
}
public int describeContents() {
@@ -7908,10 +8516,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mHighDischargeAmountSinceCharge = in.readInt();
mDischargeAmountScreenOnSinceCharge = in.readInt();
mDischargeAmountScreenOffSinceCharge = in.readInt();
- mNumDischargeStepDurations = in.readInt();
- in.readLongArray(mDischargeStepDurations);
- mNumChargeStepDurations = in.readInt();
- in.readLongArray(mChargeStepDurations);
+ mDischargeStepTracker.readFromParcel(in);
+ mChargeStepTracker.readFromParcel(in);
+ mDailyDischargeStepTracker.readFromParcel(in);
+ mDailyChargeStepTracker.readFromParcel(in);
+ mDailyStartTime = in.readLong();
+ mNextMinDailyDeadline = in.readLong();
+ mNextMaxDailyDeadline = in.readLong();
mStartCount++;
@@ -7960,6 +8571,15 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
mBluetoothStateTimer[i].readSummaryFromParcelLocked(in);
}
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mBluetoothActivityCounters[i].readSummaryFromParcelLocked(in);
+ }
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mWifiActivityCounters[i].readSummaryFromParcelLocked(in);
+ }
+
mNumConnectivityChange = mLoadedNumConnectivityChange = in.readInt();
mFlashlightOn = false;
mFlashlightOnTimer.readSummaryFromParcelLocked(in);
@@ -8202,10 +8822,13 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(getHighDischargeAmountSinceCharge());
out.writeInt(getDischargeAmountScreenOnSinceCharge());
out.writeInt(getDischargeAmountScreenOffSinceCharge());
- out.writeInt(mNumDischargeStepDurations);
- out.writeLongArray(mDischargeStepDurations);
- out.writeInt(mNumChargeStepDurations);
- out.writeLongArray(mChargeStepDurations);
+ mDischargeStepTracker.writeToParcel(out);
+ mChargeStepTracker.writeToParcel(out);
+ mDailyDischargeStepTracker.writeToParcel(out);
+ mDailyChargeStepTracker.writeToParcel(out);
+ out.writeLong(mDailyStartTime);
+ out.writeLong(mNextMinDailyDeadline);
+ out.writeLong(mNextMaxDailyDeadline);
mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
@@ -8245,6 +8868,12 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
mBluetoothStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
+ for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mBluetoothActivityCounters[i].writeSummaryFromParcelLocked(out);
+ }
+ for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mWifiActivityCounters[i].writeSummaryFromParcelLocked(out);
+ }
out.writeInt(mNumConnectivityChange);
mFlashlightOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
@@ -8549,6 +9178,15 @@ public final class BatteryStatsImpl extends BatteryStats {
mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i,
null, mOnBatteryTimeBase, in);
}
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mBluetoothActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ }
+
+ for (int i = 0; i < NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mWifiActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ }
+
mNumConnectivityChange = in.readInt();
mLoadedNumConnectivityChange = in.readInt();
mUnpluggedNumConnectivityChange = in.readInt();
@@ -8568,10 +9206,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOnSinceCharge = in.readInt();
mDischargeAmountScreenOff = in.readInt();
mDischargeAmountScreenOffSinceCharge = in.readInt();
- mNumDischargeStepDurations = in.readInt();
- in.readLongArray(mDischargeStepDurations);
- mNumChargeStepDurations = in.readInt();
- in.readLongArray(mChargeStepDurations);
+ mDischargeStepTracker.readFromParcel(in);
+ mChargeStepTracker.readFromParcel(in);
mLastWriteTime = in.readLong();
mBluetoothPingCount = in.readInt();
@@ -8696,6 +9332,12 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime);
}
+ for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mBluetoothActivityCounters[i].writeToParcel(out);
+ }
+ for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
+ mWifiActivityCounters[i].writeToParcel(out);
+ }
out.writeInt(mNumConnectivityChange);
out.writeInt(mLoadedNumConnectivityChange);
out.writeInt(mUnpluggedNumConnectivityChange);
@@ -8710,10 +9352,8 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mDischargeAmountScreenOnSinceCharge);
out.writeInt(mDischargeAmountScreenOff);
out.writeInt(mDischargeAmountScreenOffSinceCharge);
- out.writeInt(mNumDischargeStepDurations);
- out.writeLongArray(mDischargeStepDurations);
- out.writeInt(mNumChargeStepDurations);
- out.writeLongArray(mChargeStepDurations);
+ mDischargeStepTracker.writeToParcel(out);
+ mChargeStepTracker.writeToParcel(out);
out.writeLong(mLastWriteTime);
out.writeInt(getBluetoothPingCount());
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index 99286cb..113768e 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -164,6 +164,15 @@ public class HandlerCaller {
return mH.obtainMessage(what, arg1, 0, args);
}
+ public Message obtainMessageIIOOO(int what, int arg1, int arg2, Object arg3, Object arg4,
+ Object arg5) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = arg3;
+ args.arg2 = arg4;
+ args.arg3 = arg5;
+ return mH.obtainMessage(what, arg1, arg2, args);
+ }
+
public Message obtainMessageOO(int what, Object arg1, Object arg2) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = arg1;
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index b3bafa1..944eb5a 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -76,6 +76,11 @@ public class PowerProfile {
public static final String POWER_WIFI_ACTIVE = "wifi.active";
/**
+ * Operating voltage of the WiFi controller.
+ */
+ public static final String OPERATING_VOLTAGE_WIFI = "wifi.voltage";
+
+ /**
* Power consumption when GPS is on.
*/
public static final String POWER_GPS_ON = "gps.on";
@@ -96,6 +101,11 @@ public class PowerProfile {
public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
/**
+ * Operating voltage of the Bluetooth controller.
+ */
+ public static final String OPERATING_VOLTAGE_BLUETOOTH = "bluetooth.voltage";
+
+ /**
* Power consumption when screen is on, not including the backlight power.
*/
public static final String POWER_SCREEN_ON = "screen.on";
@@ -224,11 +234,13 @@ public class PowerProfile {
}
/**
- * Returns the average current in mA consumed by the subsystem
+ * Returns the average current in mA consumed by the subsystem, or the given
+ * default value if the subsystem has no recorded value.
* @param type the subsystem type
+ * @param defaultValue the value to return if the subsystem has no recorded value.
* @return the average current in milliAmps.
*/
- public double getAveragePower(String type) {
+ public double getAveragePowerOrDefault(String type, double defaultValue) {
if (sPowerMap.containsKey(type)) {
Object data = sPowerMap.get(type);
if (data instanceof Double[]) {
@@ -237,9 +249,18 @@ public class PowerProfile {
return (Double) sPowerMap.get(type);
}
} else {
- return 0;
+ return defaultValue;
}
}
+
+ /**
+ * Returns the average current in mA consumed by the subsystem
+ * @param type the subsystem type
+ * @return the average current in milliAmps.
+ */
+ public double getAveragePower(String type) {
+ return getAveragePowerOrDefault(type, 0);
+ }
/**
* Returns the average current in mA consumed by the subsystem for the given level.
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index b5338df..2983047 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -22,11 +22,13 @@ import android.os.FileUtils;
import android.os.Process;
import android.os.StrictMode;
import android.os.SystemClock;
+import android.system.OsConstants;
import android.util.Slog;
import com.android.internal.util.FastPrintWriter;
import libcore.io.IoUtils;
+import libcore.io.Libcore;
import java.io.File;
import java.io.FileInputStream;
@@ -130,6 +132,9 @@ public class ProcessCpuTracker {
private final boolean mIncludeThreads;
+ // How long a CPU jiffy is in milliseconds.
+ private final long mJiffyMillis;
+
private float mLoad1 = 0;
private float mLoad5 = 0;
private float mLoad15 = 0;
@@ -152,6 +157,7 @@ public class ProcessCpuTracker {
private int mRelIrqTime;
private int mRelSoftIrqTime;
private int mRelIdleTime;
+ private boolean mRelStatsAreGood;
private int[] mCurPids;
private int[] mCurThreadPids;
@@ -268,6 +274,8 @@ public class ProcessCpuTracker {
public ProcessCpuTracker(boolean includeThreads) {
mIncludeThreads = includeThreads;
+ long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
+ mJiffyMillis = 1000/jiffyHz;
}
public void onLoadChanged(float load1, float load5, float load15) {
@@ -285,49 +293,72 @@ public class ProcessCpuTracker {
public void update() {
if (DEBUG) Slog.v(TAG, "Update: " + this);
- mLastSampleTime = mCurrentSampleTime;
- mCurrentSampleTime = SystemClock.uptimeMillis();
- mLastSampleRealTime = mCurrentSampleRealTime;
- mCurrentSampleRealTime = SystemClock.elapsedRealtime();
+
+ final long nowUptime = SystemClock.uptimeMillis();
+ final long nowRealtime = SystemClock.elapsedRealtime();
final long[] sysCpu = mSystemCpuData;
if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT,
null, sysCpu, null)) {
// Total user time is user + nice time.
- final long usertime = sysCpu[0]+sysCpu[1];
+ final long usertime = (sysCpu[0]+sysCpu[1]) * mJiffyMillis;
// Total system time is simply system time.
- final long systemtime = sysCpu[2];
+ final long systemtime = sysCpu[2] * mJiffyMillis;
// Total idle time is simply idle time.
- final long idletime = sysCpu[3];
+ final long idletime = sysCpu[3] * mJiffyMillis;
// Total irq time is iowait + irq + softirq time.
- final long iowaittime = sysCpu[4];
- final long irqtime = sysCpu[5];
- final long softirqtime = sysCpu[6];
-
- mRelUserTime = (int)(usertime - mBaseUserTime);
- mRelSystemTime = (int)(systemtime - mBaseSystemTime);
- mRelIoWaitTime = (int)(iowaittime - mBaseIoWaitTime);
- mRelIrqTime = (int)(irqtime - mBaseIrqTime);
- mRelSoftIrqTime = (int)(softirqtime - mBaseSoftIrqTime);
- mRelIdleTime = (int)(idletime - mBaseIdleTime);
-
- if (DEBUG) {
- Slog.i("Load", "Total U:" + sysCpu[0] + " N:" + sysCpu[1]
- + " S:" + sysCpu[2] + " I:" + sysCpu[3]
- + " W:" + sysCpu[4] + " Q:" + sysCpu[5]
- + " O:" + sysCpu[6]);
- Slog.i("Load", "Rel U:" + mRelUserTime + " S:" + mRelSystemTime
- + " I:" + mRelIdleTime + " Q:" + mRelIrqTime);
- }
+ final long iowaittime = sysCpu[4] * mJiffyMillis;
+ final long irqtime = sysCpu[5] * mJiffyMillis;
+ final long softirqtime = sysCpu[6] * mJiffyMillis;
+
+ // This code is trying to avoid issues with idle time going backwards,
+ // but currently it gets into situations where it triggers most of the time. :(
+ if (true || (usertime >= mBaseUserTime && systemtime >= mBaseSystemTime
+ && iowaittime >= mBaseIoWaitTime && irqtime >= mBaseIrqTime
+ && softirqtime >= mBaseSoftIrqTime && idletime >= mBaseIdleTime)) {
+ mRelUserTime = (int)(usertime - mBaseUserTime);
+ mRelSystemTime = (int)(systemtime - mBaseSystemTime);
+ mRelIoWaitTime = (int)(iowaittime - mBaseIoWaitTime);
+ mRelIrqTime = (int)(irqtime - mBaseIrqTime);
+ mRelSoftIrqTime = (int)(softirqtime - mBaseSoftIrqTime);
+ mRelIdleTime = (int)(idletime - mBaseIdleTime);
+ mRelStatsAreGood = true;
+
+ if (DEBUG) {
+ Slog.i("Load", "Total U:" + (sysCpu[0]*mJiffyMillis)
+ + " N:" + (sysCpu[1]*mJiffyMillis)
+ + " S:" + (sysCpu[2]*mJiffyMillis) + " I:" + (sysCpu[3]*mJiffyMillis)
+ + " W:" + (sysCpu[4]*mJiffyMillis) + " Q:" + (sysCpu[5]*mJiffyMillis)
+ + " O:" + (sysCpu[6]*mJiffyMillis));
+ Slog.i("Load", "Rel U:" + mRelUserTime + " S:" + mRelSystemTime
+ + " I:" + mRelIdleTime + " Q:" + mRelIrqTime);
+ }
+
+ mBaseUserTime = usertime;
+ mBaseSystemTime = systemtime;
+ mBaseIoWaitTime = iowaittime;
+ mBaseIrqTime = irqtime;
+ mBaseSoftIrqTime = softirqtime;
+ mBaseIdleTime = idletime;
- mBaseUserTime = usertime;
- mBaseSystemTime = systemtime;
- mBaseIoWaitTime = iowaittime;
- mBaseIrqTime = irqtime;
- mBaseSoftIrqTime = softirqtime;
- mBaseIdleTime = idletime;
+ } else {
+ mRelUserTime = 0;
+ mRelSystemTime = 0;
+ mRelIoWaitTime = 0;
+ mRelIrqTime = 0;
+ mRelSoftIrqTime = 0;
+ mRelIdleTime = 0;
+ mRelStatsAreGood = false;
+ Slog.w(TAG, "/proc/stats has gone backwards; skipping CPU update");
+ return;
+ }
}
+ mLastSampleTime = mCurrentSampleTime;
+ mCurrentSampleTime = nowUptime;
+ mLastSampleRealTime = mCurrentSampleRealTime;
+ mCurrentSampleRealTime = nowRealtime;
+
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
try {
mCurPids = collectStats("/proc", -1, mFirst, mCurPids, mProcStats);
@@ -391,8 +422,8 @@ public class ProcessCpuTracker {
final long minfaults = procStats[PROCESS_STAT_MINOR_FAULTS];
final long majfaults = procStats[PROCESS_STAT_MAJOR_FAULTS];
- final long utime = procStats[PROCESS_STAT_UTIME];
- final long stime = procStats[PROCESS_STAT_STIME];
+ final long utime = procStats[PROCESS_STAT_UTIME] * mJiffyMillis;
+ final long stime = procStats[PROCESS_STAT_STIME] * mJiffyMillis;
if (utime == st.base_utime && stime == st.base_stime) {
st.rel_utime = 0;
@@ -466,8 +497,8 @@ public class ProcessCpuTracker {
st.baseName = procStatsString[0];
st.base_minfaults = procStats[PROCESS_FULL_STAT_MINOR_FAULTS];
st.base_majfaults = procStats[PROCESS_FULL_STAT_MAJOR_FAULTS];
- st.base_utime = procStats[PROCESS_FULL_STAT_UTIME];
- st.base_stime = procStats[PROCESS_FULL_STAT_STIME];
+ st.base_utime = procStats[PROCESS_FULL_STAT_UTIME] * mJiffyMillis;
+ st.base_stime = procStats[PROCESS_FULL_STAT_STIME] * mJiffyMillis;
} else {
Slog.i(TAG, "Skipping kernel process pid " + pid
+ " name " + procStatsString[0]);
@@ -553,7 +584,7 @@ public class ProcessCpuTracker {
null, statsData, null)) {
long time = statsData[PROCESS_STAT_UTIME]
+ statsData[PROCESS_STAT_STIME];
- return time;
+ return time * mJiffyMillis;
}
return 0;
}
@@ -647,6 +678,10 @@ public class ProcessCpuTracker {
return mRelIdleTime;
}
+ final public boolean hasGoodLastStats() {
+ return mRelStatsAreGood;
+ }
+
final public float getTotalCpuPercent() {
int denom = mRelUserTime+mRelSystemTime+mRelIrqTime+mRelIdleTime;
if (denom <= 0) {
@@ -750,7 +785,7 @@ public class ProcessCpuTracker {
for (int i=0; i<N; i++) {
Stats st = mWorkingProcs.get(i);
printProcessCPU(pw, st.added ? " +" : (st.removed ? " -": " "),
- st.pid, st.name, (int)(st.rel_uptime+5)/10,
+ st.pid, st.name, (int)st.rel_uptime,
st.rel_utime, st.rel_stime, 0, 0, 0, st.rel_minfaults, st.rel_majfaults);
if (!st.removed && st.workingThreads != null) {
int M = st.workingThreads.size();
@@ -758,7 +793,7 @@ public class ProcessCpuTracker {
Stats tst = st.workingThreads.get(j);
printProcessCPU(pw,
tst.added ? " +" : (tst.removed ? " -": " "),
- tst.pid, tst.name, (int)(st.rel_uptime+5)/10,
+ tst.pid, tst.name, (int)st.rel_uptime,
tst.rel_utime, tst.rel_stime, 0, 0, 0, 0, 0);
}
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 0dc242d..4d405b2 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -30,7 +30,6 @@ import android.os.SystemProperties;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
-import dalvik.system.PathClassLoader;
import dalvik.system.VMRuntime;
import java.io.BufferedReader;
import java.io.DataInputStream;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 98638ed..49d565d 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -24,8 +24,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.net.LocalServerSocket;
import android.opengl.EGL14;
-import android.os.Build;
-import android.os.Debug;
import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -36,7 +34,6 @@ import android.system.OsConstants;
import android.system.StructPollfd;
import android.util.EventLog;
import android.util.Log;
-import android.util.Slog;
import android.webkit.WebViewFactory;
import dalvik.system.DexFile;
@@ -54,7 +51,6 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
/**
@@ -355,7 +351,7 @@ public class ZygoteInit {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
- if (mResources.getColorStateList(id) == null) {
+ if (mResources.getColorStateList(id, null) == null) {
throw new IllegalArgumentException(
"Unable to find preloaded color resource #0x"
+ Integer.toHexString(id)
diff --git a/core/java/com/android/internal/policy/IPolicy.java b/core/java/com/android/internal/policy/IPolicy.java
deleted file mode 100644
index d08b3b4..0000000
--- a/core/java/com/android/internal/policy/IPolicy.java
+++ /dev/null
@@ -1,39 +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 com.android.internal.policy;
-
-import android.content.Context;
-import android.view.FallbackEventHandler;
-import android.view.LayoutInflater;
-import android.view.Window;
-import android.view.WindowManagerPolicy;
-
-/**
- * {@hide}
- */
-
-/* The implementation of this interface must be called Policy and contained
- * within the com.android.internal.policy.impl package */
-public interface IPolicy {
- public Window makeNewWindow(Context context);
-
- public LayoutInflater makeNewLayoutInflater(Context context);
-
- public WindowManagerPolicy makeNewWindowManager();
-
- public FallbackEventHandler makeNewFallbackEventHandler(Context context);
-}
diff --git a/core/java/com/android/internal/policy/PolicyManager.java b/core/java/com/android/internal/policy/PolicyManager.java
deleted file mode 100644
index 462b3a9..0000000
--- a/core/java/com/android/internal/policy/PolicyManager.java
+++ /dev/null
@@ -1,71 +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 com.android.internal.policy;
-
-import android.content.Context;
-import android.view.FallbackEventHandler;
-import android.view.LayoutInflater;
-import android.view.Window;
-import android.view.WindowManagerPolicy;
-
-/**
- * {@hide}
- */
-
-public final class PolicyManager {
- private static final String POLICY_IMPL_CLASS_NAME =
- "com.android.internal.policy.impl.Policy";
-
- private static final IPolicy sPolicy;
-
- static {
- // Pull in the actual implementation of the policy at run-time
- try {
- Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
- sPolicy = (IPolicy)policyClass.newInstance();
- } catch (ClassNotFoundException ex) {
- throw new RuntimeException(
- POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
- } catch (InstantiationException ex) {
- throw new RuntimeException(
- POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
- } catch (IllegalAccessException ex) {
- throw new RuntimeException(
- POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
- }
- }
-
- // Cannot instantiate this class
- private PolicyManager() {}
-
- // The static methods to spawn new policy-specific objects
- public static Window makeNewWindow(Context context) {
- return sPolicy.makeNewWindow(context);
- }
-
- public static LayoutInflater makeNewLayoutInflater(Context context) {
- return sPolicy.makeNewLayoutInflater(context);
- }
-
- public static WindowManagerPolicy makeNewWindowManager() {
- return sPolicy.makeNewWindowManager();
- }
-
- public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
- return sPolicy.makeNewFallbackEventHandler(context);
- }
-}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a3c0db4..2b0d244 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -1,19 +1,19 @@
/**
* Copyright (c) 2007, The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+
package com.android.internal.statusbar;
import com.android.internal.statusbar.StatusBarIcon;
@@ -43,5 +43,26 @@ oneway interface IStatusBar
void preloadRecentApps();
void cancelPreloadRecentApps();
void showScreenPinningRequest();
+
+ /**
+ * Notifies the status bar that an app transition is pending to delay applying some flags with
+ * visual impact until {@link #appTransitionReady} is called.
+ */
+ void appTransitionPending();
+
+ /**
+ * Notifies the status bar that a pending app transition has been cancelled.
+ */
+ void appTransitionCancelled();
+
+ /**
+ * Notifies the status bar that an app transition is now being executed.
+ *
+ * @param statusBarAnimationsStartTime the desired start time for all visual animations in the
+ * status bar caused by this app transition in uptime millis
+ * @param statusBarAnimationsDuration the duration for all visual animations in the status
+ * bar caused by this app transition in millis
+ */
+ void appTransitionStarting(long statusBarAnimationsStartTime, long statusBarAnimationsDuration);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 40c009f..6cb839e 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -1,16 +1,16 @@
/**
* Copyright (c) 2007, The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
@@ -61,4 +61,25 @@ interface IStatusBarService
void toggleRecentApps();
void preloadRecentApps();
void cancelPreloadRecentApps();
+
+ /**
+ * Notifies the status bar that an app transition is pending to delay applying some flags with
+ * visual impact until {@link #appTransitionReady} is called.
+ */
+ void appTransitionPending();
+
+ /**
+ * Notifies the status bar that a pending app transition has been cancelled.
+ */
+ void appTransitionCancelled();
+
+ /**
+ * Notifies the status bar that an app transition is now being executed.
+ *
+ * @param statusBarAnimationsStartTime the desired start time for all visual animations in the
+ * status bar caused by this app transition in uptime millis
+ * @param statusBarAnimationsDuration the duration for all visual animations in the status
+ * bar caused by this app transition in millis
+ */
+ void appTransitionStarting(long statusBarAnimationsStartTime, long statusBarAnimationsDuration);
}
diff --git a/core/java/com/android/internal/transition/EpicenterClipReveal.java b/core/java/com/android/internal/transition/EpicenterClipReveal.java
new file mode 100644
index 0000000..abb50c1
--- /dev/null
+++ b/core/java/com/android/internal/transition/EpicenterClipReveal.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.RectEvaluator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.transition.TransitionValues;
+import android.transition.Visibility;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * EpicenterClipReveal captures the {@link View#getClipBounds()} before and
+ * after the scene change and animates between those and the epicenter bounds
+ * during a visibility transition.
+ */
+public class EpicenterClipReveal extends Visibility {
+ private static final String PROPNAME_CLIP = "android:epicenterReveal:clip";
+ private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds";
+
+ public EpicenterClipReveal() {}
+
+ public EpicenterClipReveal(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ super.captureStartValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ super.captureEndValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ private void captureValues(TransitionValues values) {
+ final View view = values.view;
+ if (view.getVisibility() == View.GONE) {
+ return;
+ }
+
+ final Rect clip = view.getClipBounds();
+ values.values.put(PROPNAME_CLIP, clip);
+
+ if (clip == null) {
+ final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
+ values.values.put(PROPNAME_BOUNDS, bounds);
+ }
+ }
+
+ @Override
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (endValues == null) {
+ return null;
+ }
+
+ final Rect end = getBestRect(endValues);
+ final Rect start = getEpicenterOrCenter(end);
+
+ // Prepare the view.
+ view.setClipBounds(start);
+
+ return createRectAnimator(view, start, end, endValues);
+ }
+
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (startValues == null) {
+ return null;
+ }
+
+ final Rect start = getBestRect(startValues);
+ final Rect end = getEpicenterOrCenter(start);
+
+ // Prepare the view.
+ view.setClipBounds(start);
+
+ return createRectAnimator(view, start, end, endValues);
+ }
+
+ private Rect getEpicenterOrCenter(Rect bestRect) {
+ final Rect epicenter = getEpicenter();
+ if (epicenter != null) {
+ return epicenter;
+ }
+
+ int centerX = bestRect.centerX();
+ int centerY = bestRect.centerY();
+ return new Rect(centerX, centerY, centerX, centerY);
+ }
+
+ private Rect getBestRect(TransitionValues values) {
+ final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP);
+ if (clipRect == null) {
+ return (Rect) values.values.get(PROPNAME_BOUNDS);
+ }
+ return clipRect;
+ }
+
+ private Animator createRectAnimator(final View view, Rect start, Rect end,
+ TransitionValues endValues) {
+ final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP);
+ final RectEvaluator evaluator = new RectEvaluator(new Rect());
+ ObjectAnimator anim = ObjectAnimator.ofObject(view, "clipBounds", evaluator, start, end);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setClipBounds(terminalClip);
+ }
+ });
+ return anim;
+ }
+}
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index 65b56ec..64e1d10 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -35,13 +35,14 @@ public final class DumpUtils {
* trying to acquire, we use a short timeout to avoid deadlocks. The process
* is inelegant but this function is only used for debugging purposes.
*/
- public static void dumpAsync(Handler handler, final Dump dump, PrintWriter pw, long timeout) {
+ public static void dumpAsync(Handler handler, final Dump dump, PrintWriter pw,
+ final String prefix, long timeout) {
final StringWriter sw = new StringWriter();
if (handler.runWithScissors(new Runnable() {
@Override
public void run() {
PrintWriter lpw = new FastPrintWriter(sw);
- dump.dump(lpw);
+ dump.dump(lpw, prefix);
lpw.close();
}
}, timeout)) {
@@ -52,6 +53,6 @@ public final class DumpUtils {
}
public interface Dump {
- void dump(PrintWriter pw);
+ void dump(PrintWriter pw, String prefix);
}
}
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index e1e9d5e..daf745f 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -48,9 +48,12 @@ public class UserIcons {
if (icon == null) {
return null;
}
- Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
- icon.draw(new Canvas(bitmap));
+ final int width = icon.getIntrinsicWidth();
+ final int height = icon.getIntrinsicHeight();
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, width, height);
+ icon.draw(canvas);
return bitmap;
}
@@ -67,8 +70,8 @@ public class UserIcons {
// Return colored icon instead
colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
}
- Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle).mutate();
- icon.setColorFilter(Resources.getSystem().getColor(colorResId), Mode.SRC_IN);
+ Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle, null).mutate();
+ icon.setColorFilter(Resources.getSystem().getColor(colorResId, null), Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 2bd607c..0350d61 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -20,6 +20,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
+import android.util.ArrayMap;
import android.util.Base64;
import android.util.Xml;
@@ -822,7 +823,36 @@ public class XmlUtils {
int eventType = parser.getEventType();
do {
if (eventType == parser.START_TAG) {
- Object val = readThisValueXml(parser, name, callback);
+ Object val = readThisValueXml(parser, name, callback, false);
+ map.put(name[0], val);
+ } else if (eventType == parser.END_TAG) {
+ if (parser.getName().equals(endTag)) {
+ return map;
+ }
+ throw new XmlPullParserException(
+ "Expected " + endTag + " end tag at: " + parser.getName());
+ }
+ eventType = parser.next();
+ } while (eventType != parser.END_DOCUMENT);
+
+ throw new XmlPullParserException(
+ "Document ended before " + endTag + " end tag");
+ }
+
+ /**
+ * Like {@link #readThisMapXml}, but returns an ArrayMap instead of HashMap.
+ * @hide
+ */
+ public static final ArrayMap<String, ?> readThisArrayMapXml(XmlPullParser parser, String endTag,
+ String[] name, ReadMapCallback callback)
+ throws XmlPullParserException, java.io.IOException
+ {
+ ArrayMap<String, Object> map = new ArrayMap<>();
+
+ int eventType = parser.getEventType();
+ do {
+ if (eventType == parser.START_TAG) {
+ Object val = readThisValueXml(parser, name, callback, true);
map.put(name[0], val);
} else if (eventType == parser.END_TAG) {
if (parser.getName().equals(endTag)) {
@@ -854,7 +884,7 @@ public class XmlUtils {
*/
public static final ArrayList readThisListXml(XmlPullParser parser, String endTag,
String[] name) throws XmlPullParserException, java.io.IOException {
- return readThisListXml(parser, endTag, name, null);
+ return readThisListXml(parser, endTag, name, null, false);
}
/**
@@ -872,14 +902,14 @@ public class XmlUtils {
* @see #readListXml
*/
private static final ArrayList readThisListXml(XmlPullParser parser, String endTag,
- String[] name, ReadMapCallback callback)
+ String[] name, ReadMapCallback callback, boolean arrayMap)
throws XmlPullParserException, java.io.IOException {
ArrayList list = new ArrayList();
int eventType = parser.getEventType();
do {
if (eventType == parser.START_TAG) {
- Object val = readThisValueXml(parser, name, callback);
+ Object val = readThisValueXml(parser, name, callback, arrayMap);
list.add(val);
//System.out.println("Adding to list: " + val);
} else if (eventType == parser.END_TAG) {
@@ -915,7 +945,7 @@ public class XmlUtils {
*/
public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name)
throws XmlPullParserException, java.io.IOException {
- return readThisSetXml(parser, endTag, name, null);
+ return readThisSetXml(parser, endTag, name, null, false);
}
/**
@@ -937,13 +967,14 @@ public class XmlUtils {
* @hide
*/
private static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name,
- ReadMapCallback callback) throws XmlPullParserException, java.io.IOException {
+ ReadMapCallback callback, boolean arrayMap)
+ throws XmlPullParserException, java.io.IOException {
HashSet set = new HashSet();
int eventType = parser.getEventType();
do {
if (eventType == parser.START_TAG) {
- Object val = readThisValueXml(parser, name, callback);
+ Object val = readThisValueXml(parser, name, callback, arrayMap);
set.add(val);
//System.out.println("Adding to set: " + val);
} else if (eventType == parser.END_TAG) {
@@ -1292,7 +1323,7 @@ public class XmlUtils {
int eventType = parser.getEventType();
do {
if (eventType == parser.START_TAG) {
- return readThisValueXml(parser, name, null);
+ return readThisValueXml(parser, name, null, false);
} else if (eventType == parser.END_TAG) {
throw new XmlPullParserException(
"Unexpected end tag at: " + parser.getName());
@@ -1308,7 +1339,8 @@ public class XmlUtils {
}
private static final Object readThisValueXml(XmlPullParser parser, String[] name,
- ReadMapCallback callback) throws XmlPullParserException, java.io.IOException {
+ ReadMapCallback callback, boolean arrayMap)
+ throws XmlPullParserException, java.io.IOException {
final String valueName = parser.getAttributeValue(null, "name");
final String tagName = parser.getName();
@@ -1368,19 +1400,21 @@ public class XmlUtils {
return res;
} else if (tagName.equals("map")) {
parser.next();
- res = readThisMapXml(parser, "map", name);
+ res = arrayMap
+ ? readThisArrayMapXml(parser, "map", name, callback)
+ : readThisMapXml(parser, "map", name, callback);
name[0] = valueName;
//System.out.println("Returning value for " + valueName + ": " + res);
return res;
} else if (tagName.equals("list")) {
parser.next();
- res = readThisListXml(parser, "list", name);
+ res = readThisListXml(parser, "list", name, callback, arrayMap);
name[0] = valueName;
//System.out.println("Returning value for " + valueName + ": " + res);
return res;
} else if (tagName.equals("set")) {
parser.next();
- res = readThisSetXml(parser, "set", name);
+ res = readThisSetXml(parser, "set", name, callback, arrayMap);
name[0] = valueName;
//System.out.println("Returning value for " + valueName + ": " + res);
return res;
diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
index d5d3602..e6d911c 100644
--- a/core/java/com/android/internal/view/StandaloneActionMode.java
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -47,7 +47,7 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call
mCallback = callback;
mMenu = new MenuBuilder(view.getContext()).setDefaultShowAsAction(
- MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
mMenu.setCallback(this);
mFocusable = isFocusable;
}
@@ -64,12 +64,12 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call
@Override
public void setTitle(int resId) {
- setTitle(mContext.getString(resId));
+ setTitle(resId != 0 ? mContext.getString(resId) : null);
}
@Override
public void setSubtitle(int resId) {
- setSubtitle(mContext.getString(resId));
+ setSubtitle(resId != 0 ? mContext.getString(resId) : null);
}
@Override
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index ed676bb..00af401 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -18,6 +18,8 @@ package com.android.internal.view.menu;
import android.content.Context;
import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.view.ActionProvider;
import android.view.ContextMenu.ContextMenuInfo;
@@ -42,6 +44,7 @@ public class ActionMenuItem implements MenuItem {
private Drawable mIconDrawable;
private int mIconResId = NO_ICON;
+ private TintInfo mIconTintInfo;
private Context mContext;
@@ -158,12 +161,14 @@ public class ActionMenuItem implements MenuItem {
public MenuItem setIcon(Drawable icon) {
mIconDrawable = icon;
mIconResId = NO_ICON;
+ applyIconTint();
return this;
}
public MenuItem setIcon(int iconRes) {
mIconResId = iconRes;
mIconDrawable = mContext.getDrawable(iconRes);
+ applyIconTint();
return this;
}
@@ -274,4 +279,48 @@ public class ActionMenuItem implements MenuItem {
// No need to save the listener; ActionMenuItem does not support collapsing items.
return this;
}
+
+ @Override
+ public MenuItem setIconTintList(ColorStateList tintList) {
+ if (mIconTintInfo == null) {
+ mIconTintInfo = new TintInfo();
+ }
+ mIconTintInfo.mTintList = tintList;
+ mIconTintInfo.mHasTintList = true;
+ applyIconTint();
+ return this;
+ }
+
+ @Override
+ public MenuItem setIconTintMode(PorterDuff.Mode tintMode) {
+ if (mIconTintInfo == null) {
+ mIconTintInfo = new TintInfo();
+ }
+ mIconTintInfo.mTintMode = tintMode;
+ mIconTintInfo.mHasTintMode = true;
+ applyIconTint();
+ return this;
+ }
+
+ private void applyIconTint() {
+ final TintInfo tintInfo = mIconTintInfo;
+ if (mIconDrawable != null && tintInfo != null) {
+ if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {
+ mIconDrawable = mIconDrawable.mutate();
+ if (tintInfo.mHasTintList) {
+ mIconDrawable.setTintList(tintInfo.mTintList);
+ }
+ if (tintInfo.mHasTintMode) {
+ mIconDrawable.setTintMode(tintInfo.mTintMode);
+ }
+ }
+ }
+ }
+
+ private static class TintInfo {
+ ColorStateList mTintList;
+ PorterDuff.Mode mTintMode;
+ boolean mHasTintMode;
+ boolean mHasTintList;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 7eec392..f75b139 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -217,14 +217,14 @@ public class ActionMenuItemView extends TextView
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return true;
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
final CharSequence cdesc = getContentDescription();
if (!TextUtils.isEmpty(cdesc)) {
event.getText().add(cdesc);
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 692bdac..29ac3f3 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -276,8 +276,8 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (mItemData != null && mItemData.hasSubMenu()) {
info.setCanOpenPopup(true);
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 3b1f20d..ef4e546 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -21,6 +21,8 @@ import com.android.internal.view.menu.MenuView.ItemView;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.ActionProvider;
@@ -60,6 +62,11 @@ public final class MenuItemImpl implements MenuItem {
* needed).
*/
private int mIconResId = NO_ICON;
+
+ /**
+ * Tint info for the icon
+ */
+ private TintInfo mIconTintInfo;
/** The menu to which this item belongs */
private MenuBuilder mMenu;
@@ -385,10 +392,10 @@ public final class MenuItemImpl implements MenuItem {
}
if (mIconResId != NO_ICON) {
- Drawable icon = mMenu.getContext().getDrawable(mIconResId);
+ mIconDrawable = mMenu.getContext().getDrawable(mIconResId);
mIconResId = NO_ICON;
- mIconDrawable = icon;
- return icon;
+ applyIconTint();
+ return mIconDrawable;
}
return null;
@@ -397,6 +404,7 @@ public final class MenuItemImpl implements MenuItem {
public MenuItem setIcon(Drawable icon) {
mIconResId = NO_ICON;
mIconDrawable = icon;
+ applyIconTint();
mMenu.onItemsChanged(false);
return this;
@@ -670,4 +678,48 @@ public final class MenuItemImpl implements MenuItem {
public boolean isActionViewExpanded() {
return mIsActionViewExpanded;
}
+
+ @Override
+ public MenuItem setIconTintList(ColorStateList tintList) {
+ if (mIconTintInfo == null) {
+ mIconTintInfo = new TintInfo();
+ }
+ mIconTintInfo.mTintList = tintList;
+ mIconTintInfo.mHasTintList = true;
+ applyIconTint();
+ return this;
+ }
+
+ @Override
+ public MenuItem setIconTintMode(PorterDuff.Mode tintMode) {
+ if (mIconTintInfo == null) {
+ mIconTintInfo = new TintInfo();
+ }
+ mIconTintInfo.mTintMode = tintMode;
+ mIconTintInfo.mHasTintMode = true;
+ applyIconTint();
+ return this;
+ }
+
+ private void applyIconTint() {
+ final TintInfo tintInfo = mIconTintInfo;
+ if (mIconDrawable != null && tintInfo != null) {
+ if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {
+ mIconDrawable = mIconDrawable.mutate();
+ if (tintInfo.mHasTintList) {
+ mIconDrawable.setTintList(tintInfo.mTintList);
+ }
+ if (tintInfo.mHasTintMode) {
+ mIconDrawable.setTintMode(tintInfo.mTintMode);
+ }
+ }
+ }
+ }
+
+ private static class TintInfo {
+ ColorStateList mTintList;
+ PorterDuff.Mode mTintMode;
+ boolean mHasTintMode;
+ boolean mHasTintList;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 99bb1ac..7d45071 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -43,8 +43,6 @@ import java.util.ArrayList;
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
View.OnAttachStateChangeListener, MenuPresenter {
- private static final String TAG = "MenuPopupHelper";
-
static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
private final Context mContext;
@@ -118,6 +116,10 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
mDropDownGravity = gravity;
}
+ public int getGravity() {
+ return mDropDownGravity;
+ }
+
public void show() {
if (!tryShow()) {
throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
@@ -128,14 +130,25 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
return mPopup;
}
+ /**
+ * Attempts to show the popup anchored to the view specified by
+ * {@link #setAnchorView(View)}.
+ *
+ * @return {@code true} if the popup was shown or was already showing prior
+ * to calling this method, {@code false} otherwise
+ */
public boolean tryShow() {
+ if (isShowing()) {
+ return true;
+ }
+
mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
mPopup.setOnDismissListener(this);
mPopup.setOnItemClickListener(this);
mPopup.setAdapter(mAdapter);
mPopup.setModal(true);
- View anchor = mAnchorView;
+ final View anchor = mAnchorView;
if (anchor != null) {
final boolean addGlobalListener = mTreeObserver == null;
mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
@@ -165,6 +178,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
}
}
+ @Override
public void onDismiss() {
mPopup = null;
mMenu.close();
@@ -186,6 +200,7 @@ public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.On
adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
}
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
dismiss();
diff --git a/core/java/com/android/internal/widget/AccessibleDateAnimator.java b/core/java/com/android/internal/widget/AccessibleDateAnimator.java
index e91a55c..f97a5d1 100644
--- a/core/java/com/android/internal/widget/AccessibleDateAnimator.java
+++ b/core/java/com/android/internal/widget/AccessibleDateAnimator.java
@@ -40,7 +40,7 @@ public class AccessibleDateAnimator extends ViewAnimator {
* Announce the currently-selected date when launched.
*/
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// Clear the event's current text so that only the current date will be spoken.
event.getText().clear();
@@ -51,6 +51,6 @@ public class AccessibleDateAnimator extends ViewAnimator {
event.getText().add(dateString);
return true;
}
- return super.dispatchPopulateAccessibilityEvent(event);
+ return super.dispatchPopulateAccessibilityEventInternal(event);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index 7937a95..a5efa82 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -23,7 +23,6 @@ import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.PixelFormat;
-import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ActionMode;
@@ -32,8 +31,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
-import java.util.List;
-
/**
* This class acts as a container for the action bar view and action mode context views.
* It applies special styles as needed to help handle animated transitions between them.
@@ -259,6 +256,15 @@ public class ActionBarContainer extends FrameLayout {
return null;
}
+ @Override
+ public ActionMode startActionModeForChild(
+ View child, ActionMode.Callback callback, int type) {
+ if (type != ActionMode.TYPE_PRIMARY) {
+ return super.startActionModeForChild(child, callback, type);
+ }
+ return null;
+ }
+
private static boolean isCollapsed(View view) {
return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
}
@@ -387,7 +393,7 @@ public class ActionBarContainer extends FrameLayout {
}
@Override
- public void setColorFilter(ColorFilter cf) {
+ public void setColorFilter(ColorFilter colorFilter) {
}
@Override
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 7c671e8..42d875d 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -17,8 +17,6 @@ package com.android.internal.widget;
import com.android.internal.R;
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
import android.widget.ActionMenuPresenter;
import android.widget.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
@@ -158,7 +156,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
removeView(mCustomView);
}
mCustomView = view;
- if (mTitleLayout != null) {
+ if (view != null && mTitleLayout != null) {
removeView(mTitleLayout);
mTitleLayout = null;
}
@@ -529,7 +527,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// Action mode started
event.setSource(this);
@@ -537,7 +535,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
event.setPackageName(getContext().getPackageName());
event.setContentDescription(mTitle);
} else {
- super.onInitializeAccessibilityEvent(event);
+ super.onInitializeAccessibilityEventInternal(event);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index cca48d3..c3a7460 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -32,7 +32,6 @@ import android.util.IntProperty;
import android.util.Log;
import android.util.Property;
import android.util.SparseArray;
-import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 654d08b..88436f8 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -1463,14 +1463,14 @@ public class ActionBarView extends AbsActionBarView implements DecorToolbar {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return true;
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
final CharSequence cdesc = getContentDescription();
if (!TextUtils.isEmpty(cdesc)) {
event.getText().add(cdesc);
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
new file mode 100644
index 0000000..64e6c69
--- /dev/null
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.internal.R;
+
+/**
+ * An extension of LinearLayout that automatically switches to vertical
+ * orientation when it can't fit its child views horizontally.
+ */
+public class ButtonBarLayout extends LinearLayout {
+ /** Spacer used in horizontal orientation. */
+ private final View mSpacer;
+
+ /** Whether the current configuration allows stacking. */
+ private final boolean mAllowStacked;
+
+ /** Whether the layout is currently stacked. */
+ private boolean mStacked;
+
+ public ButtonBarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mAllowStacked = context.getResources().getBoolean(R.bool.allow_stacked_button_bar);
+ mSpacer = findViewById(R.id.spacer);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ // Maybe we can fit the content now?
+ if (w > oldw && mStacked) {
+ setStacked(false);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ if (mAllowStacked && getOrientation() == LinearLayout.HORIZONTAL) {
+ final int measuredWidth = getMeasuredWidthAndState();
+ final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK;
+ if (measuredWidthState == MEASURED_STATE_TOO_SMALL) {
+ setStacked(true);
+
+ // Measure again in the new orientation.
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+ }
+
+ private void setStacked(boolean stacked) {
+ setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
+ setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
+
+ if (mSpacer != null) {
+ mSpacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
+ }
+
+ // Reverse the child order. This is specific to the Material button
+ // bar's layout XML and will probably not generalize.
+ final int childCount = getChildCount();
+ for (int i = childCount - 2; i >= 0; i--) {
+ bringChildToFront(getChildAt(i));
+ }
+
+ mStacked = stacked;
+ }
+}
diff --git a/core/java/com/android/internal/widget/ExploreByTouchHelper.java b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
index 0e046cb..bdf17fc 100644
--- a/core/java/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
@@ -567,7 +567,15 @@ public abstract class ExploreByTouchHelper extends View.AccessibilityDelegate {
}
// TODO: Check virtual view visibility.
if (!isAccessibilityFocused(virtualViewId)) {
+ // Clear focus from the previously focused view, if applicable.
+ if (mFocusedVirtualViewId != INVALID_ID) {
+ sendEventForVirtualView(mFocusedVirtualViewId,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ }
+
+ // Set focus on the new view.
mFocusedVirtualViewId = virtualViewId;
+
// TODO: Only invalidate virtual view bounds.
mView.invalidate();
sendEventForVirtualView(virtualViewId,
diff --git a/core/java/com/android/internal/widget/FaceUnlockView.java b/core/java/com/android/internal/widget/FaceUnlockView.java
deleted file mode 100644
index 121e601..0000000
--- a/core/java/com/android/internal/widget/FaceUnlockView.java
+++ /dev/null
@@ -1,67 +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 com.android.internal.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.RelativeLayout;
-
-public class FaceUnlockView extends RelativeLayout {
- private static final String TAG = "FaceUnlockView";
-
- public FaceUnlockView(Context context) {
- this(context, null);
- }
-
- public FaceUnlockView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- private int resolveMeasured(int measureSpec, int desired)
- {
- int result = 0;
- int specSize = MeasureSpec.getSize(measureSpec);
- switch (MeasureSpec.getMode(measureSpec)) {
- case MeasureSpec.UNSPECIFIED:
- result = desired;
- break;
- case MeasureSpec.AT_MOST:
- result = Math.max(specSize, desired);
- break;
- case MeasureSpec.EXACTLY:
- default:
- result = specSize;
- }
- return result;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int minimumWidth = getSuggestedMinimumWidth();
- final int minimumHeight = getSuggestedMinimumHeight();
- int viewWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
- int viewHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
-
- final int chosenSize = Math.min(viewWidth, viewHeight);
- final int newWidthMeasureSpec =
- MeasureSpec.makeMeasureSpec(chosenSize, MeasureSpec.AT_MOST);
- final int newHeightMeasureSpec =
- MeasureSpec.makeMeasureSpec(chosenSize, MeasureSpec.AT_MOST);
-
- super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
- }
-}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 9501f92..0cb1f38 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -31,5 +31,4 @@ interface ILockSettings {
boolean checkVoldPassword(int userId);
boolean havePattern(int userId);
boolean havePassword(int userId);
- void removeUser(int userId);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 0afc651..90821dc 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -18,14 +18,11 @@ package com.android.internal.widget;
import android.Manifest;
import android.app.ActivityManagerNative;
-import android.app.AlarmManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
-import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
@@ -39,19 +36,12 @@ import android.os.UserManager;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.provider.Settings;
-import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.Log;
-import android.view.IWindowManager;
-import android.view.View;
-import android.widget.Button;
-import com.android.internal.R;
import com.google.android.collect.Lists;
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import libcore.util.HexEncoding;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -59,6 +49,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import libcore.util.HexEncoding;
+
/**
* Utilities for the lock pattern and its settings.
*/
@@ -103,52 +95,36 @@ public class LockPatternUtils {
public static final int MIN_LOCK_PATTERN_SIZE = 4;
/**
+ * The minimum size of a valid password.
+ */
+ public static final int MIN_LOCK_PASSWORD_SIZE = 4;
+
+ /**
* The minimum number of dots the user must include in a wrong pattern
* attempt for it to be counted against the counts that affect
* {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET}
*/
public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE;
- /**
- * Tells the keyguard to show the user switcher when the keyguard is created.
- */
- public static final String KEYGUARD_SHOW_USER_SWITCHER = "showuserswitcher";
-
- /**
- * Tells the keyguard to show the security challenge when the keyguard is created.
- */
- public static final String KEYGUARD_SHOW_SECURITY_CHALLENGE = "showsecuritychallenge";
-
- /**
- * Tells the keyguard to show the widget with the specified id when the keyguard is created.
- */
- public static final String KEYGUARD_SHOW_APPWIDGET = "showappwidget";
-
- /**
- * The bit in LOCK_BIOMETRIC_WEAK_FLAGS to be used to indicate whether liveliness should
- * be used
- */
- public static final int FLAG_BIOMETRIC_WEAK_LIVELINESS = 0x1;
-
- /**
- * Pseudo-appwidget id we use to represent the default clock status widget
- */
- public static final int ID_DEFAULT_STATUS_WIDGET = -2;
-
+ @Deprecated
public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
public final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
- public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
+ @Deprecated
+ public final static String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
public final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
public final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
public final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
+ @Deprecated
public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK
= "lockscreen.biometric_weak_fallback";
+ @Deprecated
public final static String BIOMETRIC_WEAK_EVER_CHOSEN_KEY
= "lockscreen.biometricweakeverchosen";
public final static String LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS
= "lockscreen.power_button_instantly_locks";
+ @Deprecated
public final static String LOCKSCREEN_WIDGETS_ENABLED = "lockscreen.widgets_enabled";
public final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
@@ -227,7 +203,11 @@ public class LockPatternUtils {
}
public int getRequestedPasswordHistoryLength() {
- return getDevicePolicyManager().getPasswordHistoryLength(null, getCurrentOrCallingUserId());
+ return getRequestedPasswordHistoryLength(getCurrentOrCallingUserId());
+ }
+
+ private int getRequestedPasswordHistoryLength(int userId) {
+ return getDevicePolicyManager().getPasswordHistoryLength(null, userId);
}
public int getRequestedPasswordMinimumLetters() {
@@ -289,14 +269,6 @@ public class LockPatternUtils {
}
}
- public void removeUser(int userId) {
- try {
- getLockSettings().removeUser(userId);
- } catch (RemoteException re) {
- Log.e(TAG, "Couldn't remove lock settings for user " + userId);
- }
- }
-
private int getCurrentOrCallingUserId() {
if (mMultiUserMode) {
// TODO: This is a little inefficient. See if all users of this are able to
@@ -359,9 +331,10 @@ public class LockPatternUtils {
* @return Whether the password matches any in the history.
*/
public boolean checkPasswordHistory(String password) {
+ int userId = getCurrentOrCallingUserId();
String passwordHashString = new String(
- passwordToHash(password, getCurrentOrCallingUserId()), StandardCharsets.UTF_8);
- String passwordHistory = getString(PASSWORD_HISTORY_KEY);
+ passwordToHash(password, userId), StandardCharsets.UTF_8);
+ String passwordHistory = getString(PASSWORD_HISTORY_KEY, userId);
if (passwordHistory == null) {
return false;
}
@@ -383,15 +356,7 @@ public class LockPatternUtils {
* Check to see if the user has stored a lock pattern.
* @return Whether a saved pattern exists.
*/
- public boolean savedPatternExists() {
- return savedPatternExists(getCurrentOrCallingUserId());
- }
-
- /**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
- */
- public boolean savedPatternExists(int userId) {
+ private boolean savedPatternExists(int userId) {
try {
return getLockSettings().havePattern(userId);
} catch (RemoteException re) {
@@ -403,15 +368,7 @@ public class LockPatternUtils {
* Check to see if the user has stored a lock pattern.
* @return Whether a saved pattern exists.
*/
- public boolean savedPasswordExists() {
- return savedPasswordExists(getCurrentOrCallingUserId());
- }
-
- /**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
- */
- public boolean savedPasswordExists(int userId) {
+ private boolean savedPasswordExists(int userId) {
try {
return getLockSettings().havePassword(userId);
} catch (RemoteException re) {
@@ -426,86 +383,62 @@ public class LockPatternUtils {
* @return True if the user has ever chosen a pattern.
*/
public boolean isPatternEverChosen() {
- return getBoolean(PATTERN_EVER_CHOSEN_KEY, false);
+ return getBoolean(PATTERN_EVER_CHOSEN_KEY, false, getCurrentOrCallingUserId());
}
/**
- * Return true if the user has ever chosen biometric weak. This is true even if biometric
- * weak is not current set.
- *
- * @return True if the user has ever chosen biometric weak.
+ * Used by device policy manager to validate the current password
+ * information it has.
*/
- public boolean isBiometricWeakEverChosen() {
- return getBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, false);
+ public int getActivePasswordQuality() {
+ return getActivePasswordQuality(getCurrentOrCallingUserId());
}
/**
* Used by device policy manager to validate the current password
* information it has.
*/
- public int getActivePasswordQuality() {
- int activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- // Note we don't want to use getKeyguardStoredPasswordQuality() because we want this to
- // return biometric_weak if that is being used instead of the backup
- int quality =
- (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
- switch (quality) {
- case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
- if (isLockPatternEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK:
- if (isBiometricWeakInstalled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
- }
- break;
- case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
- if (isLockPasswordEnabled()) {
- activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
- }
- break;
+ public int getActivePasswordQuality(int userId) {
+ int quality = getKeyguardStoredPasswordQuality(userId);
+
+ if (isLockPasswordEnabled(quality, userId)) {
+ // Quality is a password and a password exists. Return the quality.
+ return quality;
}
- return activePasswordQuality;
+ if (isLockPatternEnabled(quality, userId)) {
+ // Quality is a pattern and a pattern exists. Return the quality.
+ return quality;
+ }
+
+ return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
}
- public void clearLock(boolean isFallback) {
- clearLock(isFallback, getCurrentOrCallingUserId());
+ public void clearLock() {
+ clearLock(getCurrentOrCallingUserId());
}
/**
* Clear any lock pattern or password.
*/
- public void clearLock(boolean isFallback, int userHandle) {
- if(!isFallback) deleteGallery(userHandle);
- saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, isFallback,
- userHandle);
- setLockPatternEnabled(false, userHandle);
- saveLockPattern(null, isFallback, userHandle);
+ public void clearLock(int userHandle) {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
- setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userHandle);
+
+ try {
+ getLockSettings().setLockPassword(null, userHandle);
+ getLockSettings().setLockPattern(null, userHandle);
+ } catch (RemoteException e) {
+ // well, we tried...
+ }
+
+ if (userHandle == UserHandle.USER_OWNER) {
+ // Set the encryption password to default.
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
+ }
+
+ getDevicePolicyManager().setActivePasswordState(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle);
+
onAfterChangingPassword(userHandle);
}
@@ -516,7 +449,7 @@ public class LockPatternUtils {
* @param disable Disables lock screen when true
*/
public void setLockScreenDisabled(boolean disable) {
- setLong(DISABLE_LOCKSCREEN_KEY, disable ? 1 : 0);
+ setBoolean(DISABLE_LOCKSCREEN_KEY, disable, getCurrentOrCallingUserId());
}
/**
@@ -526,7 +459,7 @@ public class LockPatternUtils {
* @return true if lock screen is can be disabled
*/
public boolean isLockScreenDisabled() {
- if (!isSecure() && getLong(DISABLE_LOCKSCREEN_KEY, 0) != 0) {
+ if (!isSecure() && getBoolean(DISABLE_LOCKSCREEN_KEY, false, getCurrentOrCallingUserId())) {
// Check if the number of switchable users forces the lockscreen.
final List<UserInfo> users = UserManager.get(mContext).getUsers(true);
final int userCount = users.size();
@@ -542,96 +475,57 @@ public class LockPatternUtils {
}
/**
- * Calls back SetupFaceLock to delete the temporary gallery file
- */
- public void deleteTempGallery() {
- Intent intent = new Intent().setAction("com.android.facelock.DELETE_GALLERY");
- intent.putExtra("deleteTempGallery", true);
- mContext.sendBroadcast(intent);
- }
-
- /**
- * Calls back SetupFaceLock to delete the gallery file when the lock type is changed
- */
- void deleteGallery(int userId) {
- if(usingBiometricWeak(userId)) {
- Intent intent = new Intent().setAction("com.android.facelock.DELETE_GALLERY");
- intent.putExtra("deleteGallery", true);
- mContext.sendBroadcastAsUser(intent, new UserHandle(userId));
- }
- }
-
- /**
* Save a lock pattern.
* @param pattern The new pattern to save.
*/
public void saveLockPattern(List<LockPatternView.Cell> pattern) {
- this.saveLockPattern(pattern, false);
- }
-
- /**
- * Save a lock pattern.
- * @param pattern The new pattern to save.
- */
- public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback) {
- this.saveLockPattern(pattern, isFallback, getCurrentOrCallingUserId());
+ this.saveLockPattern(pattern, getCurrentOrCallingUserId());
}
/**
* Save a lock pattern.
* @param pattern The new pattern to save.
- * @param isFallback Specifies if this is a fallback to biometric weak
* @param userId the user whose pattern is to be saved.
*/
- public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback,
- int userId) {
+ public void saveLockPattern(List<LockPatternView.Cell> pattern, int userId) {
try {
+ if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
+ throw new IllegalArgumentException("pattern must not be null and at least "
+ + MIN_LOCK_PATTERN_SIZE + " dots long.");
+ }
+
getLockSettings().setLockPattern(patternToString(pattern), userId);
DevicePolicyManager dpm = getDevicePolicyManager();
- if (pattern != null) {
- // Update the device encryption password.
- if (userId == UserHandle.USER_OWNER
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- final boolean required = isCredentialRequiredToDecrypt(true);
- if (!required) {
- clearEncryptionPassword();
- } else {
- String stringPattern = patternToString(pattern);
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, stringPattern);
- }
- }
- setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
- if (!isFallback) {
- deleteGallery(userId);
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
- pattern.size(), 0, 0, 0, 0, 0, 0, userId);
+ // Update the device encryption password.
+ if (userId == UserHandle.USER_OWNER
+ && LockPatternUtils.isDeviceEncryptionEnabled()) {
+ final boolean required = isCredentialRequiredToDecrypt(true);
+ if (!required) {
+ clearEncryptionPassword();
} else {
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, userId);
- setLong(PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
- finishBiometricWeak(userId);
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- 0, 0, 0, 0, 0, 0, 0, userId);
+ String stringPattern = patternToString(pattern);
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, stringPattern);
}
- } else {
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0,
- 0, 0, 0, 0, 0, userId);
}
+
+ setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId);
+
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId);
+ dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
+ pattern.size(), 0, 0, 0, 0, 0, 0, userId);
onAfterChangingPassword(userId);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't save lock pattern " + re);
}
}
- private void updateCryptoUserInfo() {
- int userId = getCurrentOrCallingUserId();
+ private void updateCryptoUserInfo(int userId) {
if (userId != UserHandle.USER_OWNER) {
return;
}
- final String ownerInfo = isOwnerInfoEnabled() ? getOwnerInfo(userId) : "";
+ final String ownerInfo = isOwnerInfoEnabled(userId) ? getOwnerInfo(userId) : "";
IBinder service = ServiceManager.getService("mount");
if (service == null) {
@@ -650,20 +544,25 @@ public class LockPatternUtils {
public void setOwnerInfo(String info, int userId) {
setString(LOCK_SCREEN_OWNER_INFO, info, userId);
- updateCryptoUserInfo();
+ updateCryptoUserInfo(userId);
}
public void setOwnerInfoEnabled(boolean enabled) {
- setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled);
- updateCryptoUserInfo();
+ int userId = getCurrentOrCallingUserId();
+ setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled, userId);
+ updateCryptoUserInfo(userId);
}
public String getOwnerInfo(int userId) {
- return getString(LOCK_SCREEN_OWNER_INFO);
+ return getString(LOCK_SCREEN_OWNER_INFO, userId);
}
public boolean isOwnerInfoEnabled() {
- return getBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, false);
+ return isOwnerInfoEnabled(getCurrentOrCallingUserId());
+ }
+
+ private boolean isOwnerInfoEnabled(int userId) {
+ return getBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, false, userId);
}
/**
@@ -789,19 +688,7 @@ public class LockPatternUtils {
* @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
*/
public void saveLockPassword(String password, int quality) {
- this.saveLockPassword(password, quality, false, getCurrentOrCallingUserId());
- }
-
- /**
- * Save a lock password. Does not ensure that the password is as good
- * as the requested mode, but will adjust the mode to be as good as the
- * pattern.
- * @param password The password to save
- * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
- * @param isFallback Specifies if this is a fallback to biometric weak
- */
- public void saveLockPassword(String password, int quality, boolean isFallback) {
- saveLockPassword(password, quality, isFallback, getCurrentOrCallingUserId());
+ saveLockPassword(password, quality, getCurrentOrCallingUserId());
}
/**
@@ -810,108 +697,88 @@ public class LockPatternUtils {
* pattern.
* @param password The password to save
* @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
- * @param isFallback Specifies if this is a fallback to biometric weak
* @param userHandle The userId of the user to change the password for
*/
- public void saveLockPassword(String password, int quality, boolean isFallback, int userHandle) {
+ public void saveLockPassword(String password, int quality, int userHandle) {
try {
DevicePolicyManager dpm = getDevicePolicyManager();
- if (!TextUtils.isEmpty(password)) {
- getLockSettings().setLockPassword(password, userHandle);
- int computedQuality = computePasswordQuality(password);
-
- // Update the device encryption password.
- if (userHandle == UserHandle.USER_OWNER
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- if (!isCredentialRequiredToDecrypt(true)) {
- clearEncryptionPassword();
- } else {
- boolean numeric = computedQuality
- == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
- boolean numericComplex = computedQuality
- == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
- : StorageManager.CRYPT_TYPE_PASSWORD;
- updateEncryptionPassword(type, password);
- }
+ if (password == null || password.length() < MIN_LOCK_PASSWORD_SIZE) {
+ throw new IllegalArgumentException("password must not be null and at least "
+ + "of length " + MIN_LOCK_PASSWORD_SIZE);
+ }
+
+ getLockSettings().setLockPassword(password, userHandle);
+ int computedQuality = computePasswordQuality(password);
+
+ // Update the device encryption password.
+ if (userHandle == UserHandle.USER_OWNER
+ && LockPatternUtils.isDeviceEncryptionEnabled()) {
+ if (!isCredentialRequiredToDecrypt(true)) {
+ clearEncryptionPassword();
+ } else {
+ boolean numeric = computedQuality
+ == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ boolean numericComplex = computedQuality
+ == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
+ : StorageManager.CRYPT_TYPE_PASSWORD;
+ updateEncryptionPassword(type, password);
}
+ }
- if (!isFallback) {
- deleteGallery(userHandle);
- setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
- if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
- int letters = 0;
- int uppercase = 0;
- int lowercase = 0;
- int numbers = 0;
- int symbols = 0;
- int nonletter = 0;
- for (int i = 0; i < password.length(); i++) {
- char c = password.charAt(i);
- if (c >= 'A' && c <= 'Z') {
- letters++;
- uppercase++;
- } else if (c >= 'a' && c <= 'z') {
- letters++;
- lowercase++;
- } else if (c >= '0' && c <= '9') {
- numbers++;
- nonletter++;
- } else {
- symbols++;
- nonletter++;
- }
- }
- dpm.setActivePasswordState(Math.max(quality, computedQuality),
- password.length(), letters, uppercase, lowercase,
- numbers, symbols, nonletter, userHandle);
+ setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
+ if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ int letters = 0;
+ int uppercase = 0;
+ int lowercase = 0;
+ int numbers = 0;
+ int symbols = 0;
+ int nonletter = 0;
+ for (int i = 0; i < password.length(); i++) {
+ char c = password.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ letters++;
+ uppercase++;
+ } else if (c >= 'a' && c <= 'z') {
+ letters++;
+ lowercase++;
+ } else if (c >= '0' && c <= '9') {
+ numbers++;
+ nonletter++;
} else {
- // The password is not anything.
- dpm.setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- 0, 0, 0, 0, 0, 0, 0, userHandle);
+ symbols++;
+ nonletter++;
}
- } else {
- // Case where it's a fallback for biometric weak
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- userHandle);
- setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality),
- userHandle);
- finishBiometricWeak(userHandle);
- dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- 0, 0, 0, 0, 0, 0, 0, userHandle);
}
- // Add the password to the password history. We assume all
- // password hashes have the same length for simplicity of implementation.
- String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
- if (passwordHistory == null) {
- passwordHistory = "";
- }
- int passwordHistoryLength = getRequestedPasswordHistoryLength();
- if (passwordHistoryLength == 0) {
- passwordHistory = "";
- } else {
- byte[] hash = passwordToHash(password, userHandle);
- passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
- // Cut it to contain passwordHistoryLength hashes
- // and passwordHistoryLength -1 commas.
- passwordHistory = passwordHistory.substring(0, Math.min(hash.length
- * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
- .length()));
- }
- setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
+ dpm.setActivePasswordState(Math.max(quality, computedQuality),
+ password.length(), letters, uppercase, lowercase,
+ numbers, symbols, nonletter, userHandle);
} else {
- // Empty password
- getLockSettings().setLockPassword(null, userHandle);
- if (userHandle == UserHandle.USER_OWNER) {
- // Set the encryption password to default.
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
- }
-
+ // The password is not anything.
dpm.setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0,
- userHandle);
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+ 0, 0, 0, 0, 0, 0, 0, userHandle);
}
+
+ // Add the password to the password history. We assume all
+ // password hashes have the same length for simplicity of implementation.
+ String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
+ if (passwordHistory == null) {
+ passwordHistory = "";
+ }
+ int passwordHistoryLength = getRequestedPasswordHistoryLength(userHandle);
+ if (passwordHistoryLength == 0) {
+ passwordHistory = "";
+ } else {
+ byte[] hash = passwordToHash(password, userHandle);
+ passwordHistory = new String(hash, StandardCharsets.UTF_8) + "," + passwordHistory;
+ // Cut it to contain passwordHistoryLength hashes
+ // and passwordHistoryLength -1 commas.
+ passwordHistory = passwordHistory.substring(0, Math.min(hash.length
+ * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
+ .length()));
+ }
+ setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
onAfterChangingPassword(userHandle);
} catch (RemoteException re) {
// Cant do much
@@ -971,31 +838,8 @@ public class LockPatternUtils {
* @return stored password quality
*/
public int getKeyguardStoredPasswordQuality(int userHandle) {
- int quality = (int) getLong(PASSWORD_TYPE_KEY,
+ return (int) getLong(PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
- // If the user has chosen to use weak biometric sensor, then return the backup locking
- // method and treat biometric as a special case.
- if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
- quality = (int) getLong(PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userHandle);
- }
- return quality;
- }
-
- /**
- * @return true if the lockscreen method is set to biometric weak
- */
- public boolean usingBiometricWeak() {
- return usingBiometricWeak(getCurrentOrCallingUserId());
- }
-
- /**
- * @return true if the lockscreen method is set to biometric weak
- */
- public boolean usingBiometricWeak(int userId) {
- int quality = (int) getLong(
- PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
- return quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
}
/**
@@ -1004,6 +848,10 @@ public class LockPatternUtils {
* @return The pattern.
*/
public static List<LockPatternView.Cell> stringToPattern(String string) {
+ if (string == null) {
+ return null;
+ }
+
List<LockPatternView.Cell> result = Lists.newArrayList();
final byte[] bytes = string.getBytes();
@@ -1106,126 +954,73 @@ public class LockPatternUtils {
}
/**
- * @return Whether the lock password is enabled, or if it is set as a backup for biometric weak
+ * @return Whether the lock screen is secured.
*/
- public boolean isLockPasswordEnabled() {
- long mode = getLong(PASSWORD_TYPE_KEY, 0);
- long backupMode = getLong(PASSWORD_TYPE_ALTERNATE_KEY, 0);
- final boolean passwordEnabled = mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
- || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
- final boolean backupEnabled = backupMode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
- || backupMode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- || backupMode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
- || backupMode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- || backupMode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-
- return savedPasswordExists() && (passwordEnabled ||
- (usingBiometricWeak() && backupEnabled));
+ public boolean isSecure() {
+ return isSecure(getCurrentOrCallingUserId());
}
/**
- * @return Whether the lock pattern is enabled, or if it is set as a backup for biometric weak
+ * @param userId the user for which to report the value
+ * @return Whether the lock screen is secured.
*/
- public boolean isLockPatternEnabled() {
- return isLockPatternEnabled(getCurrentOrCallingUserId());
+ public boolean isSecure(int userId) {
+ int mode = getKeyguardStoredPasswordQuality(userId);
+ return isLockPatternEnabled(mode, userId) || isLockPasswordEnabled(mode, userId);
}
/**
- * @return Whether the lock pattern is enabled, or if it is set as a backup for biometric weak
+ * @return Whether the lock password is enabled
*/
- public boolean isLockPatternEnabled(int userId) {
- final boolean backupEnabled =
- getLong(PASSWORD_TYPE_ALTERNATE_KEY,
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId)
- == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
-
- return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, false, userId)
- && (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- userId) == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
- || (usingBiometricWeak(userId) && backupEnabled));
+ public boolean isLockPasswordEnabled() {
+ return isLockPasswordEnabled(getCurrentOrCallingUserId());
}
- /**
- * @return Whether biometric weak lock is installed and that the front facing camera exists
- */
- public boolean isBiometricWeakInstalled() {
- // Check that it's installed
- PackageManager pm = mContext.getPackageManager();
- try {
- pm.getPackageInfo("com.android.facelock", PackageManager.GET_ACTIVITIES);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
-
- // Check that the camera is enabled
- if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
- return false;
- }
- if (getDevicePolicyManager().getCameraDisabled(null, getCurrentOrCallingUserId())) {
- return false;
- }
-
- // TODO: If we decide not to proceed with Face Unlock as a trustlet, this must be changed
- // back to returning true. If we become certain that Face Unlock will be a trustlet, this
- // entire function and a lot of other code can be removed.
- if (DEBUG) Log.d(TAG, "Forcing isBiometricWeakInstalled() to return false to disable it");
- return false;
+ public boolean isLockPasswordEnabled(int userId) {
+ return isLockPasswordEnabled(getKeyguardStoredPasswordQuality(userId), userId);
}
- /**
- * Set whether biometric weak liveliness is enabled.
- */
- public void setBiometricWeakLivelinessEnabled(boolean enabled) {
- long currentFlag = getLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 0L);
- long newFlag;
- if (enabled) {
- newFlag = currentFlag | FLAG_BIOMETRIC_WEAK_LIVELINESS;
- } else {
- newFlag = currentFlag & ~FLAG_BIOMETRIC_WEAK_LIVELINESS;
- }
- setLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, newFlag);
+ private boolean isLockPasswordEnabled(int mode, int userId) {
+ final boolean passwordEnabled = mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+ return passwordEnabled && savedPasswordExists(userId);
}
/**
- * @return Whether the biometric weak liveliness is enabled.
+ * @return Whether the lock pattern is enabled
*/
- public boolean isBiometricWeakLivelinessEnabled() {
- long currentFlag = getLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 0L);
- return ((currentFlag & FLAG_BIOMETRIC_WEAK_LIVELINESS) != 0);
+ public boolean isLockPatternEnabled() {
+ return isLockPatternEnabled(getCurrentOrCallingUserId());
}
- /**
- * Set whether the lock pattern is enabled.
- */
- public void setLockPatternEnabled(boolean enabled) {
- setLockPatternEnabled(enabled, getCurrentOrCallingUserId());
+ public boolean isLockPatternEnabled(int userId) {
+ return isLockPatternEnabled(getKeyguardStoredPasswordQuality(userId), userId);
}
- /**
- * Set whether the lock pattern is enabled.
- */
- public void setLockPatternEnabled(boolean enabled, int userHandle) {
- setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, enabled, userHandle);
+ private boolean isLockPatternEnabled(int mode, int userId) {
+ return mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+ && savedPatternExists(userId);
}
/**
* @return Whether the visible pattern is enabled.
*/
public boolean isVisiblePatternEnabled() {
- return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, false);
+ return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, false, getCurrentOrCallingUserId());
}
/**
* Set whether the visible pattern is enabled.
*/
public void setVisiblePatternEnabled(boolean enabled) {
- setBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, enabled);
+ int userId = getCurrentOrCallingUserId();
+
+ setBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, enabled, userId);
// Update for crypto if owner
- int userId = getCurrentOrCallingUserId();
if (userId != UserHandle.USER_OWNER) {
return;
}
@@ -1259,7 +1054,7 @@ public class LockPatternUtils {
*/
public long setLockoutAttemptDeadline() {
final long deadline = SystemClock.elapsedRealtime() + FAILED_ATTEMPT_TIMEOUT_MS;
- setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline);
+ setLong(LOCKOUT_ATTEMPT_DEADLINE, deadline, getCurrentOrCallingUserId());
return deadline;
}
@@ -1269,7 +1064,7 @@ public class LockPatternUtils {
* enter a pattern.
*/
public long getLockoutAttemptDeadline() {
- final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L);
+ final long deadline = getLong(LOCKOUT_ATTEMPT_DEADLINE, 0L, getCurrentOrCallingUserId());
final long now = SystemClock.elapsedRealtime();
if (deadline < now || deadline > (now + FAILED_ATTEMPT_TIMEOUT_MS)) {
return 0L;
@@ -1277,51 +1072,6 @@ public class LockPatternUtils {
return deadline;
}
- /**
- * @return Whether the user is permanently locked out until they verify their
- * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed
- * attempts.
- */
- public boolean isPermanentlyLocked() {
- return getBoolean(LOCKOUT_PERMANENT_KEY, false);
- }
-
- /**
- * Set the state of whether the device is permanently locked, meaning the user
- * must authenticate via other means.
- *
- * @param locked Whether the user is permanently locked out until they verify their
- * credentials. Occurs after {@link #FAILED_ATTEMPTS_BEFORE_RESET} failed
- * attempts.
- */
- public void setPermanentlyLocked(boolean locked) {
- setBoolean(LOCKOUT_PERMANENT_KEY, locked);
- }
-
- public boolean isEmergencyCallCapable() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable);
- }
-
- public boolean isPukUnlockScreenEnable() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enable_puk_unlock_screen);
- }
-
- public boolean isEmergencyCallEnabledWhileSimLocked() {
- return mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
- }
-
- /**
- * @return A formatted string of the next alarm (for showing on the lock screen),
- * or null if there is no next alarm.
- */
- public AlarmManager.AlarmClockInfo getNextAlarm() {
- AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- return alarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
- }
-
private boolean getBoolean(String secureSettingKey, boolean defaultValue, int userId) {
try {
return getLockSettings().getBoolean(secureSettingKey, defaultValue, userId);
@@ -1330,10 +1080,6 @@ public class LockPatternUtils {
}
}
- private boolean getBoolean(String secureSettingKey, boolean defaultValue) {
- return getBoolean(secureSettingKey, defaultValue, getCurrentOrCallingUserId());
- }
-
private void setBoolean(String secureSettingKey, boolean enabled, int userId) {
try {
getLockSettings().setBoolean(secureSettingKey, enabled, userId);
@@ -1343,144 +1089,6 @@ public class LockPatternUtils {
}
}
- private void setBoolean(String secureSettingKey, boolean enabled) {
- setBoolean(secureSettingKey, enabled, getCurrentOrCallingUserId());
- }
-
- public int[] getAppWidgets() {
- return getAppWidgets(UserHandle.USER_CURRENT);
- }
-
- private int[] getAppWidgets(int userId) {
- String appWidgetIdString = Settings.Secure.getStringForUser(
- mContentResolver, Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS, userId);
- String delims = ",";
- if (appWidgetIdString != null && appWidgetIdString.length() > 0) {
- String[] appWidgetStringIds = appWidgetIdString.split(delims);
- int[] appWidgetIds = new int[appWidgetStringIds.length];
- for (int i = 0; i < appWidgetStringIds.length; i++) {
- String appWidget = appWidgetStringIds[i];
- try {
- appWidgetIds[i] = Integer.decode(appWidget);
- } catch (NumberFormatException e) {
- Log.d(TAG, "Error when parsing widget id " + appWidget);
- return null;
- }
- }
- return appWidgetIds;
- }
- return new int[0];
- }
-
- private static String combineStrings(int[] list, String separator) {
- int listLength = list.length;
-
- switch (listLength) {
- case 0: {
- return "";
- }
- case 1: {
- return Integer.toString(list[0]);
- }
- }
-
- int strLength = 0;
- int separatorLength = separator.length();
-
- String[] stringList = new String[list.length];
- for (int i = 0; i < listLength; i++) {
- stringList[i] = Integer.toString(list[i]);
- strLength += stringList[i].length();
- if (i < listLength - 1) {
- strLength += separatorLength;
- }
- }
-
- StringBuilder sb = new StringBuilder(strLength);
-
- for (int i = 0; i < listLength; i++) {
- sb.append(list[i]);
- if (i < listLength - 1) {
- sb.append(separator);
- }
- }
-
- return sb.toString();
- }
-
- // appwidget used when appwidgets are disabled (we make an exception for
- // default clock widget)
- public void writeFallbackAppWidgetId(int appWidgetId) {
- Settings.Secure.putIntForUser(mContentResolver,
- Settings.Secure.LOCK_SCREEN_FALLBACK_APPWIDGET_ID,
- appWidgetId,
- UserHandle.USER_CURRENT);
- }
-
- // appwidget used when appwidgets are disabled (we make an exception for
- // default clock widget)
- public int getFallbackAppWidgetId() {
- return Settings.Secure.getIntForUser(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_FALLBACK_APPWIDGET_ID,
- AppWidgetManager.INVALID_APPWIDGET_ID,
- UserHandle.USER_CURRENT);
- }
-
- private void writeAppWidgets(int[] appWidgetIds) {
- Settings.Secure.putStringForUser(mContentResolver,
- Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS,
- combineStrings(appWidgetIds, ","),
- UserHandle.USER_CURRENT);
- }
-
- // TODO: log an error if this returns false
- public boolean addAppWidget(int widgetId, int index) {
- int[] widgets = getAppWidgets();
- if (widgets == null) {
- return false;
- }
- if (index < 0 || index > widgets.length) {
- return false;
- }
- int[] newWidgets = new int[widgets.length + 1];
- for (int i = 0, j = 0; i < newWidgets.length; i++) {
- if (index == i) {
- newWidgets[i] = widgetId;
- i++;
- }
- if (i < newWidgets.length) {
- newWidgets[i] = widgets[j];
- j++;
- }
- }
- writeAppWidgets(newWidgets);
- return true;
- }
-
- public boolean removeAppWidget(int widgetId) {
- int[] widgets = getAppWidgets();
-
- if (widgets.length == 0) {
- return false;
- }
-
- int[] newWidgets = new int[widgets.length - 1];
- for (int i = 0, j = 0; i < widgets.length; i++) {
- if (widgets[i] == widgetId) {
- // continue...
- } else if (j >= newWidgets.length) {
- // we couldn't find the widget
- return false;
- } else {
- newWidgets[j] = widgets[i];
- j++;
- }
- }
- writeAppWidgets(newWidgets);
- return true;
- }
-
private long getLong(String secureSettingKey, long defaultValue, int userHandle) {
try {
return getLockSettings().getLong(secureSettingKey, defaultValue, userHandle);
@@ -1489,19 +1097,6 @@ public class LockPatternUtils {
}
}
- private long getLong(String secureSettingKey, long defaultValue) {
- try {
- return getLockSettings().getLong(secureSettingKey, defaultValue,
- getCurrentOrCallingUserId());
- } catch (RemoteException re) {
- return defaultValue;
- }
- }
-
- private void setLong(String secureSettingKey, long value) {
- setLong(secureSettingKey, value, getCurrentOrCallingUserId());
- }
-
private void setLong(String secureSettingKey, long value, int userHandle) {
try {
getLockSettings().setLong(secureSettingKey, value, userHandle);
@@ -1511,10 +1106,6 @@ public class LockPatternUtils {
}
}
- private String getString(String secureSettingKey) {
- return getString(secureSettingKey, getCurrentOrCallingUserId());
- }
-
private String getString(String secureSettingKey, int userHandle) {
try {
return getLockSettings().getString(secureSettingKey, null, userHandle);
@@ -1532,134 +1123,13 @@ public class LockPatternUtils {
}
}
- public boolean isSecure() {
- return isSecure(getCurrentOrCallingUserId());
- }
-
- public boolean isSecure(int userId) {
- long mode = getKeyguardStoredPasswordQuality(userId);
- final boolean isPattern = mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
- final boolean isPassword = mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
- || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
- || mode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
- final boolean secure =
- isPattern && isLockPatternEnabled(userId) && savedPatternExists(userId)
- || isPassword && savedPasswordExists(userId);
- return secure;
- }
-
- /**
- * Sets the emergency button visibility based on isEmergencyCallCapable().
- *
- * If the emergency button is visible, sets the text on the emergency button
- * to indicate what action will be taken.
- *
- * If there's currently a call in progress, the button will take them to the call
- * @param button The button to update
- * @param shown Indicates whether the given screen wants the emergency button to show at all
- * @param showIcon Indicates whether to show a phone icon for the button.
- */
- public void updateEmergencyCallButtonState(Button button, boolean shown, boolean showIcon) {
- if (isEmergencyCallCapable() && shown) {
- button.setVisibility(View.VISIBLE);
- } else {
- button.setVisibility(View.GONE);
- return;
- }
-
- int textId;
- if (isInCall()) {
- // show "return to call" text and show phone icon
- textId = R.string.lockscreen_return_to_call;
- int phoneCallIcon = showIcon ? R.drawable.stat_sys_phone_call : 0;
- button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
- } else {
- textId = R.string.lockscreen_emergency_call;
- int emergencyIcon = showIcon ? R.drawable.ic_emergency : 0;
- button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
- }
- button.setText(textId);
- }
-
- /**
- * Resumes a call in progress. Typically launched from the EmergencyCall button
- * on various lockscreens.
- */
- public void resumeCall() {
- getTelecommManager().showInCallScreen(false);
- }
-
- /**
- * @return {@code true} if there is a call currently in progress, {@code false} otherwise.
- */
- public boolean isInCall() {
- return getTelecommManager().isInCall();
- }
-
- private TelecomManager getTelecommManager() {
- return (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- }
-
- private void finishBiometricWeak(int userId) {
- setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true, userId);
-
- // Launch intent to show final screen, this also
- // moves the temporary gallery to the actual gallery
- Intent intent = new Intent();
- intent.setClassName("com.android.facelock",
- "com.android.facelock.SetupEndScreen");
- mContext.startActivityAsUser(intent, new UserHandle(userId));
- }
-
public void setPowerButtonInstantlyLocks(boolean enabled) {
- setBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, enabled);
+ setBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, enabled, getCurrentOrCallingUserId());
}
public boolean getPowerButtonInstantlyLocks() {
- return getBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, true);
- }
-
- public static boolean isSafeModeEnabled() {
- try {
- return IWindowManager.Stub.asInterface(
- ServiceManager.getService("window")).isSafeModeEnabled();
- } catch (RemoteException e) {
- // Shouldn't happen!
- }
- return false;
- }
-
- /**
- * Determine whether the user has selected any non-system widgets in keyguard
- *
- * @return true if widgets have been selected
- */
- public boolean hasWidgetsEnabledInKeyguard(int userid) {
- int widgets[] = getAppWidgets(userid);
- for (int i = 0; i < widgets.length; i++) {
- if (widgets[i] > 0) {
- return true;
- }
- }
- return false;
- }
-
- public boolean getWidgetsEnabled() {
- return getWidgetsEnabled(getCurrentOrCallingUserId());
- }
-
- public boolean getWidgetsEnabled(int userId) {
- return getBoolean(LOCKSCREEN_WIDGETS_ENABLED, false, userId);
- }
-
- public void setWidgetsEnabled(boolean enabled) {
- setWidgetsEnabled(enabled, getCurrentOrCallingUserId());
- }
-
- public void setWidgetsEnabled(boolean enabled, int userId) {
- setBoolean(LOCKSCREEN_WIDGETS_ENABLED, enabled, userId);
+ return getBoolean(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, true,
+ getCurrentOrCallingUserId());
}
public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents) {
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 9fa6882..8be34e7 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -65,8 +65,8 @@ public class LockPatternView extends View {
private boolean mDrawingProfilingStarted = false;
- private Paint mPaint = new Paint();
- private Paint mPathPaint = new Paint();
+ private final Paint mPaint = new Paint();
+ private final Paint mPathPaint = new Paint();
/**
* How many milliseconds we spend animating each circle of a lock pattern
@@ -82,7 +82,7 @@ public class LockPatternView extends View {
private static final float DRAG_THRESHHOLD = 0.0f;
private OnPatternListener mOnPatternListener;
- private ArrayList<Cell> mPattern = new ArrayList<Cell>(9);
+ private final ArrayList<Cell> mPattern = new ArrayList<Cell>(9);
/**
* Lookup table for the circles of the pattern we are currently drawing.
@@ -90,7 +90,7 @@ public class LockPatternView extends View {
* in which case we use this to hold the cells we are drawing for the in
* progress animation.
*/
- private boolean[][] mPatternDrawLookup = new boolean[3][3];
+ private final boolean[][] mPatternDrawLookup = new boolean[3][3];
/**
* the in progress point:
@@ -122,24 +122,27 @@ public class LockPatternView extends View {
private int mErrorColor;
private int mSuccessColor;
- private Interpolator mFastOutSlowInInterpolator;
- private Interpolator mLinearOutSlowInInterpolator;
+ private final Interpolator mFastOutSlowInInterpolator;
+ private final Interpolator mLinearOutSlowInInterpolator;
/**
* Represents a cell in the 3 X 3 matrix of the unlock pattern view.
*/
- public static class Cell {
- int row;
- int column;
+ public static final class Cell {
+ final int row;
+ final int column;
// keep # objects limited to 9
- static Cell[][] sCells = new Cell[3][3];
- static {
+ private static final Cell[][] sCells = createCells();
+
+ private static Cell[][] createCells() {
+ Cell[][] res = new Cell[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
- sCells[i][j] = new Cell(i, j);
+ res[i][j] = new Cell(i, j);
}
}
+ return res;
}
/**
@@ -160,11 +163,7 @@ public class LockPatternView extends View {
return column;
}
- /**
- * @param row The row of the cell.
- * @param column The column of the cell.
- */
- public static synchronized Cell of(int row, int column) {
+ public static Cell of(int row, int column) {
checkRange(row, column);
return sCells[row][column];
}
@@ -178,6 +177,7 @@ public class LockPatternView extends View {
}
}
+ @Override
public String toString() {
return "(row=" + row + ",clmn=" + column + ")";
}
@@ -269,9 +269,9 @@ public class LockPatternView extends View {
mPathPaint.setAntiAlias(true);
mPathPaint.setDither(true);
- mRegularColor = getResources().getColor(R.color.lock_pattern_view_regular_color);
- mErrorColor = getResources().getColor(R.color.lock_pattern_view_error_color);
- mSuccessColor = getResources().getColor(R.color.lock_pattern_view_success_color);
+ mRegularColor = context.getColor(R.color.lock_pattern_view_regular_color);
+ mErrorColor = context.getColor(R.color.lock_pattern_view_error_color);
+ mSuccessColor = context.getColor(R.color.lock_pattern_view_success_color);
mRegularColor = a.getColor(R.styleable.LockPatternView_regularColor, mRegularColor);
mErrorColor = a.getColor(R.styleable.LockPatternView_errorColor, mErrorColor);
mSuccessColor = a.getColor(R.styleable.LockPatternView_successColor, mSuccessColor);
@@ -722,7 +722,7 @@ public class LockPatternView extends View {
handleActionDown(event);
return true;
case MotionEvent.ACTION_UP:
- handleActionUp(event);
+ handleActionUp();
return true;
case MotionEvent.ACTION_MOVE:
handleActionMove(event);
@@ -812,7 +812,7 @@ public class LockPatternView extends View {
announceForAccessibility(mContext.getString(resId));
}
- private void handleActionUp(MotionEvent event) {
+ private void handleActionUp() {
// report pattern detected
if (!mPattern.isEmpty()) {
mPatternInProgress = false;
@@ -1119,12 +1119,15 @@ public class LockPatternView extends View {
dest.writeValue(mTactileFeedbackEnabled);
}
+ @SuppressWarnings({ "unused", "hiding" }) // Found using reflection
public static final Parcelable.Creator<SavedState> CREATOR =
new Creator<SavedState>() {
+ @Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
+ @Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
diff --git a/core/java/com/android/internal/widget/PagerAdapter.java b/core/java/com/android/internal/widget/PagerAdapter.java
new file mode 100644
index 0000000..910a720
--- /dev/null
+++ b/core/java/com/android/internal/widget/PagerAdapter.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+import android.os.Parcelable;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Base class providing the adapter to populate pages inside of
+ * a {@link android.support.v4.view.ViewPager}. You will most likely want to use a more
+ * specific implementation of this, such as
+ * {@link android.support.v4.app.FragmentPagerAdapter} or
+ * {@link android.support.v4.app.FragmentStatePagerAdapter}.
+ *
+ * <p>When you implement a PagerAdapter, you must override the following methods
+ * at minimum:</p>
+ * <ul>
+ * <li>{@link #instantiateItem(android.view.ViewGroup, int)}</li>
+ * <li>{@link #destroyItem(android.view.ViewGroup, int, Object)}</li>
+ * <li>{@link #getCount()}</li>
+ * <li>{@link #isViewFromObject(android.view.View, Object)}</li>
+ * </ul>
+ *
+ * <p>PagerAdapter is more general than the adapters used for
+ * {@link android.widget.AdapterView AdapterViews}. Instead of providing a
+ * View recycling mechanism directly ViewPager uses callbacks to indicate the
+ * steps taken during an update. A PagerAdapter may implement a form of View
+ * recycling if desired or use a more sophisticated method of managing page
+ * Views such as Fragment transactions where each page is represented by its
+ * own Fragment.</p>
+ *
+ * <p>ViewPager associates each page with a key Object instead of working with
+ * Views directly. This key is used to track and uniquely identify a given page
+ * independent of its position in the adapter. A call to the PagerAdapter method
+ * {@link #startUpdate(android.view.ViewGroup)} indicates that the contents of the ViewPager
+ * are about to change. One or more calls to {@link #instantiateItem(android.view.ViewGroup, int)}
+ * and/or {@link #destroyItem(android.view.ViewGroup, int, Object)} will follow, and the end
+ * of an update will be signaled by a call to {@link #finishUpdate(android.view.ViewGroup)}.
+ * By the time {@link #finishUpdate(android.view.ViewGroup) finishUpdate} returns the views
+ * associated with the key objects returned by
+ * {@link #instantiateItem(android.view.ViewGroup, int) instantiateItem} should be added to
+ * the parent ViewGroup passed to these methods and the views associated with
+ * the keys passed to {@link #destroyItem(android.view.ViewGroup, int, Object) destroyItem}
+ * should be removed. The method {@link #isViewFromObject(android.view.View, Object)} identifies
+ * whether a page View is associated with a given key object.</p>
+ *
+ * <p>A very simple PagerAdapter may choose to use the page Views themselves
+ * as key objects, returning them from {@link #instantiateItem(android.view.ViewGroup, int)}
+ * after creation and adding them to the parent ViewGroup. A matching
+ * {@link #destroyItem(android.view.ViewGroup, int, Object)} implementation would remove the
+ * View from the parent ViewGroup and {@link #isViewFromObject(android.view.View, Object)}
+ * could be implemented as <code>return view == object;</code>.</p>
+ *
+ * <p>PagerAdapter supports data set changes. Data set changes must occur on the
+ * main thread and must end with a call to {@link #notifyDataSetChanged()} similar
+ * to AdapterView adapters derived from {@link android.widget.BaseAdapter}. A data
+ * set change may involve pages being added, removed, or changing position. The
+ * ViewPager will keep the current page active provided the adapter implements
+ * the method {@link #getItemPosition(Object)}.</p>
+ */
+public abstract class PagerAdapter {
+ private DataSetObservable mObservable = new DataSetObservable();
+
+ public static final int POSITION_UNCHANGED = -1;
+ public static final int POSITION_NONE = -2;
+
+ /**
+ * Return the number of views available.
+ */
+ public abstract int getCount();
+
+ /**
+ * Called when a change in the shown pages is going to start being made.
+ * @param container The containing View which is displaying this adapter's
+ * page views.
+ */
+ public void startUpdate(ViewGroup container) {
+ startUpdate((View) container);
+ }
+
+ /**
+ * Create the page for the given position. The adapter is responsible
+ * for adding the view to the container given here, although it only
+ * must ensure this is done by the time it returns from
+ * {@link #finishUpdate(android.view.ViewGroup)}.
+ *
+ * @param container The containing View in which the page will be shown.
+ * @param position The page position to be instantiated.
+ * @return Returns an Object representing the new page. This does not
+ * need to be a View, but can be some other container of the page.
+ */
+ public Object instantiateItem(ViewGroup container, int position) {
+ return instantiateItem((View) container, position);
+ }
+
+ /**
+ * Remove a page for the given position. The adapter is responsible
+ * for removing the view from its container, although it only must ensure
+ * this is done by the time it returns from {@link #finishUpdate(android.view.ViewGroup)}.
+ *
+ * @param container The containing View from which the page will be removed.
+ * @param position The page position to be removed.
+ * @param object The same object that was returned by
+ * {@link #instantiateItem(android.view.View, int)}.
+ */
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ destroyItem((View) container, position, object);
+ }
+
+ /**
+ * Called to inform the adapter of which item is currently considered to
+ * be the "primary", that is the one show to the user as the current page.
+ *
+ * @param container The containing View from which the page will be removed.
+ * @param position The page position that is now the primary.
+ * @param object The same object that was returned by
+ * {@link #instantiateItem(android.view.View, int)}.
+ */
+ public void setPrimaryItem(ViewGroup container, int position, Object object) {
+ setPrimaryItem((View) container, position, object);
+ }
+
+ /**
+ * Called when the a change in the shown pages has been completed. At this
+ * point you must ensure that all of the pages have actually been added or
+ * removed from the container as appropriate.
+ * @param container The containing View which is displaying this adapter's
+ * page views.
+ */
+ public void finishUpdate(ViewGroup container) {
+ finishUpdate((View) container);
+ }
+
+ /**
+ * Called when a change in the shown pages is going to start being made.
+ * @param container The containing View which is displaying this adapter's
+ * page views.
+ *
+ * @deprecated Use {@link #startUpdate(android.view.ViewGroup)}
+ */
+ public void startUpdate(View container) {
+ }
+
+ /**
+ * Create the page for the given position. The adapter is responsible
+ * for adding the view to the container given here, although it only
+ * must ensure this is done by the time it returns from
+ * {@link #finishUpdate(android.view.ViewGroup)}.
+ *
+ * @param container The containing View in which the page will be shown.
+ * @param position The page position to be instantiated.
+ * @return Returns an Object representing the new page. This does not
+ * need to be a View, but can be some other container of the page.
+ *
+ * @deprecated Use {@link #instantiateItem(android.view.ViewGroup, int)}
+ */
+ public Object instantiateItem(View container, int position) {
+ throw new UnsupportedOperationException(
+ "Required method instantiateItem was not overridden");
+ }
+
+ /**
+ * Remove a page for the given position. The adapter is responsible
+ * for removing the view from its container, although it only must ensure
+ * this is done by the time it returns from {@link #finishUpdate(android.view.View)}.
+ *
+ * @param container The containing View from which the page will be removed.
+ * @param position The page position to be removed.
+ * @param object The same object that was returned by
+ * {@link #instantiateItem(android.view.View, int)}.
+ *
+ * @deprecated Use {@link #destroyItem(android.view.ViewGroup, int, Object)}
+ */
+ public void destroyItem(View container, int position, Object object) {
+ throw new UnsupportedOperationException("Required method destroyItem was not overridden");
+ }
+
+ /**
+ * Called to inform the adapter of which item is currently considered to
+ * be the "primary", that is the one show to the user as the current page.
+ *
+ * @param container The containing View from which the page will be removed.
+ * @param position The page position that is now the primary.
+ * @param object The same object that was returned by
+ * {@link #instantiateItem(android.view.View, int)}.
+ *
+ * @deprecated Use {@link #setPrimaryItem(android.view.ViewGroup, int, Object)}
+ */
+ public void setPrimaryItem(View container, int position, Object object) {
+ }
+
+ /**
+ * Called when the a change in the shown pages has been completed. At this
+ * point you must ensure that all of the pages have actually been added or
+ * removed from the container as appropriate.
+ * @param container The containing View which is displaying this adapter's
+ * page views.
+ *
+ * @deprecated Use {@link #finishUpdate(android.view.ViewGroup)}
+ */
+ public void finishUpdate(View container) {
+ }
+
+ /**
+ * Determines whether a page View is associated with a specific key object
+ * as returned by {@link #instantiateItem(android.view.ViewGroup, int)}. This method is
+ * required for a PagerAdapter to function properly.
+ *
+ * @param view Page View to check for association with <code>object</code>
+ * @param object Object to check for association with <code>view</code>
+ * @return true if <code>view</code> is associated with the key object <code>object</code>
+ */
+ public abstract boolean isViewFromObject(View view, Object object);
+
+ /**
+ * Save any instance state associated with this adapter and its pages that should be
+ * restored if the current UI state needs to be reconstructed.
+ *
+ * @return Saved state for this adapter
+ */
+ public Parcelable saveState() {
+ return null;
+ }
+
+ /**
+ * Restore any instance state associated with this adapter and its pages
+ * that was previously saved by {@link #saveState()}.
+ *
+ * @param state State previously saved by a call to {@link #saveState()}
+ * @param loader A ClassLoader that should be used to instantiate any restored objects
+ */
+ public void restoreState(Parcelable state, ClassLoader loader) {
+ }
+
+ /**
+ * Called when the host view is attempting to determine if an item's position
+ * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
+ * item has not changed or {@link #POSITION_NONE} if the item is no longer present
+ * in the adapter.
+ *
+ * <p>The default implementation assumes that items will never
+ * change position and always returns {@link #POSITION_UNCHANGED}.
+ *
+ * @param object Object representing an item, previously returned by a call to
+ * {@link #instantiateItem(android.view.View, int)}.
+ * @return object's new position index from [0, {@link #getCount()}),
+ * {@link #POSITION_UNCHANGED} if the object's position has not changed,
+ * or {@link #POSITION_NONE} if the item is no longer present.
+ */
+ public int getItemPosition(Object object) {
+ return POSITION_UNCHANGED;
+ }
+
+ /**
+ * This method should be called by the application if the data backing this adapter has changed
+ * and associated views should update.
+ */
+ public void notifyDataSetChanged() {
+ mObservable.notifyChanged();
+ }
+
+ /**
+ * Register an observer to receive callbacks related to the adapter's data changing.
+ *
+ * @param observer The {@link android.database.DataSetObserver} which will receive callbacks.
+ */
+ public void registerDataSetObserver(DataSetObserver observer) {
+ mObservable.registerObserver(observer);
+ }
+
+ /**
+ * Unregister an observer from callbacks related to the adapter's data changing.
+ *
+ * @param observer The {@link android.database.DataSetObserver} which will be unregistered.
+ */
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ mObservable.unregisterObserver(observer);
+ }
+
+ /**
+ * This method may be called by the ViewPager to obtain a title string
+ * to describe the specified page. This method may return null
+ * indicating no title for this page. The default implementation returns
+ * null.
+ *
+ * @param position The position of the title requested
+ * @return A title for the requested page
+ */
+ public CharSequence getPageTitle(int position) {
+ return null;
+ }
+
+ /**
+ * Returns the proportional width of a given page as a percentage of the
+ * ViewPager's measured width from (0.f-1.f]
+ *
+ * @param position The position of the page requested
+ * @return Proportional width for the given page position
+ */
+ public float getPageWidth(int position) {
+ return 1.f;
+ }
+}
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 4e48454..01e835b 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -593,15 +593,13 @@ public class ResolverDrawerLayout extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ResolverDrawerLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ResolverDrawerLayout.class.getName();
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ResolverDrawerLayout.class.getName());
if (isEnabled()) {
if (mCollapseOffset != 0) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index d6bd1d6..ffd9b24 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -31,7 +31,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
@@ -150,7 +149,9 @@ public class ScrollingTabContainerView extends HorizontalScrollView
addView(mTabSpinner, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if (mTabSpinner.getAdapter() == null) {
- mTabSpinner.setAdapter(new TabAdapter());
+ final TabAdapter adapter = new TabAdapter(mContext);
+ adapter.setDropDownViewContext(mTabSpinner.getPopupContext());
+ mTabSpinner.setAdapter(adapter);
}
if (mTabSelector != null) {
removeCallbacks(mTabSelector);
@@ -276,8 +277,8 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
}
- private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) {
- final TabView tabView = new TabView(getContext(), tab, forAdapter);
+ private TabView createTabView(Context context, ActionBar.Tab tab, boolean forAdapter) {
+ final TabView tabView = new TabView(context, tab, forAdapter);
if (forAdapter) {
tabView.setBackgroundDrawable(null);
tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,
@@ -294,7 +295,7 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
public void addTab(ActionBar.Tab tab, boolean setSelected) {
- TabView tabView = createTabView(tab, false);
+ TabView tabView = createTabView(mContext, tab, false);
mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0,
LayoutParams.MATCH_PARENT, 1));
if (mTabSpinner != null) {
@@ -309,7 +310,7 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
- final TabView tabView = createTabView(tab, false);
+ final TabView tabView = createTabView(mContext, tab, false);
mTabLayout.addView(tabView, position, new LinearLayout.LayoutParams(
0, LayoutParams.MATCH_PARENT, 1));
if (mTabSpinner != null) {
@@ -391,17 +392,9 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public CharSequence getAccessibilityClassName() {
// This view masquerades as an action bar tab.
- event.setClassName(ActionBar.Tab.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- // This view masquerades as an action bar tab.
- info.setClassName(ActionBar.Tab.class.getName());
+ return ActionBar.Tab.class.getName();
}
@Override
@@ -514,6 +507,16 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
private class TabAdapter extends BaseAdapter {
+ private Context mDropDownContext;
+
+ public TabAdapter(Context context) {
+ setDropDownViewContext(context);
+ }
+
+ public void setDropDownViewContext(Context context) {
+ mDropDownContext = context;
+ }
+
@Override
public int getCount() {
return mTabLayout.getChildCount();
@@ -532,7 +535,18 @@ public class ScrollingTabContainerView extends HorizontalScrollView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
- convertView = createTabView((ActionBar.Tab) getItem(position), true);
+ convertView = createTabView(mContext, (ActionBar.Tab) getItem(position), true);
+ } else {
+ ((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
+ }
+ return convertView;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = createTabView(mDropDownContext,
+ (ActionBar.Tab) getItem(position), true);
} else {
((TabView) convertView).bindTab((ActionBar.Tab) getItem(position));
}
diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
deleted file mode 100644
index 5f3c5f9..0000000
--- a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
+++ /dev/null
@@ -1,442 +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 com.android.internal.widget;
-
-import java.lang.Math;
-
-import com.android.internal.R;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.StateSet;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.widget.RemoteViews.RemoteView;
-
-/**
- * A layout that switches between its children based on the requested layout height.
- * Each child specifies its minimum and maximum valid height. Results are undefined
- * if children specify overlapping ranges. A child may specify the maximum height
- * as 'unbounded' to indicate that it is willing to be displayed arbitrarily tall.
- *
- * <p>
- * See {@link SizeAdaptiveLayout.LayoutParams} for a full description of the
- * layout parameters used by SizeAdaptiveLayout.
- */
-@RemoteView
-public class SizeAdaptiveLayout extends ViewGroup {
-
- private static final String TAG = "SizeAdaptiveLayout";
- private static final boolean DEBUG = false;
- private static final boolean REPORT_BAD_BOUNDS = true;
- private static final long CROSSFADE_TIME = 250;
-
- // TypedArray indices
- private static final int MIN_VALID_HEIGHT =
- R.styleable.SizeAdaptiveLayout_Layout_layout_minHeight;
- private static final int MAX_VALID_HEIGHT =
- R.styleable.SizeAdaptiveLayout_Layout_layout_maxHeight;
-
- // view state
- private View mActiveChild;
- private View mLastActive;
-
- // animation state
- private AnimatorSet mTransitionAnimation;
- private AnimatorListener mAnimatorListener;
- private ObjectAnimator mFadePanel;
- private ObjectAnimator mFadeView;
- private int mCanceledAnimationCount;
- private View mEnteringView;
- private View mLeavingView;
- // View used to hide larger views under smaller ones to create a uniform crossfade
- private View mModestyPanel;
- private int mModestyPanelTop;
-
- public SizeAdaptiveLayout(Context context) {
- this(context, null);
- }
-
- public SizeAdaptiveLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public SizeAdaptiveLayout(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- initialize();
- }
-
- private void initialize() {
- mModestyPanel = new View(getContext());
- // If the SizeAdaptiveLayout has a solid background, use it as a transition hint.
- Drawable background = getBackground();
- if (background instanceof StateListDrawable) {
- StateListDrawable sld = (StateListDrawable) background;
- sld.setState(StateSet.WILD_CARD);
- background = sld.getCurrent();
- }
- if (background instanceof ColorDrawable) {
- mModestyPanel.setBackgroundDrawable(background);
- }
- SizeAdaptiveLayout.LayoutParams layout =
- new SizeAdaptiveLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
- mModestyPanel.setLayoutParams(layout);
- addView(mModestyPanel);
- mFadePanel = ObjectAnimator.ofFloat(mModestyPanel, "alpha", 0f);
- mFadeView = ObjectAnimator.ofFloat(null, "alpha", 0f);
- mAnimatorListener = new BringToFrontOnEnd();
- mTransitionAnimation = new AnimatorSet();
- mTransitionAnimation.play(mFadeView).with(mFadePanel);
- mTransitionAnimation.setDuration(CROSSFADE_TIME);
- mTransitionAnimation.addListener(mAnimatorListener);
- }
-
- /**
- * Visible for testing
- * @hide
- */
- public Animator getTransitionAnimation() {
- return mTransitionAnimation;
- }
-
- /**
- * Visible for testing
- * @hide
- */
- public View getModestyPanel() {
- return mModestyPanel;
- }
-
- @Override
- public void onAttachedToWindow() {
- mLastActive = null;
- // make sure all views start off invisible.
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).setVisibility(View.GONE);
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (DEBUG) Log.d(TAG, this + " measure spec: " +
- MeasureSpec.toString(heightMeasureSpec));
- View model = selectActiveChild(heightMeasureSpec);
- if (model == null) {
- setMeasuredDimension(0, 0);
- return;
- }
- SizeAdaptiveLayout.LayoutParams lp =
- (SizeAdaptiveLayout.LayoutParams) model.getLayoutParams();
- if (DEBUG) Log.d(TAG, "active min: " + lp.minHeight + " max: " + lp.maxHeight);
- measureChild(model, widthMeasureSpec, heightMeasureSpec);
- int childHeight = model.getMeasuredHeight();
- int childWidth = model.getMeasuredHeight();
- int childState = combineMeasuredStates(0, model.getMeasuredState());
- if (DEBUG) Log.d(TAG, "measured child at: " + childHeight);
- int resolvedWidth = resolveSizeAndState(childWidth, widthMeasureSpec, childState);
- int resolvedHeight = resolveSizeAndState(childHeight, heightMeasureSpec, childState);
- if (DEBUG) Log.d(TAG, "resolved to: " + resolvedHeight);
- int boundedHeight = clampSizeToBounds(resolvedHeight, model);
- if (DEBUG) Log.d(TAG, "bounded to: " + boundedHeight);
- setMeasuredDimension(resolvedWidth, boundedHeight);
- }
-
- private int clampSizeToBounds(int measuredHeight, View child) {
- SizeAdaptiveLayout.LayoutParams lp =
- (SizeAdaptiveLayout.LayoutParams) child.getLayoutParams();
- int heightIn = View.MEASURED_SIZE_MASK & measuredHeight;
- int height = Math.max(heightIn, lp.minHeight);
- if (lp.maxHeight != SizeAdaptiveLayout.LayoutParams.UNBOUNDED) {
- height = Math.min(height, lp.maxHeight);
- }
-
- if (REPORT_BAD_BOUNDS && heightIn != height) {
- Log.d(TAG, this + "child view " + child + " " +
- "measured out of bounds at " + heightIn +"px " +
- "clamped to " + height + "px");
- }
-
- return height;
- }
-
- //TODO extend to width and height
- private View selectActiveChild(int heightMeasureSpec) {
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
- View unboundedView = null;
- View tallestView = null;
- int tallestViewSize = 0;
- View smallestView = null;
- int smallestViewSize = Integer.MAX_VALUE;
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child != mModestyPanel) {
- SizeAdaptiveLayout.LayoutParams lp =
- (SizeAdaptiveLayout.LayoutParams) child.getLayoutParams();
- if (DEBUG) Log.d(TAG, "looking at " + i +
- " with min: " + lp.minHeight +
- " max: " + lp.maxHeight);
- if (lp.maxHeight == SizeAdaptiveLayout.LayoutParams.UNBOUNDED &&
- unboundedView == null) {
- unboundedView = child;
- }
- if (lp.maxHeight > tallestViewSize) {
- tallestViewSize = lp.maxHeight;
- tallestView = child;
- }
- if (lp.minHeight < smallestViewSize) {
- smallestViewSize = lp.minHeight;
- smallestView = child;
- }
- if (heightMode != MeasureSpec.UNSPECIFIED &&
- heightSize >= lp.minHeight && heightSize <= lp.maxHeight) {
- if (DEBUG) Log.d(TAG, " found exact match, finishing early");
- return child;
- }
- }
- }
- if (unboundedView != null) {
- tallestView = unboundedView;
- }
- if (heightMode == MeasureSpec.UNSPECIFIED || heightSize > tallestViewSize) {
- return tallestView;
- } else {
- return smallestView;
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (DEBUG) Log.d(TAG, this + " onlayout height: " + (bottom - top));
- mLastActive = mActiveChild;
- int measureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top,
- View.MeasureSpec.EXACTLY);
- mActiveChild = selectActiveChild(measureSpec);
- if (mActiveChild == null) return;
-
- mActiveChild.setVisibility(View.VISIBLE);
-
- if (mLastActive != mActiveChild && mLastActive != null) {
- if (DEBUG) Log.d(TAG, this + " changed children from: " + mLastActive +
- " to: " + mActiveChild);
-
- mEnteringView = mActiveChild;
- mLeavingView = mLastActive;
-
- mEnteringView.setAlpha(1f);
-
- mModestyPanel.setAlpha(1f);
- mModestyPanel.bringToFront();
- mModestyPanelTop = mLeavingView.getHeight();
- mModestyPanel.setVisibility(View.VISIBLE);
- // TODO: mModestyPanel background should be compatible with mLeavingView
-
- mLeavingView.bringToFront();
-
- if (mTransitionAnimation.isRunning()) {
- mTransitionAnimation.cancel();
- }
- mFadeView.setTarget(mLeavingView);
- mFadeView.setFloatValues(0f);
- mFadePanel.setFloatValues(0f);
- mTransitionAnimation.setupStartValues();
- mTransitionAnimation.start();
- }
- final int childWidth = mActiveChild.getMeasuredWidth();
- final int childHeight = mActiveChild.getMeasuredHeight();
- // TODO investigate setting LAYER_TYPE_HARDWARE on mLastActive
- mActiveChild.layout(0, 0, childWidth, childHeight);
-
- if (DEBUG) Log.d(TAG, "got modesty offset of " + mModestyPanelTop);
- mModestyPanel.layout(0, mModestyPanelTop, childWidth, mModestyPanelTop + childHeight);
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- if (DEBUG) Log.d(TAG, "generate layout from attrs");
- return new SizeAdaptiveLayout.LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- if (DEBUG) Log.d(TAG, "generate default layout from viewgroup");
- return new SizeAdaptiveLayout.LayoutParams(p);
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- if (DEBUG) Log.d(TAG, "generate default layout from null");
- return new SizeAdaptiveLayout.LayoutParams();
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
- return p instanceof SizeAdaptiveLayout.LayoutParams;
- }
-
- /**
- * Per-child layout information associated with ViewSizeAdaptiveLayout.
- *
- * TODO extend to width and height
- *
- * @attr ref android.R.styleable#SizeAdaptiveLayout_Layout_layout_minHeight
- * @attr ref android.R.styleable#SizeAdaptiveLayout_Layout_layout_maxHeight
- */
- public static class LayoutParams extends ViewGroup.LayoutParams {
-
- /**
- * Indicates the minimum valid height for the child.
- */
- @ViewDebug.ExportedProperty(category = "layout")
- public int minHeight;
-
- /**
- * Indicates the maximum valid height for the child.
- */
- @ViewDebug.ExportedProperty(category = "layout")
- public int maxHeight;
-
- /**
- * Constant value for maxHeight that indicates there is not maximum height.
- */
- public static final int UNBOUNDED = -1;
-
- /**
- * {@inheritDoc}
- */
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
- if (DEBUG) {
- Log.d(TAG, "construct layout from attrs");
- for (int i = 0; i < attrs.getAttributeCount(); i++) {
- Log.d(TAG, " " + attrs.getAttributeName(i) + " = " +
- attrs.getAttributeValue(i));
- }
- }
- TypedArray a =
- c.obtainStyledAttributes(attrs,
- R.styleable.SizeAdaptiveLayout_Layout);
-
- minHeight = a.getDimensionPixelSize(MIN_VALID_HEIGHT, 0);
- if (DEBUG) Log.d(TAG, "got minHeight of: " + minHeight);
-
- try {
- maxHeight = a.getLayoutDimension(MAX_VALID_HEIGHT, UNBOUNDED);
- if (DEBUG) Log.d(TAG, "got maxHeight of: " + maxHeight);
- } catch (Exception e) {
- if (DEBUG) Log.d(TAG, "caught exception looking for maxValidHeight " + e);
- }
-
- a.recycle();
- }
-
- /**
- * Creates a new set of layout parameters with the specified width, height
- * and valid height bounds.
- *
- * @param width the width, either {@link #MATCH_PARENT},
- * {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #MATCH_PARENT},
- * {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param minHeight the minimum height of this child
- * @param maxHeight the maximum height of this child
- * or {@link #UNBOUNDED} if the child can grow forever
- */
- public LayoutParams(int width, int height, int minHeight, int maxHeight) {
- super(width, height);
- this.minHeight = minHeight;
- this.maxHeight = maxHeight;
- }
-
- /**
- * {@inheritDoc}
- */
- public LayoutParams(int width, int height) {
- this(width, height, UNBOUNDED, UNBOUNDED);
- }
-
- /**
- * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
- */
- public LayoutParams() {
- this(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- }
-
- /**
- * {@inheritDoc}
- */
- public LayoutParams(ViewGroup.LayoutParams p) {
- super(p);
- minHeight = UNBOUNDED;
- maxHeight = UNBOUNDED;
- }
-
- public String debug(String output) {
- return output + "SizeAdaptiveLayout.LayoutParams={" +
- ", max=" + maxHeight +
- ", max=" + minHeight + "}";
- }
- }
-
- class BringToFrontOnEnd implements AnimatorListener {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCanceledAnimationCount == 0) {
- mLeavingView.setVisibility(View.GONE);
- mModestyPanel.setVisibility(View.GONE);
- mEnteringView.bringToFront();
- mEnteringView = null;
- mLeavingView = null;
- } else {
- mCanceledAnimationCount--;
- }
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCanceledAnimationCount++;
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- if (DEBUG) Log.d(TAG, "fade animation repeated: should never happen.");
- assert(false);
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- }
- }
-}
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index a206e7f..8c395ec 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -31,7 +31,6 @@ import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.view.View;
import android.view.accessibility.CaptioningManager.CaptionStyle;
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index 8d1f73a..54df87b 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -27,8 +27,6 @@ import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
new file mode 100644
index 0000000..f916e6f
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -0,0 +1,2866 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.DrawableRes;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.FocusFinder;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityRecord;
+import android.view.animation.Interpolator;
+import android.widget.EdgeEffect;
+import android.widget.Scroller;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Layout manager that allows the user to flip left and right
+ * through pages of data. You supply an implementation of a
+ * {@link android.support.v4.view.PagerAdapter} to generate the pages that the view shows.
+ *
+ * <p>Note this class is currently under early design and
+ * development. The API will likely change in later updates of
+ * the compatibility library, requiring changes to the source code
+ * of apps when they are compiled against the newer version.</p>
+ *
+ * <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
+ * which is a convenient way to supply and manage the lifecycle of each page.
+ * There are standard adapters implemented for using fragments with the ViewPager,
+ * which cover the most common use cases. These are
+ * {@link android.support.v4.app.FragmentPagerAdapter} and
+ * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
+ * classes have simple code showing how to build a full user interface
+ * with them.
+ *
+ * <p>For more information about how to use ViewPager, read <a
+ * href="{@docRoot}training/implementing-navigation/lateral.html">Creating Swipe Views with
+ * Tabs</a>.</p>
+ *
+ * <p>Below is a more complicated example of ViewPager, using it in conjunction
+ * with {@link android.app.ActionBar} tabs. You can find other examples of using
+ * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
+ *
+ * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
+ * complete}
+ */
+public class ViewPager extends ViewGroup {
+ private static final String TAG = "ViewPager";
+ private static final boolean DEBUG = false;
+
+ private static final boolean USE_CACHE = false;
+
+ private static final int DEFAULT_OFFSCREEN_PAGES = 1;
+ private static final int MAX_SETTLE_DURATION = 600; // ms
+ private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
+
+ private static final int DEFAULT_GUTTER_SIZE = 16; // dips
+
+ private static final int MIN_FLING_VELOCITY = 400; // dips
+
+ private static final int[] LAYOUT_ATTRS = new int[] {
+ com.android.internal.R.attr.layout_gravity
+ };
+
+ /**
+ * Used to track what the expected number of items in the adapter should be.
+ * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
+ */
+ private int mExpectedAdapterCount;
+
+ static class ItemInfo {
+ Object object;
+ int position;
+ boolean scrolling;
+ float widthFactor;
+ float offset;
+ }
+
+ private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
+ @Override
+ public int compare(ItemInfo lhs, ItemInfo rhs) {
+ return lhs.position - rhs.position;
+ }
+ };
+
+ private static final Interpolator sInterpolator = new Interpolator() {
+ public float getInterpolation(float t) {
+ t -= 1.0f;
+ return t * t * t * t * t + 1.0f;
+ }
+ };
+
+ private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
+ private final ItemInfo mTempItem = new ItemInfo();
+
+ private final Rect mTempRect = new Rect();
+
+ private PagerAdapter mAdapter;
+ private int mCurItem; // Index of currently displayed page.
+ private int mRestoredCurItem = -1;
+ private Parcelable mRestoredAdapterState = null;
+ private ClassLoader mRestoredClassLoader = null;
+ private Scroller mScroller;
+ private PagerObserver mObserver;
+
+ private int mPageMargin;
+ private Drawable mMarginDrawable;
+ private int mTopPageBounds;
+ private int mBottomPageBounds;
+
+ // Offsets of the first and last items, if known.
+ // Set during population, used to determine if we are at the beginning
+ // or end of the pager data set during touch scrolling.
+ private float mFirstOffset = -Float.MAX_VALUE;
+ private float mLastOffset = Float.MAX_VALUE;
+
+ private int mChildWidthMeasureSpec;
+ private int mChildHeightMeasureSpec;
+ private boolean mInLayout;
+
+ private boolean mScrollingCacheEnabled;
+
+ private boolean mPopulatePending;
+ private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
+
+ private boolean mIsBeingDragged;
+ private boolean mIsUnableToDrag;
+ private int mDefaultGutterSize;
+ private int mGutterSize;
+ private int mTouchSlop;
+ /**
+ * Position of the last motion event.
+ */
+ private float mLastMotionX;
+ private float mLastMotionY;
+ private float mInitialMotionX;
+ private float mInitialMotionY;
+ /**
+ * ID of the active pointer. This is used to retain consistency during
+ * drags/flings if multiple pointers are used.
+ */
+ private int mActivePointerId = INVALID_POINTER;
+ /**
+ * Sentinel value for no current active pointer.
+ * Used by {@link #mActivePointerId}.
+ */
+ private static final int INVALID_POINTER = -1;
+
+ /**
+ * Determines speed during touch scrolling
+ */
+ private VelocityTracker mVelocityTracker;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
+ private int mFlingDistance;
+ private int mCloseEnough;
+
+ // If the pager is at least this close to its final position, complete the scroll
+ // on touch down and let the user interact with the content inside instead of
+ // "catching" the flinging pager.
+ private static final int CLOSE_ENOUGH = 2; // dp
+
+ private boolean mFakeDragging;
+ private long mFakeDragBeginTime;
+
+ private EdgeEffect mLeftEdge;
+ private EdgeEffect mRightEdge;
+
+ private boolean mFirstLayout = true;
+ private boolean mNeedCalculatePageOffsets = false;
+ private boolean mCalledSuper;
+ private int mDecorChildCount;
+
+ private OnPageChangeListener mOnPageChangeListener;
+ private OnPageChangeListener mInternalPageChangeListener;
+ private OnAdapterChangeListener mAdapterChangeListener;
+ private PageTransformer mPageTransformer;
+
+ private static final int DRAW_ORDER_DEFAULT = 0;
+ private static final int DRAW_ORDER_FORWARD = 1;
+ private static final int DRAW_ORDER_REVERSE = 2;
+ private int mDrawingOrder;
+ private ArrayList<View> mDrawingOrderedChildren;
+ private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
+
+ /**
+ * Indicates that the pager is in an idle, settled state. The current page
+ * is fully in view and no animation is in progress.
+ */
+ public static final int SCROLL_STATE_IDLE = 0;
+
+ /**
+ * Indicates that the pager is currently being dragged by the user.
+ */
+ public static final int SCROLL_STATE_DRAGGING = 1;
+
+ /**
+ * Indicates that the pager is in the process of settling to a final position.
+ */
+ public static final int SCROLL_STATE_SETTLING = 2;
+
+ private final Runnable mEndScrollRunnable = new Runnable() {
+ public void run() {
+ setScrollState(SCROLL_STATE_IDLE);
+ populate();
+ }
+ };
+
+ private int mScrollState = SCROLL_STATE_IDLE;
+
+ /**
+ * Callback interface for responding to changing state of the selected page.
+ */
+ public interface OnPageChangeListener {
+
+ /**
+ * This method will be invoked when the current page is scrolled, either as part
+ * of a programmatically initiated smooth scroll or a user initiated touch scroll.
+ *
+ * @param position Position index of the first page currently being displayed.
+ * Page position+1 will be visible if positionOffset is nonzero.
+ * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
+ * @param positionOffsetPixels Value in pixels indicating the offset from position.
+ */
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
+
+ /**
+ * This method will be invoked when a new page becomes selected. Animation is not
+ * necessarily complete.
+ *
+ * @param position Position index of the new selected page.
+ */
+ public void onPageSelected(int position);
+
+ /**
+ * Called when the scroll state changes. Useful for discovering when the user
+ * begins dragging, when the pager is automatically settling to the current page,
+ * or when it is fully stopped/idle.
+ *
+ * @param state The new scroll state.
+ * @see com.android.internal.widget.ViewPager#SCROLL_STATE_IDLE
+ * @see com.android.internal.widget.ViewPager#SCROLL_STATE_DRAGGING
+ * @see com.android.internal.widget.ViewPager#SCROLL_STATE_SETTLING
+ */
+ public void onPageScrollStateChanged(int state);
+ }
+
+ /**
+ * Simple implementation of the {@link OnPageChangeListener} interface with stub
+ * implementations of each method. Extend this if you do not intend to override
+ * every method of {@link OnPageChangeListener}.
+ */
+ public static class SimpleOnPageChangeListener implements OnPageChangeListener {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ // This space for rent
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ // This space for rent
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ // This space for rent
+ }
+ }
+
+ /**
+ * A PageTransformer is invoked whenever a visible/attached page is scrolled.
+ * This offers an opportunity for the application to apply a custom transformation
+ * to the page views using animation properties.
+ *
+ * <p>As property animation is only supported as of Android 3.0 and forward,
+ * setting a PageTransformer on a ViewPager on earlier platform versions will
+ * be ignored.</p>
+ */
+ public interface PageTransformer {
+ /**
+ * Apply a property transformation to the given page.
+ *
+ * @param page Apply the transformation to this page
+ * @param position Position of page relative to the current front-and-center
+ * position of the pager. 0 is front and center. 1 is one full
+ * page position to the right, and -1 is one page position to the left.
+ */
+ public void transformPage(View page, float position);
+ }
+
+ /**
+ * Used internally to monitor when adapters are switched.
+ */
+ interface OnAdapterChangeListener {
+ public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
+ }
+
+ /**
+ * Used internally to tag special types of child views that should be added as
+ * pager decorations by default.
+ */
+ interface Decor {}
+
+ public ViewPager(Context context) {
+ super(context);
+ initViewPager();
+ }
+
+ public ViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initViewPager();
+ }
+
+ void initViewPager() {
+ setWillNotDraw(false);
+ setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+ setFocusable(true);
+ final Context context = getContext();
+ mScroller = new Scroller(context, sInterpolator);
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ final float density = context.getResources().getDisplayMetrics().density;
+
+ mTouchSlop = configuration.getScaledPagingTouchSlop();
+ mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mLeftEdge = new EdgeEffect(context);
+ mRightEdge = new EdgeEffect(context);
+
+ mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
+ mCloseEnough = (int) (CLOSE_ENOUGH * density);
+ mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
+
+ setAccessibilityDelegate(new MyAccessibilityDelegate());
+
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeCallbacks(mEndScrollRunnable);
+ super.onDetachedFromWindow();
+ }
+
+ private void setScrollState(int newState) {
+ if (mScrollState == newState) {
+ return;
+ }
+
+ mScrollState = newState;
+ if (mPageTransformer != null) {
+ // PageTransformers can do complex things that benefit from hardware layers.
+ enableLayers(newState != SCROLL_STATE_IDLE);
+ }
+ if (mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageScrollStateChanged(newState);
+ }
+ }
+
+ /**
+ * Set a PagerAdapter that will supply views for this pager as needed.
+ *
+ * @param adapter Adapter to use
+ */
+ public void setAdapter(PagerAdapter adapter) {
+ if (mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(mObserver);
+ mAdapter.startUpdate(this);
+ for (int i = 0; i < mItems.size(); i++) {
+ final ItemInfo ii = mItems.get(i);
+ mAdapter.destroyItem(this, ii.position, ii.object);
+ }
+ mAdapter.finishUpdate(this);
+ mItems.clear();
+ removeNonDecorViews();
+ mCurItem = 0;
+ scrollTo(0, 0);
+ }
+
+ final PagerAdapter oldAdapter = mAdapter;
+ mAdapter = adapter;
+ mExpectedAdapterCount = 0;
+
+ if (mAdapter != null) {
+ if (mObserver == null) {
+ mObserver = new PagerObserver();
+ }
+ mAdapter.registerDataSetObserver(mObserver);
+ mPopulatePending = false;
+ final boolean wasFirstLayout = mFirstLayout;
+ mFirstLayout = true;
+ mExpectedAdapterCount = mAdapter.getCount();
+ if (mRestoredCurItem >= 0) {
+ mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
+ setCurrentItemInternal(mRestoredCurItem, false, true);
+ mRestoredCurItem = -1;
+ mRestoredAdapterState = null;
+ mRestoredClassLoader = null;
+ } else if (!wasFirstLayout) {
+ populate();
+ } else {
+ requestLayout();
+ }
+ }
+
+ if (mAdapterChangeListener != null && oldAdapter != adapter) {
+ mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
+ }
+ }
+
+ private void removeNonDecorViews() {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.isDecor) {
+ removeViewAt(i);
+ i--;
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current adapter supplying pages.
+ *
+ * @return The currently registered PagerAdapter
+ */
+ public PagerAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
+ mAdapterChangeListener = listener;
+ }
+
+ private int getClientWidth() {
+ return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+ }
+
+ /**
+ * Set the currently selected page. If the ViewPager has already been through its first
+ * layout with its current adapter there will be a smooth animated transition between
+ * the current item and the specified item.
+ *
+ * @param item Item index to select
+ */
+ public void setCurrentItem(int item) {
+ mPopulatePending = false;
+ setCurrentItemInternal(item, !mFirstLayout, false);
+ }
+
+ /**
+ * Set the currently selected page.
+ *
+ * @param item Item index to select
+ * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
+ */
+ public void setCurrentItem(int item, boolean smoothScroll) {
+ mPopulatePending = false;
+ setCurrentItemInternal(item, smoothScroll, false);
+ }
+
+ public int getCurrentItem() {
+ return mCurItem;
+ }
+
+ void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
+ setCurrentItemInternal(item, smoothScroll, always, 0);
+ }
+
+ void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
+ if (mAdapter == null || mAdapter.getCount() <= 0) {
+ setScrollingCacheEnabled(false);
+ return;
+ }
+ if (!always && mCurItem == item && mItems.size() != 0) {
+ setScrollingCacheEnabled(false);
+ return;
+ }
+
+ if (item < 0) {
+ item = 0;
+ } else if (item >= mAdapter.getCount()) {
+ item = mAdapter.getCount() - 1;
+ }
+ final int pageLimit = mOffscreenPageLimit;
+ if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
+ // We are doing a jump by more than one page. To avoid
+ // glitches, we want to keep all current pages in the view
+ // until the scroll ends.
+ for (int i=0; i<mItems.size(); i++) {
+ mItems.get(i).scrolling = true;
+ }
+ }
+ final boolean dispatchSelected = mCurItem != item;
+
+ if (mFirstLayout) {
+ // We don't have any idea how big we are yet and shouldn't have any pages either.
+ // Just set things up and let the pending layout handle things.
+ mCurItem = item;
+ if (dispatchSelected && mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageSelected(item);
+ }
+ if (dispatchSelected && mInternalPageChangeListener != null) {
+ mInternalPageChangeListener.onPageSelected(item);
+ }
+ requestLayout();
+ } else {
+ populate(item);
+ scrollToItem(item, smoothScroll, velocity, dispatchSelected);
+ }
+ }
+
+ private void scrollToItem(int item, boolean smoothScroll, int velocity,
+ boolean dispatchSelected) {
+ final ItemInfo curInfo = infoForPosition(item);
+ int destX = 0;
+ if (curInfo != null) {
+ final int width = getClientWidth();
+ destX = (int) (width * Math.max(mFirstOffset,
+ Math.min(curInfo.offset, mLastOffset)));
+ }
+ if (smoothScroll) {
+ smoothScrollTo(destX, 0, velocity);
+ if (dispatchSelected && mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageSelected(item);
+ }
+ if (dispatchSelected && mInternalPageChangeListener != null) {
+ mInternalPageChangeListener.onPageSelected(item);
+ }
+ } else {
+ if (dispatchSelected && mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageSelected(item);
+ }
+ if (dispatchSelected && mInternalPageChangeListener != null) {
+ mInternalPageChangeListener.onPageSelected(item);
+ }
+ completeScroll(false);
+ scrollTo(destX, 0);
+ pageScrolled(destX);
+ }
+ }
+
+ /**
+ * Set a listener that will be invoked whenever the page changes or is incrementally
+ * scrolled. See {@link OnPageChangeListener}.
+ *
+ * @param listener Listener to set
+ */
+ public void setOnPageChangeListener(OnPageChangeListener listener) {
+ mOnPageChangeListener = listener;
+ }
+
+ /**
+ * Set a {@link PageTransformer} that will be called for each attached page whenever
+ * the scroll position is changed. This allows the application to apply custom property
+ * transformations to each page, overriding the default sliding look and feel.
+ *
+ * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
+ * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
+ *
+ * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
+ * to be drawn from last to first instead of first to last.
+ * @param transformer PageTransformer that will modify each page's animation properties
+ */
+ public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
+ final boolean hasTransformer = transformer != null;
+ final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
+ mPageTransformer = transformer;
+ setChildrenDrawingOrderEnabled(hasTransformer);
+ if (hasTransformer) {
+ mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
+ } else {
+ mDrawingOrder = DRAW_ORDER_DEFAULT;
+ }
+ if (needsPopulate) populate();
+ }
+
+ @Override
+ protected int getChildDrawingOrder(int childCount, int i) {
+ final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
+ final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
+ return result;
+ }
+
+ /**
+ * Set a separate OnPageChangeListener for internal use by the support library.
+ *
+ * @param listener Listener to set
+ * @return The old listener that was set, if any.
+ */
+ OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
+ OnPageChangeListener oldListener = mInternalPageChangeListener;
+ mInternalPageChangeListener = listener;
+ return oldListener;
+ }
+
+ /**
+ * Returns the number of pages that will be retained to either side of the
+ * current page in the view hierarchy in an idle state. Defaults to 1.
+ *
+ * @return How many pages will be kept offscreen on either side
+ * @see #setOffscreenPageLimit(int)
+ */
+ public int getOffscreenPageLimit() {
+ return mOffscreenPageLimit;
+ }
+
+ /**
+ * Set the number of pages that should be retained to either side of the
+ * current page in the view hierarchy in an idle state. Pages beyond this
+ * limit will be recreated from the adapter when needed.
+ *
+ * <p>This is offered as an optimization. If you know in advance the number
+ * of pages you will need to support or have lazy-loading mechanisms in place
+ * on your pages, tweaking this setting can have benefits in perceived smoothness
+ * of paging animations and interaction. If you have a small number of pages (3-4)
+ * that you can keep active all at once, less time will be spent in layout for
+ * newly created view subtrees as the user pages back and forth.</p>
+ *
+ * <p>You should keep this limit low, especially if your pages have complex layouts.
+ * This setting defaults to 1.</p>
+ *
+ * @param limit How many pages will be kept offscreen in an idle state.
+ */
+ public void setOffscreenPageLimit(int limit) {
+ if (limit < DEFAULT_OFFSCREEN_PAGES) {
+ Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
+ DEFAULT_OFFSCREEN_PAGES);
+ limit = DEFAULT_OFFSCREEN_PAGES;
+ }
+ if (limit != mOffscreenPageLimit) {
+ mOffscreenPageLimit = limit;
+ populate();
+ }
+ }
+
+ /**
+ * Set the margin between pages.
+ *
+ * @param marginPixels Distance between adjacent pages in pixels
+ * @see #getPageMargin()
+ * @see #setPageMarginDrawable(android.graphics.drawable.Drawable)
+ * @see #setPageMarginDrawable(int)
+ */
+ public void setPageMargin(int marginPixels) {
+ final int oldMargin = mPageMargin;
+ mPageMargin = marginPixels;
+
+ final int width = getWidth();
+ recomputeScrollPosition(width, width, marginPixels, oldMargin);
+
+ requestLayout();
+ }
+
+ /**
+ * Return the margin between pages.
+ *
+ * @return The size of the margin in pixels
+ */
+ public int getPageMargin() {
+ return mPageMargin;
+ }
+
+ /**
+ * Set a drawable that will be used to fill the margin between pages.
+ *
+ * @param d Drawable to display between pages
+ */
+ public void setPageMarginDrawable(Drawable d) {
+ mMarginDrawable = d;
+ if (d != null) refreshDrawableState();
+ setWillNotDraw(d == null);
+ invalidate();
+ }
+
+ /**
+ * Set a drawable that will be used to fill the margin between pages.
+ *
+ * @param resId Resource ID of a drawable to display between pages
+ */
+ public void setPageMarginDrawable(@DrawableRes int resId) {
+ setPageMarginDrawable(getContext().getDrawable(resId));
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mMarginDrawable;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ final Drawable d = mMarginDrawable;
+ if (d != null && d.isStateful()) {
+ d.setState(getDrawableState());
+ }
+ }
+
+ // We want the duration of the page snap animation to be influenced by the distance that
+ // the screen has to travel, however, we don't want this duration to be effected in a
+ // purely linear fashion. Instead, we use this method to moderate the effect that the distance
+ // of travel has on the overall snap duration.
+ float distanceInfluenceForSnapDuration(float f) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return (float) Math.sin(f);
+ }
+
+ /**
+ * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param x the number of pixels to scroll by on the X axis
+ * @param y the number of pixels to scroll by on the Y axis
+ */
+ void smoothScrollTo(int x, int y) {
+ smoothScrollTo(x, y, 0);
+ }
+
+ /**
+ * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
+ *
+ * @param x the number of pixels to scroll by on the X axis
+ * @param y the number of pixels to scroll by on the Y axis
+ * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
+ */
+ void smoothScrollTo(int x, int y, int velocity) {
+ if (getChildCount() == 0) {
+ // Nothing to do.
+ setScrollingCacheEnabled(false);
+ return;
+ }
+ int sx = getScrollX();
+ int sy = getScrollY();
+ int dx = x - sx;
+ int dy = y - sy;
+ if (dx == 0 && dy == 0) {
+ completeScroll(false);
+ populate();
+ setScrollState(SCROLL_STATE_IDLE);
+ return;
+ }
+
+ setScrollingCacheEnabled(true);
+ setScrollState(SCROLL_STATE_SETTLING);
+
+ final int width = getClientWidth();
+ final int halfWidth = width / 2;
+ final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
+ final float distance = halfWidth + halfWidth *
+ distanceInfluenceForSnapDuration(distanceRatio);
+
+ int duration = 0;
+ velocity = Math.abs(velocity);
+ if (velocity > 0) {
+ duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+ } else {
+ final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
+ final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
+ duration = (int) ((pageDelta + 1) * 100);
+ }
+ duration = Math.min(duration, MAX_SETTLE_DURATION);
+
+ mScroller.startScroll(sx, sy, dx, dy, duration);
+ postInvalidateOnAnimation();
+ }
+
+ ItemInfo addNewItem(int position, int index) {
+ ItemInfo ii = new ItemInfo();
+ ii.position = position;
+ ii.object = mAdapter.instantiateItem(this, position);
+ ii.widthFactor = mAdapter.getPageWidth(position);
+ if (index < 0 || index >= mItems.size()) {
+ mItems.add(ii);
+ } else {
+ mItems.add(index, ii);
+ }
+ return ii;
+ }
+
+ void dataSetChanged() {
+ // This method only gets called if our observer is attached, so mAdapter is non-null.
+
+ final int adapterCount = mAdapter.getCount();
+ mExpectedAdapterCount = adapterCount;
+ boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
+ mItems.size() < adapterCount;
+ int newCurrItem = mCurItem;
+
+ boolean isUpdating = false;
+ for (int i = 0; i < mItems.size(); i++) {
+ final ItemInfo ii = mItems.get(i);
+ final int newPos = mAdapter.getItemPosition(ii.object);
+
+ if (newPos == PagerAdapter.POSITION_UNCHANGED) {
+ continue;
+ }
+
+ if (newPos == PagerAdapter.POSITION_NONE) {
+ mItems.remove(i);
+ i--;
+
+ if (!isUpdating) {
+ mAdapter.startUpdate(this);
+ isUpdating = true;
+ }
+
+ mAdapter.destroyItem(this, ii.position, ii.object);
+ needPopulate = true;
+
+ if (mCurItem == ii.position) {
+ // Keep the current item in the valid range
+ newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
+ needPopulate = true;
+ }
+ continue;
+ }
+
+ if (ii.position != newPos) {
+ if (ii.position == mCurItem) {
+ // Our current item changed position. Follow it.
+ newCurrItem = newPos;
+ }
+
+ ii.position = newPos;
+ needPopulate = true;
+ }
+ }
+
+ if (isUpdating) {
+ mAdapter.finishUpdate(this);
+ }
+
+ Collections.sort(mItems, COMPARATOR);
+
+ if (needPopulate) {
+ // Reset our known page widths; populate will recompute them.
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.isDecor) {
+ lp.widthFactor = 0.f;
+ }
+ }
+
+ setCurrentItemInternal(newCurrItem, false, true);
+ requestLayout();
+ }
+ }
+
+ void populate() {
+ populate(mCurItem);
+ }
+
+ void populate(int newCurrentItem) {
+ ItemInfo oldCurInfo = null;
+ int focusDirection = View.FOCUS_FORWARD;
+ if (mCurItem != newCurrentItem) {
+ focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ oldCurInfo = infoForPosition(mCurItem);
+ mCurItem = newCurrentItem;
+ }
+
+ if (mAdapter == null) {
+ sortChildDrawingOrder();
+ return;
+ }
+
+ // Bail now if we are waiting to populate. This is to hold off
+ // on creating views from the time the user releases their finger to
+ // fling to a new position until we have finished the scroll to
+ // that position, avoiding glitches from happening at that point.
+ if (mPopulatePending) {
+ if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
+ sortChildDrawingOrder();
+ return;
+ }
+
+ // Also, don't populate until we are attached to a window. This is to
+ // avoid trying to populate before we have restored our view hierarchy
+ // state and conflicting with what is restored.
+ if (getWindowToken() == null) {
+ return;
+ }
+
+ mAdapter.startUpdate(this);
+
+ final int pageLimit = mOffscreenPageLimit;
+ final int startPos = Math.max(0, mCurItem - pageLimit);
+ final int N = mAdapter.getCount();
+ final int endPos = Math.min(N-1, mCurItem + pageLimit);
+
+ if (N != mExpectedAdapterCount) {
+ String resName;
+ try {
+ resName = getResources().getResourceName(getId());
+ } catch (Resources.NotFoundException e) {
+ resName = Integer.toHexString(getId());
+ }
+ throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
+ " contents without calling PagerAdapter#notifyDataSetChanged!" +
+ " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
+ " Pager id: " + resName +
+ " Pager class: " + getClass() +
+ " Problematic adapter: " + mAdapter.getClass());
+ }
+
+ // Locate the currently focused item or add it if needed.
+ int curIndex = -1;
+ ItemInfo curItem = null;
+ for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
+ final ItemInfo ii = mItems.get(curIndex);
+ if (ii.position >= mCurItem) {
+ if (ii.position == mCurItem) curItem = ii;
+ break;
+ }
+ }
+
+ if (curItem == null && N > 0) {
+ curItem = addNewItem(mCurItem, curIndex);
+ }
+
+ // Fill 3x the available width or up to the number of offscreen
+ // pages requested to either side, whichever is larger.
+ // If we have no current item we have no work to do.
+ if (curItem != null) {
+ float extraWidthLeft = 0.f;
+ int itemIndex = curIndex - 1;
+ ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
+ final int clientWidth = getClientWidth();
+ final float leftWidthNeeded = clientWidth <= 0 ? 0 :
+ 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
+ for (int pos = mCurItem - 1; pos >= 0; pos--) {
+ if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
+ if (ii == null) {
+ break;
+ }
+ if (pos == ii.position && !ii.scrolling) {
+ mItems.remove(itemIndex);
+ mAdapter.destroyItem(this, pos, ii.object);
+ if (DEBUG) {
+ Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
+ " view: " + ((View) ii.object));
+ }
+ itemIndex--;
+ curIndex--;
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
+ }
+ } else if (ii != null && pos == ii.position) {
+ extraWidthLeft += ii.widthFactor;
+ itemIndex--;
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
+ } else {
+ ii = addNewItem(pos, itemIndex + 1);
+ extraWidthLeft += ii.widthFactor;
+ curIndex++;
+ ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
+ }
+ }
+
+ float extraWidthRight = curItem.widthFactor;
+ itemIndex = curIndex + 1;
+ if (extraWidthRight < 2.f) {
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
+ final float rightWidthNeeded = clientWidth <= 0 ? 0 :
+ (float) getPaddingRight() / (float) clientWidth + 2.f;
+ for (int pos = mCurItem + 1; pos < N; pos++) {
+ if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
+ if (ii == null) {
+ break;
+ }
+ if (pos == ii.position && !ii.scrolling) {
+ mItems.remove(itemIndex);
+ mAdapter.destroyItem(this, pos, ii.object);
+ if (DEBUG) {
+ Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
+ " view: " + ((View) ii.object));
+ }
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
+ }
+ } else if (ii != null && pos == ii.position) {
+ extraWidthRight += ii.widthFactor;
+ itemIndex++;
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
+ } else {
+ ii = addNewItem(pos, itemIndex);
+ itemIndex++;
+ extraWidthRight += ii.widthFactor;
+ ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
+ }
+ }
+ }
+
+ calculatePageOffsets(curItem, curIndex, oldCurInfo);
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "Current page list:");
+ for (int i=0; i<mItems.size(); i++) {
+ Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
+ }
+ }
+
+ mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
+
+ mAdapter.finishUpdate(this);
+
+ // Check width measurement of current pages and drawing sort order.
+ // Update LayoutParams as needed.
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.childIndex = i;
+ if (!lp.isDecor && lp.widthFactor == 0.f) {
+ // 0 means requery the adapter for this, it doesn't have a valid width.
+ final ItemInfo ii = infoForChild(child);
+ if (ii != null) {
+ lp.widthFactor = ii.widthFactor;
+ lp.position = ii.position;
+ }
+ }
+ }
+ sortChildDrawingOrder();
+
+ if (hasFocus()) {
+ View currentFocused = findFocus();
+ ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
+ if (ii == null || ii.position != mCurItem) {
+ for (int i=0; i<getChildCount(); i++) {
+ View child = getChildAt(i);
+ ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem) {
+ if (child.requestFocus(focusDirection)) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void sortChildDrawingOrder() {
+ if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
+ if (mDrawingOrderedChildren == null) {
+ mDrawingOrderedChildren = new ArrayList<View>();
+ } else {
+ mDrawingOrderedChildren.clear();
+ }
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ mDrawingOrderedChildren.add(child);
+ }
+ Collections.sort(mDrawingOrderedChildren, sPositionComparator);
+ }
+ }
+
+ private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
+ final int N = mAdapter.getCount();
+ final int width = getClientWidth();
+ final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
+ // Fix up offsets for later layout.
+ if (oldCurInfo != null) {
+ final int oldCurPosition = oldCurInfo.position;
+ // Base offsets off of oldCurInfo.
+ if (oldCurPosition < curItem.position) {
+ int itemIndex = 0;
+ ItemInfo ii = null;
+ float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
+ for (int pos = oldCurPosition + 1;
+ pos <= curItem.position && itemIndex < mItems.size(); pos++) {
+ ii = mItems.get(itemIndex);
+ while (pos > ii.position && itemIndex < mItems.size() - 1) {
+ itemIndex++;
+ ii = mItems.get(itemIndex);
+ }
+ while (pos < ii.position) {
+ // We don't have an item populated for this,
+ // ask the adapter for an offset.
+ offset += mAdapter.getPageWidth(pos) + marginOffset;
+ pos++;
+ }
+ ii.offset = offset;
+ offset += ii.widthFactor + marginOffset;
+ }
+ } else if (oldCurPosition > curItem.position) {
+ int itemIndex = mItems.size() - 1;
+ ItemInfo ii = null;
+ float offset = oldCurInfo.offset;
+ for (int pos = oldCurPosition - 1;
+ pos >= curItem.position && itemIndex >= 0; pos--) {
+ ii = mItems.get(itemIndex);
+ while (pos < ii.position && itemIndex > 0) {
+ itemIndex--;
+ ii = mItems.get(itemIndex);
+ }
+ while (pos > ii.position) {
+ // We don't have an item populated for this,
+ // ask the adapter for an offset.
+ offset -= mAdapter.getPageWidth(pos) + marginOffset;
+ pos--;
+ }
+ offset -= ii.widthFactor + marginOffset;
+ ii.offset = offset;
+ }
+ }
+ }
+
+ // Base all offsets off of curItem.
+ final int itemCount = mItems.size();
+ float offset = curItem.offset;
+ int pos = curItem.position - 1;
+ mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
+ mLastOffset = curItem.position == N - 1 ?
+ curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
+ // Previous pages
+ for (int i = curIndex - 1; i >= 0; i--, pos--) {
+ final ItemInfo ii = mItems.get(i);
+ while (pos > ii.position) {
+ offset -= mAdapter.getPageWidth(pos--) + marginOffset;
+ }
+ offset -= ii.widthFactor + marginOffset;
+ ii.offset = offset;
+ if (ii.position == 0) mFirstOffset = offset;
+ }
+ offset = curItem.offset + curItem.widthFactor + marginOffset;
+ pos = curItem.position + 1;
+ // Next pages
+ for (int i = curIndex + 1; i < itemCount; i++, pos++) {
+ final ItemInfo ii = mItems.get(i);
+ while (pos < ii.position) {
+ offset += mAdapter.getPageWidth(pos++) + marginOffset;
+ }
+ if (ii.position == N - 1) {
+ mLastOffset = offset + ii.widthFactor - 1;
+ }
+ ii.offset = offset;
+ offset += ii.widthFactor + marginOffset;
+ }
+
+ mNeedCalculatePageOffsets = false;
+ }
+
+ /**
+ * This is the persistent state that is saved by ViewPager. Only needed
+ * if you are creating a sublass of ViewPager that must save its own
+ * state, in which case it should implement a subclass of this which
+ * contains that state.
+ */
+ public static class SavedState extends BaseSavedState {
+ int position;
+ Parcelable adapterState;
+ ClassLoader loader;
+
+ public SavedState(Parcel source) {
+ super(source);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(position);
+ out.writeParcelable(adapterState, flags);
+ }
+
+ @Override
+ public String toString() {
+ return "FragmentPager.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " position=" + position + "}";
+ }
+
+ public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+
+ SavedState(Parcel in, ClassLoader loader) {
+ super(in);
+ if (loader == null) {
+ loader = getClass().getClassLoader();
+ }
+ position = in.readInt();
+ adapterState = in.readParcelable(loader);
+ this.loader = loader;
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+ ss.position = mCurItem;
+ if (mAdapter != null) {
+ ss.adapterState = mAdapter.saveState();
+ }
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState ss = (SavedState)state;
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ if (mAdapter != null) {
+ mAdapter.restoreState(ss.adapterState, ss.loader);
+ setCurrentItemInternal(ss.position, false, true);
+ } else {
+ mRestoredCurItem = ss.position;
+ mRestoredAdapterState = ss.adapterState;
+ mRestoredClassLoader = ss.loader;
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (!checkLayoutParams(params)) {
+ params = generateLayoutParams(params);
+ }
+ final LayoutParams lp = (LayoutParams) params;
+ lp.isDecor |= child instanceof Decor;
+ if (mInLayout) {
+ if (lp != null && lp.isDecor) {
+ throw new IllegalStateException("Cannot add pager decor view during layout");
+ }
+ lp.needsMeasure = true;
+ addViewInLayout(child, index, params);
+ } else {
+ super.addView(child, index, params);
+ }
+
+ if (USE_CACHE) {
+ if (child.getVisibility() != GONE) {
+ child.setDrawingCacheEnabled(mScrollingCacheEnabled);
+ } else {
+ child.setDrawingCacheEnabled(false);
+ }
+ }
+ }
+
+ @Override
+ public void removeView(View view) {
+ if (mInLayout) {
+ removeViewInLayout(view);
+ } else {
+ super.removeView(view);
+ }
+ }
+
+ ItemInfo infoForChild(View child) {
+ for (int i=0; i<mItems.size(); i++) {
+ ItemInfo ii = mItems.get(i);
+ if (mAdapter.isViewFromObject(child, ii.object)) {
+ return ii;
+ }
+ }
+ return null;
+ }
+
+ ItemInfo infoForAnyChild(View child) {
+ ViewParent parent;
+ while ((parent=child.getParent()) != this) {
+ if (parent == null || !(parent instanceof View)) {
+ return null;
+ }
+ child = (View)parent;
+ }
+ return infoForChild(child);
+ }
+
+ ItemInfo infoForPosition(int position) {
+ for (int i = 0; i < mItems.size(); i++) {
+ ItemInfo ii = mItems.get(i);
+ if (ii.position == position) {
+ return ii;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // For simple implementation, our internal size is always 0.
+ // We depend on the container to specify the layout size of
+ // our view. We can't really know what it is since we will be
+ // adding and removing different arbitrary views and do not
+ // want the layout to change as this happens.
+ setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
+ getDefaultSize(0, heightMeasureSpec));
+
+ final int measuredWidth = getMeasuredWidth();
+ final int maxGutterSize = measuredWidth / 10;
+ mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
+
+ // Children are just made to fill our space.
+ int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
+ int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
+
+ /*
+ * Make sure all children have been properly measured. Decor views first.
+ * Right now we cheat and make this less complicated by assuming decor
+ * views won't intersect. We will pin to edges based on gravity.
+ */
+ int size = getChildCount();
+ for (int i = 0; i < size; ++i) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp != null && lp.isDecor) {
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ int widthMode = MeasureSpec.AT_MOST;
+ int heightMode = MeasureSpec.AT_MOST;
+ boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
+ boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
+
+ if (consumeVertical) {
+ widthMode = MeasureSpec.EXACTLY;
+ } else if (consumeHorizontal) {
+ heightMode = MeasureSpec.EXACTLY;
+ }
+
+ int widthSize = childWidthSize;
+ int heightSize = childHeightSize;
+ if (lp.width != LayoutParams.WRAP_CONTENT) {
+ widthMode = MeasureSpec.EXACTLY;
+ if (lp.width != LayoutParams.FILL_PARENT) {
+ widthSize = lp.width;
+ }
+ }
+ if (lp.height != LayoutParams.WRAP_CONTENT) {
+ heightMode = MeasureSpec.EXACTLY;
+ if (lp.height != LayoutParams.FILL_PARENT) {
+ heightSize = lp.height;
+ }
+ }
+ final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
+ final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
+ child.measure(widthSpec, heightSpec);
+
+ if (consumeVertical) {
+ childHeightSize -= child.getMeasuredHeight();
+ } else if (consumeHorizontal) {
+ childWidthSize -= child.getMeasuredWidth();
+ }
+ }
+ }
+ }
+
+ mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
+ mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
+
+ // Make sure we have created all fragments that we need to have shown.
+ mInLayout = true;
+ populate();
+ mInLayout = false;
+
+ // Page views next.
+ size = getChildCount();
+ for (int i = 0; i < size; ++i) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
+ + ": " + mChildWidthMeasureSpec);
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp == null || !lp.isDecor) {
+ final int widthSpec = MeasureSpec.makeMeasureSpec(
+ (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
+ child.measure(widthSpec, mChildHeightMeasureSpec);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ // Make sure scroll position is set correctly.
+ if (w != oldw) {
+ recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
+ }
+ }
+
+ private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
+ if (oldWidth > 0 && !mItems.isEmpty()) {
+ final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin;
+ final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight()
+ + oldMargin;
+ final int xpos = getScrollX();
+ final float pageOffset = (float) xpos / oldWidthWithMargin;
+ final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
+
+ scrollTo(newOffsetPixels, getScrollY());
+ if (!mScroller.isFinished()) {
+ // We now return to your regularly scheduled scroll, already in progress.
+ final int newDuration = mScroller.getDuration() - mScroller.timePassed();
+ ItemInfo targetInfo = infoForPosition(mCurItem);
+ mScroller.startScroll(newOffsetPixels, 0,
+ (int) (targetInfo.offset * width), 0, newDuration);
+ }
+ } else {
+ final ItemInfo ii = infoForPosition(mCurItem);
+ final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
+ final int scrollPos = (int) (scrollOffset *
+ (width - getPaddingLeft() - getPaddingRight()));
+ if (scrollPos != getScrollX()) {
+ completeScroll(false);
+ scrollTo(scrollPos, getScrollY());
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int count = getChildCount();
+ int width = r - l;
+ int height = b - t;
+ int paddingLeft = getPaddingLeft();
+ int paddingTop = getPaddingTop();
+ int paddingRight = getPaddingRight();
+ int paddingBottom = getPaddingBottom();
+ final int scrollX = getScrollX();
+
+ int decorCount = 0;
+
+ // First pass - decor views. We need to do this in two passes so that
+ // we have the proper offsets for non-decor views later.
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ int childLeft = 0;
+ int childTop = 0;
+ if (lp.isDecor) {
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ switch (hgrav) {
+ default:
+ childLeft = paddingLeft;
+ break;
+ case Gravity.LEFT:
+ childLeft = paddingLeft;
+ paddingLeft += child.getMeasuredWidth();
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
+ paddingLeft);
+ break;
+ case Gravity.RIGHT:
+ childLeft = width - paddingRight - child.getMeasuredWidth();
+ paddingRight += child.getMeasuredWidth();
+ break;
+ }
+ switch (vgrav) {
+ default:
+ childTop = paddingTop;
+ break;
+ case Gravity.TOP:
+ childTop = paddingTop;
+ paddingTop += child.getMeasuredHeight();
+ break;
+ case Gravity.CENTER_VERTICAL:
+ childTop = Math.max((height - child.getMeasuredHeight()) / 2,
+ paddingTop);
+ break;
+ case Gravity.BOTTOM:
+ childTop = height - paddingBottom - child.getMeasuredHeight();
+ paddingBottom += child.getMeasuredHeight();
+ break;
+ }
+ childLeft += scrollX;
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(),
+ childTop + child.getMeasuredHeight());
+ decorCount++;
+ }
+ }
+ }
+
+ final int childWidth = width - paddingLeft - paddingRight;
+ // Page views. Do this once we have the right padding offsets from above.
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ ItemInfo ii;
+ if (!lp.isDecor && (ii = infoForChild(child)) != null) {
+ int loff = (int) (childWidth * ii.offset);
+ int childLeft = paddingLeft + loff;
+ int childTop = paddingTop;
+ if (lp.needsMeasure) {
+ // This was added during layout and needs measurement.
+ // Do it now that we know what we're working with.
+ lp.needsMeasure = false;
+ final int widthSpec = MeasureSpec.makeMeasureSpec(
+ (int) (childWidth * lp.widthFactor),
+ MeasureSpec.EXACTLY);
+ final int heightSpec = MeasureSpec.makeMeasureSpec(
+ (int) (height - paddingTop - paddingBottom),
+ MeasureSpec.EXACTLY);
+ child.measure(widthSpec, heightSpec);
+ }
+ if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+ + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+ + "x" + child.getMeasuredHeight());
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(),
+ childTop + child.getMeasuredHeight());
+ }
+ }
+ }
+ mTopPageBounds = paddingTop;
+ mBottomPageBounds = height - paddingBottom;
+ mDecorChildCount = decorCount;
+
+ if (mFirstLayout) {
+ scrollToItem(mCurItem, false, 0, false);
+ }
+ mFirstLayout = false;
+ }
+
+ @Override
+ public void computeScroll() {
+ if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
+ int oldX = getScrollX();
+ int oldY = getScrollY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+
+ if (oldX != x || oldY != y) {
+ scrollTo(x, y);
+ if (!pageScrolled(x)) {
+ mScroller.abortAnimation();
+ scrollTo(0, y);
+ }
+ }
+
+ // Keep on drawing until the animation has finished.
+ postInvalidateOnAnimation();
+ return;
+ }
+
+ // Done with scroll, clean up state.
+ completeScroll(true);
+ }
+
+ private boolean pageScrolled(int xpos) {
+ if (mItems.size() == 0) {
+ mCalledSuper = false;
+ onPageScrolled(0, 0, 0);
+ if (!mCalledSuper) {
+ throw new IllegalStateException(
+ "onPageScrolled did not call superclass implementation");
+ }
+ return false;
+ }
+ final ItemInfo ii = infoForCurrentScrollPosition();
+ final int width = getClientWidth();
+ final int widthWithMargin = width + mPageMargin;
+ final float marginOffset = (float) mPageMargin / width;
+ final int currentPage = ii.position;
+ final float pageOffset = (((float) xpos / width) - ii.offset) /
+ (ii.widthFactor + marginOffset);
+ final int offsetPixels = (int) (pageOffset * widthWithMargin);
+
+ mCalledSuper = false;
+ onPageScrolled(currentPage, pageOffset, offsetPixels);
+ if (!mCalledSuper) {
+ throw new IllegalStateException(
+ "onPageScrolled did not call superclass implementation");
+ }
+ return true;
+ }
+
+ /**
+ * This method will be invoked when the current page is scrolled, either as part
+ * of a programmatically initiated smooth scroll or a user initiated touch scroll.
+ * If you override this method you must call through to the superclass implementation
+ * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
+ * returns.
+ *
+ * @param position Position index of the first page currently being displayed.
+ * Page position+1 will be visible if positionOffset is nonzero.
+ * @param offset Value from [0, 1) indicating the offset from the page at position.
+ * @param offsetPixels Value in pixels indicating the offset from position.
+ */
+ protected void onPageScrolled(int position, float offset, int offsetPixels) {
+ // Offset any decor views if needed - keep them on-screen at all times.
+ if (mDecorChildCount > 0) {
+ final int scrollX = getScrollX();
+ int paddingLeft = getPaddingLeft();
+ int paddingRight = getPaddingRight();
+ final int width = getWidth();
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (!lp.isDecor) continue;
+
+ final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int childLeft = 0;
+ switch (hgrav) {
+ default:
+ childLeft = paddingLeft;
+ break;
+ case Gravity.LEFT:
+ childLeft = paddingLeft;
+ paddingLeft += child.getWidth();
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
+ paddingLeft);
+ break;
+ case Gravity.RIGHT:
+ childLeft = width - paddingRight - child.getMeasuredWidth();
+ paddingRight += child.getMeasuredWidth();
+ break;
+ }
+ childLeft += scrollX;
+
+ final int childOffset = childLeft - child.getLeft();
+ if (childOffset != 0) {
+ child.offsetLeftAndRight(childOffset);
+ }
+ }
+ }
+
+ if (mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
+ }
+ if (mInternalPageChangeListener != null) {
+ mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
+ }
+
+ if (mPageTransformer != null) {
+ final int scrollX = getScrollX();
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (lp.isDecor) continue;
+
+ final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
+ mPageTransformer.transformPage(child, transformPos);
+ }
+ }
+
+ mCalledSuper = true;
+ }
+
+ private void completeScroll(boolean postEvents) {
+ boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
+ if (needPopulate) {
+ // Done with scroll, no longer want to cache view drawing.
+ setScrollingCacheEnabled(false);
+ mScroller.abortAnimation();
+ int oldX = getScrollX();
+ int oldY = getScrollY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+ if (oldX != x || oldY != y) {
+ scrollTo(x, y);
+ }
+ }
+ mPopulatePending = false;
+ for (int i=0; i<mItems.size(); i++) {
+ ItemInfo ii = mItems.get(i);
+ if (ii.scrolling) {
+ needPopulate = true;
+ ii.scrolling = false;
+ }
+ }
+ if (needPopulate) {
+ if (postEvents) {
+ postOnAnimation(mEndScrollRunnable);
+ } else {
+ mEndScrollRunnable.run();
+ }
+ }
+ }
+
+ private boolean isGutterDrag(float x, float dx) {
+ return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);
+ }
+
+ private void enableLayers(boolean enable) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final int layerType = enable ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE;
+ getChildAt(i).setLayerType(layerType, null);
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ /*
+ * This method JUST determines whether we want to intercept the motion.
+ * If we return true, onMotionEvent will be called and we do the actual
+ * scrolling there.
+ */
+
+ final int action = ev.getAction() & MotionEvent.ACTION_MASK;
+
+ // Always take care of the touch gesture being complete.
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ // Release the drag.
+ if (DEBUG) Log.v(TAG, "Intercept done!");
+ mIsBeingDragged = false;
+ mIsUnableToDrag = false;
+ mActivePointerId = INVALID_POINTER;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ return false;
+ }
+
+ // Nothing more to do here if we have decided whether or not we
+ // are dragging.
+ if (action != MotionEvent.ACTION_DOWN) {
+ if (mIsBeingDragged) {
+ if (DEBUG) Log.v(TAG, "Intercept returning true!");
+ return true;
+ }
+ if (mIsUnableToDrag) {
+ if (DEBUG) Log.v(TAG, "Intercept returning false!");
+ return false;
+ }
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE: {
+ /*
+ * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
+ * whether the user has moved far enough from his original down touch.
+ */
+
+ /*
+ * Locally do absolute value. mLastMotionY is set to the y value
+ * of the down event.
+ */
+ final int activePointerId = mActivePointerId;
+ if (activePointerId == INVALID_POINTER) {
+ // If we don't have a valid id, the touch down wasn't on content.
+ break;
+ }
+
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ final float x = ev.getX(pointerIndex);
+ final float dx = x - mLastMotionX;
+ final float xDiff = Math.abs(dx);
+ final float y = ev.getY(pointerIndex);
+ final float yDiff = Math.abs(y - mInitialMotionY);
+ if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
+
+ if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
+ canScroll(this, false, (int) dx, (int) x, (int) y)) {
+ // Nested view has scrollable area under this point. Let it be handled there.
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mIsUnableToDrag = true;
+ return false;
+ }
+ if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
+ if (DEBUG) Log.v(TAG, "Starting drag!");
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent(true);
+ setScrollState(SCROLL_STATE_DRAGGING);
+ mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
+ mInitialMotionX - mTouchSlop;
+ mLastMotionY = y;
+ setScrollingCacheEnabled(true);
+ } else if (yDiff > mTouchSlop) {
+ // The finger has moved enough in the vertical
+ // direction to be counted as a drag... abort
+ // any attempt to drag horizontally, to work correctly
+ // with children that have scrolling containers.
+ if (DEBUG) Log.v(TAG, "Starting unable to drag!");
+ mIsUnableToDrag = true;
+ }
+ if (mIsBeingDragged) {
+ // Scroll to follow the motion event
+ if (performDrag(x)) {
+ postInvalidateOnAnimation();
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ /*
+ * Remember location of down touch.
+ * ACTION_DOWN always refers to pointer index 0.
+ */
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = mInitialMotionY = ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+ mIsUnableToDrag = false;
+
+ mScroller.computeScrollOffset();
+ if (mScrollState == SCROLL_STATE_SETTLING &&
+ Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
+ // Let the user 'catch' the pager as it animates.
+ mScroller.abortAnimation();
+ mPopulatePending = false;
+ populate();
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent(true);
+ setScrollState(SCROLL_STATE_DRAGGING);
+ } else {
+ completeScroll(false);
+ mIsBeingDragged = false;
+ }
+
+ if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+ + " mIsBeingDragged=" + mIsBeingDragged
+ + "mIsUnableToDrag=" + mIsUnableToDrag);
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ /*
+ * The only time we want to intercept motion events is if we are in the
+ * drag mode.
+ */
+ return mIsBeingDragged;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mFakeDragging) {
+ // A fake drag is in progress already, ignore this real one
+ // but still eat the touch events.
+ // (It is likely that the user is multi-touching the screen.)
+ return true;
+ }
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+ // Don't handle edge touches immediately -- they may actually belong to one of our
+ // descendants.
+ return false;
+ }
+
+ if (mAdapter == null || mAdapter.getCount() == 0) {
+ // Nothing to present or scroll; nothing to touch.
+ return false;
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ final int action = ev.getAction();
+ boolean needsInvalidate = false;
+
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ mScroller.abortAnimation();
+ mPopulatePending = false;
+ populate();
+
+ // Remember where the motion event started
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = mInitialMotionY = ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+ break;
+ }
+ case MotionEvent.ACTION_MOVE:
+ if (!mIsBeingDragged) {
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(pointerIndex);
+ final float xDiff = Math.abs(x - mLastMotionX);
+ final float y = ev.getY(pointerIndex);
+ final float yDiff = Math.abs(y - mLastMotionY);
+ if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
+ if (xDiff > mTouchSlop && xDiff > yDiff) {
+ if (DEBUG) Log.v(TAG, "Starting drag!");
+ mIsBeingDragged = true;
+ requestParentDisallowInterceptTouchEvent(true);
+ mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
+ mInitialMotionX - mTouchSlop;
+ mLastMotionY = y;
+ setScrollState(SCROLL_STATE_DRAGGING);
+ setScrollingCacheEnabled(true);
+
+ // Disallow Parent Intercept, just in case
+ ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+ }
+ // Not else! Note that mIsBeingDragged can be set above.
+ if (mIsBeingDragged) {
+ // Scroll to follow the motion event
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(activePointerIndex);
+ needsInvalidate |= performDrag(x);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mIsBeingDragged) {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+ mPopulatePending = true;
+ final int width = getClientWidth();
+ final int scrollX = getScrollX();
+ final ItemInfo ii = infoForCurrentScrollPosition();
+ final int currentPage = ii.position;
+ final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
+ final int activePointerIndex =
+ ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(activePointerIndex);
+ final int totalDelta = (int) (x - mInitialMotionX);
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
+ totalDelta);
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);
+
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ mLeftEdge.onRelease();
+ mRightEdge.onRelease();
+ needsInvalidate = true;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsBeingDragged) {
+ scrollToItem(mCurItem, true, 0, false);
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ mLeftEdge.onRelease();
+ mRightEdge.onRelease();
+ needsInvalidate = true;
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int index = ev.getActionIndex();
+ final float x = ev.getX(index);
+ mLastMotionX = x;
+ mActivePointerId = ev.getPointerId(index);
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
+ break;
+ }
+ if (needsInvalidate) {
+ postInvalidateOnAnimation();
+ }
+ return true;
+ }
+
+ private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+ }
+
+ private boolean performDrag(float x) {
+ boolean needsInvalidate = false;
+
+ final float deltaX = mLastMotionX - x;
+ mLastMotionX = x;
+
+ float oldScrollX = getScrollX();
+ float scrollX = oldScrollX + deltaX;
+ final int width = getClientWidth();
+
+ float leftBound = width * mFirstOffset;
+ float rightBound = width * mLastOffset;
+ boolean leftAbsolute = true;
+ boolean rightAbsolute = true;
+
+ final ItemInfo firstItem = mItems.get(0);
+ final ItemInfo lastItem = mItems.get(mItems.size() - 1);
+ if (firstItem.position != 0) {
+ leftAbsolute = false;
+ leftBound = firstItem.offset * width;
+ }
+ if (lastItem.position != mAdapter.getCount() - 1) {
+ rightAbsolute = false;
+ rightBound = lastItem.offset * width;
+ }
+
+ if (scrollX < leftBound) {
+ if (leftAbsolute) {
+ float over = leftBound - scrollX;
+ mLeftEdge.onPull(Math.abs(over) / width);
+ needsInvalidate = true;
+ }
+ scrollX = leftBound;
+ } else if (scrollX > rightBound) {
+ if (rightAbsolute) {
+ float over = scrollX - rightBound;
+ mRightEdge.onPull(Math.abs(over) / width);
+ needsInvalidate = true;
+ }
+ scrollX = rightBound;
+ }
+ // Don't lose the rounded component
+ mLastMotionX += scrollX - (int) scrollX;
+ scrollTo((int) scrollX, getScrollY());
+ pageScrolled((int) scrollX);
+
+ return needsInvalidate;
+ }
+
+ /**
+ * @return Info about the page at the current scroll position.
+ * This can be synthetic for a missing middle page; the 'object' field can be null.
+ */
+ private ItemInfo infoForCurrentScrollPosition() {
+ final int width = getClientWidth();
+ final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
+ final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
+ int lastPos = -1;
+ float lastOffset = 0.f;
+ float lastWidth = 0.f;
+ boolean first = true;
+
+ ItemInfo lastItem = null;
+ for (int i = 0; i < mItems.size(); i++) {
+ ItemInfo ii = mItems.get(i);
+ float offset;
+ if (!first && ii.position != lastPos + 1) {
+ // Create a synthetic item for a missing page.
+ ii = mTempItem;
+ ii.offset = lastOffset + lastWidth + marginOffset;
+ ii.position = lastPos + 1;
+ ii.widthFactor = mAdapter.getPageWidth(ii.position);
+ i--;
+ }
+ offset = ii.offset;
+
+ final float leftBound = offset;
+ final float rightBound = offset + ii.widthFactor + marginOffset;
+ if (first || scrollOffset >= leftBound) {
+ if (scrollOffset < rightBound || i == mItems.size() - 1) {
+ return ii;
+ }
+ } else {
+ return lastItem;
+ }
+ first = false;
+ lastPos = ii.position;
+ lastOffset = offset;
+ lastWidth = ii.widthFactor;
+ lastItem = ii;
+ }
+
+ return lastItem;
+ }
+
+ private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
+ int targetPage;
+ if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
+ targetPage = velocity > 0 ? currentPage : currentPage + 1;
+ } else {
+ final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
+ targetPage = (int) (currentPage + pageOffset + truncator);
+ }
+
+ if (mItems.size() > 0) {
+ final ItemInfo firstItem = mItems.get(0);
+ final ItemInfo lastItem = mItems.get(mItems.size() - 1);
+
+ // Only let the user target pages we have items for
+ targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
+ }
+
+ return targetPage;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ boolean needsInvalidate = false;
+
+ final int overScrollMode = getOverScrollMode();
+ if (overScrollMode == View.OVER_SCROLL_ALWAYS ||
+ (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS &&
+ mAdapter != null && mAdapter.getCount() > 1)) {
+ if (!mLeftEdge.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+ final int width = getWidth();
+
+ canvas.rotate(270);
+ canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
+ mLeftEdge.setSize(height, width);
+ needsInvalidate |= mLeftEdge.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mRightEdge.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+ canvas.rotate(90);
+ canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
+ mRightEdge.setSize(height, width);
+ needsInvalidate |= mRightEdge.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+ } else {
+ mLeftEdge.finish();
+ mRightEdge.finish();
+ }
+
+ if (needsInvalidate) {
+ // Keep animating
+ postInvalidateOnAnimation();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Draw the margin drawable between pages if needed.
+ if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
+ final int scrollX = getScrollX();
+ final int width = getWidth();
+
+ final float marginOffset = (float) mPageMargin / width;
+ int itemIndex = 0;
+ ItemInfo ii = mItems.get(0);
+ float offset = ii.offset;
+ final int itemCount = mItems.size();
+ final int firstPos = ii.position;
+ final int lastPos = mItems.get(itemCount - 1).position;
+ for (int pos = firstPos; pos < lastPos; pos++) {
+ while (pos > ii.position && itemIndex < itemCount) {
+ ii = mItems.get(++itemIndex);
+ }
+
+ float drawAt;
+ if (pos == ii.position) {
+ drawAt = (ii.offset + ii.widthFactor) * width;
+ offset = ii.offset + ii.widthFactor + marginOffset;
+ } else {
+ float widthFactor = mAdapter.getPageWidth(pos);
+ drawAt = (offset + widthFactor) * width;
+ offset += widthFactor + marginOffset;
+ }
+
+ if (drawAt + mPageMargin > scrollX) {
+ mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
+ (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
+ mMarginDrawable.draw(canvas);
+ }
+
+ if (drawAt > scrollX + width) {
+ break; // No more visible, no sense in continuing
+ }
+ }
+ }
+ }
+
+ /**
+ * Start a fake drag of the pager.
+ *
+ * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
+ * with the touch scrolling of another view, while still letting the ViewPager
+ * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
+ * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
+ * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
+ *
+ * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
+ * is already in progress, this method will return false.
+ *
+ * @return true if the fake drag began successfully, false if it could not be started.
+ *
+ * @see #fakeDragBy(float)
+ * @see #endFakeDrag()
+ */
+ public boolean beginFakeDrag() {
+ if (mIsBeingDragged) {
+ return false;
+ }
+ mFakeDragging = true;
+ setScrollState(SCROLL_STATE_DRAGGING);
+ mInitialMotionX = mLastMotionX = 0;
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ final long time = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ mVelocityTracker.addMovement(ev);
+ ev.recycle();
+ mFakeDragBeginTime = time;
+ return true;
+ }
+
+ /**
+ * End a fake drag of the pager.
+ *
+ * @see #beginFakeDrag()
+ * @see #fakeDragBy(float)
+ */
+ public void endFakeDrag() {
+ if (!mFakeDragging) {
+ throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
+ }
+
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
+ mPopulatePending = true;
+ final int width = getClientWidth();
+ final int scrollX = getScrollX();
+ final ItemInfo ii = infoForCurrentScrollPosition();
+ final int currentPage = ii.position;
+ final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
+ final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
+ totalDelta);
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);
+ endDrag();
+
+ mFakeDragging = false;
+ }
+
+ /**
+ * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
+ *
+ * @param xOffset Offset in pixels to drag by.
+ * @see #beginFakeDrag()
+ * @see #endFakeDrag()
+ */
+ public void fakeDragBy(float xOffset) {
+ if (!mFakeDragging) {
+ throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
+ }
+
+ mLastMotionX += xOffset;
+
+ float oldScrollX = getScrollX();
+ float scrollX = oldScrollX - xOffset;
+ final int width = getClientWidth();
+
+ float leftBound = width * mFirstOffset;
+ float rightBound = width * mLastOffset;
+
+ final ItemInfo firstItem = mItems.get(0);
+ final ItemInfo lastItem = mItems.get(mItems.size() - 1);
+ if (firstItem.position != 0) {
+ leftBound = firstItem.offset * width;
+ }
+ if (lastItem.position != mAdapter.getCount() - 1) {
+ rightBound = lastItem.offset * width;
+ }
+
+ if (scrollX < leftBound) {
+ scrollX = leftBound;
+ } else if (scrollX > rightBound) {
+ scrollX = rightBound;
+ }
+ // Don't lose the rounded component
+ mLastMotionX += scrollX - (int) scrollX;
+ scrollTo((int) scrollX, getScrollY());
+ pageScrolled((int) scrollX);
+
+ // Synthesize an event for the VelocityTracker.
+ final long time = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
+ mLastMotionX, 0, 0);
+ mVelocityTracker.addMovement(ev);
+ ev.recycle();
+ }
+
+ /**
+ * Returns true if a fake drag is in progress.
+ *
+ * @return true if currently in a fake drag, false otherwise.
+ *
+ * @see #beginFakeDrag()
+ * @see #fakeDragBy(float)
+ * @see #endFakeDrag()
+ */
+ public boolean isFakeDragging() {
+ return mFakeDragging;
+ }
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = ev.getActionIndex();
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionX = ev.getX(newPointerIndex);
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ private void endDrag() {
+ mIsBeingDragged = false;
+ mIsUnableToDrag = false;
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void setScrollingCacheEnabled(boolean enabled) {
+ if (mScrollingCacheEnabled != enabled) {
+ mScrollingCacheEnabled = enabled;
+ if (USE_CACHE) {
+ final int size = getChildCount();
+ for (int i = 0; i < size; ++i) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ child.setDrawingCacheEnabled(enabled);
+ }
+ }
+ }
+ }
+ }
+
+ public boolean canScrollHorizontally(int direction) {
+ if (mAdapter == null) {
+ return false;
+ }
+
+ final int width = getClientWidth();
+ final int scrollX = getScrollX();
+ if (direction < 0) {
+ return (scrollX > (int) (width * mFirstOffset));
+ } else if (direction > 0) {
+ return (scrollX < (int) (width * mLastOffset));
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Tests scrollability within child views of v given a delta of dx.
+ *
+ * @param v View to test for horizontal scrollability
+ * @param checkV Whether the view v passed should itself be checked for scrollability (true),
+ * or just its children (false).
+ * @param dx Delta scrolled in pixels
+ * @param x X coordinate of the active touch point
+ * @param y Y coordinate of the active touch point
+ * @return true if child views of v can be scrolled by delta of dx.
+ */
+ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
+ if (v instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) v;
+ final int scrollX = v.getScrollX();
+ final int scrollY = v.getScrollY();
+ final int count = group.getChildCount();
+ // Count backwards - let topmost views consume scroll distance first.
+ for (int i = count - 1; i >= 0; i--) {
+ // TODO: Add versioned support here for transformed views.
+ // This will not work for transformed views in Honeycomb+
+ final View child = group.getChildAt(i);
+ if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
+ y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
+ canScroll(child, true, dx, x + scrollX - child.getLeft(),
+ y + scrollY - child.getTop())) {
+ return true;
+ }
+ }
+ }
+
+ return checkV && v.canScrollHorizontally(-dx);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ // Let the focused view and/or our descendants get the key first
+ return super.dispatchKeyEvent(event) || executeKeyEvent(event);
+ }
+
+ /**
+ * You can call this function yourself to have the scroll view perform
+ * scrolling from a key event, just as if the event had been dispatched to
+ * it by the view hierarchy.
+ *
+ * @param event The key event to execute.
+ * @return Return true if the event was handled, else false.
+ */
+ public boolean executeKeyEvent(KeyEvent event) {
+ boolean handled = false;
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ handled = arrowScroll(FOCUS_LEFT);
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ handled = arrowScroll(FOCUS_RIGHT);
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if (event.hasNoModifiers()) {
+ handled = arrowScroll(FOCUS_FORWARD);
+ } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+ handled = arrowScroll(FOCUS_BACKWARD);
+ }
+ break;
+ }
+ }
+ return handled;
+ }
+
+ public boolean arrowScroll(int direction) {
+ View currentFocused = findFocus();
+ if (currentFocused == this) {
+ currentFocused = null;
+ } else if (currentFocused != null) {
+ boolean isChild = false;
+ for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
+ parent = parent.getParent()) {
+ if (parent == this) {
+ isChild = true;
+ break;
+ }
+ }
+ if (!isChild) {
+ // This would cause the focus search down below to fail in fun ways.
+ final StringBuilder sb = new StringBuilder();
+ sb.append(currentFocused.getClass().getSimpleName());
+ for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
+ parent = parent.getParent()) {
+ sb.append(" => ").append(parent.getClass().getSimpleName());
+ }
+ Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
+ "current focused view " + sb.toString());
+ currentFocused = null;
+ }
+ }
+
+ boolean handled = false;
+
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
+ direction);
+ if (nextFocused != null && nextFocused != currentFocused) {
+ if (direction == View.FOCUS_LEFT) {
+ // If there is nothing to the left, or this is causing us to
+ // jump to the right, then what we really want to do is page left.
+ final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
+ final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
+ if (currentFocused != null && nextLeft >= currLeft) {
+ handled = pageLeft();
+ } else {
+ handled = nextFocused.requestFocus();
+ }
+ } else if (direction == View.FOCUS_RIGHT) {
+ // If there is nothing to the right, or this is causing us to
+ // jump to the left, then what we really want to do is page right.
+ final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
+ final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
+ if (currentFocused != null && nextLeft <= currLeft) {
+ handled = pageRight();
+ } else {
+ handled = nextFocused.requestFocus();
+ }
+ }
+ } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
+ // Trying to move left and nothing there; try to page.
+ handled = pageLeft();
+ } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
+ // Trying to move right and nothing there; try to page.
+ handled = pageRight();
+ }
+ if (handled) {
+ playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+ }
+ return handled;
+ }
+
+ private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
+ if (outRect == null) {
+ outRect = new Rect();
+ }
+ if (child == null) {
+ outRect.set(0, 0, 0, 0);
+ return outRect;
+ }
+ outRect.left = child.getLeft();
+ outRect.right = child.getRight();
+ outRect.top = child.getTop();
+ outRect.bottom = child.getBottom();
+
+ ViewParent parent = child.getParent();
+ while (parent instanceof ViewGroup && parent != this) {
+ final ViewGroup group = (ViewGroup) parent;
+ outRect.left += group.getLeft();
+ outRect.right += group.getRight();
+ outRect.top += group.getTop();
+ outRect.bottom += group.getBottom();
+
+ parent = group.getParent();
+ }
+ return outRect;
+ }
+
+ boolean pageLeft() {
+ if (mCurItem > 0) {
+ setCurrentItem(mCurItem-1, true);
+ return true;
+ }
+ return false;
+ }
+
+ boolean pageRight() {
+ if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
+ setCurrentItem(mCurItem+1, true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * We only want the current page that is being shown to be focusable.
+ */
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ final int focusableCount = views.size();
+
+ final int descendantFocusability = getDescendantFocusability();
+
+ if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ ItemInfo ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem) {
+ child.addFocusables(views, direction, focusableMode);
+ }
+ }
+ }
+ }
+
+ // we add ourselves (if focusable) in all cases except for when we are
+ // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
+ // to avoid the focus search finding layouts when a more precise search
+ // among the focusable children would be more interesting.
+ if (
+ descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
+ // No focusable descendants
+ (focusableCount == views.size())) {
+ // Note that we can't call the superclass here, because it will
+ // add all views in. So we need to do the same thing View does.
+ if (!isFocusable()) {
+ return;
+ }
+ if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
+ isInTouchMode() && !isFocusableInTouchMode()) {
+ return;
+ }
+ if (views != null) {
+ views.add(this);
+ }
+ }
+ }
+
+ /**
+ * We only want the current page that is being shown to be touchable.
+ */
+ @Override
+ public void addTouchables(ArrayList<View> views) {
+ // Note that we don't call super.addTouchables(), which means that
+ // we don't call View.addTouchables(). This is okay because a ViewPager
+ // is itself not touchable.
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ ItemInfo ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem) {
+ child.addTouchables(views);
+ }
+ }
+ }
+ }
+
+ /**
+ * We only want the current page that is being shown to be focusable.
+ */
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+ int index;
+ int increment;
+ int end;
+ int count = getChildCount();
+ if ((direction & FOCUS_FORWARD) != 0) {
+ index = 0;
+ increment = 1;
+ end = count;
+ } else {
+ index = count - 1;
+ increment = -1;
+ end = -1;
+ }
+ for (int i = index; i != end; i += increment) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ ItemInfo ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem) {
+ if (child.requestFocus(direction, previouslyFocusedRect)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ // Dispatch scroll events from this ViewPager.
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ // Dispatch all other accessibility events from the current page.
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ final ItemInfo ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem &&
+ child.dispatchPopulateAccessibilityEvent(event)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams();
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return generateDefaultLayoutParams();
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams && super.checkLayoutParams(p);
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ class MyAccessibilityDelegate extends AccessibilityDelegate {
+
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+ event.setClassName(ViewPager.class.getName());
+ final AccessibilityRecord record = AccessibilityRecord.obtain();
+ record.setScrollable(canScroll());
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED
+ && mAdapter != null) {
+ record.setItemCount(mAdapter.getCount());
+ record.setFromIndex(mCurItem);
+ record.setToIndex(mCurItem);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setClassName(ViewPager.class.getName());
+ info.setScrollable(canScroll());
+ if (canScrollHorizontally(1)) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ }
+ if (canScrollHorizontally(-1)) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (super.performAccessibilityAction(host, action, args)) {
+ return true;
+ }
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+ if (canScrollHorizontally(1)) {
+ setCurrentItem(mCurItem + 1);
+ return true;
+ }
+ } return false;
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
+ if (canScrollHorizontally(-1)) {
+ setCurrentItem(mCurItem - 1);
+ return true;
+ }
+ } return false;
+ }
+ return false;
+ }
+
+ private boolean canScroll() {
+ return (mAdapter != null) && (mAdapter.getCount() > 1);
+ }
+ }
+
+ private class PagerObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ dataSetChanged();
+ }
+ @Override
+ public void onInvalidated() {
+ dataSetChanged();
+ }
+ }
+
+ /**
+ * Layout parameters that should be supplied for views added to a
+ * ViewPager.
+ */
+ public static class LayoutParams extends ViewGroup.LayoutParams {
+ /**
+ * true if this view is a decoration on the pager itself and not
+ * a view supplied by the adapter.
+ */
+ public boolean isDecor;
+
+ /**
+ * Gravity setting for use on decor views only:
+ * Where to position the view page within the overall ViewPager
+ * container; constants are defined in {@link android.view.Gravity}.
+ */
+ public int gravity;
+
+ /**
+ * Width as a 0-1 multiplier of the measured pager width
+ */
+ float widthFactor = 0.f;
+
+ /**
+ * true if this view was added during layout and needs to be measured
+ * before being positioned.
+ */
+ boolean needsMeasure;
+
+ /**
+ * Adapter position this view is for if !isDecor
+ */
+ int position;
+
+ /**
+ * Current child index within the ViewPager that this view occupies
+ */
+ int childIndex;
+
+ public LayoutParams() {
+ super(FILL_PARENT, FILL_PARENT);
+ }
+
+ public LayoutParams(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
+ gravity = a.getInteger(0, Gravity.TOP);
+ a.recycle();
+ }
+ }
+
+ static class ViewPositionComparator implements Comparator<View> {
+ @Override
+ public int compare(View lhs, View rhs) {
+ final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
+ final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
+ if (llp.isDecor != rlp.isDecor) {
+ return llp.isDecor ? 1 : -1;
+ }
+ return llp.position - rlp.position;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java
deleted file mode 100644
index 9e7a649..0000000
--- a/core/java/com/android/internal/widget/WaveView.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget;
-
-import java.util.ArrayList;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.media.AudioAttributes;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.R;
-
-/**
- * A special widget containing a center and outer ring. Moving the center ring to the outer ring
- * causes an event that can be caught by implementing OnTriggerListener.
- */
-public class WaveView extends View implements ValueAnimator.AnimatorUpdateListener {
- private static final String TAG = "WaveView";
- private static final boolean DBG = false;
- private static final int WAVE_COUNT = 20; // default wave count
- private static final long VIBRATE_SHORT = 20; // msec
- private static final long VIBRATE_LONG = 20; // msec
-
- // Lock state machine states
- private static final int STATE_RESET_LOCK = 0;
- private static final int STATE_READY = 1;
- private static final int STATE_START_ATTEMPT = 2;
- private static final int STATE_ATTEMPTING = 3;
- private static final int STATE_UNLOCK_ATTEMPT = 4;
- private static final int STATE_UNLOCK_SUCCESS = 5;
-
- // Animation properties.
- private static final long DURATION = 300; // duration of transitional animations
- private static final long FINAL_DURATION = 200; // duration of final animations when unlocking
- private static final long RING_DELAY = 1300; // when to start fading animated rings
- private static final long FINAL_DELAY = 200; // delay for unlock success animation
- private static final long SHORT_DELAY = 100; // for starting one animation after another.
- private static final long WAVE_DURATION = 2000; // amount of time for way to expand/decay
- private static final long RESET_TIMEOUT = 3000; // elapsed time of inactivity before we reset
- private static final long DELAY_INCREMENT = 15; // increment per wave while tracking motion
- private static final long DELAY_INCREMENT2 = 12; // increment per wave while not tracking
- private static final long WAVE_DELAY = WAVE_DURATION / WAVE_COUNT; // initial propagation delay
-
- /**
- * The scale by which to multiply the unlock handle width to compute the radius
- * in which it can be grabbed when accessibility is disabled.
- */
- private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED = 0.5f;
-
- /**
- * The scale by which to multiply the unlock handle width to compute the radius
- * in which it can be grabbed when accessibility is enabled (more generous).
- */
- private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.0f;
-
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
- private Vibrator mVibrator;
- private OnTriggerListener mOnTriggerListener;
- private ArrayList<DrawableHolder> mDrawables = new ArrayList<DrawableHolder>(3);
- private ArrayList<DrawableHolder> mLightWaves = new ArrayList<DrawableHolder>(WAVE_COUNT);
- private boolean mFingerDown = false;
- private float mRingRadius = 182.0f; // Radius of bitmap ring. Used to snap halo to it
- private int mSnapRadius = 136; // minimum threshold for drag unlock
- private int mWaveCount = WAVE_COUNT; // number of waves
- private long mWaveTimerDelay = WAVE_DELAY;
- private int mCurrentWave = 0;
- private float mLockCenterX; // center of widget as dictated by widget size
- private float mLockCenterY;
- private float mMouseX; // current mouse position as of last touch event
- private float mMouseY;
- private DrawableHolder mUnlockRing;
- private DrawableHolder mUnlockDefault;
- private DrawableHolder mUnlockHalo;
- private int mLockState = STATE_RESET_LOCK;
- private int mGrabbedState = OnTriggerListener.NO_HANDLE;
- private boolean mWavesRunning;
- private boolean mFinishWaves;
-
- public WaveView(Context context) {
- this(context, null);
- }
-
- public WaveView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- // TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveView);
- // mOrientation = a.getInt(R.styleable.WaveView_orientation, HORIZONTAL);
- // a.recycle();
-
- initDrawables();
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mLockCenterX = 0.5f * w;
- mLockCenterY = 0.5f * h;
- super.onSizeChanged(w, h, oldw, oldh);
- }
-
- @Override
- protected int getSuggestedMinimumWidth() {
- // View should be large enough to contain the unlock ring + halo
- return mUnlockRing.getWidth() + mUnlockHalo.getWidth();
- }
-
- @Override
- protected int getSuggestedMinimumHeight() {
- // View should be large enough to contain the unlock ring + halo
- return mUnlockRing.getHeight() + mUnlockHalo.getHeight();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
- int width;
- int height;
-
- if (widthSpecMode == MeasureSpec.AT_MOST) {
- width = Math.min(widthSpecSize, getSuggestedMinimumWidth());
- } else if (widthSpecMode == MeasureSpec.EXACTLY) {
- width = widthSpecSize;
- } else {
- width = getSuggestedMinimumWidth();
- }
-
- if (heightSpecMode == MeasureSpec.AT_MOST) {
- height = Math.min(heightSpecSize, getSuggestedMinimumWidth());
- } else if (heightSpecMode == MeasureSpec.EXACTLY) {
- height = heightSpecSize;
- } else {
- height = getSuggestedMinimumHeight();
- }
-
- setMeasuredDimension(width, height);
- }
-
- private void initDrawables() {
- mUnlockRing = new DrawableHolder(createDrawable(R.drawable.unlock_ring));
- mUnlockRing.setX(mLockCenterX);
- mUnlockRing.setY(mLockCenterY);
- mUnlockRing.setScaleX(0.1f);
- mUnlockRing.setScaleY(0.1f);
- mUnlockRing.setAlpha(0.0f);
- mDrawables.add(mUnlockRing);
-
- mUnlockDefault = new DrawableHolder(createDrawable(R.drawable.unlock_default));
- mUnlockDefault.setX(mLockCenterX);
- mUnlockDefault.setY(mLockCenterY);
- mUnlockDefault.setScaleX(0.1f);
- mUnlockDefault.setScaleY(0.1f);
- mUnlockDefault.setAlpha(0.0f);
- mDrawables.add(mUnlockDefault);
-
- mUnlockHalo = new DrawableHolder(createDrawable(R.drawable.unlock_halo));
- mUnlockHalo.setX(mLockCenterX);
- mUnlockHalo.setY(mLockCenterY);
- mUnlockHalo.setScaleX(0.1f);
- mUnlockHalo.setScaleY(0.1f);
- mUnlockHalo.setAlpha(0.0f);
- mDrawables.add(mUnlockHalo);
-
- BitmapDrawable wave = createDrawable(R.drawable.unlock_wave);
- for (int i = 0; i < mWaveCount; i++) {
- DrawableHolder holder = new DrawableHolder(wave);
- mLightWaves.add(holder);
- holder.setAlpha(0.0f);
- }
- }
-
- private void waveUpdateFrame(float mouseX, float mouseY, boolean fingerDown) {
- double distX = mouseX - mLockCenterX;
- double distY = mouseY - mLockCenterY;
- int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
- double touchA = Math.atan2(distX, distY);
- float ringX = (float) (mLockCenterX + mRingRadius * Math.sin(touchA));
- float ringY = (float) (mLockCenterY + mRingRadius * Math.cos(touchA));
-
- switch (mLockState) {
- case STATE_RESET_LOCK:
- if (DBG) Log.v(TAG, "State RESET_LOCK");
- mWaveTimerDelay = WAVE_DELAY;
- for (int i = 0; i < mLightWaves.size(); i++) {
- DrawableHolder holder = mLightWaves.get(i);
- holder.addAnimTo(300, 0, "alpha", 0.0f, false);
- }
- for (int i = 0; i < mLightWaves.size(); i++) {
- mLightWaves.get(i).startAnimations(this);
- }
-
- mUnlockRing.addAnimTo(DURATION, 0, "x", mLockCenterX, true);
- mUnlockRing.addAnimTo(DURATION, 0, "y", mLockCenterY, true);
- mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 0.1f, true);
- mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 0.1f, true);
- mUnlockRing.addAnimTo(DURATION, 0, "alpha", 0.0f, true);
-
- mUnlockDefault.removeAnimationFor("x");
- mUnlockDefault.removeAnimationFor("y");
- mUnlockDefault.removeAnimationFor("scaleX");
- mUnlockDefault.removeAnimationFor("scaleY");
- mUnlockDefault.removeAnimationFor("alpha");
- mUnlockDefault.setX(mLockCenterX);
- mUnlockDefault.setY(mLockCenterY);
- mUnlockDefault.setScaleX(0.1f);
- mUnlockDefault.setScaleY(0.1f);
- mUnlockDefault.setAlpha(0.0f);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
-
- mUnlockHalo.removeAnimationFor("x");
- mUnlockHalo.removeAnimationFor("y");
- mUnlockHalo.removeAnimationFor("scaleX");
- mUnlockHalo.removeAnimationFor("scaleY");
- mUnlockHalo.removeAnimationFor("alpha");
- mUnlockHalo.setX(mLockCenterX);
- mUnlockHalo.setY(mLockCenterY);
- mUnlockHalo.setScaleX(0.1f);
- mUnlockHalo.setScaleY(0.1f);
- mUnlockHalo.setAlpha(0.0f);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "x", mLockCenterX, true);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "y", mLockCenterY, true);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
- mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
-
- removeCallbacks(mLockTimerActions);
-
- mLockState = STATE_READY;
- break;
-
- case STATE_READY:
- if (DBG) Log.v(TAG, "State READY");
- mWaveTimerDelay = WAVE_DELAY;
- break;
-
- case STATE_START_ATTEMPT:
- if (DBG) Log.v(TAG, "State START_ATTEMPT");
- mUnlockDefault.removeAnimationFor("x");
- mUnlockDefault.removeAnimationFor("y");
- mUnlockDefault.removeAnimationFor("scaleX");
- mUnlockDefault.removeAnimationFor("scaleY");
- mUnlockDefault.removeAnimationFor("alpha");
- mUnlockDefault.setX(mLockCenterX + 182);
- mUnlockDefault.setY(mLockCenterY);
- mUnlockDefault.setScaleX(0.1f);
- mUnlockDefault.setScaleY(0.1f);
- mUnlockDefault.setAlpha(0.0f);
-
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, false);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, false);
- mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, false);
-
- mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 1.0f, true);
- mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 1.0f, true);
- mUnlockRing.addAnimTo(DURATION, 0, "alpha", 1.0f, true);
-
- mLockState = STATE_ATTEMPTING;
- break;
-
- case STATE_ATTEMPTING:
- if (DBG) Log.v(TAG, "State ATTEMPTING (fingerDown = " + fingerDown + ")");
- if (dragDistance > mSnapRadius) {
- mFinishWaves = true; // don't start any more waves.
- if (fingerDown) {
- mUnlockHalo.addAnimTo(0, 0, "x", ringX, true);
- mUnlockHalo.addAnimTo(0, 0, "y", ringY, true);
- mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
- mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
- mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
- } else {
- if (DBG) Log.v(TAG, "up detected, moving to STATE_UNLOCK_ATTEMPT");
- mLockState = STATE_UNLOCK_ATTEMPT;
- }
- } else {
- // If waves have stopped, we need to kick them off again...
- if (!mWavesRunning) {
- mWavesRunning = true;
- mFinishWaves = false;
- // mWaveTimerDelay = WAVE_DELAY;
- postDelayed(mAddWaveAction, mWaveTimerDelay);
- }
- mUnlockHalo.addAnimTo(0, 0, "x", mouseX, true);
- mUnlockHalo.addAnimTo(0, 0, "y", mouseY, true);
- mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
- mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
- mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
- }
- break;
-
- case STATE_UNLOCK_ATTEMPT:
- if (DBG) Log.v(TAG, "State UNLOCK_ATTEMPT");
- if (dragDistance > mSnapRadius) {
- for (int n = 0; n < mLightWaves.size(); n++) {
- DrawableHolder wave = mLightWaves.get(n);
- long delay = 1000L*(6 + n - mCurrentWave)/10L;
- wave.addAnimTo(FINAL_DURATION, delay, "x", ringX, true);
- wave.addAnimTo(FINAL_DURATION, delay, "y", ringY, true);
- wave.addAnimTo(FINAL_DURATION, delay, "scaleX", 0.1f, true);
- wave.addAnimTo(FINAL_DURATION, delay, "scaleY", 0.1f, true);
- wave.addAnimTo(FINAL_DURATION, delay, "alpha", 0.0f, true);
- }
- for (int i = 0; i < mLightWaves.size(); i++) {
- mLightWaves.get(i).startAnimations(this);
- }
-
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "x", ringX, false);
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "y", ringY, false);
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "scaleX", 0.1f, false);
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "scaleY", 0.1f, false);
- mUnlockRing.addAnimTo(FINAL_DURATION, 0, "alpha", 0.0f, false);
-
- mUnlockRing.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
-
- mUnlockDefault.removeAnimationFor("x");
- mUnlockDefault.removeAnimationFor("y");
- mUnlockDefault.removeAnimationFor("scaleX");
- mUnlockDefault.removeAnimationFor("scaleY");
- mUnlockDefault.removeAnimationFor("alpha");
- mUnlockDefault.setX(ringX);
- mUnlockDefault.setY(ringY);
- mUnlockDefault.setScaleX(0.1f);
- mUnlockDefault.setScaleY(0.1f);
- mUnlockDefault.setAlpha(0.0f);
-
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "x", ringX, true);
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "y", ringY, true);
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "scaleX", 1.0f, true);
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "scaleY", 1.0f, true);
- mUnlockDefault.addAnimTo(FINAL_DURATION, 0, "alpha", 1.0f, true);
-
- mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
- mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
- mUnlockDefault.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
-
- mUnlockHalo.addAnimTo(FINAL_DURATION, 0, "x", ringX, false);
- mUnlockHalo.addAnimTo(FINAL_DURATION, 0, "y", ringY, false);
-
- mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
- mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
- mUnlockHalo.addAnimTo(FINAL_DURATION, FINAL_DELAY, "alpha", 0.0f, false);
-
- removeCallbacks(mLockTimerActions);
-
- postDelayed(mLockTimerActions, RESET_TIMEOUT);
-
- dispatchTriggerEvent(OnTriggerListener.CENTER_HANDLE);
- mLockState = STATE_UNLOCK_SUCCESS;
- } else {
- mLockState = STATE_RESET_LOCK;
- }
- break;
-
- case STATE_UNLOCK_SUCCESS:
- if (DBG) Log.v(TAG, "State UNLOCK_SUCCESS");
- removeCallbacks(mAddWaveAction);
- break;
-
- default:
- if (DBG) Log.v(TAG, "Unknown state " + mLockState);
- break;
- }
- mUnlockDefault.startAnimations(this);
- mUnlockHalo.startAnimations(this);
- mUnlockRing.startAnimations(this);
- }
-
- BitmapDrawable createDrawable(int resId) {
- Resources res = getResources();
- Bitmap bitmap = BitmapFactory.decodeResource(res, resId);
- return new BitmapDrawable(res, bitmap);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- waveUpdateFrame(mMouseX, mMouseY, mFingerDown);
- for (int i = 0; i < mDrawables.size(); ++i) {
- mDrawables.get(i).draw(canvas);
- }
- for (int i = 0; i < mLightWaves.size(); ++i) {
- mLightWaves.get(i).draw(canvas);
- }
- }
-
- private final Runnable mLockTimerActions = new Runnable() {
- public void run() {
- if (DBG) Log.v(TAG, "LockTimerActions");
- // reset lock after inactivity
- if (mLockState == STATE_ATTEMPTING) {
- if (DBG) Log.v(TAG, "Timer resets to STATE_RESET_LOCK");
- mLockState = STATE_RESET_LOCK;
- }
- // for prototype, reset after successful unlock
- if (mLockState == STATE_UNLOCK_SUCCESS) {
- if (DBG) Log.v(TAG, "Timer resets to STATE_RESET_LOCK after success");
- mLockState = STATE_RESET_LOCK;
- }
- invalidate();
- }
- };
-
- private final Runnable mAddWaveAction = new Runnable() {
- public void run() {
- double distX = mMouseX - mLockCenterX;
- double distY = mMouseY - mLockCenterY;
- int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
- if (mLockState == STATE_ATTEMPTING && dragDistance < mSnapRadius
- && mWaveTimerDelay >= WAVE_DELAY) {
- mWaveTimerDelay = Math.min(WAVE_DURATION, mWaveTimerDelay + DELAY_INCREMENT);
-
- DrawableHolder wave = mLightWaves.get(mCurrentWave);
- wave.setAlpha(0.0f);
- wave.setScaleX(0.2f);
- wave.setScaleY(0.2f);
- wave.setX(mMouseX);
- wave.setY(mMouseY);
-
- wave.addAnimTo(WAVE_DURATION, 0, "x", mLockCenterX, true);
- wave.addAnimTo(WAVE_DURATION, 0, "y", mLockCenterY, true);
- wave.addAnimTo(WAVE_DURATION*2/3, 0, "alpha", 1.0f, true);
- wave.addAnimTo(WAVE_DURATION, 0, "scaleX", 1.0f, true);
- wave.addAnimTo(WAVE_DURATION, 0, "scaleY", 1.0f, true);
-
- wave.addAnimTo(1000, RING_DELAY, "alpha", 0.0f, false);
- wave.startAnimations(WaveView.this);
-
- mCurrentWave = (mCurrentWave+1) % mWaveCount;
- if (DBG) Log.v(TAG, "WaveTimerDelay: start new wave in " + mWaveTimerDelay);
- } else {
- mWaveTimerDelay += DELAY_INCREMENT2;
- }
- if (mFinishWaves) {
- // sentinel used to restart the waves after they've stopped
- mWavesRunning = false;
- } else {
- postDelayed(mAddWaveAction, mWaveTimerDelay);
- }
- }
- };
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_HOVER_ENTER:
- event.setAction(MotionEvent.ACTION_DOWN);
- break;
- case MotionEvent.ACTION_HOVER_MOVE:
- event.setAction(MotionEvent.ACTION_MOVE);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- event.setAction(MotionEvent.ACTION_UP);
- break;
- }
- onTouchEvent(event);
- event.setAction(action);
- }
- return super.onHoverEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
- mMouseX = event.getX();
- mMouseY = event.getY();
- boolean handled = false;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- removeCallbacks(mLockTimerActions);
- mFingerDown = true;
- tryTransitionToStartAttemptState(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_MOVE:
- tryTransitionToStartAttemptState(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_UP:
- if (DBG) Log.v(TAG, "ACTION_UP");
- mFingerDown = false;
- postDelayed(mLockTimerActions, RESET_TIMEOUT);
- setGrabbedState(OnTriggerListener.NO_HANDLE);
- // Normally the state machine is driven by user interaction causing redraws.
- // However, when there's no more user interaction and no running animations,
- // the state machine stops advancing because onDraw() never gets called.
- // The following ensures we advance to the next state in this case,
- // either STATE_UNLOCK_ATTEMPT or STATE_RESET_LOCK.
- waveUpdateFrame(mMouseX, mMouseY, mFingerDown);
- handled = true;
- break;
-
- case MotionEvent.ACTION_CANCEL:
- mFingerDown = false;
- handled = true;
- break;
- }
- invalidate();
- return handled ? true : super.onTouchEvent(event);
- }
-
- /**
- * Tries to transition to start attempt state.
- *
- * @param event A motion event.
- */
- private void tryTransitionToStartAttemptState(MotionEvent event) {
- final float dx = event.getX() - mUnlockHalo.getX();
- final float dy = event.getY() - mUnlockHalo.getY();
- float dist = (float) Math.hypot(dx, dy);
- if (dist <= getScaledGrabHandleRadius()) {
- setGrabbedState(OnTriggerListener.CENTER_HANDLE);
- if (mLockState == STATE_READY) {
- mLockState = STATE_START_ATTEMPT;
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- announceUnlockHandle();
- }
- }
- }
- }
-
- /**
- * @return The radius in which the handle is grabbed scaled based on
- * whether accessibility is enabled.
- */
- private float getScaledGrabHandleRadius() {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mUnlockHalo.getWidth();
- } else {
- return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED * mUnlockHalo.getWidth();
- }
- }
-
- /**
- * Announces the unlock handle if accessibility is enabled.
- */
- private void announceUnlockHandle() {
- setContentDescription(mContext.getString(R.string.description_target_unlock_tablet));
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- setContentDescription(null);
- }
-
- /**
- * Triggers haptic feedback.
- */
- private synchronized void vibrate(long duration) {
- final boolean hapticEnabled = Settings.System.getIntForUser(
- mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
- UserHandle.USER_CURRENT) != 0;
- if (hapticEnabled) {
- if (mVibrator == null) {
- mVibrator = (android.os.Vibrator) getContext()
- .getSystemService(Context.VIBRATOR_SERVICE);
- }
- mVibrator.vibrate(duration, VIBRATION_ATTRIBUTES);
- }
- }
-
- /**
- * Registers a callback to be invoked when the user triggers an event.
- *
- * @param listener the OnDialTriggerListener to attach to this view
- */
- public void setOnTriggerListener(OnTriggerListener listener) {
- mOnTriggerListener = listener;
- }
-
- /**
- * Dispatches a trigger event to listener. Ignored if a listener is not set.
- * @param whichHandle the handle that triggered the event.
- */
- private void dispatchTriggerEvent(int whichHandle) {
- vibrate(VIBRATE_LONG);
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onTrigger(this, whichHandle);
- }
- }
-
- /**
- * Sets the current grabbed state, and dispatches a grabbed state change
- * event to our listener.
- */
- private void setGrabbedState(int newState) {
- if (newState != mGrabbedState) {
- mGrabbedState = newState;
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
- }
- }
- }
-
- public interface OnTriggerListener {
- /**
- * Sent when the user releases the handle.
- */
- public static final int NO_HANDLE = 0;
-
- /**
- * Sent when the user grabs the center handle
- */
- public static final int CENTER_HANDLE = 10;
-
- /**
- * Called when the user drags the center ring beyond a threshold.
- */
- void onTrigger(View v, int whichHandle);
-
- /**
- * Called when the "grabbed state" changes (i.e. when the user either grabs or releases
- * one of the handles.)
- *
- * @param v the view that was triggered
- * @param grabbedState the new state: {@link #NO_HANDLE}, {@link #CENTER_HANDLE},
- */
- void onGrabbedStateChange(View v, int grabbedState);
- }
-
- public void onAnimationUpdate(ValueAnimator animation) {
- invalidate();
- }
-
- public void reset() {
- if (DBG) Log.v(TAG, "reset() : resets state to STATE_RESET_LOCK");
- mLockState = STATE_RESET_LOCK;
- invalidate();
- }
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/Ease.java b/core/java/com/android/internal/widget/multiwaveview/Ease.java
deleted file mode 100644
index 7f90c44..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/Ease.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget.multiwaveview;
-
-import android.animation.TimeInterpolator;
-
-class Ease {
- private static final float DOMAIN = 1.0f;
- private static final float DURATION = 1.0f;
- private static final float START = 0.0f;
-
- static class Linear {
- public static final TimeInterpolator easeNone = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return input;
- }
- };
- }
-
- static class Cubic {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*((input=input/DURATION-1)*input*input + 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1.0f) ?
- (DOMAIN/2*input*input*input + START)
- : (DOMAIN/2*((input-=2)*input*input + 2) + START);
- }
- };
- }
-
- static class Quad {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation (float input) {
- return DOMAIN*(input/=DURATION)*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN *(input/=DURATION)*(input-2) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input + START)
- : (-DOMAIN/2 * ((--input)*(input-2) - 1) + START);
- }
- };
- }
-
- static class Quart {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN * ((input=input/DURATION-1)*input*input*input - 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input*input*input + START)
- : (-DOMAIN/2 * ((input-=2)*input*input*input - 2) + START);
- }
- };
- }
-
- static class Quint {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*(input/=DURATION)*input*input*input*input + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN*((input=input/DURATION-1)*input*input*input*input + 1) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return ((input/=DURATION/2) < 1) ?
- (DOMAIN/2*input*input*input*input*input + START)
- : (DOMAIN/2*((input-=2)*input*input*input*input + 2) + START);
- }
- };
- }
-
- static class Sine {
- public static final TimeInterpolator easeIn = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN * (float) Math.cos(input/DURATION * (Math.PI/2)) + DOMAIN + START;
- }
- };
- public static final TimeInterpolator easeOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return DOMAIN * (float) Math.sin(input/DURATION * (Math.PI/2)) + START;
- }
- };
- public static final TimeInterpolator easeInOut = new TimeInterpolator() {
- public float getInterpolation(float input) {
- return -DOMAIN/2 * ((float)Math.cos(Math.PI*input/DURATION) - 1.0f) + START;
- }
- };
- }
-
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
deleted file mode 100644
index 11ac19e..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
+++ /dev/null
@@ -1,1383 +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 com.android.internal.widget.multiwaveview;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.media.AudioAttributes;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-
-/**
- * A re-usable widget containing a center, outer ring and wave animation.
- */
-public class GlowPadView extends View {
- private static final String TAG = "GlowPadView";
- private static final boolean DEBUG = false;
-
- // Wave state machine
- private static final int STATE_IDLE = 0;
- private static final int STATE_START = 1;
- private static final int STATE_FIRST_TOUCH = 2;
- private static final int STATE_TRACKING = 3;
- private static final int STATE_SNAP = 4;
- private static final int STATE_FINISH = 5;
-
- // Animation properties.
- private static final float SNAP_MARGIN_DEFAULT = 20.0f; // distance to ring before we snap to it
-
- public interface OnTriggerListener {
- int NO_HANDLE = 0;
- int CENTER_HANDLE = 1;
- public void onGrabbed(View v, int handle);
- public void onReleased(View v, int handle);
- public void onTrigger(View v, int target);
- public void onGrabbedStateChange(View v, int handle);
- public void onFinishFinalAnimation();
- }
-
- // Tuneable parameters for animation
- private static final int WAVE_ANIMATION_DURATION = 1000;
- private static final int RETURN_TO_HOME_DELAY = 1200;
- private static final int RETURN_TO_HOME_DURATION = 200;
- private static final int HIDE_ANIMATION_DELAY = 200;
- private static final int HIDE_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DURATION = 200;
- private static final int SHOW_ANIMATION_DELAY = 50;
- private static final int INITIAL_SHOW_HANDLE_DURATION = 200;
- private static final int REVEAL_GLOW_DELAY = 0;
- private static final int REVEAL_GLOW_DURATION = 0;
-
- private static final float TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.3f;
- private static final float TARGET_SCALE_EXPANDED = 1.0f;
- private static final float TARGET_SCALE_COLLAPSED = 0.8f;
- private static final float RING_SCALE_EXPANDED = 1.0f;
- private static final float RING_SCALE_COLLAPSED = 0.5f;
-
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
-
- private ArrayList<TargetDrawable> mTargetDrawables = new ArrayList<TargetDrawable>();
- private AnimationBundle mWaveAnimations = new AnimationBundle();
- private AnimationBundle mTargetAnimations = new AnimationBundle();
- private AnimationBundle mGlowAnimations = new AnimationBundle();
- private ArrayList<String> mTargetDescriptions;
- private ArrayList<String> mDirectionDescriptions;
- private OnTriggerListener mOnTriggerListener;
- private TargetDrawable mHandleDrawable;
- private TargetDrawable mOuterRing;
- private Vibrator mVibrator;
-
- private int mFeedbackCount = 3;
- private int mVibrationDuration = 0;
- private int mGrabbedState;
- private int mActiveTarget = -1;
- private float mGlowRadius;
- private float mWaveCenterX;
- private float mWaveCenterY;
- private int mMaxTargetHeight;
- private int mMaxTargetWidth;
- private float mRingScaleFactor = 1f;
- private boolean mAllowScaling;
-
- private float mOuterRadius = 0.0f;
- private float mSnapMargin = 0.0f;
- private float mFirstItemOffset = 0.0f;
- private boolean mMagneticTargets = false;
- private boolean mDragging;
- private int mNewTargetResources;
-
- private class AnimationBundle extends ArrayList<Tweener> {
- private static final long serialVersionUID = 0xA84D78726F127468L;
- private boolean mSuspended;
-
- public void start() {
- if (mSuspended) return; // ignore attempts to start animations
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.start();
- }
- }
-
- public void cancel() {
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.cancel();
- }
- clear();
- }
-
- public void stop() {
- final int count = size();
- for (int i = 0; i < count; i++) {
- Tweener anim = get(i);
- anim.animator.end();
- }
- clear();
- }
-
- public void setSuspended(boolean suspend) {
- mSuspended = suspend;
- }
- };
-
- private AnimatorListener mResetListener = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
- dispatchOnFinishFinalAnimation();
- }
- };
-
- private AnimatorListener mResetListenerWithPing = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- ping();
- switchToState(STATE_IDLE, mWaveCenterX, mWaveCenterY);
- dispatchOnFinishFinalAnimation();
- }
- };
-
- private AnimatorUpdateListener mUpdateListener = new AnimatorUpdateListener() {
- public void onAnimationUpdate(ValueAnimator animation) {
- invalidate();
- }
- };
-
- private boolean mAnimatingTargets;
- private AnimatorListener mTargetUpdateListener = new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- if (mNewTargetResources != 0) {
- internalSetTargetResources(mNewTargetResources);
- mNewTargetResources = 0;
- hideTargets(false, false);
- }
- mAnimatingTargets = false;
- }
- };
- private int mTargetResourceId;
- private int mTargetDescriptionsResourceId;
- private int mDirectionDescriptionsResourceId;
- private boolean mAlwaysTrackFinger;
- private int mHorizontalInset;
- private int mVerticalInset;
- private int mGravity = Gravity.TOP;
- private boolean mInitialLayout = true;
- private Tweener mBackgroundAnimator;
- private PointCloud mPointCloud;
- private float mInnerRadius;
- private int mPointerId;
-
- public GlowPadView(Context context) {
- this(context, null);
- }
-
- public GlowPadView(Context context, AttributeSet attrs) {
- super(context, attrs);
- Resources res = context.getResources();
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GlowPadView);
- mInnerRadius = a.getDimension(R.styleable.GlowPadView_innerRadius, mInnerRadius);
- mOuterRadius = a.getDimension(R.styleable.GlowPadView_outerRadius, mOuterRadius);
- mSnapMargin = a.getDimension(R.styleable.GlowPadView_snapMargin, mSnapMargin);
- mFirstItemOffset = (float) Math.toRadians(
- a.getFloat(R.styleable.GlowPadView_firstItemOffset,
- (float) Math.toDegrees(mFirstItemOffset)));
- mVibrationDuration = a.getInt(R.styleable.GlowPadView_vibrationDuration,
- mVibrationDuration);
- mFeedbackCount = a.getInt(R.styleable.GlowPadView_feedbackCount,
- mFeedbackCount);
- mAllowScaling = a.getBoolean(R.styleable.GlowPadView_allowScaling, false);
- TypedValue handle = a.peekValue(R.styleable.GlowPadView_handleDrawable);
- mHandleDrawable = new TargetDrawable(res, handle != null ? handle.resourceId : 0);
- mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
- mOuterRing = new TargetDrawable(res,
- getResourceId(a, R.styleable.GlowPadView_outerRingDrawable));
-
- mAlwaysTrackFinger = a.getBoolean(R.styleable.GlowPadView_alwaysTrackFinger, false);
- mMagneticTargets = a.getBoolean(R.styleable.GlowPadView_magneticTargets, mMagneticTargets);
-
- int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable);
- Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null;
- mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f);
-
- mPointCloud = new PointCloud(pointDrawable);
- mPointCloud.makePointCloud(mInnerRadius, mOuterRadius);
- mPointCloud.glowManager.setRadius(mGlowRadius);
-
- TypedValue outValue = new TypedValue();
-
- // Read array of target drawables
- if (a.getValue(R.styleable.GlowPadView_targetDrawables, outValue)) {
- internalSetTargetResources(outValue.resourceId);
- }
- if (mTargetDrawables == null || mTargetDrawables.size() == 0) {
- throw new IllegalStateException("Must specify at least one target drawable");
- }
-
- // Read array of target descriptions
- if (a.getValue(R.styleable.GlowPadView_targetDescriptions, outValue)) {
- final int resourceId = outValue.resourceId;
- if (resourceId == 0) {
- throw new IllegalStateException("Must specify target descriptions");
- }
- setTargetDescriptionsResourceId(resourceId);
- }
-
- // Read array of direction descriptions
- if (a.getValue(R.styleable.GlowPadView_directionDescriptions, outValue)) {
- final int resourceId = outValue.resourceId;
- if (resourceId == 0) {
- throw new IllegalStateException("Must specify direction descriptions");
- }
- setDirectionDescriptionsResourceId(resourceId);
- }
-
- mGravity = a.getInt(R.styleable.GlowPadView_gravity, Gravity.TOP);
-
- a.recycle();
-
- setVibrateEnabled(mVibrationDuration > 0);
-
- assignDefaultsIfNeeded();
- }
-
- private int getResourceId(TypedArray a, int id) {
- TypedValue tv = a.peekValue(id);
- return tv == null ? 0 : tv.resourceId;
- }
-
- private void dump() {
- Log.v(TAG, "Outer Radius = " + mOuterRadius);
- Log.v(TAG, "SnapMargin = " + mSnapMargin);
- Log.v(TAG, "FeedbackCount = " + mFeedbackCount);
- Log.v(TAG, "VibrationDuration = " + mVibrationDuration);
- Log.v(TAG, "GlowRadius = " + mGlowRadius);
- Log.v(TAG, "WaveCenterX = " + mWaveCenterX);
- Log.v(TAG, "WaveCenterY = " + mWaveCenterY);
- }
-
- public void suspendAnimations() {
- mWaveAnimations.setSuspended(true);
- mTargetAnimations.setSuspended(true);
- mGlowAnimations.setSuspended(true);
- }
-
- public void resumeAnimations() {
- mWaveAnimations.setSuspended(false);
- mTargetAnimations.setSuspended(false);
- mGlowAnimations.setSuspended(false);
- mWaveAnimations.start();
- mTargetAnimations.start();
- mGlowAnimations.start();
- }
-
- @Override
- protected int getSuggestedMinimumWidth() {
- // View should be large enough to contain the background + handle and
- // target drawable on either edge.
- return (int) (Math.max(mOuterRing.getWidth(), 2 * mOuterRadius) + mMaxTargetWidth);
- }
-
- @Override
- protected int getSuggestedMinimumHeight() {
- // View should be large enough to contain the unlock ring + target and
- // target drawable on either edge
- return (int) (Math.max(mOuterRing.getHeight(), 2 * mOuterRadius) + mMaxTargetHeight);
- }
-
- /**
- * This gets the suggested width accounting for the ring's scale factor.
- */
- protected int getScaledSuggestedMinimumWidth() {
- return (int) (mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius)
- + mMaxTargetWidth);
- }
-
- /**
- * This gets the suggested height accounting for the ring's scale factor.
- */
- protected int getScaledSuggestedMinimumHeight() {
- return (int) (mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius)
- + mMaxTargetHeight);
- }
-
- private int resolveMeasured(int measureSpec, int desired)
- {
- int result = 0;
- int specSize = MeasureSpec.getSize(measureSpec);
- switch (MeasureSpec.getMode(measureSpec)) {
- case MeasureSpec.UNSPECIFIED:
- result = desired;
- break;
- case MeasureSpec.AT_MOST:
- result = Math.min(specSize, desired);
- break;
- case MeasureSpec.EXACTLY:
- default:
- result = specSize;
- }
- return result;
- }
-
- private void switchToState(int state, float x, float y) {
- switch (state) {
- case STATE_IDLE:
- deactivateTargets();
- hideGlow(0, 0, 0.0f, null);
- startBackgroundAnimation(0, 0.0f);
- mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
- mHandleDrawable.setAlpha(1.0f);
- break;
-
- case STATE_START:
- startBackgroundAnimation(0, 0.0f);
- break;
-
- case STATE_FIRST_TOUCH:
- mHandleDrawable.setAlpha(0.0f);
- deactivateTargets();
- showTargets(true);
- startBackgroundAnimation(INITIAL_SHOW_HANDLE_DURATION, 1.0f);
- setGrabbedState(OnTriggerListener.CENTER_HANDLE);
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- announceTargets();
- }
- break;
-
- case STATE_TRACKING:
- mHandleDrawable.setAlpha(0.0f);
- showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 1.0f, null);
- break;
-
- case STATE_SNAP:
- // TODO: Add transition states (see list_selector_background_transition.xml)
- mHandleDrawable.setAlpha(0.0f);
- showGlow(REVEAL_GLOW_DURATION , REVEAL_GLOW_DELAY, 0.0f, null);
- break;
-
- case STATE_FINISH:
- doFinish();
- break;
- }
- }
-
- private void showGlow(int duration, int delay, float finalAlpha,
- AnimatorListener finishListener) {
- mGlowAnimations.cancel();
- mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
- "ease", Ease.Cubic.easeIn,
- "delay", delay,
- "alpha", finalAlpha,
- "onUpdate", mUpdateListener,
- "onComplete", finishListener));
- mGlowAnimations.start();
- }
-
- private void hideGlow(int duration, int delay, float finalAlpha,
- AnimatorListener finishListener) {
- mGlowAnimations.cancel();
- mGlowAnimations.add(Tweener.to(mPointCloud.glowManager, duration,
- "ease", Ease.Quart.easeOut,
- "delay", delay,
- "alpha", finalAlpha,
- "x", 0.0f,
- "y", 0.0f,
- "onUpdate", mUpdateListener,
- "onComplete", finishListener));
- mGlowAnimations.start();
- }
-
- private void deactivateTargets() {
- final int count = mTargetDrawables.size();
- for (int i = 0; i < count; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- }
- mActiveTarget = -1;
- }
-
- /**
- * Dispatches a trigger event to listener. Ignored if a listener is not set.
- * @param whichTarget the target that was triggered.
- */
- private void dispatchTriggerEvent(int whichTarget) {
- vibrate();
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onTrigger(this, whichTarget);
- }
- }
-
- private void dispatchOnFinishFinalAnimation() {
- if (mOnTriggerListener != null) {
- mOnTriggerListener.onFinishFinalAnimation();
- }
- }
-
- private void doFinish() {
- final int activeTarget = mActiveTarget;
- final boolean targetHit = activeTarget != -1;
-
- if (targetHit) {
- if (DEBUG) Log.v(TAG, "Finish with target hit = " + targetHit);
-
- highlightSelected(activeTarget);
-
- // Inform listener of any active targets. Typically only one will be active.
- hideGlow(RETURN_TO_HOME_DURATION, RETURN_TO_HOME_DELAY, 0.0f, mResetListener);
- dispatchTriggerEvent(activeTarget);
- if (!mAlwaysTrackFinger) {
- // Force ring and targets to finish animation to final expanded state
- mTargetAnimations.stop();
- }
- } else {
- // Animate handle back to the center based on current state.
- hideGlow(HIDE_ANIMATION_DURATION, 0, 0.0f, mResetListenerWithPing);
- hideTargets(true, false);
- }
-
- setGrabbedState(OnTriggerListener.NO_HANDLE);
- }
-
- private void highlightSelected(int activeTarget) {
- // Highlight the given target and fade others
- mTargetDrawables.get(activeTarget).setState(TargetDrawable.STATE_ACTIVE);
- hideUnselected(activeTarget);
- }
-
- private void hideUnselected(int active) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- if (i != active) {
- mTargetDrawables.get(i).setAlpha(0.0f);
- }
- }
- }
-
- private void hideTargets(boolean animate, boolean expanded) {
- mTargetAnimations.cancel();
- // Note: these animations should complete at the same time so that we can swap out
- // the target assets asynchronously from the setTargetResources() call.
- mAnimatingTargets = animate;
- final int duration = animate ? HIDE_ANIMATION_DURATION : 0;
- final int delay = animate ? HIDE_ANIMATION_DELAY : 0;
-
- final float targetScale = expanded ?
- TARGET_SCALE_EXPANDED : TARGET_SCALE_COLLAPSED;
- final int length = mTargetDrawables.size();
- final TimeInterpolator interpolator = Ease.Cubic.easeOut;
- for (int i = 0; i < length; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- mTargetAnimations.add(Tweener.to(target, duration,
- "ease", interpolator,
- "alpha", 0.0f,
- "scaleX", targetScale,
- "scaleY", targetScale,
- "delay", delay,
- "onUpdate", mUpdateListener));
- }
-
- float ringScaleTarget = expanded ?
- RING_SCALE_EXPANDED : RING_SCALE_COLLAPSED;
- ringScaleTarget *= mRingScaleFactor;
- mTargetAnimations.add(Tweener.to(mOuterRing, duration,
- "ease", interpolator,
- "alpha", 0.0f,
- "scaleX", ringScaleTarget,
- "scaleY", ringScaleTarget,
- "delay", delay,
- "onUpdate", mUpdateListener,
- "onComplete", mTargetUpdateListener));
-
- mTargetAnimations.start();
- }
-
- private void showTargets(boolean animate) {
- mTargetAnimations.stop();
- mAnimatingTargets = animate;
- final int delay = animate ? SHOW_ANIMATION_DELAY : 0;
- final int duration = animate ? SHOW_ANIMATION_DURATION : 0;
- final int length = mTargetDrawables.size();
- for (int i = 0; i < length; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- target.setState(TargetDrawable.STATE_INACTIVE);
- mTargetAnimations.add(Tweener.to(target, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 1.0f,
- "scaleX", 1.0f,
- "scaleY", 1.0f,
- "delay", delay,
- "onUpdate", mUpdateListener));
- }
-
- float ringScale = mRingScaleFactor * RING_SCALE_EXPANDED;
- mTargetAnimations.add(Tweener.to(mOuterRing, duration,
- "ease", Ease.Cubic.easeOut,
- "alpha", 1.0f,
- "scaleX", ringScale,
- "scaleY", ringScale,
- "delay", delay,
- "onUpdate", mUpdateListener,
- "onComplete", mTargetUpdateListener));
-
- mTargetAnimations.start();
- }
-
- private void vibrate() {
- final boolean hapticEnabled = Settings.System.getIntForUser(
- mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 1,
- UserHandle.USER_CURRENT) != 0;
- if (mVibrator != null && hapticEnabled) {
- mVibrator.vibrate(mVibrationDuration, VIBRATION_ATTRIBUTES);
- }
- }
-
- private ArrayList<TargetDrawable> loadDrawableArray(int resourceId) {
- Resources res = getContext().getResources();
- TypedArray array = res.obtainTypedArray(resourceId);
- final int count = array.length();
- ArrayList<TargetDrawable> drawables = new ArrayList<TargetDrawable>(count);
- for (int i = 0; i < count; i++) {
- TypedValue value = array.peekValue(i);
- TargetDrawable target = new TargetDrawable(res, value != null ? value.resourceId : 0);
- drawables.add(target);
- }
- array.recycle();
- return drawables;
- }
-
- private void internalSetTargetResources(int resourceId) {
- final ArrayList<TargetDrawable> targets = loadDrawableArray(resourceId);
- mTargetDrawables = targets;
- mTargetResourceId = resourceId;
-
- int maxWidth = mHandleDrawable.getWidth();
- int maxHeight = mHandleDrawable.getHeight();
- final int count = targets.size();
- for (int i = 0; i < count; i++) {
- TargetDrawable target = targets.get(i);
- maxWidth = Math.max(maxWidth, target.getWidth());
- maxHeight = Math.max(maxHeight, target.getHeight());
- }
- if (mMaxTargetWidth != maxWidth || mMaxTargetHeight != maxHeight) {
- mMaxTargetWidth = maxWidth;
- mMaxTargetHeight = maxHeight;
- requestLayout(); // required to resize layout and call updateTargetPositions()
- } else {
- updateTargetPositions(mWaveCenterX, mWaveCenterY);
- updatePointCloudPosition(mWaveCenterX, mWaveCenterY);
- }
- }
-
- /**
- * Loads an array of drawables from the given resourceId.
- *
- * @param resourceId
- */
- public void setTargetResources(int resourceId) {
- if (mAnimatingTargets) {
- // postpone this change until we return to the initial state
- mNewTargetResources = resourceId;
- } else {
- internalSetTargetResources(resourceId);
- }
- }
-
- public int getTargetResourceId() {
- return mTargetResourceId;
- }
-
- /**
- * Sets the resource id specifying the target descriptions for accessibility.
- *
- * @param resourceId The resource id.
- */
- public void setTargetDescriptionsResourceId(int resourceId) {
- mTargetDescriptionsResourceId = resourceId;
- if (mTargetDescriptions != null) {
- mTargetDescriptions.clear();
- }
- }
-
- /**
- * Gets the resource id specifying the target descriptions for accessibility.
- *
- * @return The resource id.
- */
- public int getTargetDescriptionsResourceId() {
- return mTargetDescriptionsResourceId;
- }
-
- /**
- * Sets the resource id specifying the target direction descriptions for accessibility.
- *
- * @param resourceId The resource id.
- */
- public void setDirectionDescriptionsResourceId(int resourceId) {
- mDirectionDescriptionsResourceId = resourceId;
- if (mDirectionDescriptions != null) {
- mDirectionDescriptions.clear();
- }
- }
-
- /**
- * Gets the resource id specifying the target direction descriptions.
- *
- * @return The resource id.
- */
- public int getDirectionDescriptionsResourceId() {
- return mDirectionDescriptionsResourceId;
- }
-
- /**
- * Enable or disable vibrate on touch.
- *
- * @param enabled
- */
- public void setVibrateEnabled(boolean enabled) {
- if (enabled && mVibrator == null) {
- mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
- } else {
- mVibrator = null;
- }
- }
-
- /**
- * Starts wave animation.
- *
- */
- public void ping() {
- if (mFeedbackCount > 0) {
- boolean doWaveAnimation = true;
- final AnimationBundle waveAnimations = mWaveAnimations;
-
- // Don't do a wave if there's already one in progress
- if (waveAnimations.size() > 0 && waveAnimations.get(0).animator.isRunning()) {
- long t = waveAnimations.get(0).animator.getCurrentPlayTime();
- if (t < WAVE_ANIMATION_DURATION/2) {
- doWaveAnimation = false;
- }
- }
-
- if (doWaveAnimation) {
- startWaveAnimation();
- }
- }
- }
-
- private void stopAndHideWaveAnimation() {
- mWaveAnimations.cancel();
- mPointCloud.waveManager.setAlpha(0.0f);
- }
-
- private void startWaveAnimation() {
- mWaveAnimations.cancel();
- mPointCloud.waveManager.setAlpha(1.0f);
- mPointCloud.waveManager.setRadius(mHandleDrawable.getWidth()/2.0f);
- mWaveAnimations.add(Tweener.to(mPointCloud.waveManager, WAVE_ANIMATION_DURATION,
- "ease", Ease.Quad.easeOut,
- "delay", 0,
- "radius", 2.0f * mOuterRadius,
- "onUpdate", mUpdateListener,
- "onComplete",
- new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animator) {
- mPointCloud.waveManager.setRadius(0.0f);
- mPointCloud.waveManager.setAlpha(0.0f);
- }
- }));
- mWaveAnimations.start();
- }
-
- /**
- * Resets the widget to default state and cancels all animation. If animate is 'true', will
- * animate objects into place. Otherwise, objects will snap back to place.
- *
- * @param animate
- */
- public void reset(boolean animate) {
- mGlowAnimations.stop();
- mTargetAnimations.stop();
- startBackgroundAnimation(0, 0.0f);
- stopAndHideWaveAnimation();
- hideTargets(animate, false);
- hideGlow(0, 0, 0.0f, null);
- Tweener.reset();
- }
-
- private void startBackgroundAnimation(int duration, float alpha) {
- final Drawable background = getBackground();
- if (mAlwaysTrackFinger && background != null) {
- if (mBackgroundAnimator != null) {
- mBackgroundAnimator.animator.cancel();
- }
- mBackgroundAnimator = Tweener.to(background, duration,
- "ease", Ease.Cubic.easeIn,
- "alpha", (int)(255.0f * alpha),
- "delay", SHOW_ANIMATION_DELAY);
- mBackgroundAnimator.animator.start();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getActionMasked();
- boolean handled = false;
- switch (action) {
- case MotionEvent.ACTION_POINTER_DOWN:
- case MotionEvent.ACTION_DOWN:
- if (DEBUG) Log.v(TAG, "*** DOWN ***");
- handleDown(event);
- handleMove(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (DEBUG) Log.v(TAG, "*** MOVE ***");
- handleMove(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- case MotionEvent.ACTION_UP:
- if (DEBUG) Log.v(TAG, "*** UP ***");
- handleMove(event);
- handleUp(event);
- handled = true;
- break;
-
- case MotionEvent.ACTION_CANCEL:
- if (DEBUG) Log.v(TAG, "*** CANCEL ***");
- handleMove(event);
- handleCancel(event);
- handled = true;
- break;
-
- }
- invalidate();
- return handled ? true : super.onTouchEvent(event);
- }
-
- private void updateGlowPosition(float x, float y) {
- float dx = x - mOuterRing.getX();
- float dy = y - mOuterRing.getY();
- dx *= 1f / mRingScaleFactor;
- dy *= 1f / mRingScaleFactor;
- mPointCloud.glowManager.setX(mOuterRing.getX() + dx);
- mPointCloud.glowManager.setY(mOuterRing.getY() + dy);
- }
-
- private void handleDown(MotionEvent event) {
- int actionIndex = event.getActionIndex();
- float eventX = event.getX(actionIndex);
- float eventY = event.getY(actionIndex);
- switchToState(STATE_START, eventX, eventY);
- if (!trySwitchToFirstTouchState(eventX, eventY)) {
- mDragging = false;
- } else {
- mPointerId = event.getPointerId(actionIndex);
- updateGlowPosition(eventX, eventY);
- }
- }
-
- private void handleUp(MotionEvent event) {
- if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
- int actionIndex = event.getActionIndex();
- if (event.getPointerId(actionIndex) == mPointerId) {
- switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
- }
- }
-
- private void handleCancel(MotionEvent event) {
- if (DEBUG && mDragging) Log.v(TAG, "** Handle CANCEL");
-
- // Drop the active target if canceled.
- mActiveTarget = -1;
-
- int actionIndex = event.findPointerIndex(mPointerId);
- actionIndex = actionIndex == -1 ? 0 : actionIndex;
- switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
- }
-
- private void handleMove(MotionEvent event) {
- int activeTarget = -1;
- final int historySize = event.getHistorySize();
- ArrayList<TargetDrawable> targets = mTargetDrawables;
- int ntargets = targets.size();
- float x = 0.0f;
- float y = 0.0f;
- float activeAngle = 0.0f;
- int actionIndex = event.findPointerIndex(mPointerId);
-
- if (actionIndex == -1) {
- return; // no data for this pointer
- }
-
- for (int k = 0; k < historySize + 1; k++) {
- float eventX = k < historySize ? event.getHistoricalX(actionIndex, k)
- : event.getX(actionIndex);
- float eventY = k < historySize ? event.getHistoricalY(actionIndex, k)
- : event.getY(actionIndex);
- // tx and ty are relative to wave center
- float tx = eventX - mWaveCenterX;
- float ty = eventY - mWaveCenterY;
- float touchRadius = (float) Math.hypot(tx, ty);
- final float scale = touchRadius > mOuterRadius ? mOuterRadius / touchRadius : 1.0f;
- float limitX = tx * scale;
- float limitY = ty * scale;
- double angleRad = Math.atan2(-ty, tx);
-
- if (!mDragging) {
- trySwitchToFirstTouchState(eventX, eventY);
- }
-
- if (mDragging) {
- // For multiple targets, snap to the one that matches
- final float snapRadius = mRingScaleFactor * mOuterRadius - mSnapMargin;
- final float snapDistance2 = snapRadius * snapRadius;
- // Find first target in range
- for (int i = 0; i < ntargets; i++) {
- TargetDrawable target = targets.get(i);
-
- double targetMinRad = mFirstItemOffset + (i - 0.5) * 2 * Math.PI / ntargets;
- double targetMaxRad = mFirstItemOffset + (i + 0.5) * 2 * Math.PI / ntargets;
- if (target.isEnabled()) {
- boolean angleMatches =
- (angleRad > targetMinRad && angleRad <= targetMaxRad) ||
- (angleRad + 2 * Math.PI > targetMinRad &&
- angleRad + 2 * Math.PI <= targetMaxRad) ||
- (angleRad - 2 * Math.PI > targetMinRad &&
- angleRad - 2 * Math.PI <= targetMaxRad);
- if (angleMatches && (dist2(tx, ty) > snapDistance2)) {
- activeTarget = i;
- activeAngle = (float) -angleRad;
- }
- }
- }
- }
- x = limitX;
- y = limitY;
- }
-
- if (!mDragging) {
- return;
- }
-
- if (activeTarget != -1) {
- switchToState(STATE_SNAP, x,y);
- updateGlowPosition(x, y);
- } else {
- switchToState(STATE_TRACKING, x, y);
- updateGlowPosition(x, y);
- }
-
- if (mActiveTarget != activeTarget) {
- // Defocus the old target
- if (mActiveTarget != -1) {
- TargetDrawable target = targets.get(mActiveTarget);
- if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
- target.setState(TargetDrawable.STATE_INACTIVE);
- }
- if (mMagneticTargets) {
- updateTargetPosition(mActiveTarget, mWaveCenterX, mWaveCenterY);
- }
- }
- // Focus the new target
- if (activeTarget != -1) {
- TargetDrawable target = targets.get(activeTarget);
- if (target.hasState(TargetDrawable.STATE_FOCUSED)) {
- target.setState(TargetDrawable.STATE_FOCUSED);
- }
- if (mMagneticTargets) {
- updateTargetPosition(activeTarget, mWaveCenterX, mWaveCenterY, activeAngle);
- }
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- String targetContentDescription = getTargetDescription(activeTarget);
- announceForAccessibility(targetContentDescription);
- }
- }
- }
- mActiveTarget = activeTarget;
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_HOVER_ENTER:
- event.setAction(MotionEvent.ACTION_DOWN);
- break;
- case MotionEvent.ACTION_HOVER_MOVE:
- event.setAction(MotionEvent.ACTION_MOVE);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- event.setAction(MotionEvent.ACTION_UP);
- break;
- }
- onTouchEvent(event);
- event.setAction(action);
- }
- super.onHoverEvent(event);
- return true;
- }
-
- /**
- * Sets the current grabbed state, and dispatches a grabbed state change
- * event to our listener.
- */
- private void setGrabbedState(int newState) {
- if (newState != mGrabbedState) {
- if (newState != OnTriggerListener.NO_HANDLE) {
- vibrate();
- }
- mGrabbedState = newState;
- if (mOnTriggerListener != null) {
- if (newState == OnTriggerListener.NO_HANDLE) {
- mOnTriggerListener.onReleased(this, OnTriggerListener.CENTER_HANDLE);
- } else {
- mOnTriggerListener.onGrabbed(this, OnTriggerListener.CENTER_HANDLE);
- }
- mOnTriggerListener.onGrabbedStateChange(this, newState);
- }
- }
- }
-
- private boolean trySwitchToFirstTouchState(float x, float y) {
- final float tx = x - mWaveCenterX;
- final float ty = y - mWaveCenterY;
- if (mAlwaysTrackFinger || dist2(tx,ty) <= getScaledGlowRadiusSquared()) {
- if (DEBUG) Log.v(TAG, "** Handle HIT");
- switchToState(STATE_FIRST_TOUCH, x, y);
- updateGlowPosition(tx, ty);
- mDragging = true;
- return true;
- }
- return false;
- }
-
- private void assignDefaultsIfNeeded() {
- if (mOuterRadius == 0.0f) {
- mOuterRadius = Math.max(mOuterRing.getWidth(), mOuterRing.getHeight())/2.0f;
- }
- if (mSnapMargin == 0.0f) {
- mSnapMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- SNAP_MARGIN_DEFAULT, getContext().getResources().getDisplayMetrics());
- }
- if (mInnerRadius == 0.0f) {
- mInnerRadius = mHandleDrawable.getWidth() / 10.0f;
- }
- }
-
- private void computeInsets(int dx, int dy) {
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- mHorizontalInset = 0;
- break;
- case Gravity.RIGHT:
- mHorizontalInset = dx;
- break;
- case Gravity.CENTER_HORIZONTAL:
- default:
- mHorizontalInset = dx / 2;
- break;
- }
- switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
- case Gravity.TOP:
- mVerticalInset = 0;
- break;
- case Gravity.BOTTOM:
- mVerticalInset = dy;
- break;
- case Gravity.CENTER_VERTICAL:
- default:
- mVerticalInset = dy / 2;
- break;
- }
- }
-
- /**
- * Given the desired width and height of the ring and the allocated width and height, compute
- * how much we need to scale the ring.
- */
- private float computeScaleFactor(int desiredWidth, int desiredHeight,
- int actualWidth, int actualHeight) {
-
- // Return unity if scaling is not allowed.
- if (!mAllowScaling) return 1f;
-
- final int layoutDirection = getLayoutDirection();
- final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-
- float scaleX = 1f;
- float scaleY = 1f;
-
- // We use the gravity as a cue for whether we want to scale on a particular axis.
- // We only scale to fit horizontally if we're not pinned to the left or right. Likewise,
- // we only scale to fit vertically if we're not pinned to the top or bottom. In these
- // cases, we want the ring to hang off the side or top/bottom, respectively.
- switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- case Gravity.RIGHT:
- break;
- case Gravity.CENTER_HORIZONTAL:
- default:
- if (desiredWidth > actualWidth) {
- scaleX = (1f * actualWidth - mMaxTargetWidth) /
- (desiredWidth - mMaxTargetWidth);
- }
- break;
- }
- switch (absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK) {
- case Gravity.TOP:
- case Gravity.BOTTOM:
- break;
- case Gravity.CENTER_VERTICAL:
- default:
- if (desiredHeight > actualHeight) {
- scaleY = (1f * actualHeight - mMaxTargetHeight) /
- (desiredHeight - mMaxTargetHeight);
- }
- break;
- }
- return Math.min(scaleX, scaleY);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int minimumWidth = getSuggestedMinimumWidth();
- final int minimumHeight = getSuggestedMinimumHeight();
- int computedWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
- int computedHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
-
- mRingScaleFactor = computeScaleFactor(minimumWidth, minimumHeight,
- computedWidth, computedHeight);
-
- int scaledWidth = getScaledSuggestedMinimumWidth();
- int scaledHeight = getScaledSuggestedMinimumHeight();
-
- computeInsets(computedWidth - scaledWidth, computedHeight - scaledHeight);
- setMeasuredDimension(computedWidth, computedHeight);
- }
-
- private float getRingWidth() {
- return mRingScaleFactor * Math.max(mOuterRing.getWidth(), 2 * mOuterRadius);
- }
-
- private float getRingHeight() {
- return mRingScaleFactor * Math.max(mOuterRing.getHeight(), 2 * mOuterRadius);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- final int width = right - left;
- final int height = bottom - top;
-
- // Target placement width/height. This puts the targets on the greater of the ring
- // width or the specified outer radius.
- final float placementWidth = getRingWidth();
- final float placementHeight = getRingHeight();
- float newWaveCenterX = mHorizontalInset
- + Math.max(width, mMaxTargetWidth + placementWidth) / 2;
- float newWaveCenterY = mVerticalInset
- + Math.max(height, + mMaxTargetHeight + placementHeight) / 2;
-
- if (mInitialLayout) {
- stopAndHideWaveAnimation();
- hideTargets(false, false);
- mInitialLayout = false;
- }
-
- mOuterRing.setPositionX(newWaveCenterX);
- mOuterRing.setPositionY(newWaveCenterY);
-
- mPointCloud.setScale(mRingScaleFactor);
-
- mHandleDrawable.setPositionX(newWaveCenterX);
- mHandleDrawable.setPositionY(newWaveCenterY);
-
- updateTargetPositions(newWaveCenterX, newWaveCenterY);
- updatePointCloudPosition(newWaveCenterX, newWaveCenterY);
- updateGlowPosition(newWaveCenterX, newWaveCenterY);
-
- mWaveCenterX = newWaveCenterX;
- mWaveCenterY = newWaveCenterY;
-
- if (DEBUG) dump();
- }
-
- private void updateTargetPosition(int i, float centerX, float centerY) {
- final float angle = getAngle(getSliceAngle(), i);
- updateTargetPosition(i, centerX, centerY, angle);
- }
-
- private void updateTargetPosition(int i, float centerX, float centerY, float angle) {
- final float placementRadiusX = getRingWidth() / 2;
- final float placementRadiusY = getRingHeight() / 2;
- if (i >= 0) {
- ArrayList<TargetDrawable> targets = mTargetDrawables;
- final TargetDrawable targetIcon = targets.get(i);
- targetIcon.setPositionX(centerX);
- targetIcon.setPositionY(centerY);
- targetIcon.setX(placementRadiusX * (float) Math.cos(angle));
- targetIcon.setY(placementRadiusY * (float) Math.sin(angle));
- }
- }
-
- private void updateTargetPositions(float centerX, float centerY) {
- updateTargetPositions(centerX, centerY, false);
- }
-
- private void updateTargetPositions(float centerX, float centerY, boolean skipActive) {
- final int size = mTargetDrawables.size();
- final float alpha = getSliceAngle();
- // Reposition the target drawables if the view changed.
- for (int i = 0; i < size; i++) {
- if (!skipActive || i != mActiveTarget) {
- updateTargetPosition(i, centerX, centerY, getAngle(alpha, i));
- }
- }
- }
-
- private float getAngle(float alpha, int i) {
- return mFirstItemOffset + alpha * i;
- }
-
- private float getSliceAngle() {
- return (float) (-2.0f * Math.PI / mTargetDrawables.size());
- }
-
- private void updatePointCloudPosition(float centerX, float centerY) {
- mPointCloud.setCenter(centerX, centerY);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mPointCloud.draw(canvas);
- mOuterRing.draw(canvas);
- final int ntargets = mTargetDrawables.size();
- for (int i = 0; i < ntargets; i++) {
- TargetDrawable target = mTargetDrawables.get(i);
- if (target != null) {
- target.draw(canvas);
- }
- }
- mHandleDrawable.draw(canvas);
- }
-
- public void setOnTriggerListener(OnTriggerListener listener) {
- mOnTriggerListener = listener;
- }
-
- private float square(float d) {
- return d * d;
- }
-
- private float dist2(float dx, float dy) {
- return dx*dx + dy*dy;
- }
-
- private float getScaledGlowRadiusSquared() {
- final float scaledTapRadius;
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- scaledTapRadius = TAP_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mGlowRadius;
- } else {
- scaledTapRadius = mGlowRadius;
- }
- return square(scaledTapRadius);
- }
-
- private void announceTargets() {
- StringBuilder utterance = new StringBuilder();
- final int targetCount = mTargetDrawables.size();
- for (int i = 0; i < targetCount; i++) {
- String targetDescription = getTargetDescription(i);
- String directionDescription = getDirectionDescription(i);
- if (!TextUtils.isEmpty(targetDescription)
- && !TextUtils.isEmpty(directionDescription)) {
- String text = String.format(directionDescription, targetDescription);
- utterance.append(text);
- }
- }
- if (utterance.length() > 0) {
- announceForAccessibility(utterance.toString());
- }
- }
-
- private String getTargetDescription(int index) {
- if (mTargetDescriptions == null || mTargetDescriptions.isEmpty()) {
- mTargetDescriptions = loadDescriptions(mTargetDescriptionsResourceId);
- if (mTargetDrawables.size() != mTargetDescriptions.size()) {
- Log.w(TAG, "The number of target drawables must be"
- + " equal to the number of target descriptions.");
- return null;
- }
- }
- return mTargetDescriptions.get(index);
- }
-
- private String getDirectionDescription(int index) {
- if (mDirectionDescriptions == null || mDirectionDescriptions.isEmpty()) {
- mDirectionDescriptions = loadDescriptions(mDirectionDescriptionsResourceId);
- if (mTargetDrawables.size() != mDirectionDescriptions.size()) {
- Log.w(TAG, "The number of target drawables must be"
- + " equal to the number of direction descriptions.");
- return null;
- }
- }
- return mDirectionDescriptions.get(index);
- }
-
- private ArrayList<String> loadDescriptions(int resourceId) {
- TypedArray array = getContext().getResources().obtainTypedArray(resourceId);
- final int count = array.length();
- ArrayList<String> targetContentDescriptions = new ArrayList<String>(count);
- for (int i = 0; i < count; i++) {
- String contentDescription = array.getString(i);
- targetContentDescriptions.add(contentDescription);
- }
- array.recycle();
- return targetContentDescriptions;
- }
-
- public int getResourceIdForTarget(int index) {
- final TargetDrawable drawable = mTargetDrawables.get(index);
- return drawable == null ? 0 : drawable.getResourceId();
- }
-
- public void setEnableTarget(int resourceId, boolean enabled) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.getResourceId() == resourceId) {
- target.setEnabled(enabled);
- break; // should never be more than one match
- }
- }
- }
-
- /**
- * Gets the position of a target in the array that matches the given resource.
- * @param resourceId
- * @return the index or -1 if not found
- */
- public int getTargetPosition(int resourceId) {
- for (int i = 0; i < mTargetDrawables.size(); i++) {
- final TargetDrawable target = mTargetDrawables.get(i);
- if (target.getResourceId() == resourceId) {
- return i; // should never be more than one match
- }
- }
- return -1;
- }
-
- private boolean replaceTargetDrawables(Resources res, int existingResourceId,
- int newResourceId) {
- if (existingResourceId == 0 || newResourceId == 0) {
- return false;
- }
-
- boolean result = false;
- final ArrayList<TargetDrawable> drawables = mTargetDrawables;
- final int size = drawables.size();
- for (int i = 0; i < size; i++) {
- final TargetDrawable target = drawables.get(i);
- if (target != null && target.getResourceId() == existingResourceId) {
- target.setDrawable(res, newResourceId);
- result = true;
- }
- }
-
- if (result) {
- requestLayout(); // in case any given drawable's size changes
- }
-
- return result;
- }
-
- /**
- * Searches the given package for a resource to use to replace the Drawable on the
- * target with the given resource id
- * @param component of the .apk that contains the resource
- * @param name of the metadata in the .apk
- * @param existingResId the resource id of the target to search for
- * @return true if found in the given package and replaced at least one target Drawables
- */
- public boolean replaceTargetDrawablesIfPresent(ComponentName component, String name,
- int existingResId) {
- if (existingResId == 0) return false;
-
- boolean replaced = false;
- if (component != null) {
- try {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the search icon specified in the activity meta-data
- Bundle metaData = packageManager.getActivityInfo(
- component, PackageManager.GET_META_DATA).metaData;
- if (metaData != null) {
- int iconResId = metaData.getInt(name);
- if (iconResId != 0) {
- Resources res = packageManager.getResourcesForActivity(component);
- replaced = replaceTargetDrawables(res, existingResId, iconResId);
- }
- }
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Failed to swap drawable; "
- + component.flattenToShortString() + " not found", e);
- } catch (Resources.NotFoundException nfe) {
- Log.w(TAG, "Failed to swap drawable from "
- + component.flattenToShortString(), nfe);
- }
- }
- if (!replaced) {
- // Restore the original drawable
- replaceTargetDrawables(mContext.getResources(), existingResId, existingResId);
- }
- return replaced;
- }
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/PointCloud.java b/core/java/com/android/internal/widget/multiwaveview/PointCloud.java
deleted file mode 100644
index 6f26b99..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/PointCloud.java
+++ /dev/null
@@ -1,225 +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 com.android.internal.widget.multiwaveview;
-
-import java.util.ArrayList;
-
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-public class PointCloud {
- private static final float MIN_POINT_SIZE = 2.0f;
- private static final float MAX_POINT_SIZE = 4.0f;
- private static final int INNER_POINTS = 8;
- private static final String TAG = "PointCloud";
- private ArrayList<Point> mPointCloud = new ArrayList<Point>();
- private Drawable mDrawable;
- private float mCenterX;
- private float mCenterY;
- private Paint mPaint;
- private float mScale = 1.0f;
- private static final float PI = (float) Math.PI;
-
- // These allow us to have multiple concurrent animations.
- WaveManager waveManager = new WaveManager();
- GlowManager glowManager = new GlowManager();
- private float mOuterRadius;
-
- public class WaveManager {
- private float radius = 50;
- private float alpha = 0.0f;
-
- public void setRadius(float r) {
- radius = r;
- }
-
- public float getRadius() {
- return radius;
- }
-
- public void setAlpha(float a) {
- alpha = a;
- }
-
- public float getAlpha() {
- return alpha;
- }
- };
-
- public class GlowManager {
- private float x;
- private float y;
- private float radius = 0.0f;
- private float alpha = 0.0f;
-
- public void setX(float x1) {
- x = x1;
- }
-
- public float getX() {
- return x;
- }
-
- public void setY(float y1) {
- y = y1;
- }
-
- public float getY() {
- return y;
- }
-
- public void setAlpha(float a) {
- alpha = a;
- }
-
- public float getAlpha() {
- return alpha;
- }
-
- public void setRadius(float r) {
- radius = r;
- }
-
- public float getRadius() {
- return radius;
- }
- }
-
- class Point {
- float x;
- float y;
- float radius;
-
- public Point(float x2, float y2, float r) {
- x = (float) x2;
- y = (float) y2;
- radius = r;
- }
- }
-
- public PointCloud(Drawable drawable) {
- mPaint = new Paint();
- mPaint.setFilterBitmap(true);
- mPaint.setColor(Color.rgb(255, 255, 255)); // TODO: make configurable
- mPaint.setAntiAlias(true);
- mPaint.setDither(true);
-
- mDrawable = drawable;
- if (mDrawable != null) {
- drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
- }
- }
-
- public void setCenter(float x, float y) {
- mCenterX = x;
- mCenterY = y;
- }
-
- public void makePointCloud(float innerRadius, float outerRadius) {
- if (innerRadius == 0) {
- Log.w(TAG, "Must specify an inner radius");
- return;
- }
- mOuterRadius = outerRadius;
- mPointCloud.clear();
- final float pointAreaRadius = (outerRadius - innerRadius);
- final float ds = (2.0f * PI * innerRadius / INNER_POINTS);
- final int bands = (int) Math.round(pointAreaRadius / ds);
- final float dr = pointAreaRadius / bands;
- float r = innerRadius;
- for (int b = 0; b <= bands; b++, r += dr) {
- float circumference = 2.0f * PI * r;
- final int pointsInBand = (int) (circumference / ds);
- float eta = PI/2.0f;
- float dEta = 2.0f * PI / pointsInBand;
- for (int i = 0; i < pointsInBand; i++) {
- float x = r * (float) Math.cos(eta);
- float y = r * (float) Math.sin(eta);
- eta += dEta;
- mPointCloud.add(new Point(x, y, r));
- }
- }
- }
-
- public void setScale(float scale) {
- mScale = scale;
- }
-
- public float getScale() {
- return mScale;
- }
-
- public int getAlphaForPoint(Point point) {
- // Contribution from positional glow
- float glowDistance = (float) Math.hypot(glowManager.x - point.x, glowManager.y - point.y);
- float glowAlpha = 0.0f;
- if (glowDistance < glowManager.radius) {
- float cosf = (float) Math.cos(PI * 0.25f * glowDistance / glowManager.radius);
- glowAlpha = glowManager.alpha * Math.max(0.0f, (float) Math.pow(cosf, 10.0f));
- }
-
- // Compute contribution from Wave
- float radius = (float) Math.hypot(point.x, point.y);
- float waveAlpha = 0.0f;
- if (radius < waveManager.radius * 2) {
- float distanceToWaveRing = (radius - waveManager.radius);
- float cosf = (float) Math.cos(PI * 0.5f * distanceToWaveRing / waveManager.radius);
- waveAlpha = waveManager.alpha * Math.max(0.0f, (float) Math.pow(cosf, 6.0f));
- }
- return (int) (Math.max(glowAlpha, waveAlpha) * 255);
- }
-
- private float interp(float min, float max, float f) {
- return min + (max - min) * f;
- }
-
- public void draw(Canvas canvas) {
- ArrayList<Point> points = mPointCloud;
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(mScale, mScale, mCenterX, mCenterY);
- for (int i = 0; i < points.size(); i++) {
- Point point = points.get(i);
- final float pointSize = interp(MAX_POINT_SIZE, MIN_POINT_SIZE,
- point.radius / mOuterRadius);
- final float px = point.x + mCenterX;
- final float py = point.y + mCenterY;
- int alpha = getAlphaForPoint(point);
-
- if (alpha == 0) continue;
-
- if (mDrawable != null) {
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- final float cx = mDrawable.getIntrinsicWidth() * 0.5f;
- final float cy = mDrawable.getIntrinsicHeight() * 0.5f;
- final float s = pointSize / MAX_POINT_SIZE;
- canvas.scale(s, s, px, py);
- canvas.translate(px - cx, py - cy);
- mDrawable.setAlpha(alpha);
- mDrawable.draw(canvas);
- canvas.restore();
- } else {
- mPaint.setAlpha(alpha);
- canvas.drawCircle(px, py, pointSize, mPaint);
- }
- }
- canvas.restore();
- }
-
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
deleted file mode 100644
index 5a4c441..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget.multiwaveview;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.util.Log;
-
-public class TargetDrawable {
- private static final String TAG = "TargetDrawable";
- private static final boolean DEBUG = false;
-
- public static final int[] STATE_ACTIVE =
- { android.R.attr.state_enabled, android.R.attr.state_active };
- public static final int[] STATE_INACTIVE =
- { android.R.attr.state_enabled, -android.R.attr.state_active };
- public static final int[] STATE_FOCUSED =
- { android.R.attr.state_enabled, -android.R.attr.state_active,
- android.R.attr.state_focused };
-
- private float mTranslationX = 0.0f;
- private float mTranslationY = 0.0f;
- private float mPositionX = 0.0f;
- private float mPositionY = 0.0f;
- private float mScaleX = 1.0f;
- private float mScaleY = 1.0f;
- private float mAlpha = 1.0f;
- private Drawable mDrawable;
- private boolean mEnabled = true;
- private final int mResourceId;
-
- public TargetDrawable(Resources res, int resId) {
- mResourceId = resId;
- setDrawable(res, resId);
- }
-
- public void setDrawable(Resources res, int resId) {
- // Note we explicitly don't set mResourceId to resId since we allow the drawable to be
- // swapped at runtime and want to re-use the existing resource id for identification.
- Drawable drawable = resId == 0 ? null : res.getDrawable(resId);
- // Mutate the drawable so we can animate shared drawable properties.
- mDrawable = drawable != null ? drawable.mutate() : null;
- resizeDrawables();
- setState(STATE_INACTIVE);
- }
-
- public TargetDrawable(TargetDrawable other) {
- mResourceId = other.mResourceId;
- // Mutate the drawable so we can animate shared drawable properties.
- mDrawable = other.mDrawable != null ? other.mDrawable.mutate() : null;
- resizeDrawables();
- setState(STATE_INACTIVE);
- }
-
- public void setState(int [] state) {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- d.setState(state);
- }
- }
-
- public boolean hasState(int [] state) {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- // TODO: this doesn't seem to work
- return d.getStateDrawableIndex(state) != -1;
- }
- return false;
- }
-
- /**
- * Returns true if the drawable is a StateListDrawable and is in the focused state.
- *
- * @return
- */
- public boolean isActive() {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- int[] states = d.getState();
- for (int i = 0; i < states.length; i++) {
- if (states[i] == android.R.attr.state_focused) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns true if this target is enabled. Typically an enabled target contains a valid
- * drawable in a valid state. Currently all targets with valid drawables are valid.
- *
- * @return
- */
- public boolean isEnabled() {
- return mDrawable != null && mEnabled;
- }
-
- /**
- * Makes drawables in a StateListDrawable all the same dimensions.
- * If not a StateListDrawable, then justs sets the bounds to the intrinsic size of the
- * drawable.
- */
- private void resizeDrawables() {
- if (mDrawable instanceof StateListDrawable) {
- StateListDrawable d = (StateListDrawable) mDrawable;
- int maxWidth = 0;
- int maxHeight = 0;
- for (int i = 0; i < d.getStateCount(); i++) {
- Drawable childDrawable = d.getStateDrawable(i);
- maxWidth = Math.max(maxWidth, childDrawable.getIntrinsicWidth());
- maxHeight = Math.max(maxHeight, childDrawable.getIntrinsicHeight());
- }
- if (DEBUG) Log.v(TAG, "union of childDrawable rects " + d + " to: "
- + maxWidth + "x" + maxHeight);
- d.setBounds(0, 0, maxWidth, maxHeight);
- for (int i = 0; i < d.getStateCount(); i++) {
- Drawable childDrawable = d.getStateDrawable(i);
- if (DEBUG) Log.v(TAG, "sizing drawable " + childDrawable + " to: "
- + maxWidth + "x" + maxHeight);
- childDrawable.setBounds(0, 0, maxWidth, maxHeight);
- }
- } else if (mDrawable != null) {
- mDrawable.setBounds(0, 0,
- mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
- }
- }
-
- public void setX(float x) {
- mTranslationX = x;
- }
-
- public void setY(float y) {
- mTranslationY = y;
- }
-
- public void setScaleX(float x) {
- mScaleX = x;
- }
-
- public void setScaleY(float y) {
- mScaleY = y;
- }
-
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- }
-
- public float getX() {
- return mTranslationX;
- }
-
- public float getY() {
- return mTranslationY;
- }
-
- public float getScaleX() {
- return mScaleX;
- }
-
- public float getScaleY() {
- return mScaleY;
- }
-
- public float getAlpha() {
- return mAlpha;
- }
-
- public void setPositionX(float x) {
- mPositionX = x;
- }
-
- public void setPositionY(float y) {
- mPositionY = y;
- }
-
- public float getPositionX() {
- return mPositionX;
- }
-
- public float getPositionY() {
- return mPositionY;
- }
-
- public int getWidth() {
- return mDrawable != null ? mDrawable.getIntrinsicWidth() : 0;
- }
-
- public int getHeight() {
- return mDrawable != null ? mDrawable.getIntrinsicHeight() : 0;
- }
-
- public void draw(Canvas canvas) {
- if (mDrawable == null || !mEnabled) {
- return;
- }
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(mScaleX, mScaleY, mPositionX, mPositionY);
- canvas.translate(mTranslationX + mPositionX, mTranslationY + mPositionY);
- canvas.translate(-0.5f * getWidth(), -0.5f * getHeight());
- mDrawable.setAlpha((int) Math.round(mAlpha * 255f));
- mDrawable.draw(canvas);
- canvas.restore();
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- public int getResourceId() {
- return mResourceId;
- }
-}
diff --git a/core/java/com/android/internal/widget/multiwaveview/Tweener.java b/core/java/com/android/internal/widget/multiwaveview/Tweener.java
deleted file mode 100644
index d559d9d..0000000
--- a/core/java/com/android/internal/widget/multiwaveview/Tweener.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget.multiwaveview;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map.Entry;
-
-import android.animation.Animator.AnimatorListener;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.util.Log;
-
-class Tweener {
- private static final String TAG = "Tweener";
- private static final boolean DEBUG = false;
-
- ObjectAnimator animator;
- private static HashMap<Object, Tweener> sTweens = new HashMap<Object, Tweener>();
-
- public Tweener(ObjectAnimator anim) {
- animator = anim;
- }
-
- private static void remove(Animator animator) {
- Iterator<Entry<Object, Tweener>> iter = sTweens.entrySet().iterator();
- while (iter.hasNext()) {
- Entry<Object, Tweener> entry = iter.next();
- if (entry.getValue().animator == animator) {
- if (DEBUG) Log.v(TAG, "Removing tweener " + sTweens.get(entry.getKey())
- + " sTweens.size() = " + sTweens.size());
- iter.remove();
- break; // an animator can only be attached to one object
- }
- }
- }
-
- public static Tweener to(Object object, long duration, Object... vars) {
- long delay = 0;
- AnimatorUpdateListener updateListener = null;
- AnimatorListener listener = null;
- TimeInterpolator interpolator = null;
-
- // Iterate through arguments and discover properties to animate
- ArrayList<PropertyValuesHolder> props = new ArrayList<PropertyValuesHolder>(vars.length/2);
- for (int i = 0; i < vars.length; i+=2) {
- if (!(vars[i] instanceof String)) {
- throw new IllegalArgumentException("Key must be a string: " + vars[i]);
- }
- String key = (String) vars[i];
- Object value = vars[i+1];
- if ("simultaneousTween".equals(key)) {
- // TODO
- } else if ("ease".equals(key)) {
- interpolator = (TimeInterpolator) value; // TODO: multiple interpolators?
- } else if ("onUpdate".equals(key) || "onUpdateListener".equals(key)) {
- updateListener = (AnimatorUpdateListener) value;
- } else if ("onComplete".equals(key) || "onCompleteListener".equals(key)) {
- listener = (AnimatorListener) value;
- } else if ("delay".equals(key)) {
- delay = ((Number) value).longValue();
- } else if ("syncWith".equals(key)) {
- // TODO
- } else if (value instanceof float[]) {
- props.add(PropertyValuesHolder.ofFloat(key,
- ((float[])value)[0], ((float[])value)[1]));
- } else if (value instanceof int[]) {
- props.add(PropertyValuesHolder.ofInt(key,
- ((int[])value)[0], ((int[])value)[1]));
- } else if (value instanceof Number) {
- float floatValue = ((Number)value).floatValue();
- props.add(PropertyValuesHolder.ofFloat(key, floatValue));
- } else {
- throw new IllegalArgumentException(
- "Bad argument for key \"" + key + "\" with value " + value.getClass());
- }
- }
-
- // Re-use existing tween, if present
- Tweener tween = sTweens.get(object);
- ObjectAnimator anim = null;
- if (tween == null) {
- anim = ObjectAnimator.ofPropertyValuesHolder(object,
- props.toArray(new PropertyValuesHolder[props.size()]));
- tween = new Tweener(anim);
- sTweens.put(object, tween);
- if (DEBUG) Log.v(TAG, "Added new Tweener " + tween);
- } else {
- anim = sTweens.get(object).animator;
- replace(props, object); // Cancel all animators for given object
- }
-
- if (interpolator != null) {
- anim.setInterpolator(interpolator);
- }
-
- // Update animation with properties discovered in loop above
- anim.setStartDelay(delay);
- anim.setDuration(duration);
- if (updateListener != null) {
- anim.removeAllUpdateListeners(); // There should be only one
- anim.addUpdateListener(updateListener);
- }
- if (listener != null) {
- anim.removeAllListeners(); // There should be only one.
- anim.addListener(listener);
- }
- anim.addListener(mCleanupListener);
-
- return tween;
- }
-
- Tweener from(Object object, long duration, Object... vars) {
- // TODO: for v of vars
- // toVars[v] = object[v]
- // object[v] = vars[v]
- return Tweener.to(object, duration, vars);
- }
-
- // Listener to watch for completed animations and remove them.
- private static AnimatorListener mCleanupListener = new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- remove(animation);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- remove(animation);
- }
- };
-
- public static void reset() {
- if (DEBUG) {
- Log.v(TAG, "Reset()");
- if (sTweens.size() > 0) {
- Log.v(TAG, "Cleaning up " + sTweens.size() + " animations");
- }
- }
- sTweens.clear();
- }
-
- private static void replace(ArrayList<PropertyValuesHolder> props, Object... args) {
- for (final Object killobject : args) {
- Tweener tween = sTweens.get(killobject);
- if (tween != null) {
- tween.animator.cancel();
- if (props != null) {
- tween.animator.setValues(
- props.toArray(new PropertyValuesHolder[props.size()]));
- } else {
- sTweens.remove(tween);
- }
- }
- }
- }
-}
diff --git a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
new file mode 100644
index 0000000..3f4b980
--- /dev/null
+++ b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.backup.BackupDataInputStream;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupHelper;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SyncAdapterType;
+import android.content.SyncStatusObserver;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper for backing up account sync settings (whether or not a service should be synced). The
+ * sync settings are backed up as a JSON object containing all the necessary information for
+ * restoring the sync settings later.
+ */
+public class AccountSyncSettingsBackupHelper implements BackupHelper {
+
+ private static final String TAG = "AccountSyncSettingsBackupHelper";
+ private static final boolean DEBUG = false;
+
+ private static final int STATE_VERSION = 1;
+ private static final int MD5_BYTE_SIZE = 16;
+ private static final int SYNC_REQUEST_LATCH_TIMEOUT_SECONDS = 1;
+
+ private static final String JSON_FORMAT_HEADER_KEY = "account_data";
+ private static final String JSON_FORMAT_ENCODING = "UTF-8";
+ private static final int JSON_FORMAT_VERSION = 1;
+
+ private static final String KEY_VERSION = "version";
+ private static final String KEY_MASTER_SYNC_ENABLED = "masterSyncEnabled";
+ private static final String KEY_ACCOUNTS = "accounts";
+ private static final String KEY_ACCOUNT_NAME = "name";
+ private static final String KEY_ACCOUNT_TYPE = "type";
+ private static final String KEY_ACCOUNT_AUTHORITIES = "authorities";
+ private static final String KEY_AUTHORITY_NAME = "name";
+ private static final String KEY_AUTHORITY_SYNC_STATE = "syncState";
+ private static final String KEY_AUTHORITY_SYNC_ENABLED = "syncEnabled";
+
+ private Context mContext;
+ private AccountManager mAccountManager;
+
+ public AccountSyncSettingsBackupHelper(Context context) {
+ mContext = context;
+ mAccountManager = AccountManager.get(mContext);
+ }
+
+ /**
+ * Take a snapshot of the current account sync settings and write them to the given output.
+ */
+ @Override
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput output,
+ ParcelFileDescriptor newState) {
+ try {
+ JSONObject dataJSON = serializeAccountSyncSettingsToJSON();
+
+ if (DEBUG) {
+ Log.d(TAG, "Account sync settings JSON: " + dataJSON);
+ }
+
+ // Encode JSON data to bytes.
+ byte[] dataBytes = dataJSON.toString().getBytes(JSON_FORMAT_ENCODING);
+ byte[] oldMd5Checksum = readOldMd5Checksum(oldState);
+ byte[] newMd5Checksum = generateMd5Checksum(dataBytes);
+ if (!Arrays.equals(oldMd5Checksum, newMd5Checksum)) {
+ int dataSize = dataBytes.length;
+ output.writeEntityHeader(JSON_FORMAT_HEADER_KEY, dataSize);
+ output.writeEntityData(dataBytes, dataSize);
+
+ Log.i(TAG, "Backup successful.");
+ } else {
+ Log.i(TAG, "Old and new MD5 checksums match. Skipping backup.");
+ }
+
+ writeNewMd5Checksum(newState, newMd5Checksum);
+ } catch (JSONException | IOException | NoSuchAlgorithmException e) {
+ Log.e(TAG, "Couldn't backup account sync settings\n" + e);
+ }
+ }
+
+ /**
+ * Fetch and serialize Account and authority information as a JSON Array.
+ */
+ private JSONObject serializeAccountSyncSettingsToJSON() throws JSONException {
+ Account[] accounts = mAccountManager.getAccounts();
+ SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(
+ mContext.getUserId());
+
+ // Create a map of Account types to authorities. Later this will make it easier for us to
+ // generate our JSON.
+ HashMap<String, List<String>> accountTypeToAuthorities = new HashMap<String,
+ List<String>>();
+ for (SyncAdapterType syncAdapter : syncAdapters) {
+ // Skip adapters that aren’t visible to the user.
+ if (!syncAdapter.isUserVisible()) {
+ continue;
+ }
+ if (!accountTypeToAuthorities.containsKey(syncAdapter.accountType)) {
+ accountTypeToAuthorities.put(syncAdapter.accountType, new ArrayList<String>());
+ }
+ accountTypeToAuthorities.get(syncAdapter.accountType).add(syncAdapter.authority);
+ }
+
+ // Generate JSON.
+ JSONObject backupJSON = new JSONObject();
+ backupJSON.put(KEY_VERSION, JSON_FORMAT_VERSION);
+ backupJSON.put(KEY_MASTER_SYNC_ENABLED, ContentResolver.getMasterSyncAutomatically());
+
+ JSONArray accountJSONArray = new JSONArray();
+ for (Account account : accounts) {
+ List<String> authorities = accountTypeToAuthorities.get(account.type);
+
+ // We ignore Accounts that don't have any authorities because there would be no sync
+ // settings for us to restore.
+ if (authorities == null || authorities.isEmpty()) {
+ continue;
+ }
+
+ JSONObject accountJSON = new JSONObject();
+ accountJSON.put(KEY_ACCOUNT_NAME, account.name);
+ accountJSON.put(KEY_ACCOUNT_TYPE, account.type);
+
+ // Add authorities for this Account type and check whether or not sync is enabled.
+ JSONArray authoritiesJSONArray = new JSONArray();
+ for (String authority : authorities) {
+ int syncState = ContentResolver.getIsSyncable(account, authority);
+ boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority);
+
+ JSONObject authorityJSON = new JSONObject();
+ authorityJSON.put(KEY_AUTHORITY_NAME, authority);
+ authorityJSON.put(KEY_AUTHORITY_SYNC_STATE, syncState);
+ authorityJSON.put(KEY_AUTHORITY_SYNC_ENABLED, syncEnabled);
+ authoritiesJSONArray.put(authorityJSON);
+ }
+ accountJSON.put(KEY_ACCOUNT_AUTHORITIES, authoritiesJSONArray);
+
+ accountJSONArray.put(accountJSON);
+ }
+ backupJSON.put(KEY_ACCOUNTS, accountJSONArray);
+
+ return backupJSON;
+ }
+
+ /**
+ * Read the MD5 checksum from the old state.
+ *
+ * @return the old MD5 checksum
+ */
+ private byte[] readOldMd5Checksum(ParcelFileDescriptor oldState) throws IOException {
+ DataInputStream dataInput = new DataInputStream(
+ new FileInputStream(oldState.getFileDescriptor()));
+
+ byte[] oldMd5Checksum = new byte[MD5_BYTE_SIZE];
+ try {
+ int stateVersion = dataInput.readInt();
+ if (stateVersion <= STATE_VERSION) {
+ // If the state version is a version we can understand then read the MD5 sum,
+ // otherwise we return an empty byte array for the MD5 sum which will force a
+ // backup.
+ for (int i = 0; i < MD5_BYTE_SIZE; i++) {
+ oldMd5Checksum[i] = dataInput.readByte();
+ }
+ } else {
+ Log.i(TAG, "Backup state version is: " + stateVersion
+ + " (support only up to version " + STATE_VERSION + ")");
+ }
+ } catch (EOFException eof) {
+ // Initial state may be empty.
+ } finally {
+ dataInput.close();
+ }
+ return oldMd5Checksum;
+ }
+
+ /**
+ * Write the given checksum to the file descriptor.
+ */
+ private void writeNewMd5Checksum(ParcelFileDescriptor newState, byte[] md5Checksum)
+ throws IOException {
+ DataOutputStream dataOutput = new DataOutputStream(
+ new BufferedOutputStream(new FileOutputStream(newState.getFileDescriptor())));
+
+ dataOutput.writeInt(STATE_VERSION);
+ dataOutput.write(md5Checksum);
+ dataOutput.close();
+ }
+
+ private byte[] generateMd5Checksum(byte[] data) throws NoSuchAlgorithmException {
+ if (data == null) {
+ return null;
+ }
+
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ return md5.digest(data);
+ }
+
+ /**
+ * Restore account sync settings from the given data input stream.
+ */
+ @Override
+ public void restoreEntity(BackupDataInputStream data) {
+ byte[] dataBytes = new byte[data.size()];
+ try {
+ // Read the data and convert it to a String.
+ data.read(dataBytes);
+ String dataString = new String(dataBytes, JSON_FORMAT_ENCODING);
+
+ // Convert data to a JSON object.
+ JSONObject dataJSON = new JSONObject(dataString);
+ boolean masterSyncEnabled = dataJSON.getBoolean(KEY_MASTER_SYNC_ENABLED);
+ JSONArray accountJSONArray = dataJSON.getJSONArray(KEY_ACCOUNTS);
+
+ boolean currentMasterSyncEnabled = ContentResolver.getMasterSyncAutomatically();
+ if (currentMasterSyncEnabled) {
+ // Disable master sync to prevent any syncs from running.
+ ContentResolver.setMasterSyncAutomatically(false);
+ }
+
+ try {
+ HashSet<Account> currentAccounts = getAccountsHashSet();
+ for (int i = 0; i < accountJSONArray.length(); i++) {
+ JSONObject accountJSON = (JSONObject) accountJSONArray.get(i);
+ String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
+ String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);
+
+ Account account = new Account(accountName, accountType);
+
+ // Check if the account already exists. Accounts that don't exist on the device
+ // yet won't be restored.
+ if (currentAccounts.contains(account)) {
+ restoreExistingAccountSyncSettingsFromJSON(accountJSON);
+ }
+ }
+ } finally {
+ // Set the master sync preference to the value from the backup set.
+ ContentResolver.setMasterSyncAutomatically(masterSyncEnabled);
+ }
+
+ Log.i(TAG, "Restore successful.");
+ } catch (IOException | JSONException e) {
+ Log.e(TAG, "Couldn't restore account sync settings\n" + e);
+ }
+ }
+
+ /**
+ * Helper method - fetch accounts and return them as a HashSet.
+ *
+ * @return Accounts in a HashSet.
+ */
+ private HashSet<Account> getAccountsHashSet() {
+ Account[] accounts = mAccountManager.getAccounts();
+ HashSet<Account> accountHashSet = new HashSet<Account>();
+ for (Account account : accounts) {
+ accountHashSet.add(account);
+ }
+ return accountHashSet;
+ }
+
+ /**
+ * Restore account sync settings using the given JSON. This function won't work if the account
+ * doesn't exist yet.
+ */
+ private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON)
+ throws JSONException {
+ // Restore authorities.
+ JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES);
+ String accountName = accountJSON.getString(KEY_ACCOUNT_NAME);
+ String accountType = accountJSON.getString(KEY_ACCOUNT_TYPE);
+ final Account account = new Account(accountName, accountType);
+ for (int i = 0; i < authorities.length(); i++) {
+ JSONObject authority = (JSONObject) authorities.get(i);
+ final String authorityName = authority.getString(KEY_AUTHORITY_NAME);
+ boolean syncEnabled = authority.getBoolean(KEY_AUTHORITY_SYNC_ENABLED);
+
+ // Cancel any active syncs.
+ if (ContentResolver.isSyncActive(account, authorityName)) {
+ ContentResolver.cancelSync(account, authorityName);
+ }
+
+ boolean overwriteSync = true;
+ Bundle initializationExtras = createSyncInitializationBundle();
+ int currentSyncState = ContentResolver.getIsSyncable(account, authorityName);
+ if (currentSyncState < 0) {
+ // Requesting a sync is an asynchronous operation, so we setup a countdown latch to
+ // wait for it to finish. Initialization syncs are generally very brief and
+ // shouldn't take too much time to finish.
+ final CountDownLatch latch = new CountDownLatch(1);
+ Object syncStatusObserverHandle = ContentResolver.addStatusChangeListener(
+ ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, new SyncStatusObserver() {
+ @Override
+ public void onStatusChanged(int which) {
+ if (!ContentResolver.isSyncActive(account, authorityName)) {
+ latch.countDown();
+ }
+ }
+ });
+
+ // If we set sync settings for a sync that hasn't been initialized yet, we run the
+ // risk of having our changes overwritten later on when the sync gets initialized.
+ // To prevent this from happening we will manually initiate the sync adapter. We
+ // also explicitly pass in a Bundle with SYNC_EXTRAS_INITIALIZE to prevent a data
+ // sync from running after the initialization sync. Two syncs will be scheduled, but
+ // the second one (data sync) will override the first one (initialization sync) and
+ // still behave as an initialization sync because of the Bundle.
+ ContentResolver.requestSync(account, authorityName, initializationExtras);
+
+ boolean done = false;
+ try {
+ done = latch.await(SYNC_REQUEST_LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "CountDownLatch interrupted\n" + e);
+ done = false;
+ }
+ if (!done) {
+ overwriteSync = false;
+ Log.i(TAG, "CountDownLatch timed out, skipping '" + authorityName
+ + "' authority.");
+ }
+ ContentResolver.removeStatusChangeListener(syncStatusObserverHandle);
+ }
+
+ if (overwriteSync) {
+ ContentResolver.setSyncAutomatically(account, authorityName, syncEnabled);
+ Log.i(TAG, "Set sync automatically for '" + authorityName + "': " + syncEnabled);
+ }
+ }
+ }
+
+ private Bundle createSyncInitializationBundle() {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ return extras;
+ }
+
+ @Override
+ public void writeNewStateDescription(ParcelFileDescriptor newState) {
+
+ }
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 35a1a5a..b5f2f37 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -86,6 +86,8 @@ public class SystemBackupAgent extends BackupAgentHelper {
}
addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files, keys));
addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this));
+ addHelper("account_sync_settings",
+ new AccountSyncSettingsBackupHelper(SystemBackupAgent.this));
super.onBackup(oldState, data, newState);
}
@@ -118,6 +120,8 @@ public class SystemBackupAgent extends BackupAgentHelper {
new String[] { WALLPAPER_IMAGE },
new String[] { WALLPAPER_IMAGE_KEY} ));
addHelper("recents", new RecentsBackupHelper(SystemBackupAgent.this));
+ addHelper("account_sync_settings",
+ new AccountSyncSettingsBackupHelper(SystemBackupAgent.this));
try {
super.onRestore(data, appVersionCode, newState);
diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java
index 0dde465..d45982e 100644
--- a/core/java/com/android/server/net/NetlinkTracker.java
+++ b/core/java/com/android/server/net/NetlinkTracker.java
@@ -24,11 +24,9 @@ import android.util.Log;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Objects;
import java.util.Set;
/**
diff --git a/core/java/org/apache/http/conn/ConnectTimeoutException.java b/core/java/org/apache/http/conn/ConnectTimeoutException.java
new file mode 100644
index 0000000..6cc6922
--- /dev/null
+++ b/core/java/org/apache/http/conn/ConnectTimeoutException.java
@@ -0,0 +1,69 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ConnectTimeoutException.java $
+ * $Revision: 617645 $
+ * $Date: 2008-02-01 13:05:31 -0800 (Fri, 01 Feb 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn;
+
+import java.io.InterruptedIOException;
+
+/**
+ * A timeout while connecting to an HTTP server or waiting for an
+ * available connection from an HttpConnectionManager.
+ *
+ * @author <a href="mailto:laura@lwerner.org">Laura Werner</a>
+ *
+ * @since 4.0
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public class ConnectTimeoutException extends InterruptedIOException {
+
+ private static final long serialVersionUID = -4816682903149535989L;
+
+ /**
+ * Creates a ConnectTimeoutException with a <tt>null</tt> detail message.
+ */
+ public ConnectTimeoutException() {
+ super();
+ }
+
+ /**
+ * Creates a ConnectTimeoutException with the specified detail message.
+ *
+ * @param message The exception detail message
+ */
+ public ConnectTimeoutException(final String message) {
+ super(message);
+ }
+
+}
diff --git a/core/java/org/apache/http/conn/scheme/HostNameResolver.java b/core/java/org/apache/http/conn/scheme/HostNameResolver.java
new file mode 100644
index 0000000..30ef298
--- /dev/null
+++ b/core/java/org/apache/http/conn/scheme/HostNameResolver.java
@@ -0,0 +1,47 @@
+/*
+ * $HeadURL:$
+ * $Revision:$
+ * $Date:$
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.scheme;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+/**
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public interface HostNameResolver {
+
+ InetAddress resolve (String hostname) throws IOException;
+
+}
diff --git a/core/java/org/apache/http/conn/scheme/LayeredSocketFactory.java b/core/java/org/apache/http/conn/scheme/LayeredSocketFactory.java
new file mode 100644
index 0000000..b9f5348
--- /dev/null
+++ b/core/java/org/apache/http/conn/scheme/LayeredSocketFactory.java
@@ -0,0 +1,77 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/LayeredSocketFactory.java $
+ * $Revision: 645850 $
+ * $Date: 2008-04-08 04:08:52 -0700 (Tue, 08 Apr 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.scheme;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+/**
+ * A {@link SocketFactory SocketFactory} for layered sockets (SSL/TLS).
+ * See there for things to consider when implementing a socket factory.
+ *
+ * @author Michael Becke
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ * @since 4.0
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public interface LayeredSocketFactory extends SocketFactory {
+
+ /**
+ * Returns a socket connected to the given host that is layered over an
+ * existing socket. Used primarily for creating secure sockets through
+ * proxies.
+ *
+ * @param socket the existing socket
+ * @param host the host name/IP
+ * @param port the port on the host
+ * @param autoClose a flag for closing the underling socket when the created
+ * socket is closed
+ *
+ * @return Socket a new socket
+ *
+ * @throws IOException if an I/O error occurs while creating the socket
+ * @throws UnknownHostException if the IP address of the host cannot be
+ * determined
+ */
+ Socket createSocket(
+ Socket socket,
+ String host,
+ int port,
+ boolean autoClose
+ ) throws IOException, UnknownHostException;
+
+}
diff --git a/core/java/org/apache/http/conn/scheme/SocketFactory.java b/core/java/org/apache/http/conn/scheme/SocketFactory.java
new file mode 100644
index 0000000..c6bc03c
--- /dev/null
+++ b/core/java/org/apache/http/conn/scheme/SocketFactory.java
@@ -0,0 +1,143 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/scheme/SocketFactory.java $
+ * $Revision: 645850 $
+ * $Date: 2008-04-08 04:08:52 -0700 (Tue, 08 Apr 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.scheme;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.params.HttpParams;
+
+/**
+ * A factory for creating and connecting sockets.
+ * The factory encapsulates the logic for establishing a socket connection.
+ * <br/>
+ * Both {@link java.lang.Object#equals(java.lang.Object) Object.equals()}
+ * and {@link java.lang.Object#hashCode() Object.hashCode()}
+ * must be overridden for the correct operation of some connection managers.
+ *
+ * @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
+ * @author Michael Becke
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public interface SocketFactory {
+
+ /**
+ * Creates a new, unconnected socket.
+ * The socket should subsequently be passed to
+ * {@link #connectSocket connectSocket}.
+ *
+ * @return a new socket
+ *
+ * @throws IOException if an I/O error occurs while creating the socket
+ */
+ Socket createSocket()
+ throws IOException
+ ;
+
+
+ /**
+ * Connects a socket to the given host.
+ *
+ * @param sock the socket to connect, as obtained from
+ * {@link #createSocket createSocket}.
+ * <code>null</code> indicates that a new socket
+ * should be created and connected.
+ * @param host the host to connect to
+ * @param port the port to connect to on the host
+ * @param localAddress the local address to bind the socket to, or
+ * <code>null</code> for any
+ * @param localPort the port on the local machine,
+ * 0 or a negative number for any
+ * @param params additional {@link HttpParams parameters} for connecting
+ *
+ * @return the connected socket. The returned object may be different
+ * from the <code>sock</code> argument if this factory supports
+ * a layered protocol.
+ *
+ * @throws IOException if an I/O error occurs
+ * @throws UnknownHostException if the IP address of the target host
+ * can not be determined
+ * @throws ConnectTimeoutException if the socket cannot be connected
+ * within the time limit defined in the <code>params</code>
+ */
+ Socket connectSocket(
+ Socket sock,
+ String host,
+ int port,
+ InetAddress localAddress,
+ int localPort,
+ HttpParams params
+ ) throws IOException, UnknownHostException, ConnectTimeoutException;
+
+
+ /**
+ * Checks whether a socket provides a secure connection.
+ * The socket must be {@link #connectSocket connected}
+ * by this factory.
+ * The factory will <i>not</i> perform I/O operations
+ * in this method.
+ * <br/>
+ * As a rule of thumb, plain sockets are not secure and
+ * TLS/SSL sockets are secure. However, there may be
+ * application specific deviations. For example, a plain
+ * socket to a host in the same intranet ("trusted zone")
+ * could be considered secure. On the other hand, a
+ * TLS/SSL socket could be considered insecure based on
+ * the cypher suite chosen for the connection.
+ *
+ * @param sock the connected socket to check
+ *
+ * @return <code>true</code> if the connection of the socket
+ * should be considered secure, or
+ * <code>false</code> if it should not
+ *
+ * @throws IllegalArgumentException
+ * if the argument is invalid, for example because it is
+ * not a connected socket or was created by a different
+ * socket factory.
+ * Note that socket factories are <i>not</i> required to
+ * check these conditions, they may simply return a default
+ * value when called with an invalid socket argument.
+ */
+ boolean isSecure(Socket sock)
+ throws IllegalArgumentException
+ ;
+
+}
diff --git a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
new file mode 100644
index 0000000..e264f1c
--- /dev/null
+++ b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
@@ -0,0 +1,288 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java $
+ * $Revision: 653041 $
+ * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.ssl;
+
+import java.util.regex.Pattern;
+
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+
+import javax.net.ssl.DistinguishedNameParser;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Abstract base class for all standard {@link X509HostnameVerifier}
+ * implementations.
+ *
+ * @author Julius Davies
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public abstract class AbstractVerifier implements X509HostnameVerifier {
+
+ private static final Pattern IPV4_PATTERN = Pattern.compile(
+ "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
+
+ /**
+ * This contains a list of 2nd-level domains that aren't allowed to
+ * have wildcards when combined with country-codes.
+ * For example: [*.co.uk].
+ * <p/>
+ * The [*.co.uk] problem is an interesting one. Should we just hope
+ * that CA's would never foolishly allow such a certificate to happen?
+ * Looks like we're the only implementation guarding against this.
+ * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check.
+ */
+ private final static String[] BAD_COUNTRY_2LDS =
+ { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
+ "lg", "ne", "net", "or", "org" };
+
+ static {
+ // Just in case developer forgot to manually sort the array. :-)
+ Arrays.sort(BAD_COUNTRY_2LDS);
+ }
+
+ public AbstractVerifier() {
+ super();
+ }
+
+ public final void verify(String host, SSLSocket ssl)
+ throws IOException {
+ if(host == null) {
+ throw new NullPointerException("host to verify is null");
+ }
+
+ SSLSession session = ssl.getSession();
+ Certificate[] certs = session.getPeerCertificates();
+ X509Certificate x509 = (X509Certificate) certs[0];
+ verify(host, x509);
+ }
+
+ public final boolean verify(String host, SSLSession session) {
+ try {
+ Certificate[] certs = session.getPeerCertificates();
+ X509Certificate x509 = (X509Certificate) certs[0];
+ verify(host, x509);
+ return true;
+ }
+ catch(SSLException e) {
+ return false;
+ }
+ }
+
+ public final void verify(String host, X509Certificate cert)
+ throws SSLException {
+ String[] cns = getCNs(cert);
+ String[] subjectAlts = getDNSSubjectAlts(cert);
+ verify(host, cns, subjectAlts);
+ }
+
+ public final void verify(final String host, final String[] cns,
+ final String[] subjectAlts,
+ final boolean strictWithSubDomains)
+ throws SSLException {
+
+ // Build the list of names we're going to check. Our DEFAULT and
+ // STRICT implementations of the HostnameVerifier only use the
+ // first CN provided. All other CNs are ignored.
+ // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).
+ LinkedList<String> names = new LinkedList<String>();
+ if(cns != null && cns.length > 0 && cns[0] != null) {
+ names.add(cns[0]);
+ }
+ if(subjectAlts != null) {
+ for (String subjectAlt : subjectAlts) {
+ if (subjectAlt != null) {
+ names.add(subjectAlt);
+ }
+ }
+ }
+
+ if(names.isEmpty()) {
+ String msg = "Certificate for <" + host + "> doesn't contain CN or DNS subjectAlt";
+ throw new SSLException(msg);
+ }
+
+ // StringBuffer for building the error message.
+ StringBuffer buf = new StringBuffer();
+
+ // We're can be case-insensitive when comparing the host we used to
+ // establish the socket to the hostname in the certificate.
+ String hostName = host.trim().toLowerCase(Locale.ENGLISH);
+ boolean match = false;
+ for(Iterator<String> it = names.iterator(); it.hasNext();) {
+ // Don't trim the CN, though!
+ String cn = it.next();
+ cn = cn.toLowerCase(Locale.ENGLISH);
+ // Store CN in StringBuffer in case we need to report an error.
+ buf.append(" <");
+ buf.append(cn);
+ buf.append('>');
+ if(it.hasNext()) {
+ buf.append(" OR");
+ }
+
+ // The CN better have at least two dots if it wants wildcard
+ // action. It also can't be [*.co.uk] or [*.co.jp] or
+ // [*.org.uk], etc...
+ boolean doWildcard = cn.startsWith("*.") &&
+ cn.indexOf('.', 2) != -1 &&
+ acceptableCountryWildcard(cn) &&
+ !isIPv4Address(host);
+
+ if(doWildcard) {
+ match = hostName.endsWith(cn.substring(1));
+ if(match && strictWithSubDomains) {
+ // If we're in strict mode, then [*.foo.com] is not
+ // allowed to match [a.b.foo.com]
+ match = countDots(hostName) == countDots(cn);
+ }
+ } else {
+ match = hostName.equals(cn);
+ }
+ if(match) {
+ break;
+ }
+ }
+ if(!match) {
+ throw new SSLException("hostname in certificate didn't match: <" + host + "> !=" + buf);
+ }
+ }
+
+ public static boolean acceptableCountryWildcard(String cn) {
+ int cnLen = cn.length();
+ if(cnLen >= 7 && cnLen <= 9) {
+ // Look for the '.' in the 3rd-last position:
+ if(cn.charAt(cnLen - 3) == '.') {
+ // Trim off the [*.] and the [.XX].
+ String s = cn.substring(2, cnLen - 3);
+ // And test against the sorted array of bad 2lds:
+ int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s);
+ return x < 0;
+ }
+ }
+ return true;
+ }
+
+ public static String[] getCNs(X509Certificate cert) {
+ DistinguishedNameParser dnParser =
+ new DistinguishedNameParser(cert.getSubjectX500Principal());
+ List<String> cnList = dnParser.getAllMostSpecificFirst("cn");
+
+ if(!cnList.isEmpty()) {
+ String[] cns = new String[cnList.size()];
+ cnList.toArray(cns);
+ return cns;
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Extracts the array of SubjectAlt DNS names from an X509Certificate.
+ * Returns null if there aren't any.
+ * <p/>
+ * Note: Java doesn't appear able to extract international characters
+ * from the SubjectAlts. It can only extract international characters
+ * from the CN field.
+ * <p/>
+ * (Or maybe the version of OpenSSL I'm using to test isn't storing the
+ * international characters correctly in the SubjectAlts?).
+ *
+ * @param cert X509Certificate
+ * @return Array of SubjectALT DNS names stored in the certificate.
+ */
+ public static String[] getDNSSubjectAlts(X509Certificate cert) {
+ LinkedList<String> subjectAltList = new LinkedList<String>();
+ Collection<List<?>> c = null;
+ try {
+ c = cert.getSubjectAlternativeNames();
+ }
+ catch(CertificateParsingException cpe) {
+ Logger.getLogger(AbstractVerifier.class.getName())
+ .log(Level.FINE, "Error parsing certificate.", cpe);
+ }
+ if(c != null) {
+ for (List<?> aC : c) {
+ List<?> list = aC;
+ int type = ((Integer) list.get(0)).intValue();
+ // If type is 2, then we've got a dNSName
+ if (type == 2) {
+ String s = (String) list.get(1);
+ subjectAltList.add(s);
+ }
+ }
+ }
+ if(!subjectAltList.isEmpty()) {
+ String[] subjectAlts = new String[subjectAltList.size()];
+ subjectAltList.toArray(subjectAlts);
+ return subjectAlts;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Counts the number of dots "." in a string.
+ * @param s string to count dots from
+ * @return number of dots
+ */
+ public static int countDots(final String s) {
+ int count = 0;
+ for(int i = 0; i < s.length(); i++) {
+ if(s.charAt(i) == '.') {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private static boolean isIPv4Address(final String input) {
+ return IPV4_PATTERN.matcher(input).matches();
+ }
+}
diff --git a/core/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java b/core/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java
new file mode 100644
index 0000000..c2bf4c4
--- /dev/null
+++ b/core/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java
@@ -0,0 +1,59 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java $
+ * $Revision: 617642 $
+ * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.ssl;
+
+/**
+ * The ALLOW_ALL HostnameVerifier essentially turns hostname verification
+ * off. This implementation is a no-op, and never throws the SSLException.
+ *
+ * @author Julius Davies
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public class AllowAllHostnameVerifier extends AbstractVerifier {
+
+ public final void verify(
+ final String host,
+ final String[] cns,
+ final String[] subjectAlts) {
+ // Allow everything - so never blowup.
+ }
+
+ @Override
+ public final String toString() {
+ return "ALLOW_ALL";
+ }
+
+}
diff --git a/core/java/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java b/core/java/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java
new file mode 100644
index 0000000..48a7bf9
--- /dev/null
+++ b/core/java/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java
@@ -0,0 +1,67 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/BrowserCompatHostnameVerifier.java $
+ * $Revision: 617642 $
+ * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.ssl;
+
+import javax.net.ssl.SSLException;
+
+/**
+ * The HostnameVerifier that works the same way as Curl and Firefox.
+ * <p/>
+ * The hostname must match either the first CN, or any of the subject-alts.
+ * A wildcard can occur in the CN, and in any of the subject-alts.
+ * <p/>
+ * The only difference between BROWSER_COMPATIBLE and STRICT is that a wildcard
+ * (such as "*.foo.com") with BROWSER_COMPATIBLE matches all subdomains,
+ * including "a.b.foo.com".
+ *
+ * @author Julius Davies
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public class BrowserCompatHostnameVerifier extends AbstractVerifier {
+
+ public final void verify(
+ final String host,
+ final String[] cns,
+ final String[] subjectAlts) throws SSLException {
+ verify(host, cns, subjectAlts, false);
+ }
+
+ @Override
+ public final String toString() {
+ return "BROWSER_COMPATIBLE";
+ }
+
+}
diff --git a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
new file mode 100644
index 0000000..4d53d40
--- /dev/null
+++ b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
@@ -0,0 +1,408 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java $
+ * $Revision: 659194 $
+ * $Date: 2008-05-22 11:33:47 -0700 (Thu, 22 May 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.ssl;
+
+import org.apache.http.conn.scheme.HostNameResolver;
+import org.apache.http.conn.scheme.LayeredSocketFactory;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+
+/**
+ * Layered socket factory for TLS/SSL connections, based on JSSE.
+ *.
+ * <p>
+ * SSLSocketFactory can be used to validate the identity of the HTTPS
+ * server against a list of trusted certificates and to authenticate to
+ * the HTTPS server using a private key.
+ * </p>
+ *
+ * <p>
+ * SSLSocketFactory will enable server authentication when supplied with
+ * a {@link KeyStore truststore} file containg one or several trusted
+ * certificates. The client secure socket will reject the connection during
+ * the SSL session handshake if the target HTTPS server attempts to
+ * authenticate itself with a non-trusted certificate.
+ * </p>
+ *
+ * <p>
+ * Use JDK keytool utility to import a trusted certificate and generate a truststore file:
+ * <pre>
+ * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
+ * </pre>
+ * </p>
+ *
+ * <p>
+ * SSLSocketFactory will enable client authentication when supplied with
+ * a {@link KeyStore keystore} file containg a private key/public certificate
+ * pair. The client secure socket will use the private key to authenticate
+ * itself to the target HTTPS server during the SSL session handshake if
+ * requested to do so by the server.
+ * The target HTTPS server will in its turn verify the certificate presented
+ * by the client in order to establish client's authenticity
+ * </p>
+ *
+ * <p>
+ * Use the following sequence of actions to generate a keystore file
+ * </p>
+ * <ul>
+ * <li>
+ * <p>
+ * Use JDK keytool utility to generate a new key
+ * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
+ * For simplicity use the same password for the key as that of the keystore
+ * </p>
+ * </li>
+ * <li>
+ * <p>
+ * Issue a certificate signing request (CSR)
+ * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
+ * </p>
+ * </li>
+ * <li>
+ * <p>
+ * Send the certificate request to the trusted Certificate Authority for signature.
+ * One may choose to act as her own CA and sign the certificate request using a PKI
+ * tool, such as OpenSSL.
+ * </p>
+ * </li>
+ * <li>
+ * <p>
+ * Import the trusted CA root certificate
+ * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
+ * </p>
+ * </li>
+ * <li>
+ * <p>
+ * Import the PKCS#7 file containg the complete certificate chain
+ * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
+ * </p>
+ * </li>
+ * <li>
+ * <p>
+ * Verify the content the resultant keystore file
+ * <pre>keytool -list -v -keystore my.keystore</pre>
+ * </p>
+ * </li>
+ * </ul>
+ * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ * @author Julius Davies
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public class SSLSocketFactory implements LayeredSocketFactory {
+
+ public static final String TLS = "TLS";
+ public static final String SSL = "SSL";
+ public static final String SSLV2 = "SSLv2";
+
+ public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
+ = new AllowAllHostnameVerifier();
+
+ public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
+ = new BrowserCompatHostnameVerifier();
+
+ public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
+ = new StrictHostnameVerifier();
+
+ /*
+ * Put defaults into holder class to avoid class preloading creating an
+ * instance of the classes referenced.
+ */
+ private static class NoPreloadHolder {
+ /**
+ * The factory using the default JVM settings for secure connections.
+ */
+ private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory();
+ }
+
+ /**
+ * Gets an singleton instance of the SSLProtocolSocketFactory.
+ * @return a SSLProtocolSocketFactory
+ */
+ public static SSLSocketFactory getSocketFactory() {
+ return NoPreloadHolder.DEFAULT_FACTORY;
+ }
+
+ private final SSLContext sslcontext;
+ private final javax.net.ssl.SSLSocketFactory socketfactory;
+ private final HostNameResolver nameResolver;
+ private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
+
+ public SSLSocketFactory(
+ String algorithm,
+ final KeyStore keystore,
+ final String keystorePassword,
+ final KeyStore truststore,
+ final SecureRandom random,
+ final HostNameResolver nameResolver)
+ throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
+ {
+ super();
+ if (algorithm == null) {
+ algorithm = TLS;
+ }
+ KeyManager[] keymanagers = null;
+ if (keystore != null) {
+ keymanagers = createKeyManagers(keystore, keystorePassword);
+ }
+ TrustManager[] trustmanagers = null;
+ if (truststore != null) {
+ trustmanagers = createTrustManagers(truststore);
+ }
+ this.sslcontext = SSLContext.getInstance(algorithm);
+ this.sslcontext.init(keymanagers, trustmanagers, random);
+ this.socketfactory = this.sslcontext.getSocketFactory();
+ this.nameResolver = nameResolver;
+ }
+
+ public SSLSocketFactory(
+ final KeyStore keystore,
+ final String keystorePassword,
+ final KeyStore truststore)
+ throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
+ {
+ this(TLS, keystore, keystorePassword, truststore, null, null);
+ }
+
+ public SSLSocketFactory(final KeyStore keystore, final String keystorePassword)
+ throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
+ {
+ this(TLS, keystore, keystorePassword, null, null, null);
+ }
+
+ public SSLSocketFactory(final KeyStore truststore)
+ throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
+ {
+ this(TLS, null, null, truststore, null, null);
+ }
+
+ /**
+ * Constructs an HttpClient SSLSocketFactory backed by the given JSSE
+ * SSLSocketFactory.
+ *
+ * @hide
+ */
+ public SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory) {
+ super();
+ this.sslcontext = null;
+ this.socketfactory = socketfactory;
+ this.nameResolver = null;
+ }
+
+ /**
+ * Creates the default SSL socket factory.
+ * This constructor is used exclusively to instantiate the factory for
+ * {@link #getSocketFactory getSocketFactory}.
+ */
+ private SSLSocketFactory() {
+ super();
+ this.sslcontext = null;
+ this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory();
+ this.nameResolver = null;
+ }
+
+ private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password)
+ throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
+ if (keystore == null) {
+ throw new IllegalArgumentException("Keystore may not be null");
+ }
+ KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
+ KeyManagerFactory.getDefaultAlgorithm());
+ kmfactory.init(keystore, password != null ? password.toCharArray(): null);
+ return kmfactory.getKeyManagers();
+ }
+
+ private static TrustManager[] createTrustManagers(final KeyStore keystore)
+ throws KeyStoreException, NoSuchAlgorithmException {
+ if (keystore == null) {
+ throw new IllegalArgumentException("Keystore may not be null");
+ }
+ TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmfactory.init(keystore);
+ return tmfactory.getTrustManagers();
+ }
+
+
+ // non-javadoc, see interface org.apache.http.conn.SocketFactory
+ public Socket createSocket()
+ throws IOException {
+
+ // the cast makes sure that the factory is working as expected
+ return (SSLSocket) this.socketfactory.createSocket();
+ }
+
+
+ // non-javadoc, see interface org.apache.http.conn.SocketFactory
+ public Socket connectSocket(
+ final Socket sock,
+ final String host,
+ final int port,
+ final InetAddress localAddress,
+ int localPort,
+ final HttpParams params
+ ) throws IOException {
+
+ if (host == null) {
+ throw new IllegalArgumentException("Target host may not be null.");
+ }
+ if (params == null) {
+ throw new IllegalArgumentException("Parameters may not be null.");
+ }
+
+ SSLSocket sslsock = (SSLSocket)
+ ((sock != null) ? sock : createSocket());
+
+ if ((localAddress != null) || (localPort > 0)) {
+
+ // we need to bind explicitly
+ if (localPort < 0)
+ localPort = 0; // indicates "any"
+
+ InetSocketAddress isa =
+ new InetSocketAddress(localAddress, localPort);
+ sslsock.bind(isa);
+ }
+
+ int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
+ int soTimeout = HttpConnectionParams.getSoTimeout(params);
+
+ InetSocketAddress remoteAddress;
+ if (this.nameResolver != null) {
+ remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port);
+ } else {
+ remoteAddress = new InetSocketAddress(host, port);
+ }
+
+ sslsock.connect(remoteAddress, connTimeout);
+
+ sslsock.setSoTimeout(soTimeout);
+ try {
+ hostnameVerifier.verify(host, sslsock);
+ // verifyHostName() didn't blowup - good!
+ } catch (IOException iox) {
+ // close the socket before re-throwing the exception
+ try { sslsock.close(); } catch (Exception x) { /*ignore*/ }
+ throw iox;
+ }
+
+ return sslsock;
+ }
+
+
+ /**
+ * Checks whether a socket connection is secure.
+ * This factory creates TLS/SSL socket connections
+ * which, by default, are considered secure.
+ * <br/>
+ * Derived classes may override this method to perform
+ * runtime checks, for example based on the cypher suite.
+ *
+ * @param sock the connected socket
+ *
+ * @return <code>true</code>
+ *
+ * @throws IllegalArgumentException if the argument is invalid
+ */
+ public boolean isSecure(Socket sock)
+ throws IllegalArgumentException {
+
+ if (sock == null) {
+ throw new IllegalArgumentException("Socket may not be null.");
+ }
+ // This instanceof check is in line with createSocket() above.
+ if (!(sock instanceof SSLSocket)) {
+ throw new IllegalArgumentException
+ ("Socket not created by this factory.");
+ }
+ // This check is performed last since it calls the argument object.
+ if (sock.isClosed()) {
+ throw new IllegalArgumentException("Socket is closed.");
+ }
+
+ return true;
+
+ } // isSecure
+
+
+ // non-javadoc, see interface LayeredSocketFactory
+ public Socket createSocket(
+ final Socket socket,
+ final String host,
+ final int port,
+ final boolean autoClose
+ ) throws IOException, UnknownHostException {
+ SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
+ socket,
+ host,
+ port,
+ autoClose
+ );
+ hostnameVerifier.verify(host, sslSocket);
+ // verifyHostName() didn't blowup - good!
+ return sslSocket;
+ }
+
+ public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) {
+ if ( hostnameVerifier == null ) {
+ throw new IllegalArgumentException("Hostname verifier may not be null");
+ }
+ this.hostnameVerifier = hostnameVerifier;
+ }
+
+ public X509HostnameVerifier getHostnameVerifier() {
+ return hostnameVerifier;
+ }
+
+}
diff --git a/core/java/org/apache/http/conn/ssl/StrictHostnameVerifier.java b/core/java/org/apache/http/conn/ssl/StrictHostnameVerifier.java
new file mode 100644
index 0000000..bd9e70d
--- /dev/null
+++ b/core/java/org/apache/http/conn/ssl/StrictHostnameVerifier.java
@@ -0,0 +1,74 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/StrictHostnameVerifier.java $
+ * $Revision: 617642 $
+ * $Date: 2008-02-01 12:54:07 -0800 (Fri, 01 Feb 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.ssl;
+
+import javax.net.ssl.SSLException;
+
+/**
+ * The Strict HostnameVerifier works the same way as Sun Java 1.4, Sun
+ * Java 5, Sun Java 6-rc. It's also pretty close to IE6. This
+ * implementation appears to be compliant with RFC 2818 for dealing with
+ * wildcards.
+ * <p/>
+ * The hostname must match either the first CN, or any of the subject-alts.
+ * A wildcard can occur in the CN, and in any of the subject-alts. The
+ * one divergence from IE6 is how we only check the first CN. IE6 allows
+ * a match against any of the CNs present. We decided to follow in
+ * Sun Java 1.4's footsteps and only check the first CN. (If you need
+ * to check all the CN's, feel free to write your own implementation!).
+ * <p/>
+ * A wildcard such as "*.foo.com" matches only subdomains in the same
+ * level, for example "a.foo.com". It does not match deeper subdomains
+ * such as "a.b.foo.com".
+ *
+ * @author Julius Davies
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public class StrictHostnameVerifier extends AbstractVerifier {
+
+ public final void verify(
+ final String host,
+ final String[] cns,
+ final String[] subjectAlts) throws SSLException {
+ verify(host, cns, subjectAlts, true);
+ }
+
+ @Override
+ public final String toString() {
+ return "STRICT";
+ }
+
+}
diff --git a/core/java/org/apache/http/conn/ssl/X509HostnameVerifier.java b/core/java/org/apache/http/conn/ssl/X509HostnameVerifier.java
new file mode 100644
index 0000000..e38db5f
--- /dev/null
+++ b/core/java/org/apache/http/conn/ssl/X509HostnameVerifier.java
@@ -0,0 +1,91 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/X509HostnameVerifier.java $
+ * $Revision: 618365 $
+ * $Date: 2008-02-04 10:20:08 -0800 (Mon, 04 Feb 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.conn.ssl;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+
+/**
+ * Interface for checking if a hostname matches the names stored inside the
+ * server's X.509 certificate. Implements javax.net.ssl.HostnameVerifier, but
+ * we don't actually use that interface. Instead we added some methods that
+ * take String parameters (instead of javax.net.ssl.HostnameVerifier's
+ * SSLSession). JUnit is a lot easier this way! :-)
+ * <p/>
+ * We provide the HostnameVerifier.DEFAULT, HostnameVerifier.STRICT, and
+ * HostnameVerifier.ALLOW_ALL implementations. But feel free to define
+ * your own implementation!
+ * <p/>
+ * Inspired by Sebastian Hauer's original StrictSSLProtocolSocketFactory in the
+ * HttpClient "contrib" repository.
+ *
+ * @author Julius Davies
+ * @author <a href="mailto:hauer@psicode.com">Sebastian Hauer</a>
+ *
+ * @since 4.0 (8-Dec-2006)
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public interface X509HostnameVerifier extends HostnameVerifier {
+
+ boolean verify(String host, SSLSession session);
+
+ void verify(String host, SSLSocket ssl) throws IOException;
+
+ void verify(String host, X509Certificate cert) throws SSLException;
+
+ /**
+ * Checks to see if the supplied hostname matches any of the supplied CNs
+ * or "DNS" Subject-Alts. Most implementations only look at the first CN,
+ * and ignore any additional CNs. Most implementations do look at all of
+ * the "DNS" Subject-Alts. The CNs or Subject-Alts may contain wildcards
+ * according to RFC 2818.
+ *
+ * @param cns CN fields, in order, as extracted from the X.509
+ * certificate.
+ * @param subjectAlts Subject-Alt fields of type 2 ("DNS"), as extracted
+ * from the X.509 certificate.
+ * @param host The hostname to verify.
+ * @throws SSLException If verification failed.
+ */
+ void verify(String host, String[] cns, String[] subjectAlts)
+ throws SSLException;
+
+
+}
diff --git a/core/java/org/apache/http/conn/ssl/package.html b/core/java/org/apache/http/conn/ssl/package.html
new file mode 100644
index 0000000..a5c737f
--- /dev/null
+++ b/core/java/org/apache/http/conn/ssl/package.html
@@ -0,0 +1,40 @@
+<html>
+<head>
+<!--
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/package.html $
+ * $Revision: 555193 $
+ * $Date: 2007-07-11 00:36:47 -0700 (Wed, 11 Jul 2007) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+-->
+</head>
+<body>
+TLS/SSL specific parts of the <i>HttpConn</i> API.
+
+</body>
+</html>
diff --git a/core/java/org/apache/http/params/CoreConnectionPNames.java b/core/java/org/apache/http/params/CoreConnectionPNames.java
new file mode 100644
index 0000000..9479db1
--- /dev/null
+++ b/core/java/org/apache/http/params/CoreConnectionPNames.java
@@ -0,0 +1,136 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/CoreConnectionPNames.java $
+ * $Revision: 576077 $
+ * $Date: 2007-09-16 04:50:22 -0700 (Sun, 16 Sep 2007) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.params;
+
+
+/**
+ * Defines parameter names for connections in HttpCore.
+ *
+ * @version $Revision: 576077 $
+ *
+ * @since 4.0
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public interface CoreConnectionPNames {
+
+ /**
+ * Defines the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the
+ * timeout for waiting for data. A timeout value of zero is interpreted as an infinite
+ * timeout. This value is used when no socket timeout is set in the
+ * method parameters.
+ * <p>
+ * This parameter expects a value of type {@link Integer}.
+ * </p>
+ * @see java.net.SocketOptions#SO_TIMEOUT
+ */
+ public static final String SO_TIMEOUT = "http.socket.timeout";
+
+ /**
+ * Determines whether Nagle's algorithm is to be used. The Nagle's algorithm
+ * tries to conserve bandwidth by minimizing the number of segments that are
+ * sent. When applications wish to decrease network latency and increase
+ * performance, they can disable Nagle's algorithm (that is enable TCP_NODELAY).
+ * Data will be sent earlier, at the cost of an increase in bandwidth consumption.
+ * <p>
+ * This parameter expects a value of type {@link Boolean}.
+ * </p>
+ * @see java.net.SocketOptions#TCP_NODELAY
+ */
+ public static final String TCP_NODELAY = "http.tcp.nodelay";
+
+ /**
+ * Determines the size of the internal socket buffer used to buffer data
+ * while receiving / transmitting HTTP messages.
+ * <p>
+ * This parameter expects a value of type {@link Integer}.
+ * </p>
+ */
+ public static final String SOCKET_BUFFER_SIZE = "http.socket.buffer-size";
+
+ /**
+ * Sets SO_LINGER with the specified linger time in seconds. The maximum timeout
+ * value is platform specific. Value <tt>0</tt> implies that the option is disabled.
+ * Value <tt>-1</tt> implies that the JRE default is used. The setting only affects
+ * socket close.
+ * <p>
+ * This parameter expects a value of type {@link Integer}.
+ * </p>
+ * @see java.net.SocketOptions#SO_LINGER
+ */
+ public static final String SO_LINGER = "http.socket.linger";
+
+ /**
+ * Determines the timeout until a connection is etablished. A value of zero
+ * means the timeout is not used. The default value is zero.
+ * <p>
+ * This parameter expects a value of type {@link Integer}.
+ * </p>
+ */
+ public static final String CONNECTION_TIMEOUT = "http.connection.timeout";
+
+ /**
+ * Determines whether stale connection check is to be used. Disabling
+ * stale connection check may result in slight performance improvement
+ * at the risk of getting an I/O error when executing a request over a
+ * connection that has been closed at the server side.
+ * <p>
+ * This parameter expects a value of type {@link Boolean}.
+ * </p>
+ */
+ public static final String STALE_CONNECTION_CHECK = "http.connection.stalecheck";
+
+ /**
+ * Determines the maximum line length limit. If set to a positive value, any HTTP
+ * line exceeding this limit will cause an IOException. A negative or zero value
+ * will effectively disable the check.
+ * <p>
+ * This parameter expects a value of type {@link Integer}.
+ * </p>
+ */
+ public static final String MAX_LINE_LENGTH = "http.connection.max-line-length";
+
+ /**
+ * Determines the maximum HTTP header count allowed. If set to a positive value,
+ * the number of HTTP headers received from the data stream exceeding this limit
+ * will cause an IOException. A negative or zero value will effectively disable
+ * the check.
+ * <p>
+ * This parameter expects a value of type {@link Integer}.
+ * </p>
+ */
+ public static final String MAX_HEADER_COUNT = "http.connection.max-header-count";
+
+}
diff --git a/core/java/org/apache/http/params/HttpConnectionParams.java b/core/java/org/apache/http/params/HttpConnectionParams.java
new file mode 100644
index 0000000..a7b31fc
--- /dev/null
+++ b/core/java/org/apache/http/params/HttpConnectionParams.java
@@ -0,0 +1,229 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpConnectionParams.java $
+ * $Revision: 576089 $
+ * $Date: 2007-09-16 05:39:56 -0700 (Sun, 16 Sep 2007) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.params;
+
+/**
+ * An adaptor for accessing connection parameters in {@link HttpParams}.
+ * <br/>
+ * Note that the <i>implements</i> relation to {@link CoreConnectionPNames}
+ * is for compatibility with existing application code only. References to
+ * the parameter names should use the interface, not this class.
+ *
+ * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ *
+ * @version $Revision: 576089 $
+ *
+ * @since 4.0
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * 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 HttpConnectionParams implements CoreConnectionPNames {
+
+ /**
+ */
+ private HttpConnectionParams() {
+ super();
+ }
+
+ /**
+ * Returns the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the
+ * timeout for waiting for data. A timeout value of zero is interpreted as an infinite
+ * timeout. This value is used when no socket timeout is set in the
+ * method parameters.
+ *
+ * @return timeout in milliseconds
+ */
+ public static int getSoTimeout(final HttpParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ return params.getIntParameter(CoreConnectionPNames.SO_TIMEOUT, 0);
+ }
+
+ /**
+ * Sets the default socket timeout (<tt>SO_TIMEOUT</tt>) in milliseconds which is the
+ * timeout for waiting for data. A timeout value of zero is interpreted as an infinite
+ * timeout. This value is used when no socket timeout is set in the
+ * method parameters.
+ *
+ * @param timeout Timeout in milliseconds
+ */
+ public static void setSoTimeout(final HttpParams params, int timeout) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
+
+ }
+
+ /**
+ * Tests if Nagle's algorithm is to be used.
+ *
+ * @return <tt>true</tt> if the Nagle's algorithm is to NOT be used
+ * (that is enable TCP_NODELAY), <tt>false</tt> otherwise.
+ */
+ public static boolean getTcpNoDelay(final HttpParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ return params.getBooleanParameter
+ (CoreConnectionPNames.TCP_NODELAY, true);
+ }
+
+ /**
+ * Determines whether Nagle's algorithm is to be used. The Nagle's algorithm
+ * tries to conserve bandwidth by minimizing the number of segments that are
+ * sent. When applications wish to decrease network latency and increase
+ * performance, they can disable Nagle's algorithm (that is enable TCP_NODELAY).
+ * Data will be sent earlier, at the cost of an increase in bandwidth consumption.
+ *
+ * @param value <tt>true</tt> if the Nagle's algorithm is to NOT be used
+ * (that is enable TCP_NODELAY), <tt>false</tt> otherwise.
+ */
+ public static void setTcpNoDelay(final HttpParams params, boolean value) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, value);
+ }
+
+ public static int getSocketBufferSize(final HttpParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ return params.getIntParameter
+ (CoreConnectionPNames.SOCKET_BUFFER_SIZE, -1);
+ }
+
+ public static void setSocketBufferSize(final HttpParams params, int size) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ params.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, size);
+ }
+
+ /**
+ * Returns linger-on-close timeout. Value <tt>0</tt> implies that the option is
+ * disabled. Value <tt>-1</tt> implies that the JRE default is used.
+ *
+ * @return the linger-on-close timeout
+ */
+ public static int getLinger(final HttpParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ return params.getIntParameter(CoreConnectionPNames.SO_LINGER, -1);
+ }
+
+ /**
+ * Returns linger-on-close timeout. This option disables/enables immediate return
+ * from a close() of a TCP Socket. Enabling this option with a non-zero Integer
+ * timeout means that a close() will block pending the transmission and
+ * acknowledgement of all data written to the peer, at which point the socket is
+ * closed gracefully. Value <tt>0</tt> implies that the option is
+ * disabled. Value <tt>-1</tt> implies that the JRE default is used.
+ *
+ * @param value the linger-on-close timeout
+ */
+ public static void setLinger(final HttpParams params, int value) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ params.setIntParameter(CoreConnectionPNames.SO_LINGER, value);
+ }
+
+ /**
+ * Returns the timeout until a connection is etablished. A value of zero
+ * means the timeout is not used. The default value is zero.
+ *
+ * @return timeout in milliseconds.
+ */
+ public static int getConnectionTimeout(final HttpParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ return params.getIntParameter
+ (CoreConnectionPNames.CONNECTION_TIMEOUT, 0);
+ }
+
+ /**
+ * Sets the timeout until a connection is etablished. A value of zero
+ * means the timeout is not used. The default value is zero.
+ *
+ * @param timeout Timeout in milliseconds.
+ */
+ public static void setConnectionTimeout(final HttpParams params, int timeout) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ params.setIntParameter
+ (CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
+ }
+
+ /**
+ * Tests whether stale connection check is to be used. Disabling
+ * stale connection check may result in slight performance improvement
+ * at the risk of getting an I/O error when executing a request over a
+ * connection that has been closed at the server side.
+ *
+ * @return <tt>true</tt> if stale connection check is to be used,
+ * <tt>false</tt> otherwise.
+ */
+ public static boolean isStaleCheckingEnabled(final HttpParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ return params.getBooleanParameter
+ (CoreConnectionPNames.STALE_CONNECTION_CHECK, true);
+ }
+
+ /**
+ * Defines whether stale connection check is to be used. Disabling
+ * stale connection check may result in slight performance improvement
+ * at the risk of getting an I/O error when executing a request over a
+ * connection that has been closed at the server side.
+ *
+ * @param value <tt>true</tt> if stale connection check is to be used,
+ * <tt>false</tt> otherwise.
+ */
+ public static void setStaleCheckingEnabled(final HttpParams params, boolean value) {
+ if (params == null) {
+ throw new IllegalArgumentException("HTTP parameters may not be null");
+ }
+ params.setBooleanParameter
+ (CoreConnectionPNames.STALE_CONNECTION_CHECK, value);
+ }
+
+}
diff --git a/core/java/org/apache/http/params/HttpParams.java b/core/java/org/apache/http/params/HttpParams.java
new file mode 100644
index 0000000..9562e54
--- /dev/null
+++ b/core/java/org/apache/http/params/HttpParams.java
@@ -0,0 +1,192 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/params/HttpParams.java $
+ * $Revision: 610763 $
+ * $Date: 2008-01-10 04:01:13 -0800 (Thu, 10 Jan 2008) $
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.params;
+
+/**
+ * Represents a collection of HTTP protocol and framework parameters.
+ *
+ * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ *
+ * @version $Revision: 610763 $
+ *
+ * @since 4.0
+ *
+ * @deprecated Please use {@link java.net.URL#openConnection} instead.
+ * Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
+ * for further details.
+ */
+@Deprecated
+public interface HttpParams {
+
+ /**
+ * Obtains the value of the given parameter.
+ *
+ * @param name the parent name.
+ *
+ * @return an object that represents the value of the parameter,
+ * <code>null</code> if the parameter is not set or if it
+ * is explicitly set to <code>null</code>
+ *
+ * @see #setParameter(String, Object)
+ */
+ Object getParameter(String name);
+
+ /**
+ * Assigns the value to the parameter with the given name.
+ *
+ * @param name parameter name
+ * @param value parameter value
+ */
+ HttpParams setParameter(String name, Object value);
+
+ /**
+ * Creates a copy of these parameters.
+ *
+ * @return a new set of parameters holding the same values as this one
+ */
+ HttpParams copy();
+
+ /**
+ * Removes the parameter with the specified name.
+ *
+ * @param name parameter name
+ *
+ * @return true if the parameter existed and has been removed, false else.
+ */
+ boolean removeParameter(String name);
+
+ /**
+ * Returns a {@link Long} parameter value with the given name.
+ * If the parameter is not explicitly set, the default value is returned.
+ *
+ * @param name the parent name.
+ * @param defaultValue the default value.
+ *
+ * @return a {@link Long} that represents the value of the parameter.
+ *
+ * @see #setLongParameter(String, long)
+ */
+ long getLongParameter(String name, long defaultValue);
+
+ /**
+ * Assigns a {@link Long} to the parameter with the given name
+ *
+ * @param name parameter name
+ * @param value parameter value
+ */
+ HttpParams setLongParameter(String name, long value);
+
+ /**
+ * Returns an {@link Integer} parameter value with the given name.
+ * If the parameter is not explicitly set, the default value is returned.
+ *
+ * @param name the parent name.
+ * @param defaultValue the default value.
+ *
+ * @return a {@link Integer} that represents the value of the parameter.
+ *
+ * @see #setIntParameter(String, int)
+ */
+ int getIntParameter(String name, int defaultValue);
+
+ /**
+ * Assigns an {@link Integer} to the parameter with the given name
+ *
+ * @param name parameter name
+ * @param value parameter value
+ */
+ HttpParams setIntParameter(String name, int value);
+
+ /**
+ * Returns a {@link Double} parameter value with the given name.
+ * If the parameter is not explicitly set, the default value is returned.
+ *
+ * @param name the parent name.
+ * @param defaultValue the default value.
+ *
+ * @return a {@link Double} that represents the value of the parameter.
+ *
+ * @see #setDoubleParameter(String, double)
+ */
+ double getDoubleParameter(String name, double defaultValue);
+
+ /**
+ * Assigns a {@link Double} to the parameter with the given name
+ *
+ * @param name parameter name
+ * @param value parameter value
+ */
+ HttpParams setDoubleParameter(String name, double value);
+
+ /**
+ * Returns a {@link Boolean} parameter value with the given name.
+ * If the parameter is not explicitly set, the default value is returned.
+ *
+ * @param name the parent name.
+ * @param defaultValue the default value.
+ *
+ * @return a {@link Boolean} that represents the value of the parameter.
+ *
+ * @see #setBooleanParameter(String, boolean)
+ */
+ boolean getBooleanParameter(String name, boolean defaultValue);
+
+ /**
+ * Assigns a {@link Boolean} to the parameter with the given name
+ *
+ * @param name parameter name
+ * @param value parameter value
+ */
+ HttpParams setBooleanParameter(String name, boolean value);
+
+ /**
+ * Checks if a boolean parameter is set to <code>true</code>.
+ *
+ * @param name parameter name
+ *
+ * @return <tt>true</tt> if the parameter is set to value <tt>true</tt>,
+ * <tt>false</tt> if it is not set or set to <code>false</code>
+ */
+ boolean isParameterTrue(String name);
+
+ /**
+ * Checks if a boolean parameter is not set or <code>false</code>.
+ *
+ * @param name parameter name
+ *
+ * @return <tt>true</tt> if the parameter is either not set or
+ * set to value <tt>false</tt>,
+ * <tt>false</tt> if it is set to <code>true</code>
+ */
+ boolean isParameterFalse(String name);
+
+}