summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-11-17 16:38:15 -0500
committerJustin Klaassen <justinklaassen@google.com>2017-11-17 16:38:15 -0500
commit6a65f2da209bff03cb0eb6da309710ac6ee5026d (patch)
tree48e2090e716d4178378cb0599fc5d9cffbcf3f63 /android
parent46c77c203439b3b37c99d09e326df4b1fe08c10b (diff)
downloadandroid-28-6a65f2da209bff03cb0eb6da309710ac6ee5026d.tar.gz
Import Android SDK Platform P [4456821]
/google/data/ro/projects/android/fetch_artifact \ --bid 4456821 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4456821.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: I2d206b200d7952f899a5d1647ab532638cc8dd43
Diffstat (limited to 'android')
-rw-r--r--android/animation/ValueAnimator.java31
-rw-r--r--android/app/Activity.java91
-rw-r--r--android/app/ActivityManager.java40
-rw-r--r--android/app/ActivityManagerInternal.java21
-rw-r--r--android/app/ActivityOptions.java22
-rw-r--r--android/app/ActivityThread.java35
-rw-r--r--android/app/AppOpsManager.java12
-rw-r--r--android/app/DexLoadReporter.java24
-rw-r--r--android/app/DialogFragment.java3
-rw-r--r--android/app/Fragment.java12
-rw-r--r--android/app/FragmentBreadCrumbs.java3
-rw-r--r--android/app/FragmentContainer.java3
-rw-r--r--android/app/FragmentController.java3
-rw-r--r--android/app/FragmentHostCallback.java3
-rw-r--r--android/app/FragmentManager.java12
-rw-r--r--android/app/FragmentManagerNonConfig.java3
-rw-r--r--android/app/FragmentTransaction.java3
-rw-r--r--android/app/Instrumentation.java44
-rw-r--r--android/app/ListFragment.java3
-rw-r--r--android/app/LoaderManager.java6
-rw-r--r--android/app/Notification.java311
-rw-r--r--android/app/NotificationManager.java6
-rw-r--r--android/app/SystemServiceRegistry.java41
-rw-r--r--android/app/TimePickerDialog.java3
-rw-r--r--android/app/VrManager.java16
-rw-r--r--android/app/WindowConfiguration.java9
-rw-r--r--android/app/admin/DevicePolicyManager.java7
-rw-r--r--android/app/job/JobInfo.java229
-rw-r--r--android/app/slice/Slice.java85
-rw-r--r--android/app/slice/SliceProvider.java90
-rw-r--r--android/app/slice/widget/GridView.java6
-rw-r--r--android/app/slice/widget/LargeTemplateView.java15
-rw-r--r--android/app/slice/widget/ShortcutView.java99
-rw-r--r--android/app/slice/widget/SliceView.java32
-rw-r--r--android/app/slice/widget/SliceViewUtil.java16
-rw-r--r--android/app/usage/UsageStatsManager.java3
-rw-r--r--android/app/usage/UsageStatsManagerInternal.java10
-rw-r--r--android/arch/lifecycle/AndroidViewModel.java1
-rw-r--r--android/arch/lifecycle/ComputableLiveData.java139
-rw-r--r--android/arch/lifecycle/LiveData.java410
-rw-r--r--android/arch/lifecycle/LiveDataReactiveStreams.java244
-rw-r--r--android/arch/lifecycle/LiveDataReactiveStreamsTest.java89
-rw-r--r--android/arch/lifecycle/LiveDataTest.java260
-rw-r--r--android/arch/lifecycle/ViewModelProvider.java1
-rw-r--r--android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java1
-rw-r--r--android/arch/persistence/room/Room.java1
-rw-r--r--android/arch/persistence/room/testing/MigrationTestHelper.java2
-rw-r--r--android/bluetooth/BluetoothSocket.java4
-rw-r--r--android/content/AsyncTaskLoader.java3
-rw-r--r--android/content/ContentProviderClient.java13
-rw-r--r--android/content/Context.java17
-rw-r--r--android/content/CursorLoader.java3
-rw-r--r--android/content/Intent.java16
-rw-r--r--android/content/Loader.java12
-rw-r--r--android/content/SharedPreferences.java5
-rw-r--r--android/content/pm/ActivityInfo.java11
-rw-r--r--android/content/pm/LauncherApps.java20
-rw-r--r--android/content/pm/PackageInstaller.java10
-rw-r--r--android/content/pm/PackageParser.java25
-rw-r--r--android/content/pm/ShortcutServiceInternal.java3
-rw-r--r--android/content/pm/crossprofile/CrossProfileApps.java90
-rw-r--r--android/content/res/AssetManager.java33
-rw-r--r--android/content/res/Configuration.java1
-rw-r--r--android/content/res/FontResourcesParser.java12
-rw-r--r--android/content/res/ResourcesImpl.java66
-rw-r--r--android/content/res/Resources_Delegate.java33
-rw-r--r--android/database/MergeCursor.java2
-rw-r--r--android/database/sqlite/SQLiteConnection.java26
-rw-r--r--android/database/sqlite/SQLiteCursor.java30
-rw-r--r--android/database/sqlite/SQLiteDatabase.java83
-rw-r--r--android/database/sqlite/SQLiteDatabaseConfiguration.java24
-rw-r--r--android/database/sqlite/SQLiteGlobal.java10
-rw-r--r--android/database/sqlite/SQLiteOpenHelper.java51
-rw-r--r--android/ext/services/notification/Assistant.java190
-rw-r--r--android/ext/services/notification/ChannelImpressions.java49
-rw-r--r--android/graphics/Paint.java16
-rw-r--r--android/graphics/Paint_Delegate.java2
-rw-r--r--android/graphics/Rect.java13
-rw-r--r--android/graphics/Typeface.java4
-rw-r--r--android/graphics/Typeface_Delegate.java75
-rw-r--r--android/graphics/drawable/AnimatedVectorDrawable.java5
-rw-r--r--android/graphics/drawable/RippleBackground.java137
-rw-r--r--android/graphics/drawable/RippleComponent.java244
-rw-r--r--android/graphics/drawable/RippleDrawable.java130
-rw-r--r--android/graphics/drawable/RippleForeground.java344
-rw-r--r--android/graphics/drawable/VectorDrawable.java84
-rw-r--r--android/hardware/camera2/CameraCaptureSession.java40
-rw-r--r--android/hardware/camera2/impl/CameraCaptureSessionImpl.java14
-rw-r--r--android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java7
-rw-r--r--android/hardware/camera2/impl/CameraDeviceImpl.java18
-rw-r--r--android/hardware/camera2/impl/ICameraDeviceUserWrapper.java10
-rw-r--r--android/hardware/camera2/legacy/CameraDeviceUserShim.java5
-rw-r--r--android/hardware/camera2/params/OutputConfiguration.java102
-rw-r--r--android/hardware/display/BrightnessChangeEvent.java103
-rw-r--r--android/hardware/display/DisplayManager.java16
-rw-r--r--android/hardware/display/DisplayManagerGlobal.java30
-rw-r--r--android/hardware/location/ContextHubManager.java55
-rw-r--r--android/hardware/location/ContextHubTransaction.java152
-rw-r--r--android/hardware/location/NanoAppBinary.java56
-rw-r--r--android/hardware/sidekick/SidekickInternal.java56
-rw-r--r--android/hardware/usb/AccessoryFilter.java145
-rw-r--r--android/hardware/usb/DeviceFilter.java313
-rw-r--r--android/hardware/usb/UsbManager.java4
-rw-r--r--android/location/Location.java4
-rw-r--r--android/media/AudioAttributes.java1
-rw-r--r--android/media/AudioManager.java26
-rw-r--r--android/media/BufferingParams.java384
-rw-r--r--android/media/ExifInterface.java83
-rw-r--r--android/media/MediaDrm.java2
-rw-r--r--android/media/MediaFormat.java90
-rw-r--r--android/media/MediaMetadataRetriever.java162
-rw-r--r--android/media/MediaPlayer.java180
-rw-r--r--android/net/ConnectivityManager.java29
-rw-r--r--android/net/ConnectivityMetricsEvent.java3
-rw-r--r--android/net/IpSecAlgorithm.java6
-rw-r--r--android/net/IpSecManager.java2
-rw-r--r--android/net/LocalSocketImpl.java18
-rw-r--r--android/net/MacAddress.java274
-rw-r--r--android/net/Network.java9
-rw-r--r--android/net/NetworkCapabilities.java119
-rw-r--r--android/net/NetworkIdentity.java4
-rw-r--r--android/net/NetworkInfo.java13
-rw-r--r--android/net/NetworkWatchlistManager.java75
-rw-r--r--android/net/SSLCertificateSocketFactory.java7
-rw-r--r--android/net/Uri.java6
-rw-r--r--android/net/apf/ApfFilter.java31
-rw-r--r--android/net/ip/IpClient.java18
-rw-r--r--android/net/ip/IpManager.java10
-rw-r--r--android/net/metrics/ConnectStats.java3
-rw-r--r--android/net/metrics/DefaultNetworkEvent.java66
-rw-r--r--android/net/metrics/DnsEvent.java3
-rw-r--r--android/net/metrics/NetworkEvent.java14
-rw-r--r--android/net/metrics/WakeupEvent.java33
-rw-r--r--android/net/metrics/WakeupStats.java67
-rw-r--r--android/net/wifi/WifiInfo.java3
-rw-r--r--android/net/wifi/WifiManager.java55
-rw-r--r--android/net/wifi/rtt/RangingResultCallback.java10
-rw-r--r--android/net/wifi/rtt/WifiRttManager.java87
-rw-r--r--android/os/BatteryManager.java42
-rw-r--r--android/os/BatteryStatsInternal.java35
-rw-r--r--android/os/Binder.java13
-rw-r--r--android/os/Binder_Delegate.java54
-rw-r--r--android/os/ConfigUpdate.java7
-rw-r--r--android/os/Debug.java66
-rw-r--r--android/os/Environment.java5
-rw-r--r--android/os/FileUtils.java13
-rw-r--r--android/os/HidlSupport.java23
-rw-r--r--android/os/HwBinder.java13
-rw-r--r--android/os/HwBlob.java20
-rw-r--r--android/os/HwRemoteBinder.java5
-rw-r--r--android/os/LocaleList.java6
-rw-r--r--android/os/Parcel.java2
-rw-r--r--android/os/ParcelFileDescriptor.java2
-rw-r--r--android/os/PowerManager.java52
-rw-r--r--android/os/PowerManagerInternal.java19
-rw-r--r--android/os/PowerSaveState.java2
-rw-r--r--android/os/RemoteCallbackList.java8
-rw-r--r--android/os/ShellCallback.java23
-rw-r--r--android/os/ShellCommand.java6
-rw-r--r--android/os/StrictMode.java964
-rw-r--r--android/os/SystemClock.java56
-rw-r--r--android/os/TokenWatcher.java30
-rw-r--r--android/os/UpdateEngine.java1
-rw-r--r--android/os/UserHandle.java55
-rw-r--r--android/os/UserManager.java63
-rw-r--r--android/os/storage/StorageManager.java29
-rw-r--r--android/os/strictmode/CleartextNetworkViolation.java (renamed from android/telephony/ims/feature/IRcsFeature.java)17
-rw-r--r--android/os/strictmode/ContentUriWithoutPermissionViolation.java30
-rw-r--r--android/os/strictmode/CustomViolation.java23
-rw-r--r--android/os/strictmode/DiskReadViolation.java23
-rw-r--r--android/os/strictmode/DiskWriteViolation.java23
-rw-r--r--android/os/strictmode/FileUriExposedViolation.java23
-rw-r--r--android/os/strictmode/InstanceCountViolation.java36
-rw-r--r--android/os/strictmode/IntentReceiverLeakedViolation.java24
-rw-r--r--android/os/strictmode/LeakedClosableViolation.java24
-rw-r--r--android/os/strictmode/NetworkViolation.java23
-rw-r--r--android/os/strictmode/ResourceMismatchViolation.java23
-rw-r--r--android/os/strictmode/ServiceConnectionLeakedViolation.java24
-rw-r--r--android/os/strictmode/SqliteObjectLeakedViolation.java25
-rw-r--r--android/os/strictmode/UnbufferedIoViolation.java28
-rw-r--r--android/os/strictmode/UntaggedSocketViolation.java28
-rw-r--r--android/os/strictmode/Violation.java24
-rw-r--r--android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java24
-rw-r--r--android/preference/PreferenceFragment.java8
-rw-r--r--android/provider/MediaStore.java19
-rw-r--r--android/provider/Settings.java156
-rw-r--r--android/provider/Telephony.java85
-rw-r--r--android/security/KeyStore.java54
-rw-r--r--android/service/autofill/FieldsDetection.java127
-rw-r--r--android/service/autofill/FillEventHistory.java57
-rw-r--r--android/service/autofill/FillResponse.java45
-rw-r--r--android/service/dreams/DreamService.java26
-rw-r--r--android/service/euicc/EuiccService.java9
-rw-r--r--android/service/notification/ConditionProviderService.java10
-rw-r--r--android/service/notification/NotificationListenerService.java3
-rw-r--r--android/service/voice/VoiceInteractionSession.java19
-rw-r--r--android/support/LibraryGroups.java30
-rw-r--r--android/support/LibraryVersions.java2
-rw-r--r--android/support/SourceJarTaskHelper.java60
-rw-r--r--android/support/car/drawer/CarDrawerActivity.java2
-rw-r--r--android/support/car/drawer/CarDrawerController.java55
-rw-r--r--android/support/car/utils/ColumnCalculator.java2
-rw-r--r--android/support/car/widget/CarItemAnimator.java4
-rw-r--r--android/support/car/widget/CarRecyclerView.java10
-rw-r--r--android/support/car/widget/PagedLayoutManager.java (renamed from android/support/car/widget/CarLayoutManager.java)65
-rw-r--r--android/support/car/widget/PagedListView.java267
-rw-r--r--android/support/car/widget/PagedScrollBarView.java13
-rw-r--r--android/support/mediacompat/testlib/IntentConstants.java34
-rw-r--r--android/support/mediacompat/testlib/MediaSessionConstants.java1
-rw-r--r--android/support/mediacompat/testlib/VersionConstants.java25
-rw-r--r--android/support/mediacompat/testlib/util/IntentUtil.java131
-rw-r--r--android/support/mediacompat/testlib/util/PollingCheck.java98
-rw-r--r--android/support/mediacompat/testlib/util/TestUtil.java41
-rw-r--r--android/support/text/emoji/widget/EmojiAppCompatEditText.java8
-rw-r--r--android/support/text/emoji/widget/EmojiEditText.java8
-rw-r--r--android/support/text/emoji/widget/EmojiExtractEditText.java8
-rw-r--r--android/support/transition/Transition.java4
-rw-r--r--android/support/v17/leanback/app/BaseFragment.java2
-rw-r--r--android/support/v17/leanback/app/BaseRowFragment.java14
-rw-r--r--android/support/v17/leanback/app/BaseRowSupportFragment.java12
-rw-r--r--android/support/v17/leanback/app/BrandedFragment.java2
-rw-r--r--android/support/v17/leanback/app/BrowseFragment.java153
-rw-r--r--android/support/v17/leanback/app/BrowseSupportFragment.java133
-rw-r--r--android/support/v17/leanback/app/DetailsFragment.java2
-rw-r--r--android/support/v17/leanback/app/DetailsFragmentBackgroundController.java2
-rw-r--r--android/support/v17/leanback/app/ErrorFragment.java2
-rw-r--r--android/support/v17/leanback/app/GuidedStepFragment.java17
-rw-r--r--android/support/v17/leanback/app/GuidedStepSupportFragment.java15
-rw-r--r--android/support/v17/leanback/app/HeadersFragment.java6
-rw-r--r--android/support/v17/leanback/app/ListRowDataAdapter.java16
-rw-r--r--android/support/v17/leanback/app/OnboardingFragment.java2
-rw-r--r--android/support/v17/leanback/app/PlaybackFragment.java4
-rw-r--r--android/support/v17/leanback/app/PlaybackFragmentGlueHost.java2
-rw-r--r--android/support/v17/leanback/app/RowsFragment.java4
-rw-r--r--android/support/v17/leanback/app/SearchFragment.java2
-rw-r--r--android/support/v17/leanback/app/VerticalGridFragment.java2
-rw-r--r--android/support/v17/leanback/app/VideoFragment.java2
-rw-r--r--android/support/v17/leanback/app/VideoFragmentGlueHost.java2
-rw-r--r--android/support/v17/leanback/widget/ArrayObjectAdapter.java60
-rw-r--r--android/support/v17/leanback/widget/BaseGridView.java4
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java422
-rw-r--r--android/support/v17/leanback/widget/GuidedActionAdapter.java71
-rw-r--r--android/support/v17/leanback/widget/GuidedActionDiffCallback.java65
-rw-r--r--android/support/v17/leanback/widget/ObjectAdapter.java15
-rw-r--r--android/support/v4/app/FragmentActivity.java22
-rw-r--r--android/support/v4/graphics/TypefaceCompat.java5
-rw-r--r--android/support/v4/graphics/TypefaceCompatApi26Impl.java156
-rw-r--r--android/support/v4/graphics/TypefaceCompatApi28Impl.java68
-rw-r--r--android/support/v4/media/MediaBrowserCompat.java30
-rw-r--r--android/support/v4/media/MediaBrowserServiceCompat.java12
-rw-r--r--android/support/v4/media/MediaMetadataCompat.java2
-rw-r--r--android/support/v4/view/ViewCompat.java4
-rw-r--r--android/support/v7/app/AppCompatDelegateImplV9.java21
-rw-r--r--android/support/v7/app/AppCompatViewInflater.java138
-rw-r--r--android/support/v7/util/SortedList.java44
-rw-r--r--android/support/v7/util/SortedListBatchedCallbackTest.java10
-rw-r--r--android/support/v7/util/SortedListTest.java124
-rw-r--r--android/support/v7/widget/AppCompatTextHelper.java6
-rw-r--r--android/support/v7/widget/TooltipCompatHandler.java39
-rw-r--r--android/support/v7/widget/util/SortedListAdapterCallback.java5
-rw-r--r--android/support/wear/ambient/AmbientDelegate.java42
-rw-r--r--android/support/wear/ambient/AmbientMode.java15
-rw-r--r--android/support/wear/ambient/SharedLibraryVersion.java4
-rw-r--r--android/support/wear/ambient/WearableControllerProvider.java2
-rw-r--r--android/support/wear/internal/widget/ResourcesUtil.java2
-rw-r--r--android/support/wear/internal/widget/drawer/MultiPagePresenter.java2
-rw-r--r--android/support/wear/internal/widget/drawer/MultiPageUi.java27
-rw-r--r--android/support/wear/internal/widget/drawer/SinglePagePresenter.java2
-rw-r--r--android/support/wear/internal/widget/drawer/SinglePageUi.java7
-rw-r--r--android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java2
-rw-r--r--android/support/wear/utils/MetadataConstants.java3
-rw-r--r--android/support/wear/widget/BezierSCurveInterpolator.java5
-rw-r--r--android/support/wear/widget/BoxInsetLayout.java30
-rw-r--r--android/support/wear/widget/CircledImageView.java15
-rw-r--r--android/support/wear/widget/CurvingLayoutCallback.java2
-rw-r--r--android/support/wear/widget/ProgressDrawable.java5
-rw-r--r--android/support/wear/widget/RoundedDrawable.java3
-rw-r--r--android/support/wear/widget/ScrollManager.java8
-rw-r--r--android/support/wear/widget/SimpleAnimatorListener.java2
-rw-r--r--android/support/wear/widget/SwipeDismissLayout.java5
-rw-r--r--android/support/wear/widget/WearableRecyclerView.java3
-rw-r--r--android/support/wear/widget/drawer/AbsListViewFlingWatcher.java2
-rw-r--r--android/support/wear/widget/drawer/FlingWatcherFactory.java2
-rw-r--r--android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java2
-rw-r--r--android/support/wear/widget/drawer/PageIndicatorView.java2
-rw-r--r--android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java2
-rw-r--r--android/support/wear/widget/drawer/ScrollViewFlingWatcher.java2
-rw-r--r--android/support/wear/widget/drawer/WearableActionDrawerMenu.java3
-rw-r--r--android/support/wear/widget/drawer/WearableActionDrawerView.java23
-rw-r--r--android/support/wear/widget/drawer/WearableDrawerLayout.java43
-rw-r--r--android/support/wear/widget/drawer/WearableDrawerView.java9
-rw-r--r--android/support/wear/widget/drawer/WearableNavigationDrawerView.java7
-rw-r--r--android/support/wearable/watchface/decomposition/package-info.java2
-rw-r--r--android/system/Int32Ref.java28
-rw-r--r--android/system/Int64Ref.java28
-rw-r--r--android/system/Os.java101
-rw-r--r--android/system/UnixSocketAddressTest.java51
-rw-r--r--android/telecom/Call.java66
-rw-r--r--android/telecom/CallAudioState.java90
-rw-r--r--android/telecom/Connection.java50
-rw-r--r--android/telecom/ConnectionService.java77
-rw-r--r--android/telecom/ConnectionServiceAdapter.java9
-rw-r--r--android/telecom/ConnectionServiceAdapterServant.java10
-rw-r--r--android/telecom/InCallAdapter.java35
-rw-r--r--android/telecom/InCallService.java18
-rw-r--r--android/telecom/Phone.java14
-rw-r--r--android/telecom/PhoneAccount.java10
-rw-r--r--android/telecom/RemoteConnectionService.java3
-rw-r--r--android/telecom/TelecomManager.java35
-rw-r--r--android/telephony/CarrierConfigManager.java88
-rw-r--r--android/telephony/PhoneStateListener.java27
-rw-r--r--android/telephony/SignalStrength.java61
-rw-r--r--android/telephony/SmsManager.java253
-rw-r--r--android/telephony/SmsMessage.java26
-rw-r--r--android/telephony/SubscriptionManager.java36
-rw-r--r--android/telephony/TelephonyManager.java55
-rw-r--r--android/telephony/TelephonyScanManager.java4
-rw-r--r--android/telephony/euicc/DownloadableSubscription.java19
-rw-r--r--android/telephony/ims/ImsServiceProxy.java335
-rw-r--r--android/telephony/ims/ImsServiceProxyCompat.java183
-rw-r--r--android/telephony/ims/feature/IMMTelFeature.java187
-rw-r--r--android/telephony/ims/feature/MMTelFeature.java135
-rw-r--r--android/telephony/ims/feature/RcsFeature.java4
-rw-r--r--android/telephony/mbms/DownloadStateCallback.java2
-rw-r--r--android/telephony/mbms/FileInfo.java23
-rw-r--r--android/telephony/mbms/FileServiceInfo.java2
-rw-r--r--android/telephony/mbms/MbmsDownloadReceiver.java11
-rw-r--r--android/telephony/mbms/UriPathPair.java2
-rw-r--r--android/telephony/mbms/vendor/MbmsDownloadServiceBase.java2
-rw-r--r--android/telephony/mbms/vendor/VendorUtils.java2
-rw-r--r--android/util/FeatureFlagUtils.java3
-rw-r--r--android/util/KeyValueListParser.java14
-rw-r--r--android/util/Log.java295
-rw-r--r--android/util/StatsManager.java134
-rw-r--r--android/util/TimeUtils.java8
-rw-r--r--android/util/apk/ApkSignatureSchemeV2Verifier.java192
-rw-r--r--android/util/apk/ApkVerityBuilder.java351
-rw-r--r--android/util/apk/ByteBufferDataSource.java66
-rw-r--r--android/util/apk/ByteBufferFactory.java28
-rw-r--r--android/util/apk/DataDigester.java28
-rw-r--r--android/util/apk/DataSource.java38
-rw-r--r--android/util/apk/MemoryMappedFileDataSource.java105
-rw-r--r--android/util/apk/SignatureInfo.java49
-rw-r--r--android/util/proto/ProtoOutputStream.java82
-rw-r--r--android/view/Display.java29
-rw-r--r--android/view/DisplayFrames.java189
-rw-r--r--android/view/FocusFinder.java2
-rw-r--r--android/view/IWindowManagerImpl.java6
-rw-r--r--android/view/NotificationHeaderView.java14
-rw-r--r--android/view/RenderNode.java7
-rw-r--r--android/view/RenderNodeAnimator.java10
-rw-r--r--android/view/SurfaceControl.java34
-rw-r--r--android/view/ThreadedRenderer.java7
-rw-r--r--android/view/View.java22
-rw-r--r--android/view/ViewConfiguration.java2
-rw-r--r--android/view/ViewRootImpl.java2
-rw-r--r--android/view/WindowManager.java17
-rw-r--r--android/view/WindowManagerGlobal.java3
-rw-r--r--android/view/WindowManagerInternal.java33
-rw-r--r--android/view/WindowManagerPolicy.java65
-rw-r--r--android/view/accessibility/AccessibilityInteractionClient.java91
-rw-r--r--android/view/accessibility/AccessibilityManager.java7
-rw-r--r--android/view/autofill/AutofillManager.java52
-rw-r--r--android/view/textclassifier/TextClassification.java28
-rw-r--r--android/view/textclassifier/TextClassifier.java84
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java275
-rw-r--r--android/view/textclassifier/TextLinks.java252
-rw-r--r--android/view/textclassifier/TextSelection.java53
-rw-r--r--android/view/textclassifier/logging/SmartSelectionEventTracker.java179
-rw-r--r--android/webkit/ServiceWorkerClient.java6
-rw-r--r--android/webkit/WebView.java43
-rw-r--r--android/webkit/WebViewClient.java57
-rw-r--r--android/webkit/WebViewFragment.java3
-rw-r--r--android/webkit/WebViewLibraryLoader.java4
-rw-r--r--android/webkit/WebViewProvider.java2
-rw-r--r--android/widget/AbsListView.java9
-rw-r--r--android/widget/Editor.java14
-rw-r--r--android/widget/PopupWindow.java1
-rw-r--r--android/widget/SelectionActionModeHelper.java59
379 files changed, 12364 insertions, 5717 deletions
diff --git a/android/animation/ValueAnimator.java b/android/animation/ValueAnimator.java
index ee89ca8d..cc95eb6f 100644
--- a/android/animation/ValueAnimator.java
+++ b/android/animation/ValueAnimator.java
@@ -254,6 +254,11 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
HashMap<String, PropertyValuesHolder> mValuesMap;
/**
+ * If set to non-negative value, this will override {@link #sDurationScale}.
+ */
+ private float mDurationScale = -1f;
+
+ /**
* Public constants
*/
@@ -579,8 +584,23 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
return this;
}
+ /**
+ * Overrides the global duration scale by a custom value.
+ *
+ * @param durationScale The duration scale to set; or {@code -1f} to use the global duration
+ * scale.
+ * @hide
+ */
+ public void overrideDurationScale(float durationScale) {
+ mDurationScale = durationScale;
+ }
+
+ private float resolveDurationScale() {
+ return mDurationScale >= 0f ? mDurationScale : sDurationScale;
+ }
+
private long getScaledDuration() {
- return (long)(mDuration * sDurationScale);
+ return (long)(mDuration * resolveDurationScale());
}
/**
@@ -735,7 +755,10 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
if (mSeekFraction >= 0) {
return (long) (mDuration * mSeekFraction);
}
- float durationScale = sDurationScale == 0 ? 1 : sDurationScale;
+ float durationScale = resolveDurationScale();
+ if (durationScale == 0f) {
+ durationScale = 1f;
+ }
return (long) ((AnimationUtils.currentAnimationTimeMillis() - mStartTime) / durationScale);
}
@@ -1397,7 +1420,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
- mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
+ mStartTime = mReversing
+ ? frameTime
+ : frameTime + (long) (mStartDelay * resolveDurationScale());
}
// Handle pause/resume
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 9d331a02..99f3dee7 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -16,8 +16,6 @@
package android.app;
-import static android.os.Build.VERSION_CODES.O_MR1;
-
import static java.lang.Character.MIN_VALUE;
import android.annotation.CallSuper;
@@ -136,6 +134,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+
/**
* An activity is a single, focused thing that the user can do. Almost all
* activities interact with the user, so the Activity class takes care of
@@ -194,10 +193,13 @@ import java.util.List;
* <a name="Fragments"></a>
* <h3>Fragments</h3>
*
- * <p>Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, Activity
- * implementations can make use of the {@link Fragment} class to better
+ * <p>The {@link android.support.v4.app.FragmentActivity} subclass
+ * can make use of the {@link android.support.v4.app.Fragment} class to better
* modularize their code, build more sophisticated user interfaces for larger
- * screens, and help scale their application between small and large screens.
+ * screens, and help scale their application between small and large screens.</p>
+ *
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p>
*
* <a name="ActivityLifecycle"></a>
* <h3>Activity Lifecycle</h3>
@@ -916,7 +918,10 @@ public class Activity extends ContextThemeWrapper
/**
* Return the LoaderManager for this activity, creating it if needed.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportLoaderManager()}
*/
+ @Deprecated
public LoaderManager getLoaderManager() {
return mFragments.getLoaderManager();
}
@@ -991,17 +996,6 @@ public class Activity extends ContextThemeWrapper
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
- if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) {
- final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
- final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
- ta.recycle();
-
- if (isTranslucentOrFloating) {
- throw new IllegalStateException(
- "Only fullscreen opaque activities can request orientation");
- }
- }
-
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
@@ -2407,7 +2401,10 @@ public class Activity extends ContextThemeWrapper
/**
* Return the FragmentManager for interacting with fragments associated
* with this activity.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}
*/
+ @Deprecated
public FragmentManager getFragmentManager() {
return mFragments.getFragmentManager();
}
@@ -2416,7 +2413,11 @@ public class Activity extends ContextThemeWrapper
* Called when a Fragment is being attached to this activity, immediately
* after the call to its {@link Fragment#onAttach Fragment.onAttach()}
* method and before {@link Fragment#onCreate Fragment.onCreate()}.
+ *
+ * @deprecated Use {@link
+ * android.support.v4.app.FragmentActivity#onAttachFragment(android.support.v4.app.Fragment)}
*/
+ @Deprecated
public void onAttachFragment(Fragment fragment) {
}
@@ -5118,7 +5119,11 @@ public class Activity extends ContextThemeWrapper
*
* @see Fragment#startActivity
* @see Fragment#startActivityForResult
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+ * android.support.v4.app.Fragment,Intent,int)}
*/
+ @Deprecated
public void startActivityFromFragment(@NonNull Fragment fragment,
@RequiresPermission Intent intent, int requestCode) {
startActivityFromFragment(fragment, intent, requestCode, null);
@@ -5143,7 +5148,11 @@ public class Activity extends ContextThemeWrapper
*
* @see Fragment#startActivity
* @see Fragment#startActivityForResult
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+ * android.support.v4.app.Fragment,Intent,int,Bundle)}
*/
+ @Deprecated
public void startActivityFromFragment(@NonNull Fragment fragment,
@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
startActivityForResult(fragment.mWho, intent, requestCode, options);
@@ -7304,24 +7313,25 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Request to put this Activity in a mode where the user is locked to the
- * current task.
+ * Request to put this activity in a mode where the user is locked to a restricted set of
+ * applications.
*
- * This will prevent the user from launching other apps, going to settings, or reaching the
- * home screen. This does not include those apps whose {@link android.R.attr#lockTaskMode}
- * values permit launching while locked.
+ * <p>If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns {@code true}
+ * for this component, the current task will be launched directly into LockTask mode. Only apps
+ * whitelisted by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])} can
+ * be launched while LockTask mode is active. The user will not be able to leave this mode
+ * until this activity calls {@link #stopLockTask()}. Calling this method while the device is
+ * already in LockTask mode has no effect.
*
- * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true or
- * lockTaskMode=lockTaskModeAlways for this component then the app will go directly into
- * Lock Task mode. The user will not be able to exit this mode until
- * {@link Activity#stopLockTask()} is called.
+ * <p>Otherwise, the current task will be launched into screen pinning mode. In this case, the
+ * system will prompt the user with a dialog requesting permission to use this mode.
+ * The user can exit at any time through instructions shown on the request dialog. Calling
+ * {@link #stopLockTask()} will also terminate this mode.
*
- * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false
- * then the system will prompt the user with a dialog requesting permission to enter
- * this mode. When entered through this method the user can exit at any time through
- * an action described by the request dialog. Calling stopLockTask will also exit the
- * mode.
+ * <p><strong>Note:</strong> this method can only be called when the activity is foreground.
+ * That is, between {@link #onResume()} and {@link #onPause()}.
*
+ * @see #stopLockTask()
* @see android.R.attr#lockTaskMode
*/
public void startLockTask() {
@@ -7332,25 +7342,24 @@ public class Activity extends ContextThemeWrapper
}
/**
- * Allow the user to switch away from the current task.
+ * Stop the current task from being locked.
*
- * Called to end the mode started by {@link Activity#startLockTask}. This
- * can only be called by activities that have successfully called
- * startLockTask previously.
+ * <p>Called to end the LockTask or screen pinning mode started by {@link #startLockTask()}.
+ * This can only be called by activities that have called {@link #startLockTask()} previously.
*
- * This will allow the user to exit this app and move onto other activities.
- * <p>Note: This method should only be called when the activity is user-facing. That is,
- * between onResume() and onPause().
- * <p>Note: If there are other tasks below this one that are also locked then calling this
- * method will immediately finish this task and resume the previous locked one, remaining in
- * lockTask mode.
+ * <p><strong>Note:</strong> If the device is in LockTask mode that is not initially started
+ * by this activity, then calling this method will not terminate the LockTask mode, but only
+ * finish its own task. The device will remain in LockTask mode, until the activity which
+ * started the LockTask mode calls this method, or until its whitelist authorization is revoked
+ * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
*
+ * @see #startLockTask()
* @see android.R.attr#lockTaskMode
* @see ActivityManager#getLockTaskModeState()
*/
public void stopLockTask() {
try {
- ActivityManager.getService().stopLockTaskMode();
+ ActivityManager.getService().stopLockTaskModeByToken(mToken);
} catch (RemoteException e) {
}
}
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index 8d9dc1fa..064e9782 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -682,20 +682,23 @@ public class ActivityManager {
}
/**
- * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
- * specifies the position of the created docked stack at the top half of the screen if
+ * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+ * which specifies the position of the created docked stack at the top half of the screen if
* in portrait mode or at the left half of the screen if in landscape mode.
* @hide
*/
- public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0;
+ @TestApi
+ public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
/**
- * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
+ * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+ * which
* specifies the position of the created docked stack at the bottom half of the screen if
* in portrait mode or at the right half of the screen if in landscape mode.
* @hide
*/
- public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
+ @TestApi
+ public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
/**
* Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
@@ -1925,6 +1928,33 @@ public class ActivityManager {
}
/**
+ * Moves the input task to the primary-split-screen stack.
+ * @param taskId Id of task to move.
+ * @param createMode The mode the primary split screen stack should be created in if it doesn't
+ * exist already. See
+ * {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
+ * and
+ * {@link android.app.ActivityManager
+ * #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
+ * @param toTop If the task and stack should be moved to the top.
+ * @param animate Whether we should play an animation for the moving the task
+ * @param initialBounds If the primary stack gets created, it will use these bounds for the
+ * docked stack. Pass {@code null} to use default bounds.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+ boolean animate, Rect initialBounds) throws SecurityException {
+ try {
+ getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate,
+ initialBounds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Resizes the input stack id to the given bounds.
* @param stackId Id of the stack to resize.
* @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java
index 9d14f616..a46b3c72 100644
--- a/android/app/ActivityManagerInternal.java
+++ b/android/app/ActivityManagerInternal.java
@@ -64,6 +64,27 @@ public abstract class ActivityManagerInternal {
public static final int APP_TRANSITION_SNAPSHOT = 4;
/**
+ * The bundle key to extract the assist data.
+ */
+ public static final String ASSIST_KEY_DATA = "data";
+
+ /**
+ * The bundle key to extract the assist structure.
+ */
+ public static final String ASSIST_KEY_STRUCTURE = "structure";
+
+ /**
+ * The bundle key to extract the assist content.
+ */
+ public static final String ASSIST_KEY_CONTENT = "content";
+
+ /**
+ * The bundle key to extract the assist receiver extras.
+ */
+ public static final String ASSIST_KEY_RECEIVER_EXTRAS = "receiverExtras";
+
+
+ /**
* Grant Uri permissions from one app to another. This method only extends
* permission grants if {@code callingUid} has permission to them.
*/
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index b62e4c2d..4a21f5c4 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -16,7 +16,7 @@
package android.app;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
@@ -203,10 +203,11 @@ public class ActivityOptions {
"android.activity.taskOverlayCanResume";
/**
- * Where the docked stack should be positioned.
+ * Where the split-screen-primary stack should be positioned.
* @hide
*/
- private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
+ private static final String KEY_SPLIT_SCREEN_CREATE_MODE =
+ "android:activity.splitScreenCreateMode";
/**
* Determines whether to disallow the outgoing activity from entering picture-in-picture as the
@@ -292,7 +293,7 @@ public class ActivityOptions {
@WindowConfiguration.ActivityType
private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
private int mLaunchTaskId = -1;
- private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mTaskOverlay;
private boolean mTaskOverlayCanResume;
@@ -884,7 +885,8 @@ public class ActivityOptions {
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
- mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+ mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
+ SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
if (opts.containsKey(KEY_ANIM_SPECS)) {
@@ -1194,13 +1196,13 @@ public class ActivityOptions {
}
/** @hide */
- public int getDockCreateMode() {
- return mDockCreateMode;
+ public int getSplitScreenCreateMode() {
+ return mSplitScreenCreateMode;
}
/** @hide */
- public void setDockCreateMode(int dockCreateMode) {
- mDockCreateMode = dockCreateMode;
+ public void setSplitScreenCreateMode(int splitScreenCreateMode) {
+ mSplitScreenCreateMode = splitScreenCreateMode;
}
/** @hide */
@@ -1369,7 +1371,7 @@ public class ActivityOptions {
b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
- b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
+ b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
mDisallowEnterPictureInPictureWhileLaunching);
if (mAnimSpecs != null) {
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index 2516a3e9..21e454f1 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -5533,32 +5533,8 @@ public final class ActivityThread {
View.mDebugViewAttributes =
mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0;
- /**
- * For system applications on userdebug/eng builds, log stack
- * traces of disk and network access to dropbox for analysis.
- */
- if ((data.appInfo.flags &
- (ApplicationInfo.FLAG_SYSTEM |
- ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
- StrictMode.conditionallyEnableDebugLogging();
- }
-
- /**
- * For apps targetting Honeycomb or later, we don't allow network usage
- * on the main event loop / UI thread. This is what ultimately throws
- * {@link NetworkOnMainThreadException}.
- */
- if (data.appInfo.targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
- StrictMode.enableDeathOnNetwork();
- }
-
- /**
- * For apps targetting N or later, we don't allow file:// Uri exposure.
- * This is what ultimately throws {@link FileUriExposedException}.
- */
- if (data.appInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
- StrictMode.enableDeathOnFileUriExposure();
- }
+ StrictMode.initThreadDefaults(data.appInfo);
+ StrictMode.initVmDefaults(data.appInfo);
// We deprecated Build.SERIAL and only apps that target pre NMR1
// SDK can see it. Since access to the serial is now behind a
@@ -5655,7 +5631,12 @@ public final class ActivityThread {
mResourcesManager.getConfiguration().getLocales());
if (!Process.isIsolated()) {
- setupGraphicsSupport(appContext);
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
+ try {
+ setupGraphicsSupport(appContext);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
}
// If we use profiles, setup the dex reporter to notify package manager
diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java
index 4bd85ae9..b6fb1201 100644
--- a/android/app/AppOpsManager.java
+++ b/android/app/AppOpsManager.java
@@ -254,8 +254,10 @@ public class AppOpsManager {
public static final int OP_ANSWER_PHONE_CALLS = 69;
/** @hide Run jobs when in background */
public static final int OP_RUN_ANY_IN_BACKGROUND = 70;
+ /** @hide Change Wi-Fi connectivity state */
+ public static final int OP_CHANGE_WIFI_STATE = 71;
/** @hide */
- public static final int _NUM_OP = 71;
+ public static final int _NUM_OP = 72;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -496,6 +498,7 @@ public class AppOpsManager {
OP_INSTANT_APP_START_FOREGROUND,
OP_ANSWER_PHONE_CALLS,
OP_RUN_ANY_IN_BACKGROUND,
+ OP_CHANGE_WIFI_STATE,
};
/**
@@ -574,6 +577,7 @@ public class AppOpsManager {
OPSTR_INSTANT_APP_START_FOREGROUND,
OPSTR_ANSWER_PHONE_CALLS,
null, // OP_RUN_ANY_IN_BACKGROUND
+ null, // OP_CHANGE_WIFI_STATE
};
/**
@@ -652,6 +656,7 @@ public class AppOpsManager {
"INSTANT_APP_START_FOREGROUND",
"ANSWER_PHONE_CALLS",
"RUN_ANY_IN_BACKGROUND",
+ "CHANGE_WIFI_STATE",
};
/**
@@ -730,6 +735,7 @@ public class AppOpsManager {
Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
Manifest.permission.ANSWER_PHONE_CALLS,
null, // no permission for OP_RUN_ANY_IN_BACKGROUND
+ Manifest.permission.CHANGE_WIFI_STATE,
};
/**
@@ -809,6 +815,7 @@ public class AppOpsManager {
null, // INSTANT_APP_START_FOREGROUND
null, // ANSWER_PHONE_CALLS
null, // OP_RUN_ANY_IN_BACKGROUND
+ null, // OP_CHANGE_WIFI_STATE
};
/**
@@ -887,6 +894,7 @@ public class AppOpsManager {
false, // INSTANT_APP_START_FOREGROUND
false, // ANSWER_PHONE_CALLS
false, // OP_RUN_ANY_IN_BACKGROUND
+ false, // OP_CHANGE_WIFI_STATE
};
/**
@@ -964,6 +972,7 @@ public class AppOpsManager {
AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND
AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND
+ AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE
};
/**
@@ -1045,6 +1054,7 @@ public class AppOpsManager {
false,
false, // ANSWER_PHONE_CALLS
false, // OP_RUN_ANY_IN_BACKGROUND
+ false, // OP_CHANGE_WIFI_STATE
};
/**
diff --git a/android/app/DexLoadReporter.java b/android/app/DexLoadReporter.java
index f99d1a8e..06434147 100644
--- a/android/app/DexLoadReporter.java
+++ b/android/app/DexLoadReporter.java
@@ -19,7 +19,6 @@ package android.app;
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.system.ErrnoException;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -27,8 +26,6 @@ import com.android.internal.annotations.GuardedBy;
import dalvik.system.BaseDexClassLoader;
import dalvik.system.VMRuntime;
-import libcore.io.Libcore;
-
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -155,23 +152,12 @@ import java.util.Set;
return;
}
- File realDexPath;
- try {
- // Secondary dex profiles are stored in the oat directory, next to the real dex file
- // and have the same name with 'cur.prof' appended. We use the realpath because that
- // is what installd is using when processing the dex file.
- // NOTE: Keep in sync with installd.
- realDexPath = new File(Libcore.os.realpath(dexPath));
- } catch (ErrnoException ex) {
- Slog.e(TAG, "Failed to get the real path of secondary dex " + dexPath
- + ":" + ex.getMessage());
- // Do not continue with registration if we could not retrieve the real path.
- return;
- }
-
+ // Secondary dex profiles are stored in the oat directory, next to dex file
+ // and have the same name with 'cur.prof' appended.
// NOTE: Keep this in sync with installd expectations.
- File secondaryProfileDir = new File(realDexPath.getParent(), "oat");
- File secondaryProfile = new File(secondaryProfileDir, realDexPath.getName() + ".cur.prof");
+ File dexPathFile = new File(dexPath);
+ File secondaryProfileDir = new File(dexPathFile.getParent(), "oat");
+ File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof");
// Create the profile if not already there.
// Returns true if the file was created, false if the file already exists.
diff --git a/android/app/DialogFragment.java b/android/app/DialogFragment.java
index 7e0e4d82..a0fb6eeb 100644
--- a/android/app/DialogFragment.java
+++ b/android/app/DialogFragment.java
@@ -136,7 +136,10 @@ import java.io.PrintWriter;
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
* embed}
+ *
+ * @deprecated Use {@link android.support.v4.app.DialogFragment}
*/
+@Deprecated
public class DialogFragment extends Fragment
implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
diff --git a/android/app/Fragment.java b/android/app/Fragment.java
index 93773454..a92684b5 100644
--- a/android/app/Fragment.java
+++ b/android/app/Fragment.java
@@ -256,7 +256,10 @@ import java.lang.reflect.InvocationTargetException;
* <p>After each call to this function, a new entry is on the stack, and
* pressing back will pop it to return the user to whatever previous state
* the activity UI was in.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment}
*/
+@Deprecated
public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
private static final ArrayMap<String, Class<?>> sClassMap =
new ArrayMap<String, Class<?>>();
@@ -414,7 +417,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* State information that has been retrieved from a fragment instance
* through {@link FragmentManager#saveFragmentInstanceState(Fragment)
* FragmentManager.saveFragmentInstanceState}.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment.SavedState}
*/
+ @Deprecated
public static class SavedState implements Parcelable {
final Bundle mState;
@@ -458,7 +464,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
/**
* Thrown by {@link Fragment#instantiate(Context, String, Bundle)} when
* there is an instantiation failure.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment.InstantiationException}
*/
+ @Deprecated
static public class InstantiationException extends AndroidRuntimeException {
public InstantiationException(String msg, Exception cause) {
super(msg, cause);
@@ -1031,7 +1040,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
/**
* Return the LoaderManager for this fragment, creating it if needed.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment#getLoaderManager()}
*/
+ @Deprecated
public LoaderManager getLoaderManager() {
if (mLoaderManager != null) {
return mLoaderManager;
diff --git a/android/app/FragmentBreadCrumbs.java b/android/app/FragmentBreadCrumbs.java
index d0aa0fd6..e3e47ae6 100644
--- a/android/app/FragmentBreadCrumbs.java
+++ b/android/app/FragmentBreadCrumbs.java
@@ -65,7 +65,10 @@ public class FragmentBreadCrumbs extends ViewGroup
/**
* Interface to intercept clicks on the bread crumbs.
+ *
+ * @deprecated This widget is no longer supported.
*/
+ @Deprecated
public interface OnBreadCrumbClickListener {
/**
* Called when a bread crumb is clicked.
diff --git a/android/app/FragmentContainer.java b/android/app/FragmentContainer.java
index f8836bc8..a1dd32ff 100644
--- a/android/app/FragmentContainer.java
+++ b/android/app/FragmentContainer.java
@@ -24,7 +24,10 @@ import android.view.View;
/**
* Callbacks to a {@link Fragment}'s container.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentContainer}
*/
+@Deprecated
public abstract class FragmentContainer {
/**
* Return the view with the given resource ID. May return {@code null} if the
diff --git a/android/app/FragmentController.java b/android/app/FragmentController.java
index cff94d8c..cbb58d40 100644
--- a/android/app/FragmentController.java
+++ b/android/app/FragmentController.java
@@ -37,7 +37,10 @@ import java.util.List;
* <p>
* It is the responsibility of the host to take care of the Fragment's lifecycle.
* The methods provided by {@link FragmentController} are for that purpose.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentController}
*/
+@Deprecated
public class FragmentController {
private final FragmentHostCallback<?> mHost;
diff --git a/android/app/FragmentHostCallback.java b/android/app/FragmentHostCallback.java
index 5ef23e63..1edc68ed 100644
--- a/android/app/FragmentHostCallback.java
+++ b/android/app/FragmentHostCallback.java
@@ -37,7 +37,10 @@ import java.io.PrintWriter;
* Fragments may be hosted by any object; such as an {@link Activity}. In order to
* host fragments, implement {@link FragmentHostCallback}, overriding the methods
* applicable to the host.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
*/
+@Deprecated
public abstract class FragmentHostCallback<E> extends FragmentContainer {
private final Activity mActivity;
final Context mContext;
diff --git a/android/app/FragmentManager.java b/android/app/FragmentManager.java
index 0d5cd021..12e60b87 100644
--- a/android/app/FragmentManager.java
+++ b/android/app/FragmentManager.java
@@ -74,7 +74,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
* {@link android.support.v4.app.FragmentActivity}. See the blog post
* <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
* Fragments For All</a> for more details.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager}
*/
+@Deprecated
public abstract class FragmentManager {
/**
* Representation of an entry on the fragment back stack, as created
@@ -86,7 +89,10 @@ public abstract class FragmentManager {
* <p>Note that you should never hold on to a BackStackEntry object;
* the identifier as returned by {@link #getId} is the only thing that
* will be persisted across activity instances.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
*/
+ @Deprecated
public interface BackStackEntry {
/**
* Return the unique identifier for the entry. This is the only
@@ -129,7 +135,10 @@ public abstract class FragmentManager {
/**
* Interface to watch for changes to the back stack.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
*/
+ @Deprecated
public interface OnBackStackChangedListener {
/**
* Called whenever the contents of the back stack change.
@@ -428,7 +437,10 @@ public abstract class FragmentManager {
/**
* Callback interface for listening to fragment state changes that happen
* within a given FragmentManager.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
*/
+ @Deprecated
public abstract static class FragmentLifecycleCallbacks {
/**
* Called right before the fragment's {@link Fragment#onAttach(Context)} method is called.
diff --git a/android/app/FragmentManagerNonConfig.java b/android/app/FragmentManagerNonConfig.java
index 50d3797d..beb1a15a 100644
--- a/android/app/FragmentManagerNonConfig.java
+++ b/android/app/FragmentManagerNonConfig.java
@@ -27,7 +27,10 @@ import java.util.List;
* and passed to the state save and restore process for fragments in
* {@link FragmentController#retainNonConfig()} and
* {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
*/
+@Deprecated
public class FragmentManagerNonConfig {
private final List<Fragment> mFragments;
private final List<FragmentManagerNonConfig> mChildNonConfigs;
diff --git a/android/app/FragmentTransaction.java b/android/app/FragmentTransaction.java
index c910e903..0f4a7fb5 100644
--- a/android/app/FragmentTransaction.java
+++ b/android/app/FragmentTransaction.java
@@ -21,7 +21,10 @@ import java.lang.annotation.RetentionPolicy;
* <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer
* guide.</p>
* </div>
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
*/
+@Deprecated
public abstract class FragmentTransaction {
/**
* Calls {@link #add(int, Fragment, String)} with a 0 containerViewId.
diff --git a/android/app/Instrumentation.java b/android/app/Instrumentation.java
index e260967f..d49e11f4 100644
--- a/android/app/Instrumentation.java
+++ b/android/app/Instrumentation.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -418,22 +419,51 @@ public class Instrumentation {
* different process. In addition, if the given Intent resolves to
* multiple activities, instead of displaying a dialog for the user to
* select an activity, an exception will be thrown.
- *
+ *
* <p>The function returns as soon as the activity goes idle following the
* call to its {@link Activity#onCreate}. Generally this means it has gone
* through the full initialization including {@link Activity#onResume} and
* drawn and displayed its initial window.
- *
+ *
* @param intent Description of the activity to start.
- *
+ *
* @see Context#startActivity
+ * @see #startActivitySync(Intent, Bundle)
*/
public Activity startActivitySync(Intent intent) {
+ return startActivitySync(intent, null /* options */);
+ }
+
+ /**
+ * Start a new activity and wait for it to begin running before returning.
+ * In addition to being synchronous, this method as some semantic
+ * differences from the standard {@link Context#startActivity} call: the
+ * activity component is resolved before talking with the activity manager
+ * (its class name is specified in the Intent that this method ultimately
+ * starts), and it does not allow you to start activities that run in a
+ * different process. In addition, if the given Intent resolves to
+ * multiple activities, instead of displaying a dialog for the user to
+ * select an activity, an exception will be thrown.
+ *
+ * <p>The function returns as soon as the activity goes idle following the
+ * call to its {@link Activity#onCreate}. Generally this means it has gone
+ * through the full initialization including {@link Activity#onResume} and
+ * drawn and displayed its initial window.
+ *
+ * @param intent Description of the activity to start.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See {@link android.app.ActivityOptions}
+ * for how to build the Bundle supplied here; there are no supported definitions
+ * for building it manually.
+ *
+ * @see Context#startActivity(Intent, Bundle)
+ */
+ public Activity startActivitySync(Intent intent, @Nullable Bundle options) {
validateNotAppThread();
synchronized (mSync) {
intent = new Intent(intent);
-
+
ActivityInfo ai = intent.resolveActivityInfo(
getTargetContext().getPackageManager(), 0);
if (ai == null) {
@@ -447,7 +477,7 @@ public class Instrumentation {
+ myProc + " resolved to different process "
+ ai.processName + ": " + intent);
}
-
+
intent.setComponent(new ComponentName(
ai.applicationInfo.packageName, ai.name));
final ActivityWaiter aw = new ActivityWaiter(intent);
@@ -457,7 +487,7 @@ public class Instrumentation {
}
mWaitingActivities.add(aw);
- getTargetContext().startActivity(intent);
+ getTargetContext().startActivity(intent, options);
do {
try {
@@ -465,7 +495,7 @@ public class Instrumentation {
} catch (InterruptedException e) {
}
} while (mWaitingActivities.contains(aw));
-
+
return aw.activity;
}
}
diff --git a/android/app/ListFragment.java b/android/app/ListFragment.java
index 0b96d84d..90b77b39 100644
--- a/android/app/ListFragment.java
+++ b/android/app/ListFragment.java
@@ -144,7 +144,10 @@ import android.widget.TextView;
*
* @see #setListAdapter
* @see android.widget.ListView
+ *
+ * @deprecated Use {@link android.support.v4.app.ListFragment}
*/
+@Deprecated
public class ListFragment extends Fragment {
final private Handler mHandler = new Handler();
diff --git a/android/app/LoaderManager.java b/android/app/LoaderManager.java
index 56dfc589..7969684a 100644
--- a/android/app/LoaderManager.java
+++ b/android/app/LoaderManager.java
@@ -54,11 +54,17 @@ import java.lang.reflect.Modifier;
* <p>For more information about using loaders, read the
* <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
* </div>
+ *
+ * @deprecated Use {@link android.support.v4.app.LoaderManager}
*/
+@Deprecated
public abstract class LoaderManager {
/**
* Callback interface for a client to interact with the manager.
+ *
+ * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
*/
+ @Deprecated
public interface LoaderCallbacks<D> {
/**
* Instantiate and return a new Loader for the given ID.
diff --git a/android/app/Notification.java b/android/app/Notification.java
index 8226e0fb..d5d95fb8 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -22,6 +22,7 @@ import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@ public class Notification implements Parcelable
final Bundle ex = mN.extras;
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
- bindLargeIcon(contentView);
+ bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@ public class Notification implements Parcelable
}
}
- private void bindLargeIcon(RemoteViews contentView) {
+ private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
+ boolean alwaysShowReply) {
if (mN.mLargeIcon == null && mN.largeIcon != null) {
mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
}
- if (mN.mLargeIcon != null) {
+ boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
+ if (showLargeIcon) {
contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@ public class Notification implements Parcelable
contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
- // Bind the reply action
- Action action = findReplyAction();
- contentView.setViewVisibility(R.id.reply_icon_action, action != null
- ? View.VISIBLE
- : View.GONE);
-
- if (action != null) {
- int contrastColor = resolveContrastColor();
+ }
+ // Bind the reply action
+ Action action = findReplyAction();
+
+ boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
+ int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
+ if (actionVisible) {
+ // We're only showing the icon as big if we're hiding the large icon
+ int contrastColor = resolveContrastColor();
+ int iconColor;
+ if (showLargeIcon) {
contentView.setDrawableTint(R.id.reply_icon_action,
true /* targetBackground */,
contrastColor, PorterDuff.Mode.SRC_ATOP);
- int iconColor = NotificationColorUtil.isColorLight(contrastColor)
- ? Color.BLACK : Color.WHITE;
- contentView.setDrawableTint(R.id.reply_icon_action,
- false /* targetBackground */,
- iconColor, PorterDuff.Mode.SRC_ATOP);
contentView.setOnClickPendingIntent(R.id.right_icon,
action.actionIntent);
- contentView.setOnClickPendingIntent(R.id.reply_icon_action,
- action.actionIntent);
contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
- contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
-
+ iconColor = NotificationColorUtil.isColorLight(contrastColor)
+ ? Color.BLACK : Color.WHITE;
+ } else {
+ contentView.setImageViewResource(R.id.right_icon,
+ R.drawable.ic_reply_notification_large);
+ contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+ iconColor = contrastColor;
}
+ contentView.setDrawableTint(replyId,
+ false /* targetBackground */,
+ iconColor,
+ PorterDuff.Mode.SRC_ATOP);
+ contentView.setOnClickPendingIntent(replyId,
+ action.actionIntent);
+ contentView.setRemoteInputs(replyId, action.mRemoteInputs);
+ } else {
+ contentView.setRemoteInputs(R.id.right_icon, null);
}
- contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+ contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
+ ? View.VISIBLE
+ : View.GONE);
+ contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
? View.VISIBLE
: View.GONE);
}
@@ -6055,18 +6071,12 @@ public class Notification implements Parcelable
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
- mMessages.clear();
- mHistoricMessages.clear();
mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (messages != null && messages instanceof Parcelable[]) {
- mMessages = Message.getMessagesFromBundleArray(messages);
- }
+ mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
- if (histMessages != null && histMessages instanceof Parcelable[]) {
- mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
- }
+ mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
}
/**
@@ -6074,38 +6084,34 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- if (!increasedHeight) {
- Message m = findLatestIncomingMessage();
- CharSequence title = mConversationTitle != null
- ? mConversationTitle
- : (m == null) ? null : m.mSender;
- CharSequence text = (m == null)
- ? null
- : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
- return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
- } else {
- mBuilder.mOriginalActions = mBuilder.mActions;
- mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeBigContentView();
- mBuilder.mActions = mBuilder.mOriginalActions;
- mBuilder.mOriginalActions = null;
- return remoteViews;
- }
+ mBuilder.mOriginalActions = mBuilder.mActions;
+ mBuilder.mActions = new ArrayList<>();
+ RemoteViews remoteViews = makeBigContentView();
+ mBuilder.mActions = mBuilder.mOriginalActions;
+ mBuilder.mOriginalActions = null;
+ return remoteViews;
}
private Message findLatestIncomingMessage() {
- for (int i = mMessages.size() - 1; i >= 0; i--) {
- Message m = mMessages.get(i);
+ return findLatestIncomingMessage(mMessages);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static Message findLatestIncomingMessage(
+ List<Message> messages) {
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ Message m = messages.get(i);
// Incoming messages have a non-empty sender.
if (!TextUtils.isEmpty(m.mSender)) {
return m;
}
}
- if (!mMessages.isEmpty()) {
+ if (!messages.isEmpty()) {
// No incoming messages, fall back to outgoing message
- return mMessages.get(mMessages.size() - 1);
+ return messages.get(messages.size() - 1);
}
return null;
}
@@ -6115,118 +6121,82 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeBigContentView() {
- CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+ CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
- boolean hasTitle = !TextUtils.isEmpty(title);
-
- if (mMessages.size() == 1) {
- // Special case for a single message: Use the big text style
- // so the collapsed and expanded versions match nicely.
- CharSequence bigTitle;
- CharSequence text;
- if (hasTitle) {
- bigTitle = title;
- text = makeMessageLine(mMessages.get(0), mBuilder);
- } else {
- bigTitle = mMessages.get(0).mSender;
- text = mMessages.get(0).mText;
- }
- RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getBigTextLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
- BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
- return contentView;
- }
-
+ boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
+ if (isOneToOne) {
+ // Let's add the conversationTitle in case we didn't have one before and all
+ // messages are from the same sender
+ conversationTitle = createConversationTitleFromMessages();
+ } else if (hasOnlyWhiteSpaceSenders()) {
+ isOneToOne = true;
+ }
+ boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
-
- int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
- R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
-
- // Make sure all rows are gone in case we reuse a view.
- for (int rowId : rowIds) {
- contentView.setViewVisibility(rowId, View.GONE);
- }
+ mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
+ .hideLargeIcon(isOneToOne).alwaysShowReply(true));
+ addExtras(mBuilder.mN.extras);
+ contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+ mBuilder.resolveContrastColor());
+ contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+ mBuilder.mN.mLargeIcon);
+ contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
+ isOneToOne);
+ contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+ mBuilder.mN.extras);
+ return contentView;
+ }
- int i=0;
- contentView.setViewLayoutMarginBottomDimen(R.id.line1,
- hasTitle ? R.dimen.notification_messaging_spacing : 0);
- contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
- !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
-
- int contractedChildId = View.NO_ID;
- Message contractedMessage = findLatestIncomingMessage();
- int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
- - (rowIds.length - mMessages.size()));
- while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
- Message m = mHistoricMessages.get(firstHistoricMessage + i);
- int rowId = rowIds[i];
-
- contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
+ private boolean hasOnlyWhiteSpaceSenders() {
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (!isWhiteSpace(sender)) {
+ return false;
}
-
- i++;
}
+ return true;
+ }
- int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
- while (firstMessage + i < mMessages.size() && i < rowIds.length) {
- Message m = mMessages.get(firstMessage + i);
- int rowId = rowIds[i];
-
- contentView.setViewVisibility(rowId, View.VISIBLE);
- contentView.setTextViewText(rowId, mBuilder.processTextSpans(
- makeMessageLine(m, mBuilder)));
- mBuilder.setTextViewColorSecondary(contentView, rowId);
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
- }
-
- i++;
+ private boolean isWhiteSpace(CharSequence sender) {
+ if (TextUtils.isEmpty(sender)) {
+ return true;
}
- // Clear the remaining views for reapply. Ensures that historic message views can
- // reliably be identified as being GONE and having non-null text.
- while (i < rowIds.length) {
- int rowId = rowIds[i];
- contentView.setTextViewText(rowId, null);
- i++;
+ if (sender.toString().matches("^\\s*$")) {
+ return true;
}
-
- // Record this here to allow transformation between the contracted and expanded views.
- contentView.setInt(R.id.notification_messaging, "setContractedChildId",
- contractedChildId);
- return contentView;
+ // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
+ // For the presentation that we had.
+ for (int i = 0; i < sender.length(); i++) {
+ char c = sender.charAt(i);
+ if (c != '\u200B') {
+ return false;
+ }
+ }
+ return true;
}
- private CharSequence makeMessageLine(Message m, Builder builder) {
- BidiFormatter bidi = BidiFormatter.getInstance();
- SpannableStringBuilder sb = new SpannableStringBuilder();
- boolean colorize = builder.isColorized();
- TextAppearanceSpan colorSpan;
- CharSequence messageName;
- if (TextUtils.isEmpty(m.mSender)) {
- CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
- sb.append(bidi.unicodeWrap(replyName),
- makeFontColorSpan(colorize
- ? builder.getPrimaryTextColor()
- : mBuilder.resolveContrastColor()),
- 0 /* flags */);
- } else {
- sb.append(bidi.unicodeWrap(m.mSender),
- makeFontColorSpan(colorize
- ? builder.getPrimaryTextColor()
- : Color.BLACK),
- 0 /* flags */);
+ private CharSequence createConversationTitleFromMessages() {
+ ArraySet<CharSequence> names = new ArraySet<>();
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (sender != null) {
+ names.add(sender);
+ }
+ }
+ SpannableStringBuilder title = new SpannableStringBuilder();
+ int size = names.size();
+ for (int i = 0; i < size; i++) {
+ CharSequence name = names.valueAt(i);
+ if (!TextUtils.isEmpty(title)) {
+ title.append(", ");
+ }
+ title.append(BidiFormatter.getInstance().unicodeWrap(name));
}
- CharSequence text = m.mText == null ? "" : m.mText;
- sb.append(" ").append(bidi.unicodeWrap(text));
- return sb;
+ return title;
}
/**
@@ -6234,19 +6204,9 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (increasedHeight) {
- return makeBigContentView();
- }
- Message m = findLatestIncomingMessage();
- CharSequence title = mConversationTitle != null
- ? mConversationTitle
- : (m == null) ? null : m.mSender;
- CharSequence text = (m == null)
- ? null
- : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
- return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+ RemoteViews remoteViews = makeBigContentView();
+ remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
+ return remoteViews;
}
private static TextAppearanceSpan makeFontColorSpan(int color) {
@@ -6394,7 +6354,15 @@ public class Notification implements Parcelable
return bundles;
}
- static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ /**
+ * @return A list of messages read from the bundles.
+ *
+ * @hide
+ */
+ public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ if (bundles == null) {
+ return new ArrayList<>();
+ }
List<Message> messages = new ArrayList<>(bundles.length);
for (int i = 0; i < bundles.length; i++) {
if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8455,8 @@ public class Notification implements Parcelable
boolean ambient = false;
CharSequence title;
CharSequence text;
+ boolean hideLargeIcon;
+ public boolean alwaysShowReply;
final StandardTemplateParams reset() {
hasProgress = true;
@@ -8511,6 +8481,16 @@ public class Notification implements Parcelable
return this;
}
+ final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
+ this.alwaysShowReply = alwaysShowReply;
+ return this;
+ }
+
+ final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
+ this.hideLargeIcon = hideLargeIcon;
+ return this;
+ }
+
final StandardTemplateParams ambient(boolean ambient) {
Preconditions.checkState(title == null && text == null, "must set ambient before text");
this.ambient = ambient;
@@ -8527,7 +8507,6 @@ public class Notification implements Parcelable
text = extras.getCharSequence(EXTRA_TEXT);
}
this.text = b.processLegacyText(text, ambient);
-
return this;
}
}
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index a52dc1e4..f931589b 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -758,10 +758,10 @@ public class NotificationManager {
}
/**
- * Checks the ability to read/modify notification do not disturb policy for the calling package.
+ * Checks the ability to modify notification do not disturb policy for the calling package.
*
* <p>
- * Returns true if the calling package can read/modify notification policy.
+ * Returns true if the calling package can modify notification policy.
*
* <p>
* Apps can request policy access by sending the user to the activity that matches the system
@@ -839,8 +839,6 @@ public class NotificationManager {
* Gets the current notification policy.
*
* <p>
- * Only available if policy access is granted to this package.
- * See {@link #isNotificationPolicyAccessGranted}.
*/
public Policy getNotificationPolicy() {
INotificationManager service = getService();
diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java
index 50f1f364..e48946f2 100644
--- a/android/app/SystemServiceRegistry.java
+++ b/android/app/SystemServiceRegistry.java
@@ -41,6 +41,8 @@ import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
+import android.content.pm.crossprofile.CrossProfileApps;
+import android.content.pm.crossprofile.ICrossProfileApps;
import android.content.res.Resources;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
@@ -81,6 +83,7 @@ import android.net.INetworkPolicyManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
+import android.net.NetworkWatchlistManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
@@ -134,6 +137,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
+import android.util.StatsManager;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.WindowManager;
@@ -150,6 +154,7 @@ import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.ISoundTriggerService;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.net.INetworkWatchlistManager;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
@@ -304,14 +309,14 @@ final class SystemServiceRegistry {
}});
registerService(Context.BATTERY_SERVICE, BatteryManager.class,
- new StaticServiceFetcher<BatteryManager>() {
+ new CachedServiceFetcher<BatteryManager>() {
@Override
- public BatteryManager createService() throws ServiceNotFoundException {
+ public BatteryManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBatteryStats stats = IBatteryStats.Stub.asInterface(
ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME));
IBatteryPropertiesRegistrar registrar = IBatteryPropertiesRegistrar.Stub
.asInterface(ServiceManager.getServiceOrThrow("batteryproperties"));
- return new BatteryManager(stats, registrar);
+ return new BatteryManager(ctx, stats, registrar);
}});
registerService(Context.NFC_SERVICE, NfcManager.class,
@@ -448,6 +453,13 @@ final class SystemServiceRegistry {
ctx.mMainThread.getHandler().getLooper());
}});
+ registerService(Context.STATS_MANAGER, StatsManager.class,
+ new StaticServiceFetcher<StatsManager>() {
+ @Override
+ public StatsManager createService() throws ServiceNotFoundException {
+ return new StatsManager();
+ }});
+
registerService(Context.STATUS_BAR_SERVICE, StatusBarManager.class,
new CachedServiceFetcher<StatusBarManager>() {
@Override
@@ -862,6 +874,17 @@ final class SystemServiceRegistry {
return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b));
}});
+ registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class,
+ new CachedServiceFetcher<NetworkWatchlistManager>() {
+ @Override
+ public NetworkWatchlistManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b =
+ ServiceManager.getServiceOrThrow(Context.NETWORK_WATCHLIST_SERVICE);
+ return new NetworkWatchlistManager(ctx,
+ INetworkWatchlistManager.Stub.asInterface(b));
+ }});
+
registerService(Context.SYSTEM_HEALTH_SERVICE, SystemHealthManager.class,
new CachedServiceFetcher<SystemHealthManager>() {
@Override
@@ -909,6 +932,18 @@ final class SystemServiceRegistry {
public RulesManager createService(ContextImpl ctx) {
return new RulesManager(ctx.getOuterContext());
}});
+
+ registerService(Context.CROSS_PROFILE_APPS_SERVICE, CrossProfileApps.class,
+ new CachedServiceFetcher<CrossProfileApps>() {
+ @Override
+ public CrossProfileApps createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.CROSS_PROFILE_APPS_SERVICE);
+ return new CrossProfileApps(ctx.getOuterContext(),
+ ICrossProfileApps.Stub.asInterface(b));
+ }
+ });
}
/**
diff --git a/android/app/TimePickerDialog.java b/android/app/TimePickerDialog.java
index 0f006b66..8686944b 100644
--- a/android/app/TimePickerDialog.java
+++ b/android/app/TimePickerDialog.java
@@ -152,6 +152,9 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener,
public void onClick(View view) {
if (mTimePicker.validateInput()) {
TimePickerDialog.this.onClick(TimePickerDialog.this, BUTTON_POSITIVE);
+ // Clearing focus forces the dialog to commit any pending
+ // changes, e.g. typed text in a NumberPicker.
+ mTimePicker.clearFocus();
dismiss();
}
}
diff --git a/android/app/VrManager.java b/android/app/VrManager.java
index 5c6ffa39..392387a9 100644
--- a/android/app/VrManager.java
+++ b/android/app/VrManager.java
@@ -198,4 +198,20 @@ public class VrManager {
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Sets the current standby status of the VR device. Standby mode is only used on standalone vr
+ * devices. Standby mode is a deep sleep state where it's appropriate to turn off vr mode.
+ *
+ * @param standby True if the device is entering standby, false if it's exiting standby.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_VR_MANAGER)
+ public void setStandbyEnabled(boolean standby) {
+ try {
+ mService.setStandbyEnabled(standby);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index de27b4fd..2c1fad1c 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -500,15 +500,12 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
* @hide
*/
public boolean supportSplitScreenWindowingMode() {
- return supportSplitScreenWindowingMode(mWindowingMode, mActivityType);
+ return supportSplitScreenWindowingMode(mActivityType);
}
/** @hide */
- public static boolean supportSplitScreenWindowingMode(int windowingMode, int activityType) {
- if (activityType == ACTIVITY_TYPE_ASSISTANT) {
- return false;
- }
- return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
+ public static boolean supportSplitScreenWindowingMode(int activityType) {
+ return activityType != ACTIVITY_TYPE_ASSISTANT;
}
/** @hide */
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 772c6d60..f0226b7e 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -3246,6 +3246,7 @@ public class DevicePolicyManager {
* that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
*/
public void wipeData(int flags) {
+ throwIfParentInstance("wipeData");
final String wipeReasonForUser = mContext.getString(
R.string.work_profile_deleted_description_dpm_wipe);
wipeDataInternal(flags, wipeReasonForUser);
@@ -3270,6 +3271,7 @@ public class DevicePolicyManager {
* @throws IllegalArgumentException if the input reason string is null or empty.
*/
public void wipeDataWithReason(int flags, @NonNull CharSequence reason) {
+ throwIfParentInstance("wipeDataWithReason");
Preconditions.checkNotNull(reason, "CharSequence is null");
wipeDataInternal(flags, reason.toString());
}
@@ -3283,7 +3285,6 @@ public class DevicePolicyManager {
* @hide
*/
private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
- throwIfParentInstance("wipeDataWithReason");
if (mService != null) {
try {
mService.wipeDataWithReason(flags, wipeReasonForUser);
@@ -6096,8 +6097,8 @@ public class DevicePolicyManager {
/**
* Flag used by {@link #createAndManageUser} to specify that the user should be created
- * ephemeral.
- * @hide
+ * ephemeral. Ephemeral users will be removed after switching to another user or rebooting the
+ * device.
*/
public static final int MAKE_USER_EPHEMERAL = 0x0002;
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index b640bd5b..530d84b4 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -16,6 +16,12 @@
package android.app.job;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.util.TimeUtils.formatDuration;
import android.annotation.BytesLong;
@@ -25,6 +31,8 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.ClipData;
import android.content.ComponentName;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.Uri;
import android.os.BaseBundle;
import android.os.Bundle;
@@ -56,6 +64,7 @@ public class JobInfo implements Parcelable {
NETWORK_TYPE_ANY,
NETWORK_TYPE_UNMETERED,
NETWORK_TYPE_NOT_ROAMING,
+ NETWORK_TYPE_CELLULAR,
NETWORK_TYPE_METERED,
})
@Retention(RetentionPolicy.SOURCE)
@@ -69,8 +78,21 @@ public class JobInfo implements Parcelable {
public static final int NETWORK_TYPE_UNMETERED = 2;
/** This job requires network connectivity that is not roaming. */
public static final int NETWORK_TYPE_NOT_ROAMING = 3;
- /** This job requires metered connectivity such as most cellular data networks. */
- public static final int NETWORK_TYPE_METERED = 4;
+ /** This job requires network connectivity that is a cellular network. */
+ public static final int NETWORK_TYPE_CELLULAR = 4;
+
+ /**
+ * This job requires metered connectivity such as most cellular data
+ * networks.
+ *
+ * @deprecated Cellular networks may be unmetered, or Wi-Fi networks may be
+ * metered, so this isn't a good way of selecting a specific
+ * transport. Instead, use {@link #NETWORK_TYPE_CELLULAR} or
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)}
+ * if your job requires a specific network transport.
+ */
+ @Deprecated
+ public static final int NETWORK_TYPE_METERED = NETWORK_TYPE_CELLULAR;
/** Sentinel value indicating that bytes are unknown. */
public static final int NETWORK_BYTES_UNKNOWN = -1;
@@ -253,7 +275,7 @@ public class JobInfo implements Parcelable {
private final long triggerContentMaxDelay;
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
- private final int networkType;
+ private final NetworkRequest networkRequest;
private final long networkBytes;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
@@ -385,10 +407,37 @@ public class JobInfo implements Parcelable {
}
/**
- * The kind of connectivity requirements that the job has.
+ * Return the basic description of the kind of network this job requires.
+ *
+ * @deprecated This method attempts to map {@link #getRequiredNetwork()}
+ * into the set of simple constants, which results in a loss of
+ * fidelity. Callers should move to using
+ * {@link #getRequiredNetwork()} directly.
+ * @see Builder#setRequiredNetworkType(int)
*/
+ @Deprecated
public @NetworkType int getNetworkType() {
- return networkType;
+ if (networkRequest == null) {
+ return NETWORK_TYPE_NONE;
+ } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+ return NETWORK_TYPE_UNMETERED;
+ } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+ return NETWORK_TYPE_NOT_ROAMING;
+ } else if (networkRequest.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ return NETWORK_TYPE_CELLULAR;
+ } else {
+ return NETWORK_TYPE_ANY;
+ }
+ }
+
+ /**
+ * Return the detailed description of the kind of network this job requires,
+ * or {@code null} if no specific kind of network is required.
+ *
+ * @see Builder#setRequiredNetwork(NetworkRequest)
+ */
+ public @Nullable NetworkRequest getRequiredNetwork() {
+ return networkRequest;
}
/**
@@ -438,8 +487,7 @@ public class JobInfo implements Parcelable {
* job does not recur periodically.
*/
public long getIntervalMillis() {
- final long minInterval = getMinPeriodMillis();
- return intervalMillis >= minInterval ? intervalMillis : minInterval;
+ return intervalMillis;
}
/**
@@ -447,10 +495,7 @@ public class JobInfo implements Parcelable {
* execute at any time in a window of flex length at the end of the period.
*/
public long getFlexMillis() {
- long interval = getIntervalMillis();
- long percentClamp = 5 * interval / 100;
- long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis()));
- return clampedFlex <= interval ? clampedFlex : interval;
+ return flexMillis;
}
/**
@@ -459,8 +504,7 @@ public class JobInfo implements Parcelable {
* to 30 seconds, minimum is currently 10 seconds.
*/
public long getInitialBackoffMillis() {
- final long minBackoff = getMinBackoffMillis();
- return initialBackoffMillis >= minBackoff ? initialBackoffMillis : minBackoff;
+ return initialBackoffMillis;
}
/**
@@ -538,7 +582,7 @@ public class JobInfo implements Parcelable {
if (hasLateConstraint != j.hasLateConstraint) {
return false;
}
- if (networkType != j.networkType) {
+ if (!Objects.equals(networkRequest, j.networkRequest)) {
return false;
}
if (networkBytes != j.networkBytes) {
@@ -601,7 +645,9 @@ public class JobInfo implements Parcelable {
hashCode = 31 * hashCode + Long.hashCode(triggerContentMaxDelay);
hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
- hashCode = 31 * hashCode + networkType;
+ if (networkRequest != null) {
+ hashCode = 31 * hashCode + networkRequest.hashCode();
+ }
hashCode = 31 * hashCode + Long.hashCode(networkBytes);
hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
@@ -632,7 +678,11 @@ public class JobInfo implements Parcelable {
triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
triggerContentUpdateDelay = in.readLong();
triggerContentMaxDelay = in.readLong();
- networkType = in.readInt();
+ if (in.readInt() != 0) {
+ networkRequest = NetworkRequest.CREATOR.createFromParcel(in);
+ } else {
+ networkRequest = null;
+ }
networkBytes = in.readLong();
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
@@ -661,7 +711,7 @@ public class JobInfo implements Parcelable {
: null;
triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
triggerContentMaxDelay = b.mTriggerContentMaxDelay;
- networkType = b.mNetworkType;
+ networkRequest = b.mNetworkRequest;
networkBytes = b.mNetworkBytes;
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
@@ -699,7 +749,12 @@ public class JobInfo implements Parcelable {
out.writeTypedArray(triggerContentUris, flags);
out.writeLong(triggerContentUpdateDelay);
out.writeLong(triggerContentMaxDelay);
- out.writeInt(networkType);
+ if (networkRequest != null) {
+ out.writeInt(1);
+ networkRequest.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
out.writeLong(networkBytes);
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
@@ -833,7 +888,7 @@ public class JobInfo implements Parcelable {
private int mFlags;
// Requirements.
private int mConstraintFlags;
- private int mNetworkType;
+ private NetworkRequest mNetworkRequest;
private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
private ArrayList<TriggerContentUri> mTriggerContentUris;
private long mTriggerContentUpdateDelay = -1;
@@ -934,24 +989,84 @@ public class JobInfo implements Parcelable {
}
/**
- * Set some description of the kind of network type your job needs to
- * have. Not calling this function means the network is not necessary,
- * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
- * calling this function defines network as a strict requirement for
- * your job. If the network requested is not available your job will
- * never run. See {@link #setOverrideDeadline(long)} to change this
- * behaviour.
+ * Set basic description of the kind of network your job requires. If
+ * you need more precise control over network capabilities, see
+ * {@link #setRequiredNetwork(NetworkRequest)}.
+ * <p>
+ * If your job doesn't need a network connection, you don't need to call
+ * this method, as the default value is {@link #NETWORK_TYPE_NONE}.
+ * <p>
+ * Calling this method defines network as a strict requirement for your
+ * job. If the network requested is not available your job will never
+ * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+ * Calling this method will override any requirements previously defined
+ * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
+ * want to call one of these methods.
* <p class="note">
- * Note: When your job executes in
+ * When your job executes in
* {@link JobService#onStartJob(JobParameters)}, be sure to use the
* specific network returned by {@link JobParameters#getNetwork()},
* otherwise you'll use the default network which may not meet this
* constraint.
*
+ * @see #setRequiredNetwork(NetworkRequest)
+ * @see JobInfo#getNetworkType()
* @see JobParameters#getNetwork()
*/
public Builder setRequiredNetworkType(@NetworkType int networkType) {
- mNetworkType = networkType;
+ if (networkType == NETWORK_TYPE_NONE) {
+ return setRequiredNetwork(null);
+ } else {
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder();
+
+ // All types require validated Internet
+ builder.addCapability(NET_CAPABILITY_INTERNET);
+ builder.addCapability(NET_CAPABILITY_VALIDATED);
+ builder.removeCapability(NET_CAPABILITY_NOT_VPN);
+
+ if (networkType == NETWORK_TYPE_ANY) {
+ // No other capabilities
+ } else if (networkType == NETWORK_TYPE_UNMETERED) {
+ builder.addCapability(NET_CAPABILITY_NOT_METERED);
+ } else if (networkType == NETWORK_TYPE_NOT_ROAMING) {
+ builder.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ } else if (networkType == NETWORK_TYPE_CELLULAR) {
+ builder.addTransportType(TRANSPORT_CELLULAR);
+ }
+
+ return setRequiredNetwork(builder.build());
+ }
+ }
+
+ /**
+ * Set detailed description of the kind of network your job requires.
+ * <p>
+ * If your job doesn't need a network connection, you don't need to call
+ * this method, as the default is {@code null}.
+ * <p>
+ * Calling this method defines network as a strict requirement for your
+ * job. If the network requested is not available your job will never
+ * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+ * Calling this method will override any requirements previously defined
+ * by {@link #setRequiredNetworkType(int)}; you typically only want to
+ * call one of these methods.
+ * <p class="note">
+ * When your job executes in
+ * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+ * specific network returned by {@link JobParameters#getNetwork()},
+ * otherwise you'll use the default network which may not meet this
+ * constraint.
+ *
+ * @param networkRequest The detailed description of the kind of network
+ * this job requires, or {@code null} if no specific kind of
+ * network is required. Defining a {@link NetworkSpecifier}
+ * is only supported for jobs that aren't persisted.
+ * @see #setRequiredNetworkType(int)
+ * @see JobInfo#getRequiredNetwork()
+ * @see JobParameters#getNetwork()
+ */
+ public Builder setRequiredNetwork(@Nullable NetworkRequest networkRequest) {
+ mNetworkRequest = networkRequest;
return this;
}
@@ -1140,6 +1255,21 @@ public class JobInfo implements Parcelable {
* higher.
*/
public Builder setPeriodic(long intervalMillis, long flexMillis) {
+ final long minPeriod = getMinPeriodMillis();
+ if (intervalMillis < minPeriod) {
+ Log.w(TAG, "Requested interval " + formatDuration(intervalMillis) + " for job "
+ + mJobId + " is too small; raising to " + formatDuration(minPeriod));
+ intervalMillis = minPeriod;
+ }
+
+ final long percentClamp = 5 * intervalMillis / 100;
+ final long minFlex = Math.max(percentClamp, getMinFlexMillis());
+ if (flexMillis < minFlex) {
+ Log.w(TAG, "Requested flex " + formatDuration(flexMillis) + " for job " + mJobId
+ + " is too small; raising to " + formatDuration(minFlex));
+ flexMillis = minFlex;
+ }
+
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
mFlexMillis = flexMillis;
@@ -1189,6 +1319,13 @@ public class JobInfo implements Parcelable {
*/
public Builder setBackoffCriteria(long initialBackoffMillis,
@BackoffPolicy int backoffPolicy) {
+ final long minBackoff = getMinBackoffMillis();
+ if (initialBackoffMillis < minBackoff) {
+ Log.w(TAG, "Requested backoff " + formatDuration(initialBackoffMillis) + " for job "
+ + mJobId + " is too small; raising to " + formatDuration(minBackoff));
+ initialBackoffMillis = minBackoff;
+ }
+
mBackoffPolicySet = true;
mInitialBackoffMillis = initialBackoffMillis;
mBackoffPolicy = backoffPolicy;
@@ -1213,16 +1350,22 @@ public class JobInfo implements Parcelable {
public JobInfo build() {
// Allow jobs with no constraints - What am I, a database?
if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
- mNetworkType == NETWORK_TYPE_NONE &&
+ mNetworkRequest == null &&
mTriggerContentUris == null) {
throw new IllegalArgumentException("You're trying to build a job with no " +
"constraints, this is not allowed.");
}
// Check that network estimates require network type
- if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) {
+ if (mNetworkBytes > 0 && mNetworkRequest == null) {
throw new IllegalArgumentException(
"Can't provide estimated network usage without requiring a network");
}
+ // We can't serialize network specifiers
+ if (mIsPersisted && mNetworkRequest != null
+ && mNetworkRequest.networkCapabilities.getNetworkSpecifier() != null) {
+ throw new IllegalArgumentException(
+ "Network specifiers aren't supported for persistent jobs");
+ }
// Check that a deadline was not set on a periodic job.
if (mIsPeriodic) {
if (mMaxExecutionDelayMillis != 0L) {
@@ -1257,31 +1400,7 @@ public class JobInfo implements Parcelable {
" back-off policy, so calling setBackoffCriteria with" +
" setRequiresDeviceIdle is an error.");
}
- JobInfo job = new JobInfo(this);
- if (job.isPeriodic()) {
- if (job.intervalMillis != job.getIntervalMillis()) {
- StringBuilder builder = new StringBuilder();
- builder.append("Specified interval for ")
- .append(String.valueOf(mJobId))
- .append(" is ");
- formatDuration(mIntervalMillis, builder);
- builder.append(". Clamped to ");
- formatDuration(job.getIntervalMillis(), builder);
- Log.w(TAG, builder.toString());
- }
- if (job.flexMillis != job.getFlexMillis()) {
- StringBuilder builder = new StringBuilder();
- builder.append("Specified flex for ")
- .append(String.valueOf(mJobId))
- .append(" is ");
- formatDuration(mFlexMillis, builder);
- builder.append(". Clamped to ");
- formatDuration(job.getFlexMillis(), builder);
- Log.w(TAG, builder.toString());
- }
- }
- return job;
+ return new JobInfo(this);
}
}
-
}
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index f6b6b869..616a5be3 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -21,8 +21,12 @@ import android.annotation.Nullable;
import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
+import android.content.ContentProvider;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.IContentProvider;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
@@ -54,7 +58,12 @@ public final class Slice implements Parcelable {
public @interface SliceHint{ }
/**
- * Hint that this content is a title of other content in the slice.
+ * Hint that this content is a title of other content in the slice. This can also indicate that
+ * the content should be used in the shortcut representation of the slice (icon, label, action),
+ * normally this should be indicated by adding the hint on the action containing that content.
+ *
+ * @see SliceView#MODE_SHORTCUT
+ * @see SliceItem#TYPE_ACTION
*/
public static final String HINT_TITLE = "title";
/**
@@ -100,6 +109,21 @@ public final class Slice implements Parcelable {
*/
public static final String HINT_NO_TINT = "no_tint";
/**
+ * Hint to indicate that this content should not be shown in the {@link SliceView#MODE_SMALL}
+ * and {@link SliceView#MODE_LARGE} modes of SliceView. This content may be used to populate
+ * the {@link SliceView#MODE_SHORTCUT} format of the slice.
+ * @hide
+ */
+ public static final String HINT_HIDDEN = "hidden";
+ /**
+ * Hint to indicate that this content has a toggle action associated with it. To indicate that
+ * the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the intent
+ * associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} which can be
+ * retrieved to see the new state of the toggle.
+ * @hide
+ */
+ public static final String HINT_TOGGLE = "toggle";
+ /**
* Hint to indicate that this slice is incomplete and an update will be sent once
* loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
* OS and should not be cached by apps.
@@ -112,6 +136,11 @@ public final class Slice implements Parcelable {
* @hide
*/
public static final String HINT_ALT = "alt";
+ /**
+ * Key to retrieve an extra added to an intent when a control is changed.
+ * @hide
+ */
+ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
private final SliceItem[] mItems;
private final @SliceHint String[] mHints;
@@ -398,4 +427,58 @@ public final class Slice implements Parcelable {
resolver.releaseProvider(provider);
}
}
+
+ /**
+ * Turns a slice intent into slice content. Expects an explicit intent. If there is no
+ * {@link ContentProvider} associated with the given intent this will throw
+ * {@link IllegalArgumentException}.
+ *
+ * @param context The context to use.
+ * @param intent The intent associated with a slice.
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ * @see SliceProvider#onMapIntentToUri(Intent)
+ * @see Intent
+ */
+ public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
+ Preconditions.checkNotNull(intent, "intent");
+ Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
+ "Slice intent must be explicit " + intent);
+ ContentResolver resolver = context.getContentResolver();
+
+ // Check if the intent has data for the slice uri on it and use that
+ final Uri intentData = intent.getData();
+ if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
+ return bindSlice(resolver, intentData);
+ }
+ // Otherwise ask the app
+ List<ResolveInfo> providers =
+ context.getPackageManager().queryIntentContentProviders(intent, 0);
+ if (providers == null) {
+ throw new IllegalArgumentException("Unable to resolve intent " + intent);
+ }
+ String authority = providers.get(0).providerInfo.authority;
+ Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).build();
+ IContentProvider provider = resolver.acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+ final Bundle res = provider.call(resolver.getPackageName(),
+ SliceProvider.METHOD_MAP_INTENT, null, extras);
+ if (res == null) {
+ return null;
+ }
+ return res.getParcelable(SliceProvider.EXTRA_SLICE);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ resolver.releaseProvider(provider);
+ }
+ }
}
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index 33825b4b..05f4ce6e 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -16,46 +16,69 @@
package android.app.slice;
import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.app.slice.widget.SliceView;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
+import android.os.UserHandle;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
/**
- * A SliceProvider allows app to provide content to be displayed in system
- * spaces. This content is templated and can contain actions, and the behavior
- * of how it is surfaced is specific to the system surface.
+ * A SliceProvider allows an app to provide content to be displayed in system spaces. This content
+ * is templated and can contain actions, and the behavior of how it is surfaced is specific to the
+ * system surface.
+ * <p>
+ * Slices are not currently live content. They are bound once and shown to the user. If the content
+ * changes due to a callback from user interaction, then
+ * {@link ContentResolver#notifyChange(Uri, ContentObserver)} should be used to notify the system.
+ * </p>
+ * <p>
+ * The provider needs to be declared in the manifest to provide the authority for the app. The
+ * authority for most slices is expected to match the package of the application.
+ * </p>
*
- * <p>Slices are not currently live content. They are bound once and shown to the
- * user. If the content changes due to a callback from user interaction, then
- * {@link ContentResolver#notifyChange(Uri, ContentObserver)}
- * should be used to notify the system.</p>
- *
- * <p>The provider needs to be declared in the manifest to provide the authority
- * for the app. The authority for most slices is expected to match the package
- * of the application.</p>
* <pre class="prettyprint">
* {@literal
* <provider
* android:name="com.android.mypkg.MySliceProvider"
* android:authorities="com.android.mypkg" />}
* </pre>
+ * <p>
+ * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider
+ * must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via
+ * an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an
+ * appropriate Uri representing the slice.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ * android:name="com.android.mypkg.MySliceProvider"
+ * android:authorities="com.android.mypkg">
+ * <intent-filter>
+ * <action android:name="android.intent.action.MY_SLICE_INTENT" />
+ * </intent-filter>
+ * </provider>}
+ * </pre>
*
* @see Slice
*/
public abstract class SliceProvider extends ContentProvider {
-
/**
* This is the Android platform's MIME type for a slice: URI
* containing a slice implemented through {@link SliceProvider}.
@@ -74,6 +97,14 @@ public abstract class SliceProvider extends ContentProvider {
/**
* @hide
*/
+ public static final String METHOD_MAP_INTENT = "map_slice";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_INTENT = "slice_intent";
+ /**
+ * @hide
+ */
public static final String EXTRA_SLICE = "slice";
private static final boolean DEBUG = false;
@@ -94,6 +125,20 @@ public abstract class SliceProvider extends ContentProvider {
// TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
public abstract Slice onBindSlice(Uri sliceUri);
+ /**
+ * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
+ * In that case, this method can be called and is expected to return a non-null Uri representing
+ * a slice. Otherwise this will throw {@link UnsupportedOperationException}.
+ *
+ * @return Uri representing the slice associated with the provided intent.
+ * @see {@link Slice}
+ * @see {@link SliceView#setSlice(Intent)}
+ */
+ public @NonNull Uri onMapIntentToUri(Intent intent) {
+ throw new UnsupportedOperationException(
+ "This provider has not implemented intent to uri mapping");
+ }
+
@Override
public final int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
@@ -143,14 +188,31 @@ public abstract class SliceProvider extends ContentProvider {
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (method.equals(METHOD_SLICE)) {
- getContext().enforceCallingPermission(permission.BIND_SLICE,
- "Slice binding requires the permission BIND_SLICE");
Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+ if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
+ getContext().enforceUriPermission(uri, permission.BIND_SLICE,
+ permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+ "Slice binding requires the permission BIND_SLICE");
+ }
Slice s = handleBindSlice(uri);
Bundle b = new Bundle();
b.putParcelable(EXTRA_SLICE, s);
return b;
+ } else if (method.equals(METHOD_MAP_INTENT)) {
+ getContext().enforceCallingPermission(permission.BIND_SLICE,
+ "Slice binding requires the permission BIND_SLICE");
+ Intent intent = extras.getParcelable(EXTRA_INTENT);
+ Uri uri = onMapIntentToUri(intent);
+ Bundle b = new Bundle();
+ if (uri != null) {
+ Slice s = handleBindSlice(uri);
+ b.putParcelable(EXTRA_SLICE, s);
+ } else {
+ b.putParcelable(EXTRA_SLICE, null);
+ }
+ return b;
}
return super.call(method, arg, extras);
}
diff --git a/android/app/slice/widget/GridView.java b/android/app/slice/widget/GridView.java
index 67a3c671..793abc05 100644
--- a/android/app/slice/widget/GridView.java
+++ b/android/app/slice/widget/GridView.java
@@ -126,6 +126,9 @@ public class GridView extends LinearLayout implements SliceListView {
* Returns true if this item is just an image.
*/
private boolean addItem(SliceItem item) {
+ if (item.hasHint(Slice.HINT_HIDDEN)) {
+ return false;
+ }
if (item.getType() == SliceItem.TYPE_IMAGE) {
ImageView v = new ImageView(getContext());
v.setImageIcon(item.getIcon());
@@ -145,6 +148,9 @@ public class GridView extends LinearLayout implements SliceListView {
items.addAll(item.getSlice().getItems());
}
items.forEach(i -> {
+ if (i.hasHint(Slice.HINT_HIDDEN)) {
+ return;
+ }
Context context = getContext();
switch (i.getType()) {
case SliceItem.TYPE_TEXT:
diff --git a/android/app/slice/widget/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java
index f45b2a8f..788f6fb6 100644
--- a/android/app/slice/widget/LargeTemplateView.java
+++ b/android/app/slice/widget/LargeTemplateView.java
@@ -85,9 +85,14 @@ public class LargeTemplateView extends SliceModeView {
addList(slice, items);
} else {
slice.getItems().forEach(item -> {
- if (item.hasHint(Slice.HINT_ACTIONS)) {
+ if (item.hasHint(Slice.HINT_HIDDEN)) {
+ // If it's hidden we don't show it
+ return;
+ } else if (item.hasHint(Slice.HINT_ACTIONS)) {
+ // Action groups don't show in lists
return;
} else if (item.getType() == SliceItem.TYPE_COLOR) {
+ // A color is not a list item
return;
} else if (item.getType() == SliceItem.TYPE_SLICE
&& item.hasHint(Slice.HINT_LIST)) {
@@ -108,8 +113,12 @@ public class LargeTemplateView extends SliceModeView {
private void addList(Slice slice, List<SliceItem> items) {
List<SliceItem> sliceItems = slice.getItems();
- sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
- items.addAll(sliceItems);
+ sliceItems.forEach(i -> {
+ if (!i.hasHint(Slice.HINT_HIDDEN) && i.getType() != SliceItem.TYPE_COLOR) {
+ i.addHint(Slice.HINT_LIST_ITEM);
+ items.add(i);
+ }
+ });
}
/**
diff --git a/android/app/slice/widget/ShortcutView.java b/android/app/slice/widget/ShortcutView.java
index 0bca8ce2..0b7ad0d6 100644
--- a/android/app/slice/widget/ShortcutView.java
+++ b/android/app/slice/widget/ShortcutView.java
@@ -24,13 +24,20 @@ import android.app.slice.SliceQuery;
import android.app.slice.widget.SliceView.SliceModeView;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.net.Uri;
import com.android.internal.R;
+import java.util.List;
+
/**
* @hide
*/
@@ -38,27 +45,26 @@ public class ShortcutView extends SliceModeView {
private static final String TAG = "ShortcutView";
- private PendingIntent mAction;
private Uri mUri;
+ private PendingIntent mAction;
+ private SliceItem mLabel;
+ private SliceItem mIcon;
+
private int mLargeIconSize;
private int mSmallIconSize;
public ShortcutView(Context context) {
super(context);
- mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+ final Resources res = getResources();
+ mSmallIconSize = res.getDimensionPixelSize(R.dimen.slice_icon_size);
+ mLargeIconSize = res.getDimensionPixelSize(R.dimen.slice_shortcut_size);
}
@Override
public void setSlice(Slice slice) {
removeAllViews();
- SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
- SliceItem iconItem = SliceQuery.getPrimaryIcon(slice);
- SliceItem textItem = sliceItem != null
- ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
- : SliceQuery.find(slice, SliceItem.TYPE_TEXT);
- SliceItem colorItem = sliceItem != null
- ? SliceQuery.find(sliceItem, SliceItem.TYPE_COLOR)
- : SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+ determineShortcutItems(getContext(), slice);
+ SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
if (colorItem == null) {
colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
}
@@ -67,13 +73,11 @@ public class ShortcutView extends SliceModeView {
ShapeDrawable circle = new ShapeDrawable(new OvalShape());
circle.setTint(color);
setBackground(circle);
- if (iconItem != null) {
- final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE);
+ if (mIcon != null) {
+ final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE);
final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
- SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(),
+ SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
isLarge, this /* parent */);
- mAction = sliceItem != null ? sliceItem.getAction()
- : null;
mUri = slice.getUri();
setClickable(true);
} else {
@@ -103,4 +107,69 @@ public class ShortcutView extends SliceModeView {
}
return true;
}
+
+ /**
+ * Looks at the slice and determines which items are best to use to compose the shortcut.
+ */
+ private void determineShortcutItems(Context context, Slice slice) {
+ List<String> h = slice.getHints();
+ SliceItem sliceItem = new SliceItem(slice, SliceItem.TYPE_SLICE,
+ h.toArray(new String[h.size()]));
+ SliceItem titleItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION,
+ Slice.HINT_TITLE, null);
+
+ if (titleItem != null) {
+ // Preferred case: hinted action containing hinted image and text
+ mAction = titleItem.getAction();
+ mIcon = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+ null);
+ mLabel = SliceQuery.find(titleItem.getSlice(), SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ null);
+ } else {
+ // No hinted action; just use the first one
+ SliceItem actionItem = SliceQuery.find(sliceItem, SliceItem.TYPE_ACTION, (String) null,
+ null);
+ mAction = (actionItem != null) ? actionItem.getAction() : null;
+ }
+ // First fallback: any hinted image and text
+ if (mIcon == null) {
+ mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+ null);
+ }
+ if (mLabel == null) {
+ mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ null);
+ }
+ // Second fallback: first image and text
+ if (mIcon == null) {
+ mIcon = SliceQuery.find(sliceItem, SliceItem.TYPE_IMAGE, (String) null,
+ null);
+ }
+ if (mLabel == null) {
+ mLabel = SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT, (String) null,
+ null);
+ }
+ // Final fallback: use app info
+ if (mIcon == null || mLabel == null || mAction == null) {
+ PackageManager pm = context.getPackageManager();
+ ProviderInfo providerInfo = pm.resolveContentProvider(
+ slice.getUri().getAuthority(), 0);
+ ApplicationInfo appInfo = providerInfo.applicationInfo;
+ if (appInfo != null) {
+ if (mIcon == null) {
+ Drawable icon = appInfo.loadDefaultIcon(pm);
+ mIcon = new SliceItem(SliceViewUtil.createIconFromDrawable(icon),
+ SliceItem.TYPE_IMAGE, new String[] {Slice.HINT_LARGE});
+ }
+ if (mLabel == null) {
+ mLabel = new SliceItem(pm.getApplicationLabel(appInfo),
+ SliceItem.TYPE_TEXT, null);
+ }
+ if (mAction == null) {
+ mAction = PendingIntent.getActivity(context, 0,
+ pm.getLaunchIntentForPackage(appInfo.packageName), 0);
+ }
+ }
+ }
+ }
}
diff --git a/android/app/slice/widget/SliceView.java b/android/app/slice/widget/SliceView.java
index 5bafbc03..fa1b64ce 100644
--- a/android/app/slice/widget/SliceView.java
+++ b/android/app/slice/widget/SliceView.java
@@ -115,7 +115,9 @@ public class SliceView extends ViewGroup {
*/
public static final String MODE_LARGE = "SLICE_LARGE";
/**
- * Mode indicating this slice should be presented as an icon.
+ * Mode indicating this slice should be presented as an icon. A shortcut requires an intent,
+ * icon, and label. This can be indicated by using {@link Slice#HINT_TITLE} on an action in a
+ * slice.
*/
public static final String MODE_SHORTCUT = "SLICE_ICON";
@@ -181,10 +183,25 @@ public class SliceView extends ViewGroup {
}
/**
+ * Populates this view with the {@link Slice} associated with the provided {@link Intent}. To
+ * use this method your app must have the permission
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
+ * updated with the slice identified by the provided intent changes. The lifecycle of this
+ * observer is handled by SliceView in {@link #onAttachedToWindow()} and
+ * {@link #onDetachedFromWindow()}. To unregister this observer outside of that you can call
+ * {@link #clearSlice}.
+ *
+ * @return true if a slice was found for the provided intent.
* @hide
*/
- public void showSlice(Intent intent) {
- // TODO
+ public boolean setSlice(@Nullable Intent intent) {
+ Slice s = Slice.bindSlice(mContext, intent);
+ if (s != null) {
+ return setSlice(s.getUri());
+ }
+ return s != null;
}
/**
@@ -197,8 +214,7 @@ public class SliceView extends ViewGroup {
* is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
* To unregister this observer outside of that you can call {@link #clearSlice}.
*
- * @return true if the a slice was found for the provided uri.
- * @see #clearSlice
+ * @return true if a slice was found for the provided uri.
*/
public boolean setSlice(@NonNull Uri sliceUri) {
Preconditions.checkNotNull(sliceUri,
@@ -210,11 +226,15 @@ public class SliceView extends ViewGroup {
validate(sliceUri);
Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
if (s != null) {
+ if (mObserver != null) {
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ }
mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
if (isAttachedToWindow()) {
registerSlice(sliceUri);
}
- showSlice(s);
+ mCurrentSlice = s;
+ reinflate();
}
return s != null;
}
diff --git a/android/app/slice/widget/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java
index 03669983..1cf0055b 100644
--- a/android/app/slice/widget/SliceViewUtil.java
+++ b/android/app/slice/widget/SliceViewUtil.java
@@ -28,6 +28,7 @@ import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.view.Gravity;
@@ -141,6 +142,21 @@ public class SliceViewUtil {
/**
* @hide
*/
+ public static Icon createIconFromDrawable(Drawable d) {
+ if (d instanceof BitmapDrawable) {
+ return Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
+ }
+ Bitmap b = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(b);
+ d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ d.draw(canvas);
+ return Icon.createWithBitmap(b);
+ }
+
+ /**
+ * @hide
+ */
public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
boolean isLarge, ViewGroup parent) {
ImageView v = new ImageView(context);
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index c827432a..3a3e16e0 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -261,7 +261,10 @@ public final class UsageStatsManager {
/**
* @hide
+ * Changes the app standby state to the provided bucket.
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE)
public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
try {
mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
diff --git a/android/app/usage/UsageStatsManagerInternal.java b/android/app/usage/UsageStatsManagerInternal.java
index dbaace2f..29e7439f 100644
--- a/android/app/usage/UsageStatsManagerInternal.java
+++ b/android/app/usage/UsageStatsManagerInternal.java
@@ -118,7 +118,15 @@ public abstract class UsageStatsManagerInternal {
AppIdleStateChangeListener listener);
public static abstract class AppIdleStateChangeListener {
- public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle);
+
+ /** Callback to inform listeners that the idle state has changed to a new bucket. */
+ public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle,
+ int bucket);
+
+ /**
+ * Callback to inform listeners that the parole state has changed. This means apps are
+ * allowed to do work even if they're idle or in a low bucket.
+ */
public abstract void onParoleStateChanged(boolean isParoleOn);
}
diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 106b2ef0..e8895bd0 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -37,6 +37,7 @@ public class AndroidViewModel extends ViewModel {
/**
* Return the application.
*/
+ @SuppressWarnings("TypeParameterUnusedInFormals")
@NonNull
public <T extends Application> T getApplication() {
//noinspection unchecked
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index 1ddcb1a9..f1352446 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,136 +1,9 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
+//ComputableLiveData interface for tests
package android.arch.lifecycle;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A LiveData class that can be invalidated & computed on demand.
- * <p>
- * This is an internal class for now, might be public if we see the necessity.
- *
- * @param <T> The type of the live data
- * @hide internal
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+import android.arch.lifecycle.LiveData;
public abstract class ComputableLiveData<T> {
-
- private final LiveData<T> mLiveData;
-
- private AtomicBoolean mInvalid = new AtomicBoolean(true);
- private AtomicBoolean mComputing = new AtomicBoolean(false);
-
- /**
- * Creates a computable live data which is computed when there are active observers.
- * <p>
- * It can also be invalidated via {@link #invalidate()} which will result in a call to
- * {@link #compute()} if there are active observers (or when they start observing)
- */
- @SuppressWarnings("WeakerAccess")
- public ComputableLiveData() {
- mLiveData = new LiveData<T>() {
- @Override
- protected void onActive() {
- // TODO if we make this class public, we should accept an executor
- ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- };
- }
-
- /**
- * Returns the LiveData managed by this class.
- *
- * @return A LiveData that is controlled by ComputableLiveData.
- */
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LiveData<T> getLiveData() {
- return mLiveData;
- }
-
- @VisibleForTesting
- final Runnable mRefreshRunnable = new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- boolean computed;
- do {
- computed = false;
- // compute can happen only in 1 thread but no reason to lock others.
- if (mComputing.compareAndSet(false, true)) {
- // as long as it is invalid, keep computing.
- try {
- T value = null;
- while (mInvalid.compareAndSet(true, false)) {
- computed = true;
- value = compute();
- }
- if (computed) {
- mLiveData.postValue(value);
- }
- } finally {
- // release compute lock
- mComputing.set(false);
- }
- }
- // check invalid after releasing compute lock to avoid the following scenario.
- // Thread A runs compute()
- // Thread A checks invalid, it is false
- // Main thread sets invalid to true
- // Thread B runs, fails to acquire compute lock and skips
- // Thread A releases compute lock
- // We've left invalid in set state. The check below recovers.
- } while (computed && mInvalid.get());
- }
- };
-
- // invalidation check always happens on the main thread
- @VisibleForTesting
- final Runnable mInvalidationRunnable = new Runnable() {
- @MainThread
- @Override
- public void run() {
- boolean isActive = mLiveData.hasActiveObservers();
- if (mInvalid.compareAndSet(false, true)) {
- if (isActive) {
- // TODO if we make this class public, we should accept an executor.
- ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- }
- }
- };
-
- /**
- * Invalidates the LiveData.
- * <p>
- * When there are active observers, this will trigger a call to {@link #compute()}.
- */
- public void invalidate() {
- ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
- }
-
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- protected abstract T compute();
+ public ComputableLiveData(){}
+ abstract protected T compute();
+ public LiveData<T> getLiveData() {return null;}
+ public void invalidate() {}
}
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 5b09c32f..3aea6acb 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,410 +1,4 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
+//LiveData interface for tests
package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * LiveData is a data holder class that can be observed within a given lifecycle.
- * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
- * this observer will be notified about modifications of the wrapped data only if the paired
- * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
- * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
- * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
- * about modifications. For those observers, you should manually call
- * {@link #removeObserver(Observer)}.
- *
- * <p> An observer added with a Lifecycle will be automatically removed if the corresponding
- * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
- * activities and fragments where they can safely observe LiveData and not worry about leaks:
- * they will be instantly unsubscribed when they are destroyed.
- *
- * <p>
- * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
- * to get notified when number of active {@link Observer}s change between 0 and 1.
- * This allows LiveData to release any heavy resources when it does not have any Observers that
- * are actively observing.
- * <p>
- * This class is designed to hold individual data fields of {@link ViewModel},
- * but can also be used for sharing data between different modules in your application
- * in a decoupled fashion.
- *
- * @param <T> The type of data held by this instance
- * @see ViewModel
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
-// thread.
-public abstract class LiveData<T> {
- private final Object mDataLock = new Object();
- static final int START_VERSION = -1;
- private static final Object NOT_SET = new Object();
-
- private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
-
- private LifecycleRegistry mRegistry = init();
-
- private LifecycleRegistry init() {
- LifecycleRegistry registry = new LifecycleRegistry(this);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
- return registry;
- }
-
- @Override
- public Lifecycle getLifecycle() {
- return mRegistry;
- }
- };
-
- private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
- new SafeIterableMap<>();
-
- // how many observers are in active state
- private int mActiveCount = 0;
- private volatile Object mData = NOT_SET;
- // when setData is called, we set the pending data and actual data swap happens on the main
- // thread
- private volatile Object mPendingData = NOT_SET;
- private int mVersion = START_VERSION;
-
- private boolean mDispatchingValue;
- @SuppressWarnings("FieldCanBeLocal")
- private boolean mDispatchInvalidated;
- private final Runnable mPostValueRunnable = new Runnable() {
- @Override
- public void run() {
- Object newValue;
- synchronized (mDataLock) {
- newValue = mPendingData;
- mPendingData = NOT_SET;
- }
- //noinspection unchecked
- setValue((T) newValue);
- }
- };
-
- private void considerNotify(LifecycleBoundObserver observer) {
- if (!observer.active) {
- return;
- }
- // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
- //
- // we still first check observer.active to keep it as the entrance for events. So even if
- // the observer moved to an active state, if we've not received that event, we better not
- // notify for a more predictable notification order.
- if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
- observer.activeStateChanged(false);
- return;
- }
- if (observer.lastVersion >= mVersion) {
- return;
- }
- observer.lastVersion = mVersion;
- //noinspection unchecked
- observer.observer.onChanged((T) mData);
- }
-
- private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
- if (mDispatchingValue) {
- mDispatchInvalidated = true;
- return;
- }
- mDispatchingValue = true;
- do {
- mDispatchInvalidated = false;
- if (initiator != null) {
- considerNotify(initiator);
- initiator = null;
- } else {
- for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
- mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
- considerNotify(iterator.next().getValue());
- if (mDispatchInvalidated) {
- break;
- }
- }
- }
- } while (mDispatchInvalidated);
- mDispatchingValue = false;
- }
-
- /**
- * Adds the given observer to the observers list within the lifespan of the given
- * owner. The events are dispatched on the main thread. If LiveData already has data
- * set, it will be delivered to the observer.
- * <p>
- * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
- * or {@link Lifecycle.State#RESUMED} state (active).
- * <p>
- * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
- * automatically be removed.
- * <p>
- * When data changes while the {@code owner} is not active, it will not receive any updates.
- * If it becomes active again, it will receive the last available data automatically.
- * <p>
- * LiveData keeps a strong reference to the observer and the owner as long as the
- * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
- * the observer &amp; the owner.
- * <p>
- * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
- * ignores the call.
- * <p>
- * If the given owner, observer tuple is already in the list, the call is ignored.
- * If the observer is already in the list with another owner, LiveData throws an
- * {@link IllegalArgumentException}.
- *
- * @param owner The LifecycleOwner which controls the observer
- * @param observer The observer that will receive the events
- */
- @MainThread
- public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- // ignore
- return;
- }
- LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
- LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
- if (existing != null && existing.owner != wrapper.owner) {
- throw new IllegalArgumentException("Cannot add the same observer"
- + " with different lifecycles");
- }
- if (existing != null) {
- return;
- }
- owner.getLifecycle().addObserver(wrapper);
- }
-
- /**
- * Adds the given observer to the observers list. This call is similar to
- * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
- * is always active. This means that the given observer will receive all events and will never
- * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
- * observing this LiveData.
- * While LiveData has one of such observers, it will be considered
- * as active.
- * <p>
- * If the observer was already added with an owner to this LiveData, LiveData throws an
- * {@link IllegalArgumentException}.
- *
- * @param observer The observer that will receive the events
- */
- @MainThread
- public void observeForever(@NonNull Observer<T> observer) {
- observe(ALWAYS_ON, observer);
- }
-
- /**
- * Removes the given observer from the observers list.
- *
- * @param observer The Observer to receive events.
- */
- @MainThread
- public void removeObserver(@NonNull final Observer<T> observer) {
- assertMainThread("removeObserver");
- LifecycleBoundObserver removed = mObservers.remove(observer);
- if (removed == null) {
- return;
- }
- removed.owner.getLifecycle().removeObserver(removed);
- removed.activeStateChanged(false);
- }
-
- /**
- * Removes all observers that are tied to the given {@link LifecycleOwner}.
- *
- * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
- */
- @MainThread
- public void removeObservers(@NonNull final LifecycleOwner owner) {
- assertMainThread("removeObservers");
- for (Map.Entry<Observer<T>, LifecycleBoundObserver> entry : mObservers) {
- if (entry.getValue().owner == owner) {
- removeObserver(entry.getKey());
- }
- }
- }
-
- /**
- * Posts a task to a main thread to set the given value. So if you have a following code
- * executed in the main thread:
- * <pre class="prettyprint">
- * liveData.postValue("a");
- * liveData.setValue("b");
- * </pre>
- * The value "b" would be set at first and later the main thread would override it with
- * the value "a".
- * <p>
- * If you called this method multiple times before a main thread executed a posted task, only
- * the last value would be dispatched.
- *
- * @param value The new value
- */
- protected void postValue(T value) {
- boolean postTask;
- synchronized (mDataLock) {
- postTask = mPendingData == NOT_SET;
- mPendingData = value;
- }
- if (!postTask) {
- return;
- }
- ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
- }
-
- /**
- * Sets the value. If there are active observers, the value will be dispatched to them.
- * <p>
- * This method must be called from the main thread. If you need set a value from a background
- * thread, you can use {@link #postValue(Object)}
- *
- * @param value The new value
- */
- @MainThread
- protected void setValue(T value) {
- assertMainThread("setValue");
- mVersion++;
- mData = value;
- dispatchingValue(null);
- }
-
- /**
- * Returns the current value.
- * Note that calling this method on a background thread does not guarantee that the latest
- * value set will be received.
- *
- * @return the current value
- */
- @Nullable
- public T getValue() {
- Object data = mData;
- if (data != NOT_SET) {
- //noinspection unchecked
- return (T) data;
- }
- return null;
- }
-
- int getVersion() {
- return mVersion;
- }
-
- /**
- * Called when the number of active observers change to 1 from 0.
- * <p>
- * This callback can be used to know that this LiveData is being used thus should be kept
- * up to date.
- */
- protected void onActive() {
-
- }
-
- /**
- * Called when the number of active observers change from 1 to 0.
- * <p>
- * This does not mean that there are no observers left, there may still be observers but their
- * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
- * (like an Activity in the back stack).
- * <p>
- * You can check if there are observers via {@link #hasObservers()}.
- */
- protected void onInactive() {
-
- }
-
- /**
- * Returns true if this LiveData has observers.
- *
- * @return true if this LiveData has observers
- */
- public boolean hasObservers() {
- return mObservers.size() > 0;
- }
-
- /**
- * Returns true if this LiveData has active observers.
- *
- * @return true if this LiveData has active observers
- */
- public boolean hasActiveObservers() {
- return mActiveCount > 0;
- }
-
- class LifecycleBoundObserver implements GenericLifecycleObserver {
- public final LifecycleOwner owner;
- public final Observer<T> observer;
- public boolean active;
- public int lastVersion = START_VERSION;
-
- LifecycleBoundObserver(LifecycleOwner owner, Observer<T> observer) {
- this.owner = owner;
- this.observer = observer;
- }
-
- @Override
- public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- removeObserver(observer);
- return;
- }
- // immediately set active state, so we'd never dispatch anything to inactive
- // owner
- activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
- }
-
- void activeStateChanged(boolean newActive) {
- if (newActive == active) {
- return;
- }
- active = newActive;
- boolean wasInactive = LiveData.this.mActiveCount == 0;
- LiveData.this.mActiveCount += active ? 1 : -1;
- if (wasInactive && active) {
- onActive();
- }
- if (LiveData.this.mActiveCount == 0 && !active) {
- onInactive();
- }
- if (active) {
- dispatchingValue(this);
- }
- }
- }
-
- static boolean isActiveState(State state) {
- return state.isAtLeast(STARTED);
- }
-
- private void assertMainThread(String methodName) {
- if (!ArchTaskExecutor.getInstance().isMainThread()) {
- throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
- + " thread");
- }
- }
+public class LiveData<T> {
}
diff --git a/android/arch/lifecycle/LiveDataReactiveStreams.java b/android/arch/lifecycle/LiveDataReactiveStreams.java
index 2b25bc9b..ba76f8e8 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -24,7 +24,7 @@ import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
-import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Adapts {@link LiveData} input and output to the ReactiveStreams spec.
@@ -53,83 +53,114 @@ public final class LiveDataReactiveStreams {
public static <T> Publisher<T> toPublisher(
final LifecycleOwner lifecycle, final LiveData<T> liveData) {
- return new Publisher<T>() {
+ return new LiveDataPublisher<>(lifecycle, liveData);
+ }
+
+ private static final class LiveDataPublisher<T> implements Publisher<T> {
+ final LifecycleOwner mLifecycle;
+ final LiveData<T> mLiveData;
+
+ LiveDataPublisher(final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+ this.mLifecycle = lifecycle;
+ this.mLiveData = liveData;
+ }
+
+ @Override
+ public void subscribe(Subscriber<? super T> subscriber) {
+ subscriber.onSubscribe(new LiveDataSubscription<T>(subscriber, mLifecycle, mLiveData));
+ }
+
+ static final class LiveDataSubscription<T> implements Subscription, Observer<T> {
+ final Subscriber<? super T> mSubscriber;
+ final LifecycleOwner mLifecycle;
+ final LiveData<T> mLiveData;
+
+ volatile boolean mCanceled;
+ // used on main thread only
boolean mObserving;
- boolean mCanceled;
long mRequested;
+ // used on main thread only
@Nullable
T mLatest;
+ LiveDataSubscription(final Subscriber<? super T> subscriber,
+ final LifecycleOwner lifecycle, final LiveData<T> liveData) {
+ this.mSubscriber = subscriber;
+ this.mLifecycle = lifecycle;
+ this.mLiveData = liveData;
+ }
+
+ @Override
+ public void onChanged(T t) {
+ if (mCanceled) {
+ return;
+ }
+ if (mRequested > 0) {
+ mLatest = null;
+ mSubscriber.onNext(t);
+ if (mRequested != Long.MAX_VALUE) {
+ mRequested--;
+ }
+ } else {
+ mLatest = t;
+ }
+ }
+
@Override
- public void subscribe(final Subscriber<? super T> subscriber) {
- final Observer<T> observer = new Observer<T>() {
+ public void request(final long n) {
+ if (mCanceled) {
+ return;
+ }
+ ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
- public void onChanged(@Nullable T t) {
+ public void run() {
if (mCanceled) {
return;
}
- if (mRequested > 0) {
- mLatest = null;
- subscriber.onNext(t);
- if (mRequested != Long.MAX_VALUE) {
- mRequested--;
+ if (n <= 0L) {
+ mCanceled = true;
+ if (mObserving) {
+ mLiveData.removeObserver(LiveDataSubscription.this);
+ mObserving = false;
}
- } else {
- mLatest = t;
+ mLatest = null;
+ mSubscriber.onError(
+ new IllegalArgumentException("Non-positive request"));
+ return;
}
- }
- };
- subscriber.onSubscribe(new Subscription() {
- @Override
- public void request(final long n) {
- if (n < 0 || mCanceled) {
- return;
+ // Prevent overflowage.
+ mRequested = mRequested + n >= mRequested
+ ? mRequested + n : Long.MAX_VALUE;
+ if (!mObserving) {
+ mObserving = true;
+ mLiveData.observe(mLifecycle, LiveDataSubscription.this);
+ } else if (mLatest != null) {
+ onChanged(mLatest);
+ mLatest = null;
}
- ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
- @Override
- public void run() {
- if (mCanceled) {
- return;
- }
- // Prevent overflowage.
- mRequested = mRequested + n >= mRequested
- ? mRequested + n : Long.MAX_VALUE;
- if (!mObserving) {
- mObserving = true;
- liveData.observe(lifecycle, observer);
- } else if (mLatest != null) {
- observer.onChanged(mLatest);
- mLatest = null;
- }
- }
- });
}
+ });
+ }
+ @Override
+ public void cancel() {
+ if (mCanceled) {
+ return;
+ }
+ mCanceled = true;
+ ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
- public void cancel() {
- if (mCanceled) {
- return;
+ public void run() {
+ if (mObserving) {
+ mLiveData.removeObserver(LiveDataSubscription.this);
+ mObserving = false;
}
- ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
- @Override
- public void run() {
- if (mCanceled) {
- return;
- }
- if (mObserving) {
- liveData.removeObserver(observer);
- mObserving = false;
- }
- mLatest = null;
- mCanceled = true;
- }
- });
+ mLatest = null;
}
});
}
-
- };
+ }
}
/**
@@ -145,6 +176,10 @@ public final class LiveDataReactiveStreams {
* Therefore, in the case of a hot RxJava Observable, when a new LiveData {@link Observer} is
* added, it will automatically notify with the last value held in LiveData,
* which might not be the last value emitted by the Publisher.
+ * <p>
+ * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+ * in the data that's held. In case of an error being emitted by the publisher, an error will
+ * be propagated to the main thread and the app will crash.
*
* @param <T> The type of data hold by this instance.
*/
@@ -166,67 +201,80 @@ public final class LiveDataReactiveStreams {
* added, it will automatically notify with the last value held in LiveData,
* which might not be the last value emitted by the Publisher.
*
+ * <p>
+ * Note that LiveData does NOT handle errors and it expects that errors are treated as states
+ * in the data that's held. In case of an error being emitted by the publisher, an error will
+ * be propagated to the main thread and the app will crash.
+ *
* @param <T> The type of data hold by this instance.
*/
private static class PublisherLiveData<T> extends LiveData<T> {
- private WeakReference<Subscription> mSubscriptionRef;
private final Publisher mPublisher;
- private final Object mLock = new Object();
+ final AtomicReference<LiveDataSubscriber> mSubscriber;
PublisherLiveData(@NonNull final Publisher publisher) {
mPublisher = publisher;
+ mSubscriber = new AtomicReference<>();
}
@Override
protected void onActive() {
super.onActive();
+ LiveDataSubscriber s = new LiveDataSubscriber();
+ mSubscriber.set(s);
+ mPublisher.subscribe(s);
+ }
- mPublisher.subscribe(new Subscriber<T>() {
- @Override
- public void onSubscribe(Subscription s) {
- // Don't worry about backpressure. If the stream is too noisy then
- // backpressure can be handled upstream.
- synchronized (mLock) {
- s.request(Long.MAX_VALUE);
- mSubscriptionRef = new WeakReference<>(s);
- }
- }
+ @Override
+ protected void onInactive() {
+ super.onInactive();
+ LiveDataSubscriber s = mSubscriber.getAndSet(null);
+ if (s != null) {
+ s.cancelSubscription();
+ }
+ }
- @Override
- public void onNext(final T t) {
- postValue(t);
- }
+ final class LiveDataSubscriber extends AtomicReference<Subscription>
+ implements Subscriber<T> {
- @Override
- public void onError(Throwable t) {
- synchronized (mLock) {
- mSubscriptionRef = null;
- }
- // Errors should be handled upstream, so propagate as a crash.
- throw new RuntimeException(t);
+ @Override
+ public void onSubscribe(Subscription s) {
+ if (compareAndSet(null, s)) {
+ s.request(Long.MAX_VALUE);
+ } else {
+ s.cancel();
}
+ }
- @Override
- public void onComplete() {
- synchronized (mLock) {
- mSubscriptionRef = null;
- }
- }
- });
+ @Override
+ public void onNext(T item) {
+ postValue(item);
+ }
- }
+ @Override
+ public void onError(final Throwable ex) {
+ mSubscriber.compareAndSet(this, null);
- @Override
- protected void onInactive() {
- super.onInactive();
- synchronized (mLock) {
- WeakReference<Subscription> subscriptionRef = mSubscriptionRef;
- if (subscriptionRef != null) {
- Subscription subscription = subscriptionRef.get();
- if (subscription != null) {
- subscription.cancel();
+ ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+ @Override
+ public void run() {
+ // Errors should be handled upstream, so propagate as a crash.
+ throw new RuntimeException("LiveData does not handle errors. Errors from "
+ + "publishers should be handled upstream and propagated as "
+ + "state", ex);
}
- mSubscriptionRef = null;
+ });
+ }
+
+ @Override
+ public void onComplete() {
+ mSubscriber.compareAndSet(this, null);
+ }
+
+ public void cancelSubscription() {
+ Subscription s = get();
+ if (s != null) {
+ s.cancel();
}
}
}
diff --git a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
index 7278847c..83e543c3 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
@@ -16,6 +16,9 @@
package android.arch.lifecycle;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.fail;
+
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -34,6 +37,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import io.reactivex.Flowable;
import io.reactivex.disposables.Disposable;
@@ -115,6 +119,41 @@ public class LiveDataReactiveStreamsTest {
}
@Test
+ public void convertsFromPublisherSubscribeWithDelay() {
+ PublishProcessor<String> processor = PublishProcessor.create();
+ processor.delaySubscription(100, TimeUnit.SECONDS, sBackgroundScheduler);
+ LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ processor.onNext("foo");
+ liveData.removeObserver(mObserver);
+ sBackgroundScheduler.triggerActions();
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ processor.onNext("bar");
+ processor.onNext("baz");
+
+ assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "bar", "baz")));
+ }
+
+ @Test
+ public void convertsFromPublisherThrowsException() {
+ PublishProcessor<String> processor = PublishProcessor.create();
+ LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ IllegalStateException exception = new IllegalStateException("test exception");
+ try {
+ processor.onError(exception);
+ fail("Runtime Exception expected");
+ } catch (RuntimeException ex) {
+ assertEquals(ex.getCause(), exception);
+ }
+ }
+
+ @Test
public void convertsFromPublisherWithMultipleObservers() {
final List<String> output2 = new ArrayList<>();
PublishProcessor<String> processor = PublishProcessor.create();
@@ -125,7 +164,7 @@ public class LiveDataReactiveStreamsTest {
processor.onNext("foo");
processor.onNext("bar");
- // The second mObserver should only get the newest value and any later values.
+ // The second observer should only get the newest value and any later values.
liveData.observe(mLifecycleOwner, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
@@ -140,6 +179,32 @@ public class LiveDataReactiveStreamsTest {
}
@Test
+ public void convertsFromPublisherWithMultipleObserversAfterInactive() {
+ final List<String> output2 = new ArrayList<>();
+ PublishProcessor<String> processor = PublishProcessor.create();
+ LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ processor.onNext("foo");
+ processor.onNext("bar");
+
+ // The second observer should only get the newest value and any later values.
+ liveData.observe(mLifecycleOwner, new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ output2.add(s);
+ }
+ });
+
+ liveData.removeObserver(mObserver);
+ processor.onNext("baz");
+
+ assertThat(mLiveDataOutput, is(Arrays.asList("foo", "bar")));
+ assertThat(output2, is(Arrays.asList("bar", "baz")));
+ }
+
+ @Test
public void convertsFromPublisherAfterInactive() {
PublishProcessor<String> processor = PublishProcessor.create();
LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
@@ -156,7 +221,7 @@ public class LiveDataReactiveStreamsTest {
}
@Test
- public void convertsFromPublisherManagesSubcriptions() {
+ public void convertsFromPublisherManagesSubscriptions() {
PublishProcessor<String> processor = PublishProcessor.create();
LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
@@ -198,7 +263,7 @@ public class LiveDataReactiveStreamsTest {
assertThat(
mOutputProcessor.getValues(new String[]{}),
- is(new String[] {"foo", "bar", "baz"}));
+ is(new String[]{"foo", "bar", "baz"}));
}
@Test
@@ -263,10 +328,10 @@ public class LiveDataReactiveStreamsTest {
final Subscription subscription = subscriptionSubject.blockingSingle();
subscription.request(1);
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
liveData.setValue("foo");
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
subscription.request(2);
liveData.setValue("baz");
@@ -274,7 +339,7 @@ public class LiveDataReactiveStreamsTest {
assertThat(
mOutputProcessor.getValues(new String[]{}),
- is(new String[] {"foo", "baz", "fizz"}));
+ is(new String[]{"foo", "baz", "fizz"}));
// 'nyan' will be dropped as there is nothing currently requesting a stream.
liveData.setValue("nyan");
@@ -282,13 +347,13 @@ public class LiveDataReactiveStreamsTest {
assertThat(
mOutputProcessor.getValues(new String[]{}),
- is(new String[] {"foo", "baz", "fizz"}));
+ is(new String[]{"foo", "baz", "fizz"}));
// When a new request comes in, the latest value will be pushed.
subscription.request(1);
assertThat(
mOutputProcessor.getValues(new String[]{}),
- is(new String[] {"foo", "baz", "fizz", "cat"}));
+ is(new String[]{"foo", "baz", "fizz", "cat"}));
}
@Test
@@ -301,17 +366,17 @@ public class LiveDataReactiveStreamsTest {
liveData.setValue("foo");
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{}));
sBackgroundScheduler.triggerActions();
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
liveData.setValue("bar");
liveData.setValue("baz");
- assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[] {"foo"}));
+ assertThat(mOutputProcessor.getValues(new String[]{}), is(new String[]{"foo"}));
sBackgroundScheduler.triggerActions();
assertThat(mOutputProcessor.getValues(
new String[]{}),
- is(new String[] {"foo", "bar", "baz"}));
+ is(new String[]{"foo", "bar", "baz"}));
}
}
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index 647d5d7a..c1dc54da 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -45,6 +45,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
@@ -52,11 +53,22 @@ import org.mockito.Mockito;
@RunWith(JUnit4.class)
public class LiveDataTest {
private PublicLiveData<String> mLiveData;
+ private MethodExec mActiveObserversChanged;
+
private LifecycleOwner mOwner;
- private LifecycleOwner mOwner2;
private LifecycleRegistry mRegistry;
+
+ private LifecycleOwner mOwner2;
private LifecycleRegistry mRegistry2;
- private MethodExec mActiveObserversChanged;
+
+ private LifecycleOwner mOwner3;
+ private Lifecycle mLifecycle3;
+ private Observer<String> mObserver3;
+
+ private LifecycleOwner mOwner4;
+ private Lifecycle mLifecycle4;
+ private Observer<String> mObserver4;
+
private boolean mInObserver;
@Before
@@ -67,12 +79,10 @@ public class LiveDataTest {
mLiveData.activeObserversChanged = mActiveObserversChanged;
mOwner = mock(LifecycleOwner.class);
-
mRegistry = new LifecycleRegistry(mOwner);
when(mOwner.getLifecycle()).thenReturn(mRegistry);
mOwner2 = mock(LifecycleOwner.class);
-
mRegistry2 = new LifecycleRegistry(mOwner2);
when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
@@ -80,6 +90,19 @@ public class LiveDataTest {
}
@Before
+ public void initNonLifecycleRegistry() {
+ mOwner3 = mock(LifecycleOwner.class);
+ mLifecycle3 = mock(Lifecycle.class);
+ mObserver3 = (Observer<String>) mock(Observer.class);
+ when(mOwner3.getLifecycle()).thenReturn(mLifecycle3);
+
+ mOwner4 = mock(LifecycleOwner.class);
+ mLifecycle4 = mock(Lifecycle.class);
+ mObserver4 = (Observer<String>) mock(Observer.class);
+ when(mOwner4.getLifecycle()).thenReturn(mLifecycle4);
+ }
+
+ @Before
public void swapExecutorDelegate() {
ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
}
@@ -572,100 +595,195 @@ public class LiveDataTest {
verify(mActiveObserversChanged, never()).onCall(anyBoolean());
}
- /**
- * Verifies that if a lifecycle's state changes without an event, and changes to something that
- * LiveData would become inactive in response to, LiveData will detect the change upon new data
- * being set and become inactive. Also verifies that once the lifecycle enters into a state
- * that LiveData should become active to, that it does indeed become active.
- */
@Test
- public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
- Observer<String> observer = (Observer<String>) mock(Observer.class);
- mLiveData.observe(mOwner, observer);
+ public void setValue_lifecycleIsCreatedNoEvent_liveDataBecomesInactiveAndObserverNotCalled() {
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+
+ GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
- // Marking state as CREATED should call onInactive.
- reset(mActiveObserversChanged);
- mRegistry.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged).onCall(false);
reset(mActiveObserversChanged);
+ reset(mObserver3);
+
+ // Act.
- // Setting a new value should trigger LiveData to realize the Lifecycle it is observing
- // is in a state where the LiveData should be inactive, so the LiveData will call onInactive
- // and the Observer shouldn't be affected.
mLiveData.setValue("1");
- // state is already CREATED so should not call again
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer, never()).onChanged(anyString());
+ // Assert.
+
+ verify(mActiveObserversChanged).onCall(false);
+ verify(mObserver3, never()).onChanged(anyString());
+ }
+
+ /*
+ * Arrange: LiveData was made inactive via SetValue (because the Lifecycle it was
+ * observing was in the CREATED state and no event was dispatched).
+ * Act: Lifecycle enters Started state and dispatches event.
+ * Assert: LiveData becomes active and dispatches new value to observer.
+ */
+ @Test
+ public void test_liveDataInactiveViaSetValueThenLifecycleResumes() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+
+ GenericLifecycleObserver lifecycleObserver = getGenericLifecycleObserver(mLifecycle3);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ mLiveData.setValue("1");
- // Sanity check. Because we've only marked the state as CREATED, sending ON_START
- // should re-dispatch events.
reset(mActiveObserversChanged);
- reset(observer);
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ reset(mObserver3);
+
+ // Act.
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ // Assert.
+
verify(mActiveObserversChanged).onCall(true);
- verify(observer).onChanged("1");
+ verify(mObserver3).onChanged("1");
}
- /**
- * This test verifies that LiveData will detect changes in LifecycleState that would make it
- * inactive upon the setting of new data, but only if all of the Lifecycles it's observing
- * are all in those states. It also makes sure that once it is inactive, that it will become
- * active again once one of the lifecycles it's observing moves to an appropriate state.
+ /*
+ * Arrange: One of two Lifecycles enter the CREATED state without sending an event.
+ * Act: Lifecycle's setValue method is called with new value.
+ * Assert: LiveData stays active and new value is dispatched to Lifecycle that is still at least
+ * STARTED.
*/
@Test
- public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
- Observer<String> observer1 = mock(Observer.class);
- Observer<String> observer2 = mock(Observer.class);
+ public void setValue_oneOfTwoLifecyclesAreCreatedNoEvent() {
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
+ // Arrange.
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
- // Marking the state to created won't change LiveData to be inactive.
reset(mActiveObserversChanged);
- mRegistry.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
- // After setting a value, the LiveData will stay active because there is still a STARTED
- // lifecycle being observed. The one Observer associated with the STARTED lifecycle will
- // also have been called, but the other Observer will not have been called.
- reset(observer1);
- reset(observer2);
mLiveData.setValue("1");
+
+ // Assert.
+
verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer1, never()).onChanged(anyString());
- verify(observer2).onChanged("1");
+ verify(mObserver3, never()).onChanged(anyString());
+ verify(mObserver4).onChanged("1");
+ }
- // Now we set the other Lifecycle to be inactive, live data should become inactive.
- reset(observer1);
- reset(observer2);
- mRegistry2.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged).onCall(false);
- verify(observer1, never()).onChanged(anyString());
- verify(observer2, never()).onChanged(anyString());
+ /*
+ * Arrange: Two observed Lifecycles enter the CREATED state without sending an event.
+ * Act: Lifecycle's setValue method is called with new value.
+ * Assert: LiveData becomes inactive and nothing is dispatched to either observer.
+ */
+ @Test
+ public void setValue_twoLifecyclesAreCreatedNoEvent() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
- // Now we post another value, because both lifecycles are in the Created state, live data
- // will not dispatch any values
reset(mActiveObserversChanged);
- mLiveData.setValue("2");
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer1, never()).onChanged(anyString());
- verify(observer2, never()).onChanged(anyString());
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
+
+ mLiveData.setValue("1");
+
+ // Assert.
+
+ verify(mActiveObserversChanged).onCall(false);
+ verify(mObserver3, never()).onChanged(anyString());
+ verify(mObserver3, never()).onChanged(anyString());
+ }
+
+ /*
+ * Arrange: LiveData was made inactive via SetValue (because both Lifecycles it was
+ * observing were in the CREATED state and no event was dispatched).
+ * Act: One Lifecycle enters STARTED state and dispatches lifecycle event.
+ * Assert: LiveData becomes active and dispatches new value to observer associated with started
+ * Lifecycle.
+ */
+ @Test
+ public void test_liveDataInactiveViaSetValueThenOneLifecycleResumes() {
+
+ // Arrange.
+
+ mLiveData.observe(mOwner3, mObserver3);
+ mLiveData.observe(mOwner4, mObserver4);
+
+ GenericLifecycleObserver lifecycleObserver3 = getGenericLifecycleObserver(mLifecycle3);
+ GenericLifecycleObserver lifecycleObserver4 = getGenericLifecycleObserver(mLifecycle4);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+ lifecycleObserver4.onStateChanged(mOwner4, Lifecycle.Event.ON_START);
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+ when(mLifecycle4.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
+ mLiveData.setValue("1");
- // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
- // be made active and it's associated Observer will be called with the new value, but the
- // Observer associated with the Lifecycle that is still in the Created state won't be
- // called.
reset(mActiveObserversChanged);
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ reset(mObserver3);
+ reset(mObserver4);
+
+ // Act.
+
+ when(mLifecycle3.getCurrentState()).thenReturn(Lifecycle.State.STARTED);
+ lifecycleObserver3.onStateChanged(mOwner3, Lifecycle.Event.ON_START);
+
+ // Assert.
+
verify(mActiveObserversChanged).onCall(true);
- verify(observer1).onChanged("2");
- verify(observer2, never()).onChanged(anyString());
+ verify(mObserver3).onChanged("1");
+ verify(mObserver4, never()).onChanged(anyString());
+ }
+
+ private GenericLifecycleObserver getGenericLifecycleObserver(Lifecycle lifecycle) {
+ ArgumentCaptor<GenericLifecycleObserver> captor =
+ ArgumentCaptor.forClass(GenericLifecycleObserver.class);
+ verify(lifecycle).addObserver(captor.capture());
+ return (captor.getValue());
}
@SuppressWarnings("WeakerAccess")
diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java
index 29cbab8e..a7b3aeba 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -138,6 +138,7 @@ public class ViewModelProvider {
*/
public static class NewInstanceFactory implements Factory {
+ @SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
diff --git a/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java b/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
index 6f4aa68a..373b122d 100644
--- a/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
+++ b/android/arch/persistence/room/EntityDeletionOrUpdateAdapter.java
@@ -45,6 +45,7 @@ public abstract class EntityDeletionOrUpdateAdapter<T> extends SharedSQLiteState
*
* @return An SQL query that can delete or update instances of T.
*/
+ @Override
protected abstract String createQuery();
/**
diff --git a/android/arch/persistence/room/Room.java b/android/arch/persistence/room/Room.java
index 2850b55e..9b168fce 100644
--- a/android/arch/persistence/room/Room.java
+++ b/android/arch/persistence/room/Room.java
@@ -72,6 +72,7 @@ public class Room {
return new RoomDatabase.Builder<>(context, klass, null);
}
+ @SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"})
@NonNull
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
final String fullPackage = klass.getPackage().getName();
diff --git a/android/arch/persistence/room/testing/MigrationTestHelper.java b/android/arch/persistence/room/testing/MigrationTestHelper.java
index 18e0a146..2e93bbe4 100644
--- a/android/arch/persistence/room/testing/MigrationTestHelper.java
+++ b/android/arch/persistence/room/testing/MigrationTestHelper.java
@@ -341,7 +341,7 @@ public class MigrationTestHelper extends TestWatcher {
return 0;
}
- class MigratingDelegate extends RoomOpenHelperDelegate {
+ static class MigratingDelegate extends RoomOpenHelperDelegate {
private final boolean mVerifyDroppedTables;
MigratingDelegate(DatabaseBundle databaseBundle, boolean verifyDroppedTables) {
diff --git a/android/bluetooth/BluetoothSocket.java b/android/bluetooth/BluetoothSocket.java
index 4035ee1b..05699134 100644
--- a/android/bluetooth/BluetoothSocket.java
+++ b/android/bluetooth/BluetoothSocket.java
@@ -375,7 +375,7 @@ public final class BluetoothSocket implements Closeable {
IBluetooth bluetoothProxy =
BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
- mPfd = bluetoothProxy.connectSocket(mDevice, mType,
+ mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType,
mUuid, mPort, getSecurityFlags());
synchronized (this) {
if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
@@ -417,7 +417,7 @@ public final class BluetoothSocket implements Closeable {
return -1;
}
try {
- mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName,
+ mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName,
mUuid, mPort, getSecurityFlags());
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
diff --git a/android/content/AsyncTaskLoader.java b/android/content/AsyncTaskLoader.java
index b7545bf5..6e9f09cb 100644
--- a/android/content/AsyncTaskLoader.java
+++ b/android/content/AsyncTaskLoader.java
@@ -49,7 +49,10 @@ import java.util.concurrent.Executor;
* fragment}
*
* @param <D> the data type to be loaded.
+ *
+ * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
*/
+@Deprecated
public abstract class AsyncTaskLoader<D> extends Loader<D> {
static final String TAG = "AsyncTaskLoader";
static final boolean DEBUG = false;
diff --git a/android/content/ContentProviderClient.java b/android/content/ContentProviderClient.java
index f8c139fe..2d490a03 100644
--- a/android/content/ContentProviderClient.java
+++ b/android/content/ContentProviderClient.java
@@ -22,6 +22,7 @@ import android.content.res.AssetFileDescriptor;
import android.database.CrossProcessCursorWrapper;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.DeadObjectException;
@@ -102,8 +103,16 @@ public class ContentProviderClient implements AutoCloseable {
if (sAnrHandler == null) {
sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
}
+
+ // If the remote process hangs, we're going to kill it, so we're
+ // technically okay doing blocking calls.
+ Binder.allowBlocking(mContentProvider.asBinder());
} else {
mAnrRunnable = null;
+
+ // If we're no longer watching for hangs, revert back to default
+ // blocking behavior.
+ Binder.defaultBlocking(mContentProvider.asBinder());
}
}
}
@@ -511,6 +520,10 @@ public class ContentProviderClient implements AutoCloseable {
private boolean closeInternal() {
mCloseGuard.close();
if (mClosed.compareAndSet(false, true)) {
+ // We can't do ANR checks after we cease to exist! Reset any
+ // blocking behavior changes we might have made.
+ setDetectNotResponding(0);
+
if (mStable) {
return mContentResolver.releaseProvider(mContentProvider);
} else {
diff --git a/android/content/Context.java b/android/content/Context.java
index c165fb3e..19e24ad5 100644
--- a/android/content/Context.java
+++ b/android/content/Context.java
@@ -3413,6 +3413,8 @@ public abstract class Context {
public static final String NETWORK_STATS_SERVICE = "netstats";
/** {@hide} */
public static final String NETWORK_POLICY_SERVICE = "netpolicy";
+ /** {@hide} */
+ public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
/**
* Use with {@link #getSystemService} to retrieve a {@link
@@ -4042,6 +4044,13 @@ public abstract class Context {
public static final String STATS_COMPANION_SERVICE = "statscompanion";
/**
+ * Use with {@link #getSystemService} to retrieve an {@link android.stats.StatsManager}.
+ * @hide
+ */
+ @SystemApi
+ public static final String STATS_MANAGER = "stats";
+
+ /**
* Use with {@link #getSystemService} to retrieve a {@link
* android.content.om.OverlayManager} for managing overlay packages.
*
@@ -4071,6 +4080,14 @@ public abstract class Context {
public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.pm.crossprofile.CrossProfileApps} for cross profile operations.
+ *
+ * @see #getSystemService
+ */
+ public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/android/content/CursorLoader.java b/android/content/CursorLoader.java
index c78871c3..33386e5c 100644
--- a/android/content/CursorLoader.java
+++ b/android/content/CursorLoader.java
@@ -38,7 +38,10 @@ import java.util.Arrays;
* in the desired paramters with {@link #setUri(Uri)}, {@link #setSelection(String)},
* {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
* and {@link #setProjection(String[])}.
+ *
+ * @deprecated Use {@link android.support.v4.content.CursorLoader}
*/
+@Deprecated
public class CursorLoader extends AsyncTaskLoader<Cursor> {
final ForceLoadContentObserver mObserver;
diff --git a/android/content/Intent.java b/android/content/Intent.java
index e47de752..bad452ce 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -1728,6 +1728,9 @@ public class Intent implements Parcelable, Cloneable {
* <p>
* Output: If {@link #EXTRA_RETURN_RESULT}, returns whether the install
* succeeded.
+ * <p>
+ * Requires {@link android.Manifest.permission#REQUEST_DELETE_PACKAGES}
+ * since {@link Build.VERSION_CODES#P}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
@@ -3444,11 +3447,12 @@ public class Intent implements Parcelable, Cloneable {
/**
* A broadcast action to trigger a factory reset.
*
- * <p> The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission.
+ * <p>The sender must hold the {@link android.Manifest.permission#MASTER_CLEAR} permission. The
+ * reason for the factory reset should be specified as {@link #EXTRA_REASON}.
*
* <p>Not for use by third-party applications.
*
- * @see #EXTRA_FORCE_MASTER_CLEAR
+ * @see #EXTRA_FORCE_FACTORY_RESET
*
* {@hide}
*/
@@ -4827,7 +4831,13 @@ public class Intent implements Parcelable, Cloneable {
/** @hide */
public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
- /** {@hide} */
+ /**
+ * Intent extra: the reason that the operation associated with this intent is being performed.
+ *
+ * <p>Type: String
+ * @hide
+ */
+ @SystemApi
public static final String EXTRA_REASON = "android.intent.extra.REASON";
/**
diff --git a/android/content/Loader.java b/android/content/Loader.java
index 3faf13b6..80f9a14c 100644
--- a/android/content/Loader.java
+++ b/android/content/Loader.java
@@ -48,7 +48,10 @@ import java.io.PrintWriter;
* </div>
*
* @param <D> The result returned when the load is complete
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader}
*/
+@Deprecated
public class Loader<D> {
int mId;
OnLoadCompleteListener<D> mListener;
@@ -66,7 +69,10 @@ public class Loader<D> {
* is told it has changed. You do not normally need to use this yourself;
* it is used for you by {@link CursorLoader} to take care of executing
* an update when the cursor's backing data changes.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.ForceLoadContentObserver}
*/
+ @Deprecated
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
@@ -90,7 +96,10 @@ public class Loader<D> {
* to find out when a Loader it is managing has completed so that this can
* be reported to its client. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCompleteListener}
*/
+ @Deprecated
public interface OnLoadCompleteListener<D> {
/**
* Called on the thread that created the Loader when the load is complete.
@@ -108,7 +117,10 @@ public class Loader<D> {
* to find out when a Loader it is managing has been canceled so that it
* can schedule the next Loader. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCanceledListener}
*/
+ @Deprecated
public interface OnLoadCanceledListener<D> {
/**
* Called on the thread that created the Loader when the load is canceled.
diff --git a/android/content/SharedPreferences.java b/android/content/SharedPreferences.java
index 4b09feda..d3652e88 100644
--- a/android/content/SharedPreferences.java
+++ b/android/content/SharedPreferences.java
@@ -30,6 +30,11 @@ import java.util.Set;
* when they are committed to storage. Objects that are returned from the
* various <code>get</code> methods must be treated as immutable by the application.
*
+ * <p>Note: This class provides strong consistency guarantees. It is using expensive operations
+ * which might slow down an app. Frequently changing properties or properties where loss can be
+ * tolerated should use other mechanisms. For more details read the comments on
+ * {@link Editor#commit()} and {@link Editor#apply()}.
+ *
* <p><em>Note: This class does not support use across multiple processes.</em>
*
* <div class="special reference">
diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java
index 41667c4c..837c00a7 100644
--- a/android/content/pm/ActivityInfo.java
+++ b/android/content/pm/ActivityInfo.java
@@ -455,7 +455,6 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
*/
public static final int FLAG_TURN_SCREEN_ON = 0x1000000;
-
/**
* @hide Bit in {@link #flags}: If set, this component will only be seen
* by the system user. Only works with broadcast receivers. Set from the
@@ -1001,20 +1000,12 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
* Returns true if the activity's orientation is fixed.
* @hide
*/
- public boolean isFixedOrientation() {
+ boolean isFixedOrientation() {
return isFixedOrientationLandscape() || isFixedOrientationPortrait()
|| screenOrientation == SCREEN_ORIENTATION_LOCKED;
}
/**
- * Returns true if the specified orientation is considered fixed.
- * @hide
- */
- static public boolean isFixedOrientation(int orientation) {
- return isFixedOrientationLandscape(orientation) || isFixedOrientationPortrait(orientation);
- }
-
- /**
* Returns true if the activity's orientation is fixed to landscape.
* @hide
*/
diff --git a/android/content/pm/LauncherApps.java b/android/content/pm/LauncherApps.java
index b94a410b..9e54e235 100644
--- a/android/content/pm/LauncherApps.java
+++ b/android/content/pm/LauncherApps.java
@@ -282,13 +282,13 @@ public class LauncherApps {
public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
/**
- * @hide include all pinned shortcuts by any launchers, not just by the caller,
+ * Include all pinned shortcuts by any launchers, not just by the caller,
* in the result.
- * If the caller doesn't havve the {@link android.Manifest.permission#ACCESS_SHORTCUTS}
- * permission, this flag will be ignored.
+ *
+ * The caller must be the selected assistant app to use this flag, or have the system
+ * {@code ACCESS_SHORTCUTS} permission.
*/
- @TestApi
- public static final int FLAG_MATCH_ALL_PINNED = 1 << 10;
+ public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1 << 10;
/**
* FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
@@ -302,7 +302,7 @@ public class LauncherApps {
* @hide
*/
public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
- FLAG_MATCH_ALL_KINDS | FLAG_MATCH_ALL_PINNED;
+ FLAG_MATCH_ALL_KINDS | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
/** @hide kept for unit tests */
@Deprecated
@@ -671,9 +671,13 @@ public class LauncherApps {
}
/**
- * Returns whether the caller can access the shortcut information.
+ * Returns whether the caller can access the shortcut information. Access is currently
+ * available to:
*
- * <p>Only the default launcher can access the shortcut information.
+ * <ul>
+ * <li>The current launcher (or default launcher if there is no set current launcher).</li>
+ * <li>The currently active voice interaction service.</li>
+ * </ul>
*
* <p>Note when this method returns {@code false}, it may be a temporary situation because
* the user is trying a new launcher application. The user may decide to change the default
diff --git a/android/content/pm/PackageInstaller.java b/android/content/pm/PackageInstaller.java
index f4fdcaa4..86288396 100644
--- a/android/content/pm/PackageInstaller.java
+++ b/android/content/pm/PackageInstaller.java
@@ -444,6 +444,9 @@ public class PackageInstaller {
* @param packageName The package to uninstall.
* @param statusReceiver Where to deliver the result.
*/
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
uninstall(packageName, 0 /*flags*/, statusReceiver);
}
@@ -476,6 +479,9 @@ public class PackageInstaller {
* @param versionedPackage The versioned package to uninstall.
* @param statusReceiver Where to deliver the result.
*/
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
public void uninstall(@NonNull VersionedPackage versionedPackage,
@NonNull IntentSender statusReceiver) {
uninstall(versionedPackage, 0 /*flags*/, statusReceiver);
@@ -1184,10 +1190,10 @@ public class PackageInstaller {
}
/**
- * Sets the UID that initiated package installation. This is informational
+ * Sets the UID that initiated the package installation. This is informational
* and may be used as a signal for anti-malware purposes.
*
- * @see PackageManager#EXTRA_VERIFICATION_INSTALLER_UID
+ * @see Intent#EXTRA_ORIGINATING_UID
*/
public void setOriginatingUid(int originatingUid) {
this.originatingUid = originatingUid;
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index b48829cf..1c5cf15d 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -102,6 +102,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -1708,13 +1709,33 @@ public class PackageParser {
*/
public static ApkLite parseApkLite(File apkFile, int flags)
throws PackageParserException {
- final String apkPath = apkFile.getAbsolutePath();
+ return parseApkLiteInner(apkFile, null, null, flags);
+ }
+
+ /**
+ * Utility method that retrieves lightweight details about a single APK
+ * file, including package name, split name, and install location.
+ *
+ * @param fd already open file descriptor of an apk file
+ * @param debugPathName arbitrary text name for this file, for debug output
+ * @param flags optional parse flags, such as
+ * {@link #PARSE_COLLECT_CERTIFICATES}
+ */
+ public static ApkLite parseApkLite(FileDescriptor fd, String debugPathName, int flags)
+ throws PackageParserException {
+ return parseApkLiteInner(null, fd, debugPathName, flags);
+ }
+
+ private static ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, String debugPathName,
+ int flags) throws PackageParserException {
+ final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
AssetManager assets = null;
XmlResourceParser parser = null;
try {
assets = newConfiguredAssetManager();
- int cookie = assets.addAssetPath(apkPath);
+ int cookie = fd != null
+ ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Failed to parse " + apkPath);
diff --git a/android/content/pm/ShortcutServiceInternal.java b/android/content/pm/ShortcutServiceInternal.java
index 7fc25d82..dadfaa9f 100644
--- a/android/content/pm/ShortcutServiceInternal.java
+++ b/android/content/pm/ShortcutServiceInternal.java
@@ -73,6 +73,9 @@ public abstract class ShortcutServiceInternal {
public abstract boolean hasShortcutHostPermission(int launcherUserId,
@NonNull String callingPackage, int callingPid, int callingUid);
+ public abstract void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
+ int userId);
+
public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
@Nullable IntentSender resultIntent, int userId);
diff --git a/android/content/pm/crossprofile/CrossProfileApps.java b/android/content/pm/crossprofile/CrossProfileApps.java
new file mode 100644
index 00000000..c441b5f3
--- /dev/null
+++ b/android/content/pm/crossprofile/CrossProfileApps.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm.crossprofile;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.List;
+
+/**
+ * Class for handling cross profile operations. Apps can use this class to interact with its
+ * instance in any profile that is in {@link #getTargetUserProfiles()}. For example, app can
+ * use this class to start its main activity in managed profile.
+ */
+public class CrossProfileApps {
+ private final Context mContext;
+ private final ICrossProfileApps mService;
+
+ /** @hide */
+ public CrossProfileApps(Context context, ICrossProfileApps service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Starts the specified main activity of the caller package in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch, it must be exported and has
+ * action {@link android.content.Intent#ACTION_MAIN}, category
+ * {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will
+ * be thrown.
+ * @param user The UserHandle of the profile, must be one of the users returned by
+ * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+ * be thrown.
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon, see
+ * {@link android.content.Intent#setSourceBounds(Rect)}.
+ * @param startActivityOptions Options to pass to startActivity
+ */
+ public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user,
+ @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
+ try {
+ mService.startActivityAsUser(mContext.getPackageName(),
+ component, sourceBounds, startActivityOptions, user);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of user profiles that that the caller can use when calling other APIs in this
+ * class.
+ * <p>
+ * A user profile would be considered as a valid target user profile, provided that:
+ * <ul>
+ * <li>It gets caller app installed</li>
+ * <li>It is not equal to the calling user</li>
+ * <li>It is in the same profile group of calling user profile</li>
+ * <li>It is enabled</li>
+ * </ul>
+ *
+ * @see UserManager#getUserProfiles()
+ */
+ public @NonNull List<UserHandle> getTargetUserProfiles() {
+ try {
+ return mService.getTargetUserProfiles(mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/content/res/AssetManager.java b/android/content/res/AssetManager.java
index f0adcd6c..78665609 100644
--- a/android/content/res/AssetManager.java
+++ b/android/content/res/AssetManager.java
@@ -28,8 +28,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
-import dalvik.annotation.optimization.FastNative;
-
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -694,7 +693,35 @@ public final class AssetManager implements AutoCloseable {
private native final int addAssetPathNative(String path, boolean appAsLib);
- /**
+ /**
+ * Add an additional set of assets to the asset manager from an already open
+ * FileDescriptor. Not for use by applications.
+ * This does not give full AssetManager functionality for these assets,
+ * since the origin of the file is not known for purposes of sharing,
+ * overlay resolution, and other features. However it does allow you
+ * to do simple access to the contents of the given fd as an apk file.
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ * Returns the cookie of the added asset, or 0 on failure.
+ * {@hide}
+ */
+ public int addAssetFd(FileDescriptor fd, String debugPathName) {
+ return addAssetFdInternal(fd, debugPathName, false);
+ }
+
+ private int addAssetFdInternal(FileDescriptor fd, String debugPathName,
+ boolean appAsLib) {
+ synchronized (this) {
+ int res = addAssetFdNative(fd, debugPathName, appAsLib);
+ makeStringBlocks(mStringBlocks);
+ return res;
+ }
+ }
+
+ private native int addAssetFdNative(FileDescriptor fd, String debugPathName,
+ boolean appAsLib);
+
+ /**
* Add a set of assets to overlay an already added set of assets.
*
* This is only intended for application resources. System wide resources
diff --git a/android/content/res/Configuration.java b/android/content/res/Configuration.java
index dfd3bbf0..26efda10 100644
--- a/android/content/res/Configuration.java
+++ b/android/content/res/Configuration.java
@@ -2105,6 +2105,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
break;
case DENSITY_DPI_NONE:
parts.add("nodpi");
+ break;
default:
parts.add(config.densityDpi + "dpi");
break;
diff --git a/android/content/res/FontResourcesParser.java b/android/content/res/FontResourcesParser.java
index 28e9fce3..6a4aae66 100644
--- a/android/content/res/FontResourcesParser.java
+++ b/android/content/res/FontResourcesParser.java
@@ -80,13 +80,15 @@ public class FontResourcesParser {
private int mWeight;
private int mItalic;
private int mTtcIndex;
+ private String mVariationSettings;
private int mResourceId;
public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
- int ttcIndex) {
+ @Nullable String variationSettings, int ttcIndex) {
mFileName = fileName;
mWeight = weight;
mItalic = italic;
+ mVariationSettings = variationSettings;
mTtcIndex = ttcIndex;
}
@@ -102,6 +104,10 @@ public class FontResourcesParser {
return mItalic;
}
+ public @Nullable String getVariationSettings() {
+ return mVariationSettings;
+ }
+
public int getTtcIndex() {
return mTtcIndex;
}
@@ -211,6 +217,8 @@ public class FontResourcesParser {
Typeface.RESOLVE_BY_FONT_TABLE);
int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
Typeface.RESOLVE_BY_FONT_TABLE);
+ String variationSettings = array.getString(
+ R.styleable.FontFamilyFont_fontVariationSettings);
int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
String filename = array.getString(R.styleable.FontFamilyFont_font);
array.recycle();
@@ -220,7 +228,7 @@ public class FontResourcesParser {
if (filename == null) {
return null;
}
- return new FontFileResourceEntry(filename, weight, italic, ttcIndex);
+ return new FontFileResourceEntry(filename, weight, italic, variationSettings, ttcIndex);
}
private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/android/content/res/ResourcesImpl.java b/android/content/res/ResourcesImpl.java
index 386239cf..3239212a 100644
--- a/android/content/res/ResourcesImpl.java
+++ b/android/content/res/ResourcesImpl.java
@@ -49,6 +49,8 @@ import android.util.TypedValue;
import android.util.Xml;
import android.view.DisplayAdjustments;
+import com.android.internal.util.GrowingArrayUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -117,6 +119,13 @@ public class ResourcesImpl {
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
new ConfigurationBoundResourceCache<>();
+ // A stack of all the resourceIds already referenced when parsing a resource. This is used to
+ // detect circular references in the xml.
+ // Using a ThreadLocal variable ensures that we have different stacks for multiple parallel
+ // calls to ResourcesImpl
+ private final ThreadLocal<LookupStack> mLookupStack =
+ ThreadLocal.withInitial(() -> new LookupStack());
+
/** Size of the cyclical cache used to map XML files to blocks. */
private static final int XML_BLOCK_CACHE_SIZE = 4;
@@ -784,19 +793,29 @@ public class ResourcesImpl {
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ LookupStack stack = mLookupStack.get();
try {
- if (file.endsWith(".xml")) {
- final XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
- rp.close();
- } else {
- final InputStream is = mAssets.openNonAsset(
- value.assetCookie, file, AssetManager.ACCESS_STREAMING);
- dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
- is.close();
+ // Perform a linear search to check if we have already referenced this resource before.
+ if (stack.contains(id)) {
+ throw new Exception("Recursive reference in drawable");
}
- } catch (Exception | StackOverflowError e) {
+ stack.push(id);
+ try {
+ if (file.endsWith(".xml")) {
+ final XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable");
+ dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
+ rp.close();
+ } else {
+ final InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_STREAMING);
+ dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
+ is.close();
+ }
+ } finally {
+ stack.pop();
+ }
+ } catch (Exception e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
@@ -1377,4 +1396,29 @@ public class ResourcesImpl {
}
}
}
+
+ private static class LookupStack {
+
+ // Pick a reasonable default size for the array, it is grown as needed.
+ private int[] mIds = new int[4];
+ private int mSize = 0;
+
+ public void push(int id) {
+ mIds = GrowingArrayUtils.append(mIds, mSize, id);
+ mSize++;
+ }
+
+ public boolean contains(int id) {
+ for (int i = 0; i < mSize; i++) {
+ if (mIds[i] == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void pop() {
+ mSize--;
+ }
+ }
}
diff --git a/android/content/res/Resources_Delegate.java b/android/content/res/Resources_Delegate.java
index d9c97fe2..a32d5282 100644
--- a/android/content/res/Resources_Delegate.java
+++ b/android/content/res/Resources_Delegate.java
@@ -887,30 +887,19 @@ public class Resources_Delegate {
@LayoutlibDelegate
static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
-
- if (value != null) {
- String v = value.getSecond().getValue();
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
- if (v != null) {
- // check this is a file
- File f = new File(v);
- if (f.isFile()) {
- try {
- XmlPullParser parser = ParserFactory.create(f);
+ if (v != null) {
+ ResourceValue value = v.getSecond();
- return new BridgeXmlBlockParser(parser, getContext(resources),
- mPlatformResourceFlag[0]);
- } catch (XmlPullParserException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- } catch (FileNotFoundException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- }
- }
+ try {
+ return ResourceHelper.getXmlBlockParser(getContext(resources), value);
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
}
}
diff --git a/android/database/MergeCursor.java b/android/database/MergeCursor.java
index 2c25db76..272cfa24 100644
--- a/android/database/MergeCursor.java
+++ b/android/database/MergeCursor.java
@@ -17,7 +17,7 @@
package android.database;
/**
- * A convience class that lets you present an array of Cursors as a single linear Cursor.
+ * A convenience class that lets you present an array of Cursors as a single linear Cursor.
* The schema of the cursors presented is entirely up to the creator of the MergeCursor, and
* may be different if that is desired. Calls to getColumns, getColumnIndex, etc will return the
* value for the row that the MergeCursor is currently pointing at.
diff --git a/android/database/sqlite/SQLiteConnection.java b/android/database/sqlite/SQLiteConnection.java
index c28583ea..2c93a7fe 100644
--- a/android/database/sqlite/SQLiteConnection.java
+++ b/android/database/sqlite/SQLiteConnection.java
@@ -289,12 +289,19 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private void setWalModeFromConfiguration() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
- if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+ final boolean walEnabled =
+ (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ // Use compatibility WAL unless an app explicitly set journal/synchronous mode
+ final boolean useCompatibilityWal = mConfiguration.journalMode == null
+ && mConfiguration.syncMode == null && mConfiguration.useCompatibilityWal;
+ if (walEnabled || useCompatibilityWal) {
setJournalMode("WAL");
setSyncMode(SQLiteGlobal.getWALSyncMode());
} else {
- setJournalMode(SQLiteGlobal.getDefaultJournalMode());
- setSyncMode(SQLiteGlobal.getDefaultSyncMode());
+ setJournalMode(mConfiguration.journalMode == null
+ ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
+ setSyncMode(mConfiguration.syncMode == null
+ ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
}
}
}
@@ -308,12 +315,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
}
private static String canonicalizeSyncMode(String value) {
- if (value.equals("0")) {
- return "OFF";
- } else if (value.equals("1")) {
- return "NORMAL";
- } else if (value.equals("2")) {
- return "FULL";
+ switch (value) {
+ case "0": return "OFF";
+ case "1": return "NORMAL";
+ case "2": return "FULL";
}
return value;
}
@@ -414,7 +419,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
!= mConfiguration.foreignKeyConstraintsEnabled;
boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
- & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0
+ || configuration.useCompatibilityWal != mConfiguration.useCompatibilityWal;
boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
// Update configuration parameters.
diff --git a/android/database/sqlite/SQLiteCursor.java b/android/database/sqlite/SQLiteCursor.java
index 2dc5ca43..13e6f718 100644
--- a/android/database/sqlite/SQLiteCursor.java
+++ b/android/database/sqlite/SQLiteCursor.java
@@ -22,6 +22,8 @@ import android.database.DatabaseUtils;
import android.os.StrictMode;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.util.HashMap;
import java.util.Map;
@@ -60,6 +62,9 @@ public class SQLiteCursor extends AbstractWindowedCursor {
/** Used to find out where a cursor was allocated in case it never got released. */
private final Throwable mStackTrace;
+ /** Controls fetching of rows relative to requested position **/
+ private boolean mFillWindowForwardOnly;
+
/**
* Execute a query and provide access to its result set through a Cursor
* interface. For a query such as: {@code SELECT name, birth, phone FROM
@@ -136,18 +141,19 @@ public class SQLiteCursor extends AbstractWindowedCursor {
private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
-
try {
+ Preconditions.checkArgumentNonnegative(requiredPos,
+ "requiredPos cannot be negative, but was " + requiredPos);
+
if (mCount == NO_COUNT) {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
- mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
+ mCount = mQuery.fillWindow(mWindow, requiredPos, requiredPos, true);
mCursorWindowCapacity = mWindow.getNumRows();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
- mCursorWindowCapacity);
+ int startPos = mFillWindowForwardOnly ? requiredPos : DatabaseUtils
+ .cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity);
mQuery.fillWindow(mWindow, startPos, requiredPos, false);
}
} catch (RuntimeException ex) {
@@ -252,6 +258,20 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
/**
+ * Controls fetching of rows relative to requested position.
+ *
+ * <p>Calling this method defines how rows will be loaded, but it doesn't affect rows that
+ * are already in the window. This setting is preserved if a new window is
+ * {@link #setWindow(CursorWindow) set}
+ *
+ * @param fillWindowForwardOnly if true, rows will be fetched starting from requested position
+ * up to the window's capacity. Default value is false.
+ */
+ public void setFillWindowForwardOnly(boolean fillWindowForwardOnly) {
+ mFillWindowForwardOnly = fillWindowForwardOnly;
+ }
+
+ /**
* Release the native resources, if they haven't been released yet.
*/
@Override
diff --git a/android/database/sqlite/SQLiteDatabase.java b/android/database/sqlite/SQLiteDatabase.java
index df0e262b..863fb198 100644
--- a/android/database/sqlite/SQLiteDatabase.java
+++ b/android/database/sqlite/SQLiteDatabase.java
@@ -262,7 +262,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
private SQLiteDatabase(final String path, final int openFlags,
CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
- int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs) {
+ int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs,
+ String journalMode, String syncMode) {
mCursorFactory = cursorFactory;
mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
@@ -285,6 +286,9 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
}
mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
+ mConfigurationLocked.journalMode = journalMode;
+ mConfigurationLocked.syncMode = syncMode;
+ mConfigurationLocked.useCompatibilityWal = SQLiteGlobal.isCompatibilityWalSupported();
}
@Override
@@ -720,7 +724,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
openParams.mCursorFactory, openParams.mErrorHandler,
openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
- openParams.mIdleConnectionTimeout);
+ openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
db.open();
return db;
}
@@ -746,7 +750,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
*/
public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
@DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) {
- SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1);
+ SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1, null,
+ null);
db.open();
return db;
}
@@ -2070,15 +2075,21 @@ public final class SQLiteDatabase extends SQLiteClosable {
synchronized (mLock) {
throwIfNotOpenLocked();
- if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
+ final boolean oldUseCompatibilityWal = mConfigurationLocked.useCompatibilityWal;
+ final int oldFlags = mConfigurationLocked.openFlags;
+ if (!oldUseCompatibilityWal && (oldFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
return;
}
mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING;
+ // If an app explicitly disables WAL, do not even use compatibility mode
+ mConfigurationLocked.useCompatibilityWal = false;
+
try {
mConnectionPoolLocked.reconfigure(mConfigurationLocked);
} catch (RuntimeException ex) {
- mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING;
+ mConfigurationLocked.openFlags = oldFlags;
+ mConfigurationLocked.useCompatibilityWal = oldUseCompatibilityWal;
throw ex;
}
}
@@ -2295,17 +2306,21 @@ public final class SQLiteDatabase extends SQLiteClosable {
private final DatabaseErrorHandler mErrorHandler;
private final int mLookasideSlotSize;
private final int mLookasideSlotCount;
- private long mIdleConnectionTimeout;
+ private final long mIdleConnectionTimeout;
+ private final String mJournalMode;
+ private final String mSyncMode;
private OpenParams(int openFlags, CursorFactory cursorFactory,
DatabaseErrorHandler errorHandler, int lookasideSlotSize, int lookasideSlotCount,
- long idleConnectionTimeout) {
+ long idleConnectionTimeout, String journalMode, String syncMode) {
mOpenFlags = openFlags;
mCursorFactory = cursorFactory;
mErrorHandler = errorHandler;
mLookasideSlotSize = lookasideSlotSize;
mLookasideSlotCount = lookasideSlotCount;
mIdleConnectionTimeout = idleConnectionTimeout;
+ mJournalMode = journalMode;
+ mSyncMode = syncMode;
}
/**
@@ -2372,6 +2387,28 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
/**
+ * Returns <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>.
+ * This journal mode will only be used if {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING}
+ * flag is not set, otherwise a platform will use "WAL" journal mode.
+ * @see Builder#setJournalMode(String)
+ */
+ @Nullable
+ public String getJournalMode() {
+ return mJournalMode;
+ }
+
+ /**
+ * Returns <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>.
+ * This value will only be used when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag
+ * is not set, otherwise a system wide default will be used.
+ * @see Builder#setSynchronousMode(String)
+ */
+ @Nullable
+ public String getSynchronousMode() {
+ return mSyncMode;
+ }
+
+ /**
* Creates a new instance of builder {@link Builder#Builder(OpenParams) initialized} with
* {@code this} parameters.
* @hide
@@ -2391,6 +2428,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
private int mOpenFlags;
private CursorFactory mCursorFactory;
private DatabaseErrorHandler mErrorHandler;
+ private String mJournalMode;
+ private String mSyncMode;
public Builder() {
}
@@ -2401,6 +2440,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
mOpenFlags = params.mOpenFlags;
mCursorFactory = params.mCursorFactory;
mErrorHandler = params.mErrorHandler;
+ mJournalMode = params.mJournalMode;
+ mSyncMode = params.mSyncMode;
}
/**
@@ -2532,6 +2573,30 @@ public final class SQLiteDatabase extends SQLiteClosable {
return this;
}
+
+ /**
+ * Sets <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>
+ * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
+ */
+ @NonNull
+ public Builder setJournalMode(@NonNull String journalMode) {
+ Preconditions.checkNotNull(journalMode);
+ mJournalMode = journalMode;
+ return this;
+ }
+
+ /**
+ * Sets <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>
+ * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
+ * @return
+ */
+ @NonNull
+ public Builder setSynchronousMode(@NonNull String syncMode) {
+ Preconditions.checkNotNull(syncMode);
+ mSyncMode = syncMode;
+ return this;
+ }
+
/**
* Creates an instance of {@link OpenParams} with the options that were previously set
* on this builder
@@ -2539,7 +2604,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
@NonNull
public OpenParams build() {
return new OpenParams(mOpenFlags, mCursorFactory, mErrorHandler, mLookasideSlotSize,
- mLookasideSlotCount, mIdleConnectionTimeout);
+ mLookasideSlotCount, mIdleConnectionTimeout, mJournalMode, mSyncMode);
}
}
}
@@ -2554,4 +2619,6 @@ public final class SQLiteDatabase extends SQLiteClosable {
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatabaseOpenFlags {}
+
}
+
diff --git a/android/database/sqlite/SQLiteDatabaseConfiguration.java b/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 34c9b339..a14df1eb 100644
--- a/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -111,6 +111,27 @@ public final class SQLiteDatabaseConfiguration {
public long idleConnectionTimeoutMs = Long.MAX_VALUE;
/**
+ * Enables compatibility WAL mode. Applications cannot explicitly choose compatibility WAL mode,
+ * therefore it is not exposed as a flag.
+ *
+ * <p>In this mode, only database journal mode will be changed, connection pool
+ * size will still be limited to a single connection.
+ */
+ public boolean useCompatibilityWal;
+
+ /**
+ * Journal mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
+ * <p>Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()}
+ */
+ public String journalMode;
+
+ /**
+ * Synchronous mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
+ * <p>Default is returned by {@link SQLiteGlobal#getDefaultSyncMode()}
+ */
+ public String syncMode;
+
+ /**
* Creates a database configuration with the required parameters for opening a
* database and default values for all other parameters.
*
@@ -170,6 +191,9 @@ public final class SQLiteDatabaseConfiguration {
lookasideSlotSize = other.lookasideSlotSize;
lookasideSlotCount = other.lookasideSlotCount;
idleConnectionTimeoutMs = other.idleConnectionTimeoutMs;
+ useCompatibilityWal = other.useCompatibilityWal;
+ journalMode = other.journalMode;
+ syncMode = other.syncMode;
}
/**
diff --git a/android/database/sqlite/SQLiteGlobal.java b/android/database/sqlite/SQLiteGlobal.java
index 94d5555c..d6d9764c 100644
--- a/android/database/sqlite/SQLiteGlobal.java
+++ b/android/database/sqlite/SQLiteGlobal.java
@@ -81,6 +81,16 @@ public final class SQLiteGlobal {
}
/**
+ * Returns true if compatibility WAL mode is supported. In this mode, only
+ * database journal mode is changed. Connection pool will use at most one connection.
+ */
+ public static boolean isCompatibilityWalSupported() {
+ return SystemProperties.getBoolean("debug.sqlite.compatibility_wal_supported",
+ Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.db_compatibility_wal_supported));
+ }
+
+ /**
* Gets the journal size limit in bytes.
*/
public static int getJournalSizeLimit() {
diff --git a/android/database/sqlite/SQLiteOpenHelper.java b/android/database/sqlite/SQLiteOpenHelper.java
index cc9e0f4d..49f357e6 100644
--- a/android/database/sqlite/SQLiteOpenHelper.java
+++ b/android/database/sqlite/SQLiteOpenHelper.java
@@ -17,6 +17,8 @@
package android.database.sqlite;
import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.SQLException;
@@ -24,6 +26,8 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.os.FileUtils;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.io.File;
/**
@@ -69,7 +73,8 @@ public abstract class SQLiteOpenHelper {
* {@link #onUpgrade} will be used to upgrade the database; if the database is
* newer, {@link #onDowngrade} will be used to downgrade the database
*/
- public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
+ public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+ @Nullable CursorFactory factory, int version) {
this(context, name, factory, version, null);
}
@@ -90,12 +95,33 @@ public abstract class SQLiteOpenHelper {
* @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
* corruption, or null to use the default error handler.
*/
- public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
- DatabaseErrorHandler errorHandler) {
+ public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+ @Nullable CursorFactory factory, int version,
+ @Nullable DatabaseErrorHandler errorHandler) {
this(context, name, factory, version, 0, errorHandler);
}
/**
+ * Create a helper object to create, open, and/or manage a database.
+ * This method always returns very quickly. The database is not actually
+ * created or opened until one of {@link #getWritableDatabase} or
+ * {@link #getReadableDatabase} is called.
+ *
+ * @param context to use to open or create the database
+ * @param name of the database file, or null for an in-memory database
+ * @param version number of the database (starting at 1); if the database is older,
+ * {@link #onUpgrade} will be used to upgrade the database; if the database is
+ * newer, {@link #onDowngrade} will be used to downgrade the database
+ * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}.
+ * Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be
+ * set when the helper opens the database
+ */
+ public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
+ @NonNull SQLiteDatabase.OpenParams openParams) {
+ this(context, name, version, 0, openParams.toBuilder());
+ }
+
+ /**
* Same as {@link #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)}
* but also accepts an integer minimumSupportedVersion as a convenience for upgrading very old
* versions of this database that are no longer supported. If a database with older version that
@@ -118,17 +144,26 @@ public abstract class SQLiteOpenHelper {
* @see #onUpgrade(SQLiteDatabase, int, int)
* @hide
*/
- public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
- int minimumSupportedVersion, DatabaseErrorHandler errorHandler) {
+ public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
+ @Nullable CursorFactory factory, int version,
+ int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler) {
+ this(context, name, version, minimumSupportedVersion,
+ new SQLiteDatabase.OpenParams.Builder());
+ mOpenParamsBuilder.setCursorFactory(factory);
+ mOpenParamsBuilder.setErrorHandler(errorHandler);
+ }
+
+ private SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
+ int minimumSupportedVersion,
+ @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
+ Preconditions.checkNotNull(openParamsBuilder);
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mNewVersion = version;
mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
- mOpenParamsBuilder = new SQLiteDatabase.OpenParams.Builder();
- mOpenParamsBuilder.setCursorFactory(factory);
- mOpenParamsBuilder.setErrorHandler(errorHandler);
+ mOpenParamsBuilder = openParamsBuilder;
mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
}
diff --git a/android/ext/services/notification/Assistant.java b/android/ext/services/notification/Assistant.java
index f535368b..6fe89755 100644
--- a/android/ext/services/notification/Assistant.java
+++ b/android/ext/services/notification/Assistant.java
@@ -23,16 +23,37 @@ import static android.service.notification.NotificationListenerService.Ranking
import android.app.INotificationManager;
import android.content.Context;
import android.ext.services.R;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Environment;
+import android.os.storage.StorageManager;
import android.service.notification.Adjustment;
import android.service.notification.NotificationAssistantService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Map;
/**
* Notification assistant that provides guidance on notification channel blocking
@@ -41,19 +62,112 @@ public class Assistant extends NotificationAssistantService {
private static final String TAG = "ExtAssistant";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final ArrayList<Integer> DISMISS_WITH_PREJUDICE = new ArrayList<>();
+ private static final String TAG_ASSISTANT = "assistant";
+ private static final String TAG_IMPRESSION = "impression-set";
+ private static final String ATT_KEY = "key";
+ private static final int DB_VERSION = 1;
+ private static final String ATTR_VERSION = "version";
+
+ private static final ArrayList<Integer> PREJUDICAL_DISMISSALS = new ArrayList<>();
static {
- DISMISS_WITH_PREJUDICE.add(REASON_CANCEL);
- DISMISS_WITH_PREJUDICE.add(REASON_LISTENER_CANCEL);
+ PREJUDICAL_DISMISSALS.add(REASON_CANCEL);
+ PREJUDICAL_DISMISSALS.add(REASON_LISTENER_CANCEL);
}
// key : impressions tracker
- // TODO: persist across reboots
+ // TODO: prune deleted channels and apps
ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>();
// SBN key : channel id
ArrayMap<String, String> mLiveNotifications = new ArrayMap<>();
private Ranking mFakeRanking = null;
+ private AtomicFile mFile = null;
+
+ public Assistant() {
+ }
+
+ private void loadFile() {
+ if (DEBUG) Slog.d(TAG, "loadFile");
+ AsyncTask.execute(() -> {
+ InputStream infile = null;
+ try {
+ infile = mFile.openRead();
+ readXml(infile);
+ } catch (FileNotFoundException e) {
+ // No data yet
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read channel impressions", e);
+ } catch (NumberFormatException | XmlPullParserException e) {
+ Log.e(TAG, "Unable to parse channel impressions", e);
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ });
+ }
+
+ protected void readXml(InputStream stream)
+ throws XmlPullParserException, NumberFormatException, IOException {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (!TAG_ASSISTANT.equals(parser.getName())) {
+ continue;
+ }
+ final int impressionOuterDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, impressionOuterDepth)) {
+ if (!TAG_IMPRESSION.equals(parser.getName())) {
+ continue;
+ }
+ String key = parser.getAttributeValue(null, ATT_KEY);
+ ChannelImpressions ci = new ChannelImpressions();
+ ci.populateFromXml(parser);
+ synchronized (mkeyToImpressions) {
+ ci.append(mkeyToImpressions.get(key));
+ mkeyToImpressions.put(key, ci);
+ }
+ }
+ }
+ }
+
+ private void saveFile() throws IOException {
+ AsyncTask.execute(() -> {
+ final FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save policy file", e);
+ return;
+ }
+ try {
+ final XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, StandardCharsets.UTF_8.name());
+ writeXml(out);
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to save impressions file, restoring backup", e);
+ mFile.failWrite(stream);
+ }
+ });
+ }
+
+ protected void writeXml(XmlSerializer out) throws IOException {
+ out.startDocument(null, true);
+ out.startTag(null, TAG_ASSISTANT);
+ out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
+ synchronized (mkeyToImpressions) {
+ for (Map.Entry<String, ChannelImpressions> entry
+ : mkeyToImpressions.entrySet()) {
+ // TODO: ensure channel still exists
+ out.startTag(null, TAG_IMPRESSION);
+ out.attribute(null, ATT_KEY, entry.getKey());
+ entry.getValue().writeXml(out);
+ out.endTag(null, TAG_IMPRESSION);
+ }
+ }
+ out.endTag(null, TAG_ASSISTANT);
+ out.endDocument();
+ }
@Override
public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
@@ -87,26 +201,38 @@ public class Assistant extends NotificationAssistantService {
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
NotificationStats stats, int reason) {
try {
+ boolean updatedImpressions = false;
String channelId = mLiveNotifications.remove(sbn.getKey());
String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId);
- ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, new ChannelImpressions());
- if (stats.hasSeen()) {
- ci.incrementViews();
+ synchronized (mkeyToImpressions) {
+ ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
+ new ChannelImpressions());
+ if (stats.hasSeen()) {
+ ci.incrementViews();
+ updatedImpressions = true;
+ }
+ if (PREJUDICAL_DISMISSALS.contains(reason)) {
+ if ((!sbn.isAppGroup() || sbn.getNotification().isGroupChild())
+ && !stats.hasInteracted()
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
+ && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
+ if (DEBUG) Log.i(TAG, "increment dismissals " + key);
+ ci.incrementDismissals();
+ updatedImpressions = true;
+ } else {
+ if (DEBUG) Slog.i(TAG, "reset streak " + key);
+ if (ci.getStreak() > 0) {
+ updatedImpressions = true;
+ }
+ ci.resetStreak();
+ }
+ }
+ mkeyToImpressions.put(key, ci);
}
- if (DISMISS_WITH_PREJUDICE.contains(reason)
- && !sbn.isAppGroup()
- && !sbn.getNotification().isGroupChild()
- && !stats.hasInteracted()
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
- && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
- if (DEBUG) Log.i(TAG, "increment dismissals");
- ci.incrementDismissals();
- } else {
- if (DEBUG) Slog.i(TAG, "reset streak");
- ci.resetStreak();
+ if (updatedImpressions) {
+ saveFile();
}
- mkeyToImpressions.put(key, ci);
} catch (Throwable e) {
Slog.e(TAG, "Error occurred processing removal", e);
}
@@ -121,6 +247,11 @@ public class Assistant extends NotificationAssistantService {
public void onListenerConnected() {
if (DEBUG) Log.i(TAG, "CONNECTED");
try {
+ mFile = new AtomicFile(new File(new File(
+ Environment.getDataUserCePackageDirectory(
+ StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
+ "assistant"), "block_stats.xml"));
+ loadFile();
for (StatusBarNotification sbn : getActiveNotifications()) {
onNotificationPosted(sbn);
}
@@ -129,7 +260,7 @@ public class Assistant extends NotificationAssistantService {
}
}
- private String getKey(String pkg, int userId, String channelId) {
+ protected String getKey(String pkg, int userId, String channelId) {
return pkg + "|" + userId + "|" + channelId;
}
@@ -151,6 +282,11 @@ public class Assistant extends NotificationAssistantService {
}
// for testing
+
+ protected void setFile(AtomicFile file) {
+ mFile = file;
+ }
+
protected void setFakeRanking(Ranking ranking) {
mFakeRanking = ranking;
}
@@ -162,4 +298,16 @@ public class Assistant extends NotificationAssistantService {
protected void setContext(Context context) {
mSystemContext = context;
}
+
+ protected ChannelImpressions getImpressions(String key) {
+ synchronized (mkeyToImpressions) {
+ return mkeyToImpressions.get(key);
+ }
+ }
+
+ protected void insertImpressions(String key, ChannelImpressions ci) {
+ synchronized (mkeyToImpressions) {
+ mkeyToImpressions.put(key, ci);
+ }
+ }
} \ No newline at end of file
diff --git a/android/ext/services/notification/ChannelImpressions.java b/android/ext/services/notification/ChannelImpressions.java
index 30567ccd..4ad4b241 100644
--- a/android/ext/services/notification/ChannelImpressions.java
+++ b/android/ext/services/notification/ChannelImpressions.java
@@ -18,14 +18,23 @@ package android.ext.services.notification;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Log;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
public final class ChannelImpressions implements Parcelable {
private static final String TAG = "ExtAssistant.CI";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
static final double DISMISS_TO_VIEW_RATIO_LIMIT = .8;
static final int STREAK_LIMIT = 2;
+ static final String ATT_DISMISSALS = "dismisses";
+ static final String ATT_VIEWS = "views";
+ static final String ATT_STREAK = "streak";
private int mDismissals = 0;
private int mViews = 0;
@@ -62,6 +71,14 @@ public final class ChannelImpressions implements Parcelable {
mStreak++;
}
+ public void append(ChannelImpressions additionalImpressions) {
+ if (additionalImpressions != null) {
+ mViews += additionalImpressions.getViews();
+ mStreak += additionalImpressions.getStreak();
+ mDismissals += additionalImpressions.getDismissals();
+ }
+ }
+
public void incrementViews() {
mViews++;
}
@@ -134,4 +151,36 @@ public final class ChannelImpressions implements Parcelable {
sb.append('}');
return sb.toString();
}
+
+ protected void populateFromXml(XmlPullParser parser) {
+ mDismissals = safeInt(parser, ATT_DISMISSALS, 0);
+ mStreak = safeInt(parser, ATT_STREAK, 0);
+ mViews = safeInt(parser, ATT_VIEWS, 0);
+ }
+
+ protected void writeXml(XmlSerializer out) throws IOException {
+ if (mDismissals != 0) {
+ out.attribute(null, ATT_DISMISSALS, String.valueOf(mDismissals));
+ }
+ if (mStreak != 0) {
+ out.attribute(null, ATT_STREAK, String.valueOf(mStreak));
+ }
+ if (mViews != 0) {
+ out.attribute(null, ATT_VIEWS, String.valueOf(mViews));
+ }
+ }
+
+ private static int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseInt(val, defValue);
+ }
+
+ private static int tryParseInt(String value, int defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
}
diff --git a/android/graphics/Paint.java b/android/graphics/Paint.java
index 1a06a568..317144a2 100644
--- a/android/graphics/Paint.java
+++ b/android/graphics/Paint.java
@@ -88,7 +88,7 @@ public class Paint {
* A map from a string representation of the LocaleList to Minikin's language list ID.
*/
@GuardedBy("sCacheLock")
- private static final HashMap<String, Integer> sMinikinLangListIdCache = new HashMap<>();
+ private static final HashMap<String, Integer> sMinikinLocaleListIdCache = new HashMap<>();
/**
* @hide
@@ -1445,16 +1445,16 @@ public class Paint {
private void syncTextLocalesWithMinikin() {
final String languageTags = mLocales.toLanguageTags();
- final Integer minikinLangListId;
+ final Integer minikinLocaleListId;
synchronized (sCacheLock) {
- minikinLangListId = sMinikinLangListIdCache.get(languageTags);
- if (minikinLangListId == null) {
+ minikinLocaleListId = sMinikinLocaleListIdCache.get(languageTags);
+ if (minikinLocaleListId == null) {
final int newID = nSetTextLocales(mNativePaint, languageTags);
- sMinikinLangListIdCache.put(languageTags, newID);
+ sMinikinLocaleListIdCache.put(languageTags, newID);
return;
}
}
- nSetTextLocalesByMinikinLangListId(mNativePaint, minikinLangListId.intValue());
+ nSetTextLocalesByMinikinLocaleListId(mNativePaint, minikinLocaleListId.intValue());
}
/**
@@ -2918,8 +2918,8 @@ public class Paint {
@CriticalNative
private static native void nSetTextAlign(long paintPtr, int align);
@CriticalNative
- private static native void nSetTextLocalesByMinikinLangListId(long paintPtr,
- int mMinikinLangListId);
+ private static native void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
+ int mMinikinLocaleListId);
@CriticalNative
private static native void nSetShadowLayer(long paintPtr,
float radius, float dx, float dy, int color);
diff --git a/android/graphics/Paint_Delegate.java b/android/graphics/Paint_Delegate.java
index ef452034..62427020 100644
--- a/android/graphics/Paint_Delegate.java
+++ b/android/graphics/Paint_Delegate.java
@@ -950,7 +950,7 @@ public class Paint_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+ /*package*/ static void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
int mMinikinLangListId) {
// FIXME
}
diff --git a/android/graphics/Rect.java b/android/graphics/Rect.java
index 3dc928de..aff942da 100644
--- a/android/graphics/Rect.java
+++ b/android/graphics/Rect.java
@@ -475,6 +475,19 @@ public final class Rect implements Parcelable {
}
/**
+ * If the specified rectangle intersects this rectangle, set this rectangle to that
+ * intersection, otherwise set this rectangle to the empty rectangle.
+ * @see #inset(int, int, int, int) but without checking if the rects overlap.
+ * @hide
+ */
+ public void intersectUnchecked(Rect other) {
+ left = Math.max(left, other.left);
+ top = Math.max(top, other.top);
+ right = Math.min(right, other.right);
+ bottom = Math.min(bottom, other.bottom);
+ }
+
+ /**
* If rectangles a and b intersect, return true and set this rectangle to
* that intersection, otherwise return false and do not change this
* rectangle. No check is performed to see if either rectangle is empty.
diff --git a/android/graphics/Typeface.java b/android/graphics/Typeface.java
index 9961ed64..3d65bd22 100644
--- a/android/graphics/Typeface.java
+++ b/android/graphics/Typeface.java
@@ -250,10 +250,10 @@ public class Typeface {
FontFamily fontFamily = new FontFamily();
for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
- // TODO: Add variation font support. (b/37853920)
if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
- fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
+ fontFile.getWeight(), fontFile.getItalic(),
+ FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
return null;
}
}
diff --git a/android/graphics/Typeface_Delegate.java b/android/graphics/Typeface_Delegate.java
index b9c03531..d793adee 100644
--- a/android/graphics/Typeface_Delegate.java
+++ b/android/graphics/Typeface_Delegate.java
@@ -16,19 +16,32 @@
package android.graphics;
+import com.android.SdkConstants;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.FontResourcesParser;
import android.graphics.FontFamily_Delegate.FontVariant;
import android.graphics.fonts.FontVariationAxis;
import android.text.FontConfig;
import android.util.ArrayMap;
import java.awt.Font;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.nio.file.Files;
@@ -231,12 +244,72 @@ public final class Typeface_Delegate {
return fontFamily;
}
+ /**
+ * Loads a single font or font family from disk
+ */
+ @Nullable
+ public static Typeface createFromDisk(@NonNull BridgeContext context, @NonNull String path,
+ boolean isFramework) {
+ // Check if this is an asset that we've already loaded dynamically
+ Typeface typeface = Typeface.findFromCache(context.getAssets(), path);
+ if (typeface != null) {
+ return typeface;
+ }
+
+ String lowerCaseValue = path.toLowerCase();
+ if (lowerCaseValue.endsWith(SdkConstants.DOT_XML)) {
+ // create a block parser for the file
+ Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
+ RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
+ XmlPullParser parser = null;
+ if (psiParserSupport != null && psiParserSupport) {
+ parser = context.getLayoutlibCallback().getXmlFileParser(path);
+ } else {
+ File f = new File(path);
+ if (f.isFile()) {
+ try {
+ parser = ParserFactory.create(f);
+ } catch (XmlPullParserException | FileNotFoundException e) {
+ // this is an error and not warning since the file existence is checked
+ // before
+ // attempting to parse it.
+ Bridge.getLog().error(null, "Failed to parse file " + path, e,
+ null /*data*/);
+ }
+ }
+ }
+
+ if (parser != null) {
+ BridgeXmlBlockParser blockParser =
+ new BridgeXmlBlockParser(parser, context, isFramework);
+ try {
+ FontResourcesParser.FamilyResourceEntry entry =
+ FontResourcesParser.parse(blockParser, context.getResources());
+ typeface = Typeface.createFromResources(entry, context.getAssets(), path);
+ } catch (XmlPullParserException | IOException e) {
+ Bridge.getLog().error(null, "Failed to parse file " + path, e, null /*data*/);
+ } finally {
+ blockParser.ensurePopped();
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("File %s does not exist (or is not a file)", path),
+ null /*data*/);
+ }
+ } else {
+ typeface = Typeface.createFromResources(context.getAssets(), path, 0);
+ }
+
+ return typeface;
+ }
+
@LayoutlibDelegate
/*package*/ static Typeface create(String familyName, int style) {
if (familyName != null && Files.exists(Paths.get(familyName))) {
// Workaround for b/64137851
// Support lib will call this method after failing to create the TypefaceCompat.
- return Typeface.createFromFile(familyName);
+ return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName,
+ false);
}
return Typeface.create_Original(familyName, style);
}
diff --git a/android/graphics/drawable/AnimatedVectorDrawable.java b/android/graphics/drawable/AnimatedVectorDrawable.java
index 90d6ab86..e74dc6dc 100644
--- a/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -132,7 +132,7 @@ import dalvik.annotation.optimization.FastNative;
* <td>translateY</td>
* </tr>
* <tr>
- * <td rowspan="8">&lt;path&gt;</td>
+ * <td rowspan="9">&lt;path&gt;</td>
* <td>pathData</td>
* </tr>
* <tr>
@@ -154,6 +154,9 @@ import dalvik.annotation.optimization.FastNative;
* <td>trimPathStart</td>
* </tr>
* <tr>
+ * <td>trimPathEnd</td>
+ * </tr>
+ * <tr>
* <td>trimPathOffset</td>
* </tr>
* <tr>
diff --git a/android/graphics/drawable/RippleBackground.java b/android/graphics/drawable/RippleBackground.java
index 3bf4f902..dea194e4 100644
--- a/android/graphics/drawable/RippleBackground.java
+++ b/android/graphics/drawable/RippleBackground.java
@@ -36,138 +36,69 @@ class RippleBackground extends RippleComponent {
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
- private static final int OPACITY_ENTER_DURATION = 600;
- private static final int OPACITY_ENTER_DURATION_FAST = 120;
- private static final int OPACITY_EXIT_DURATION = 480;
+ private static final int OPACITY_DURATION = 80;
- // Hardware rendering properties.
- private CanvasProperty<Paint> mPropPaint;
- private CanvasProperty<Float> mPropRadius;
- private CanvasProperty<Float> mPropX;
- private CanvasProperty<Float> mPropY;
+ private ObjectAnimator mAnimator;
- // Software rendering properties.
private float mOpacity = 0;
/** Whether this ripple is bounded. */
private boolean mIsBounded;
- public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded,
- boolean forceSoftware) {
- super(owner, bounds, forceSoftware);
+ private boolean mFocused = false;
+ private boolean mHovered = false;
+
+ public RippleBackground(RippleDrawable owner, Rect bounds, boolean isBounded) {
+ super(owner, bounds);
mIsBounded = isBounded;
}
public boolean isVisible() {
- return mOpacity > 0 || isHardwareAnimating();
+ return mOpacity > 0;
}
- @Override
- protected boolean drawSoftware(Canvas c, Paint p) {
- boolean hasContent = false;
-
+ public void draw(Canvas c, Paint p) {
final int origAlpha = p.getAlpha();
- final int alpha = (int) (origAlpha * mOpacity + 0.5f);
+ final int alpha = Math.min((int) (origAlpha * mOpacity + 0.5f), 255);
if (alpha > 0) {
p.setAlpha(alpha);
c.drawCircle(0, 0, mTargetRadius, p);
p.setAlpha(origAlpha);
- hasContent = true;
}
-
- return hasContent;
- }
-
- @Override
- protected boolean drawHardware(DisplayListCanvas c) {
- c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
- return true;
- }
-
- @Override
- protected Animator createSoftwareEnter(boolean fast) {
- // Linear enter based on current opacity.
- final int maxDuration = fast ? OPACITY_ENTER_DURATION_FAST : OPACITY_ENTER_DURATION;
- final int duration = (int) ((1 - mOpacity) * maxDuration);
-
- final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
- opacity.setAutoCancel(true);
- opacity.setDuration(duration);
- opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- return opacity;
}
- @Override
- protected Animator createSoftwareExit() {
- final AnimatorSet set = new AnimatorSet();
-
- // Linear exit after enter is completed.
- final ObjectAnimator exit = ObjectAnimator.ofFloat(this, OPACITY, 0);
- exit.setInterpolator(LINEAR_INTERPOLATOR);
- exit.setDuration(OPACITY_EXIT_DURATION);
- exit.setAutoCancel(true);
-
- final AnimatorSet.Builder builder = set.play(exit);
-
- // Linear "fast" enter based on current opacity.
- final int fastEnterDuration = mIsBounded ?
- (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
- if (fastEnterDuration > 0) {
- final ObjectAnimator enter = ObjectAnimator.ofFloat(this, OPACITY, 1);
- enter.setInterpolator(LINEAR_INTERPOLATOR);
- enter.setDuration(fastEnterDuration);
- enter.setAutoCancel(true);
-
- builder.after(enter);
+ public void setState(boolean focused, boolean hovered, boolean animateChanged) {
+ if (mHovered != hovered || mFocused != focused) {
+ mHovered = hovered;
+ mFocused = focused;
+ onStateChanged(animateChanged);
}
-
- return set;
}
- @Override
- protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
- final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
-
- final int targetAlpha = p.getAlpha();
- final int currentAlpha = (int) (mOpacity * targetAlpha + 0.5f);
- p.setAlpha(currentAlpha);
-
- mPropPaint = CanvasProperty.createPaint(p);
- mPropRadius = CanvasProperty.createFloat(mTargetRadius);
- mPropX = CanvasProperty.createFloat(0);
- mPropY = CanvasProperty.createFloat(0);
-
- final int fastEnterDuration = mIsBounded ?
- (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
-
- // Linear exit after enter is completed.
- final RenderNodeAnimator exit = new RenderNodeAnimator(
- mPropPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
- exit.setInterpolator(LINEAR_INTERPOLATOR);
- exit.setDuration(OPACITY_EXIT_DURATION);
- if (fastEnterDuration > 0) {
- exit.setStartDelay(fastEnterDuration);
- exit.setStartValue(targetAlpha);
+ private void onStateChanged(boolean animateChanged) {
+ float newOpacity = 0.0f;
+ if (mHovered) newOpacity += 1.0f;
+ if (mFocused) newOpacity += 1.0f;
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ mAnimator = null;
}
- set.add(exit);
-
- // Linear "fast" enter based on current opacity.
- if (fastEnterDuration > 0) {
- final RenderNodeAnimator enter = new RenderNodeAnimator(
- mPropPaint, RenderNodeAnimator.PAINT_ALPHA, targetAlpha);
- enter.setInterpolator(LINEAR_INTERPOLATOR);
- enter.setDuration(fastEnterDuration);
- set.add(enter);
+ if (animateChanged) {
+ mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity);
+ mAnimator.setDuration(OPACITY_DURATION);
+ mAnimator.setInterpolator(LINEAR_INTERPOLATOR);
+ mAnimator.start();
+ } else {
+ mOpacity = newOpacity;
}
-
- return set;
}
- @Override
- protected void jumpValuesToExit() {
- mOpacity = 0;
+ public void jumpToFinal() {
+ if (mAnimator != null) {
+ mAnimator.end();
+ mAnimator = null;
+ }
}
private static abstract class BackgroundProperty extends FloatProperty<RippleBackground> {
diff --git a/android/graphics/drawable/RippleComponent.java b/android/graphics/drawable/RippleComponent.java
index e83513c6..0e38826e 100644
--- a/android/graphics/drawable/RippleComponent.java
+++ b/android/graphics/drawable/RippleComponent.java
@@ -27,23 +27,14 @@ import android.view.RenderNodeAnimator;
import java.util.ArrayList;
/**
- * Abstract class that handles hardware/software hand-off and lifecycle for
- * animated ripple foreground and background components.
+ * Abstract class that handles size & positioning common to the ripple & focus states.
*/
abstract class RippleComponent {
- private final RippleDrawable mOwner;
+ protected final RippleDrawable mOwner;
/** Bounds used for computing max radius. May be modified by the owner. */
protected final Rect mBounds;
- /** Whether we can use hardware acceleration for the exit animation. */
- private boolean mHasDisplayListCanvas;
-
- private boolean mHasPendingHardwareAnimator;
- private RenderNodeAnimatorSet mHardwareAnimator;
-
- private Animator mSoftwareAnimator;
-
/** Whether we have an explicit maximum radius. */
private boolean mHasMaxRadius;
@@ -53,16 +44,9 @@ abstract class RippleComponent {
/** Screen density used to adjust pixel-based constants. */
protected float mDensityScale;
- /**
- * If set, force all ripple animations to not run on RenderThread, even if it would be
- * available.
- */
- private final boolean mForceSoftware;
-
- public RippleComponent(RippleDrawable owner, Rect bounds, boolean forceSoftware) {
+ public RippleComponent(RippleDrawable owner, Rect bounds) {
mOwner = owner;
mBounds = bounds;
- mForceSoftware = forceSoftware;
}
public void onBoundsChange() {
@@ -92,89 +76,6 @@ abstract class RippleComponent {
}
/**
- * Starts a ripple enter animation.
- *
- * @param fast whether the ripple should enter quickly
- */
- public final void enter(boolean fast) {
- cancel();
-
- mSoftwareAnimator = createSoftwareEnter(fast);
-
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.start();
- }
- }
-
- /**
- * Starts a ripple exit animation.
- */
- public final void exit() {
- cancel();
-
- if (mHasDisplayListCanvas) {
- // We don't have access to a canvas here, but we expect one on the
- // next frame. We'll start the render thread animation then.
- mHasPendingHardwareAnimator = true;
-
- // Request another frame.
- invalidateSelf();
- } else {
- mSoftwareAnimator = createSoftwareExit();
- mSoftwareAnimator.start();
- }
- }
-
- /**
- * Cancels all animations. Software animation values are left in the
- * current state, while hardware animation values jump to the end state.
- */
- public void cancel() {
- cancelSoftwareAnimations();
- endHardwareAnimations();
- }
-
- /**
- * Ends all animations, jumping values to the end state.
- */
- public void end() {
- endSoftwareAnimations();
- endHardwareAnimations();
- }
-
- /**
- * Draws the ripple to the canvas, inheriting the paint's color and alpha
- * properties.
- *
- * @param c the canvas to which the ripple should be drawn
- * @param p the paint used to draw the ripple
- * @return {@code true} if something was drawn, {@code false} otherwise
- */
- public boolean draw(Canvas c, Paint p) {
- final boolean hasDisplayListCanvas = !mForceSoftware && c.isHardwareAccelerated()
- && c instanceof DisplayListCanvas;
- if (mHasDisplayListCanvas != hasDisplayListCanvas) {
- mHasDisplayListCanvas = hasDisplayListCanvas;
-
- if (!hasDisplayListCanvas) {
- // We've switched from hardware to non-hardware mode. Panic.
- endHardwareAnimations();
- }
- }
-
- if (hasDisplayListCanvas) {
- final DisplayListCanvas hw = (DisplayListCanvas) c;
- startPendingAnimation(hw, p);
-
- if (mHardwareAnimator != null) {
- return drawHardware(hw);
- }
- }
-
- return drawSoftware(c, p);
- }
-
- /**
* Populates {@code bounds} with the maximum drawing bounds of the ripple
* relative to its center. The resulting bounds should be translated into
* parent drawable coordinates before use.
@@ -186,77 +87,10 @@ abstract class RippleComponent {
bounds.set(-r, -r, r, r);
}
- /**
- * Starts the pending hardware animation, if available.
- *
- * @param hw hardware canvas on which the animation should draw
- * @param p paint whose properties the hardware canvas should use
- */
- private void startPendingAnimation(DisplayListCanvas hw, Paint p) {
- if (mHasPendingHardwareAnimator) {
- mHasPendingHardwareAnimator = false;
-
- mHardwareAnimator = createHardwareExit(new Paint(p));
- mHardwareAnimator.start(hw);
-
- // Preemptively jump the software values to the end state now that
- // the hardware exit has read whatever values it needs.
- jumpValuesToExit();
- }
- }
-
- /**
- * Cancels any current software animations, leaving the values in their
- * current state.
- */
- private void cancelSoftwareAnimations() {
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.cancel();
- mSoftwareAnimator = null;
- }
- }
-
- /**
- * Ends any current software animations, jumping the values to their end
- * state.
- */
- private void endSoftwareAnimations() {
- if (mSoftwareAnimator != null) {
- mSoftwareAnimator.end();
- mSoftwareAnimator = null;
- }
- }
-
- /**
- * Ends any pending or current hardware animations.
- * <p>
- * Hardware animations can't synchronize values back to the software
- * thread, so there is no "cancel" equivalent.
- */
- private void endHardwareAnimations() {
- if (mHardwareAnimator != null) {
- mHardwareAnimator.end();
- mHardwareAnimator = null;
- }
-
- if (mHasPendingHardwareAnimator) {
- mHasPendingHardwareAnimator = false;
-
- // Manually jump values to their exited state. Normally we'd do that
- // later when starting the hardware exit, but we're aborting early.
- jumpValuesToExit();
- }
- }
-
protected final void invalidateSelf() {
mOwner.invalidateSelf(false);
}
- protected final boolean isHardwareAnimating() {
- return mHardwareAnimator != null && mHardwareAnimator.isRunning()
- || mHasPendingHardwareAnimator;
- }
-
protected final void onHotspotBoundsChanged() {
if (!mHasMaxRadius) {
final float halfWidth = mBounds.width() / 2.0f;
@@ -276,76 +110,4 @@ abstract class RippleComponent {
protected void onTargetRadiusChanged(float targetRadius) {
// Stub.
}
-
- protected abstract Animator createSoftwareEnter(boolean fast);
-
- protected abstract Animator createSoftwareExit();
-
- protected abstract RenderNodeAnimatorSet createHardwareExit(Paint p);
-
- protected abstract boolean drawHardware(DisplayListCanvas c);
-
- protected abstract boolean drawSoftware(Canvas c, Paint p);
-
- /**
- * Called when the hardware exit is cancelled. Jumps software values to end
- * state to ensure that software and hardware values are synchronized.
- */
- protected abstract void jumpValuesToExit();
-
- public static class RenderNodeAnimatorSet {
- private final ArrayList<RenderNodeAnimator> mAnimators = new ArrayList<>();
-
- public void add(RenderNodeAnimator anim) {
- mAnimators.add(anim);
- }
-
- public void clear() {
- mAnimators.clear();
- }
-
- public void start(DisplayListCanvas target) {
- if (target == null) {
- throw new IllegalArgumentException("Hardware canvas must be non-null");
- }
-
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.setTarget(target);
- anim.start();
- }
- }
-
- public void cancel() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.cancel();
- }
- }
-
- public void end() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- anim.end();
- }
- }
-
- public boolean isRunning() {
- final ArrayList<RenderNodeAnimator> animators = mAnimators;
- final int N = animators.size();
- for (int i = 0; i < N; i++) {
- final RenderNodeAnimator anim = animators.get(i);
- if (anim.isRunning()) {
- return true;
- }
- }
- return false;
- }
- }
}
diff --git a/android/graphics/drawable/RippleDrawable.java b/android/graphics/drawable/RippleDrawable.java
index 1727eca5..8b185f2b 100644
--- a/android/graphics/drawable/RippleDrawable.java
+++ b/android/graphics/drawable/RippleDrawable.java
@@ -16,11 +16,6 @@
package android.graphics.drawable;
-import com.android.internal.R;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo.Config;
@@ -42,6 +37,11 @@ import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.IOException;
import java.util.Arrays;
@@ -135,9 +135,6 @@ public class RippleDrawable extends LayerDrawable {
private PorterDuffColorFilter mMaskColorFilter;
private boolean mHasValidMask;
- /** Whether we expect to draw a background when visible. */
- private boolean mBackgroundActive;
-
/** The current ripple. May be actively animating or pending entry. */
private RippleForeground mRipple;
@@ -217,7 +214,7 @@ public class RippleDrawable extends LayerDrawable {
}
if (mBackground != null) {
- mBackground.end();
+ mBackground.jumpToFinal();
}
cancelExitingRipples();
@@ -266,9 +263,9 @@ public class RippleDrawable extends LayerDrawable {
}
}
- setRippleActive(focused || (enabled && pressed));
+ setRippleActive(enabled && pressed);
- setBackgroundActive(hovered, hovered);
+ setBackgroundActive(hovered, focused);
return changed;
}
@@ -283,14 +280,13 @@ public class RippleDrawable extends LayerDrawable {
}
}
- private void setBackgroundActive(boolean active, boolean focused) {
- if (mBackgroundActive != active) {
- mBackgroundActive = active;
- if (active) {
- tryBackgroundEnter(focused);
- } else {
- tryBackgroundExit();
- }
+ private void setBackgroundActive(boolean hovered, boolean focused) {
+ if (mBackground == null && (hovered || focused)) {
+ mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
+ mBackground.setup(mState.mMaxRadius, mDensity);
+ }
+ if (mBackground != null) {
+ mBackground.setState(focused, hovered, true);
}
}
@@ -327,10 +323,6 @@ public class RippleDrawable extends LayerDrawable {
tryRippleEnter();
}
- if (mBackgroundActive) {
- tryBackgroundEnter(false);
- }
-
// Skip animations, just show the correct final states.
jumpToCurrentState();
}
@@ -546,26 +538,6 @@ public class RippleDrawable extends LayerDrawable {
}
/**
- * Creates an active hotspot at the specified location.
- */
- private void tryBackgroundEnter(boolean focused) {
- if (mBackground == null) {
- final boolean isBounded = isBounded();
- mBackground = new RippleBackground(this, mHotspotBounds, isBounded, mForceSoftware);
- }
-
- mBackground.setup(mState.mMaxRadius, mDensity);
- mBackground.enter(focused);
- }
-
- private void tryBackgroundExit() {
- if (mBackground != null) {
- // Don't null out the background, we need it to draw!
- mBackground.exit();
- }
- }
-
- /**
* Attempts to start an enter animation for the active hotspot. Fails if
* there are too many animating ripples.
*/
@@ -593,7 +565,7 @@ public class RippleDrawable extends LayerDrawable {
}
mRipple.setup(mState.mMaxRadius, mDensity);
- mRipple.enter(false);
+ mRipple.enter();
}
/**
@@ -623,9 +595,7 @@ public class RippleDrawable extends LayerDrawable {
}
if (mBackground != null) {
- mBackground.end();
- mBackground = null;
- mBackgroundActive = false;
+ mBackground.setState(false, false, false);
}
cancelExitingRipples();
@@ -858,6 +828,40 @@ public class RippleDrawable extends LayerDrawable {
final float y = mHotspotBounds.exactCenterY();
canvas.translate(x, y);
+ final Paint p = getRipplePaint();
+
+ if (background != null && background.isVisible()) {
+ background.draw(canvas, p);
+ }
+
+ if (count > 0) {
+ final RippleForeground[] ripples = mExitingRipples;
+ for (int i = 0; i < count; i++) {
+ ripples[i].draw(canvas, p);
+ }
+ }
+
+ if (active != null) {
+ active.draw(canvas, p);
+ }
+
+ canvas.translate(-x, -y);
+ }
+
+ private void drawMask(Canvas canvas) {
+ mMask.draw(canvas);
+ }
+
+ Paint getRipplePaint() {
+ if (mRipplePaint == null) {
+ mRipplePaint = new Paint();
+ mRipplePaint.setAntiAlias(true);
+ mRipplePaint.setStyle(Paint.Style.FILL);
+ }
+
+ final float x = mHotspotBounds.exactCenterX();
+ final float y = mHotspotBounds.exactCenterY();
+
updateMaskShaderIfNeeded();
// Position the shader to account for canvas translation.
@@ -871,7 +875,7 @@ public class RippleDrawable extends LayerDrawable {
// half so that the ripple and background together yield full alpha.
final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
final int halfAlpha = (Color.alpha(color) / 2) << 24;
- final Paint p = getRipplePaint();
+ final Paint p = mRipplePaint;
if (mMaskColorFilter != null) {
// The ripple timing depends on the paint's alpha value, so we need
@@ -890,35 +894,7 @@ public class RippleDrawable extends LayerDrawable {
p.setShader(null);
}
- if (background != null && background.isVisible()) {
- background.draw(canvas, p);
- }
-
- if (count > 0) {
- final RippleForeground[] ripples = mExitingRipples;
- for (int i = 0; i < count; i++) {
- ripples[i].draw(canvas, p);
- }
- }
-
- if (active != null) {
- active.draw(canvas, p);
- }
-
- canvas.translate(-x, -y);
- }
-
- private void drawMask(Canvas canvas) {
- mMask.draw(canvas);
- }
-
- private Paint getRipplePaint() {
- if (mRipplePaint == null) {
- mRipplePaint = new Paint();
- mRipplePaint.setAntiAlias(true);
- mRipplePaint.setStyle(Paint.Style.FILL);
- }
- return mRipplePaint;
+ return p;
}
@Override
diff --git a/android/graphics/drawable/RippleForeground.java b/android/graphics/drawable/RippleForeground.java
index a675eaf8..0b5020cb 100644
--- a/android/graphics/drawable/RippleForeground.java
+++ b/android/graphics/drawable/RippleForeground.java
@@ -18,7 +18,6 @@ package android.graphics.drawable;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
@@ -29,8 +28,11 @@ import android.util.FloatProperty;
import android.util.MathUtils;
import android.view.DisplayListCanvas;
import android.view.RenderNodeAnimator;
+import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
+import java.util.ArrayList;
+
/**
* Draws a ripple foreground.
*/
@@ -40,7 +42,7 @@ class RippleForeground extends RippleComponent {
400f, 1.4f, 0);
// Pixel-based accelerations and velocities.
- private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
+ private static final float WAVE_TOUCH_DOWN_ACCELERATION = 2048;
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
// Bounded ripple animation properties.
@@ -49,8 +51,9 @@ class RippleForeground extends RippleComponent {
private static final int BOUNDED_OPACITY_EXIT_DURATION = 400;
private static final float MAX_BOUNDED_RADIUS = 350;
- private static final int RIPPLE_ENTER_DELAY = 80;
- private static final int OPACITY_ENTER_DURATION_FAST = 120;
+ private static final int OPACITY_ENTER_DURATION = 75;
+ private static final int OPACITY_EXIT_DURATION = 150;
+ private static final int OPACITY_HOLD_DURATION = OPACITY_ENTER_DURATION + 150;
// Parent-relative values for starting position.
private float mStartingX;
@@ -72,7 +75,7 @@ class RippleForeground extends RippleComponent {
private float mBoundedRadius = 0;
// Software rendering properties.
- private float mOpacity = 1;
+ private float mOpacity = 0;
// Values used to tween between the start and end positions.
private float mTweenRadius = 0;
@@ -82,6 +85,22 @@ class RippleForeground extends RippleComponent {
/** Whether this ripple has finished its exit animation. */
private boolean mHasFinishedExit;
+ /** Whether we can use hardware acceleration for the exit animation. */
+ private boolean mUsingProperties;
+
+ private long mEnterStartedAtMillis;
+
+ private ArrayList<RenderNodeAnimator> mPendingHwAnimators = new ArrayList<>();
+ private ArrayList<RenderNodeAnimator> mRunningHwAnimators = new ArrayList<>();
+
+ private ArrayList<Animator> mRunningSwAnimators = new ArrayList<>();
+
+ /**
+ * If set, force all ripple animations to not run on RenderThread, even if it would be
+ * available.
+ */
+ private final boolean mForceSoftware;
+
/**
* If we have a bound, don't start from 0. Start from 60% of the max out of width and height.
*/
@@ -89,8 +108,9 @@ class RippleForeground extends RippleComponent {
public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
boolean isBounded, boolean forceSoftware) {
- super(owner, bounds, forceSoftware);
+ super(owner, bounds);
+ mForceSoftware = forceSoftware;
mStartingX = startingX;
mStartingY = startingY;
@@ -109,10 +129,7 @@ class RippleForeground extends RippleComponent {
clampStartingPosition();
}
- @Override
- protected boolean drawSoftware(Canvas c, Paint p) {
- boolean hasContent = false;
-
+ private void drawSoftware(Canvas c, Paint p) {
final int origAlpha = p.getAlpha();
final int alpha = (int) (origAlpha * mOpacity + 0.5f);
final float radius = getCurrentRadius();
@@ -122,16 +139,51 @@ class RippleForeground extends RippleComponent {
p.setAlpha(alpha);
c.drawCircle(x, y, radius, p);
p.setAlpha(origAlpha);
- hasContent = true;
}
+ }
- return hasContent;
+ private void startPending(DisplayListCanvas c) {
+ if (!mPendingHwAnimators.isEmpty()) {
+ for (int i = 0; i < mPendingHwAnimators.size(); i++) {
+ RenderNodeAnimator animator = mPendingHwAnimators.get(i);
+ animator.setTarget(c);
+ animator.start();
+ mRunningHwAnimators.add(animator);
+ }
+ mPendingHwAnimators.clear();
+ }
}
- @Override
- protected boolean drawHardware(DisplayListCanvas c) {
- c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
- return true;
+ private void pruneHwFinished() {
+ if (!mRunningHwAnimators.isEmpty()) {
+ for (int i = mRunningHwAnimators.size() - 1; i >= 0; i--) {
+ if (!mRunningHwAnimators.get(i).isRunning()) {
+ mRunningHwAnimators.remove(i);
+ }
+ }
+ }
+ }
+
+ private void pruneSwFinished() {
+ if (!mRunningSwAnimators.isEmpty()) {
+ for (int i = mRunningSwAnimators.size() - 1; i >= 0; i--) {
+ if (!mRunningSwAnimators.get(i).isRunning()) {
+ mRunningSwAnimators.remove(i);
+ }
+ }
+ }
+ }
+
+ private void drawHardware(DisplayListCanvas c, Paint p) {
+ startPending(c);
+ pruneHwFinished();
+ if (mPropPaint != null) {
+ mUsingProperties = true;
+ c.drawCircle(mPropX, mPropY, mPropRadius, mPropPaint);
+ } else {
+ mUsingProperties = false;
+ drawSoftware(c, p);
+ }
}
/**
@@ -162,141 +214,152 @@ class RippleForeground extends RippleComponent {
return mHasFinishedExit;
}
- @Override
- protected Animator createSoftwareEnter(boolean fast) {
+ private long computeFadeOutDelay() {
+ long timeSinceEnter = AnimationUtils.currentAnimationTimeMillis() - mEnterStartedAtMillis;
+ if (timeSinceEnter > 0 && timeSinceEnter < OPACITY_HOLD_DURATION) {
+ return OPACITY_HOLD_DURATION - timeSinceEnter;
+ }
+ return 0;
+ }
+
+ private void startSoftwareEnter() {
+ for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+ mRunningSwAnimators.get(i).cancel();
+ }
+ mRunningSwAnimators.clear();
+
final int duration = getRadiusDuration();
final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
- tweenRadius.setAutoCancel(true);
tweenRadius.setDuration(duration);
tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
- tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
+ tweenRadius.start();
+ mRunningSwAnimators.add(tweenRadius);
final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
- tweenOrigin.setAutoCancel(true);
tweenOrigin.setDuration(duration);
tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
- tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
+ tweenOrigin.start();
+ mRunningSwAnimators.add(tweenOrigin);
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
- opacity.setAutoCancel(true);
- opacity.setDuration(OPACITY_ENTER_DURATION_FAST);
+ opacity.setDuration(OPACITY_ENTER_DURATION);
opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- final AnimatorSet set = new AnimatorSet();
- set.play(tweenOrigin).with(tweenRadius).with(opacity);
-
- return set;
+ opacity.start();
+ mRunningSwAnimators.add(opacity);
}
- private float getCurrentX() {
- return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
- }
-
- private float getCurrentY() {
- return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
- }
-
- private int getRadiusDuration() {
- final float remainingRadius = mTargetRadius - getCurrentRadius();
- return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION *
- mDensityScale) + 0.5);
- }
-
- private float getCurrentRadius() {
- return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
- }
-
- private int getOpacityExitDuration() {
- return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
- }
-
- @Override
- protected Animator createSoftwareExit() {
- final int radiusDuration;
- final int originDuration;
- final int opacityDuration;
-
- radiusDuration = getRadiusDuration();
- originDuration = radiusDuration;
- opacityDuration = getOpacityExitDuration();
-
- final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
- tweenRadius.setAutoCancel(true);
- tweenRadius.setDuration(radiusDuration);
- tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
-
- final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
- tweenOrigin.setAutoCancel(true);
- tweenOrigin.setDuration(originDuration);
- tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
-
+ private void startSoftwareExit() {
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0);
- opacity.setAutoCancel(true);
- opacity.setDuration(opacityDuration);
+ opacity.setDuration(OPACITY_EXIT_DURATION);
opacity.setInterpolator(LINEAR_INTERPOLATOR);
-
- final AnimatorSet set = new AnimatorSet();
- set.play(tweenOrigin).with(tweenRadius).with(opacity);
- set.addListener(mAnimationListener);
-
- return set;
+ opacity.addListener(mAnimationListener);
+ opacity.setStartDelay(computeFadeOutDelay());
+ opacity.start();
+ mRunningSwAnimators.add(opacity);
}
- @Override
- protected RenderNodeAnimatorSet createHardwareExit(Paint p) {
- final int radiusDuration;
- final int originDuration;
- final int opacityDuration;
-
- radiusDuration = getRadiusDuration();
- originDuration = radiusDuration;
- opacityDuration = getOpacityExitDuration();
+ private void startHardwareEnter() {
+ if (mForceSoftware) { return; }
+ mPropX = CanvasProperty.createFloat(getCurrentX());
+ mPropY = CanvasProperty.createFloat(getCurrentY());
+ mPropRadius = CanvasProperty.createFloat(getCurrentRadius());
+ final Paint paint = mOwner.getRipplePaint();
+ mPropPaint = CanvasProperty.createPaint(paint);
- final float startX = getCurrentX();
- final float startY = getCurrentY();
- final float startRadius = getCurrentRadius();
-
- p.setAlpha((int) (p.getAlpha() * mOpacity + 0.5f));
-
- mPropPaint = CanvasProperty.createPaint(p);
- mPropRadius = CanvasProperty.createFloat(startRadius);
- mPropX = CanvasProperty.createFloat(startX);
- mPropY = CanvasProperty.createFloat(startY);
+ final int radiusDuration = getRadiusDuration();
final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mTargetRadius);
radius.setDuration(radiusDuration);
radius.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(radius);
final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mTargetX);
- x.setDuration(originDuration);
+ x.setDuration(radiusDuration);
x.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(x);
final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mTargetY);
- y.setDuration(originDuration);
+ y.setDuration(radiusDuration);
y.setInterpolator(DECELERATE_INTERPOLATOR);
+ mPendingHwAnimators.add(y);
+
+ final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+ RenderNodeAnimator.PAINT_ALPHA, paint.getAlpha());
+ opacity.setDuration(OPACITY_ENTER_DURATION);
+ opacity.setInterpolator(LINEAR_INTERPOLATOR);
+ opacity.setStartValue(0);
+ mPendingHwAnimators.add(opacity);
+
+ invalidateSelf();
+ }
+
+ private void startHardwareExit() {
+ // Only run a hardware exit if we had a hardware enter to continue from
+ if (mForceSoftware || mPropPaint == null) return;
final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
RenderNodeAnimator.PAINT_ALPHA, 0);
- opacity.setDuration(opacityDuration);
+ opacity.setDuration(OPACITY_EXIT_DURATION);
opacity.setInterpolator(LINEAR_INTERPOLATOR);
opacity.addListener(mAnimationListener);
+ opacity.setStartDelay(computeFadeOutDelay());
+ mPendingHwAnimators.add(opacity);
+ invalidateSelf();
+ }
+
+ /**
+ * Starts a ripple enter animation.
+ */
+ public final void enter() {
+ mEnterStartedAtMillis = AnimationUtils.currentAnimationTimeMillis();
+ startSoftwareEnter();
+ startHardwareEnter();
+ }
- final RenderNodeAnimatorSet set = new RenderNodeAnimatorSet();
- set.add(radius);
- set.add(opacity);
- set.add(x);
- set.add(y);
+ /**
+ * Starts a ripple exit animation.
+ */
+ public final void exit() {
+ startSoftwareExit();
+ startHardwareExit();
+ }
- return set;
+ private float getCurrentX() {
+ return MathUtils.lerp(mClampedStartingX - mBounds.exactCenterX(), mTargetX, mTweenX);
}
- @Override
- protected void jumpValuesToExit() {
- mOpacity = 0;
- mTweenX = 1;
- mTweenY = 1;
- mTweenRadius = 1;
+ private float getCurrentY() {
+ return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
+ }
+
+ private int getRadiusDuration() {
+ final float remainingRadius = mTargetRadius - getCurrentRadius();
+ return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION *
+ mDensityScale) + 0.5);
+ }
+
+ private float getCurrentRadius() {
+ return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
+ }
+
+ /**
+ * Draws the ripple to the canvas, inheriting the paint's color and alpha
+ * properties.
+ *
+ * @param c the canvas to which the ripple should be drawn
+ * @param p the paint used to draw the ripple
+ */
+ public void draw(Canvas c, Paint p) {
+ final boolean hasDisplayListCanvas = !mForceSoftware && c instanceof DisplayListCanvas;
+
+ pruneSwFinished();
+ if (hasDisplayListCanvas) {
+ final DisplayListCanvas hw = (DisplayListCanvas) c;
+ drawHardware(hw, p);
+ } else {
+ drawSoftware(c, p);
+ }
}
/**
@@ -319,10 +382,39 @@ class RippleForeground extends RippleComponent {
}
}
+ /**
+ * Ends all animations, jumping values to the end state.
+ */
+ public void end() {
+ for (int i = 0; i < mRunningSwAnimators.size(); i++) {
+ mRunningSwAnimators.get(i).end();
+ }
+ mRunningSwAnimators.clear();
+ for (int i = 0; i < mRunningHwAnimators.size(); i++) {
+ mRunningHwAnimators.get(i).end();
+ }
+ mRunningHwAnimators.clear();
+ }
+
+ private void onAnimationPropertyChanged() {
+ if (!mUsingProperties) {
+ invalidateSelf();
+ }
+ }
+
private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
mHasFinishedExit = true;
+ pruneHwFinished();
+ pruneSwFinished();
+
+ if (mRunningHwAnimators.isEmpty()) {
+ mPropPaint = null;
+ mPropRadius = null;
+ mPropX = null;
+ mPropY = null;
+ }
}
};
@@ -361,7 +453,7 @@ class RippleForeground extends RippleComponent {
@Override
public void setValue(RippleForeground object, float value) {
object.mTweenRadius = value;
- object.invalidateSelf();
+ object.onAnimationPropertyChanged();
}
@Override
@@ -375,18 +467,18 @@ class RippleForeground extends RippleComponent {
*/
private static final FloatProperty<RippleForeground> TWEEN_ORIGIN =
new FloatProperty<RippleForeground>("tweenOrigin") {
- @Override
- public void setValue(RippleForeground object, float value) {
- object.mTweenX = value;
- object.mTweenY = value;
- object.invalidateSelf();
- }
+ @Override
+ public void setValue(RippleForeground object, float value) {
+ object.mTweenX = value;
+ object.mTweenY = value;
+ object.onAnimationPropertyChanged();
+ }
- @Override
- public Float get(RippleForeground object) {
- return object.mTweenX;
- }
- };
+ @Override
+ public Float get(RippleForeground object) {
+ return object.mTweenX;
+ }
+ };
/**
* Property for animating opacity between 0 and its target value.
@@ -396,7 +488,7 @@ class RippleForeground extends RippleComponent {
@Override
public void setValue(RippleForeground object, float value) {
object.mOpacity = value;
- object.invalidateSelf();
+ object.onAnimationPropertyChanged();
}
@Override
diff --git a/android/graphics/drawable/VectorDrawable.java b/android/graphics/drawable/VectorDrawable.java
index ceac3253..7b2e21a4 100644
--- a/android/graphics/drawable/VectorDrawable.java
+++ b/android/graphics/drawable/VectorDrawable.java
@@ -213,12 +213,79 @@ import dalvik.system.VMRuntime;
* &lt;/vector&gt;
* </pre>
* </li>
- * <li>And here is an example of linear gradient color, which is supported in SDK 24+.
+ * <h4>Gradient support</h4>
+ * We support 3 types of gradients: {@link android.graphics.LinearGradient},
+ * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}.
+ * <p/>
+ * And we support all of 3 types of tile modes {@link android.graphics.Shader.TileMode}:
+ * CLAMP, REPEAT, MIRROR.
+ * <p/>
+ * All of the attributes are listed in {@link android.R.styleable#GradientColor}.
+ * Note that different attributes are relevant for different types of gradient.
+ * <table border="2" align="center" cellpadding="5">
+ * <thead>
+ * <tr>
+ * <th>LinearGradient</th>
+ * <th>RadialGradient</th>
+ * <th>SweepGradient</th>
+ * </tr>
+ * </thead>
+ * <tr>
+ * <td>startColor </td>
+ * <td>startColor</td>
+ * <td>startColor</td>
+ * </tr>
+ * <tr>
+ * <td>centerColor</td>
+ * <td>centerColor</td>
+ * <td>centerColor</td>
+ * </tr>
+ * <tr>
+ * <td>endColor</td>
+ * <td>endColor</td>
+ * <td>endColor</td>
+ * </tr>
+ * <tr>
+ * <td>type</td>
+ * <td>type</td>
+ * <td>type</td>
+ * </tr>
+ * <tr>
+ * <td>tileMode</td>
+ * <td>tileMode</td>
+ * <td>tileMode</td>
+ * </tr>
+ * <tr>
+ * <td>startX</td>
+ * <td>centerX</td>
+ * <td>centerX</td>
+ * </tr>
+ * <tr>
+ * <td>startY</td>
+ * <td>centerY</td>
+ * <td>centerY</td>
+ * </tr>
+ * <tr>
+ * <td>endX</td>
+ * <td>gradientRadius</td>
+ * <td></td>
+ * </tr>
+ * <tr>
+ * <td>endY</td>
+ * <td></td>
+ * <td></td>
+ * </tr>
+ * </table>
+ * <p/>
+ * Also note that if any color item {@link android.R.styleable#GradientColorItem} is defined, then
+ * startColor, centerColor and endColor will be ignored.
+ * <p/>
* See more details in {@link android.R.styleable#GradientColor} and
* {@link android.R.styleable#GradientColorItem}.
+ * <p/>
+ * Here is a simple example that defines a linear gradient.
* <pre>
* &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
- * android:angle="90"
* android:startColor="?android:attr/colorPrimary"
* android:endColor="?android:attr/colorControlActivated"
* android:centerColor="#f00"
@@ -229,7 +296,18 @@ import dalvik.system.VMRuntime;
* android:type="linear"&gt;
* &lt;/gradient&gt;
* </pre>
- * </li>
+ * And here is a simple example that defines a radial gradient using color items.
+ * <pre>
+ * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:centerX="300"
+ * android:centerY="300"
+ * android:gradientRadius="100"
+ * android:type="radial"&gt;
+ * &lt;item android:offset="0.1" android:color="#0ff"/&gt;
+ * &lt;item android:offset="0.4" android:color="#fff"/&gt;
+ * &lt;item android:offset="0.9" android:color="#ff0"/&gt;
+ * &lt;/gradient&gt;
+ * </pre>
*
*/
diff --git a/android/hardware/camera2/CameraCaptureSession.java b/android/hardware/camera2/CameraCaptureSession.java
index da771e48..ff69bd89 100644
--- a/android/hardware/camera2/CameraCaptureSession.java
+++ b/android/hardware/camera2/CameraCaptureSession.java
@@ -249,7 +249,7 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* <p>This function can also be called in case where multiple surfaces share the same
* OutputConfiguration, and one of the surfaces becomes available after the {@link
* CameraCaptureSession} is created. In that case, the application must first create the
- * OutputConfiguration with the available Surface, then enable furture surface sharing via
+ * OutputConfiguration with the available Surface, then enable further surface sharing via
* {@link OutputConfiguration#enableSurfaceSharing}, before creating the CameraCaptureSession.
* After the CameraCaptureSession is created, and once the extra Surface becomes available, the
* application must then call {@link OutputConfiguration#addSurface} before finalizing the
@@ -645,6 +645,44 @@ public abstract class CameraCaptureSession implements AutoCloseable {
public abstract Surface getInputSurface();
/**
+ * Update {@link OutputConfiguration} after configuration finalization see
+ * {@link #finalizeOutputConfigurations}.
+ *
+ * <p>Any {@link OutputConfiguration} that has been modified via calls to
+ * {@link OutputConfiguration#addSurface} or {@link OutputConfiguration#removeSurface} must be
+ * updated. After the update call returns without throwing exceptions any newly added surfaces
+ * can be referenced in subsequent capture requests.</p>
+ *
+ * <p>Surfaces that get removed must not be part of any active repeating or single/burst
+ * request or have any pending results. Consider updating any repeating requests first via
+ * {@link #setRepeatingRequest} or {@link #setRepeatingBurst} and then wait for the last frame
+ * number when the sequence completes {@link CaptureCallback#onCaptureSequenceCompleted}
+ * before calling updateOutputConfiguration to remove a previously active Surface.</p>
+ *
+ * <p>Surfaces that get added must not be part of any other registered
+ * {@link OutputConfiguration}.</p>
+ *
+ * @param config Modified output configuration.
+ *
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error.
+ * @throws IllegalArgumentException if an attempt was made to add a {@link Surface} already
+ * in use by another buffer-producing API, such as MediaCodec or
+ * a different camera device or {@link OutputConfiguration}; or
+ * new surfaces are not compatible (see
+ * {@link OutputConfiguration#enableSurfaceSharing}); or a
+ * {@link Surface} that was removed from the modified
+ * {@link OutputConfiguration} still has pending requests.
+ * @throws IllegalStateException if this session is no longer active, either because the session
+ * was explicitly closed, a new session has been created
+ * or the camera device has been closed.
+ */
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Subclasses must override this method");
+ }
+
+ /**
* Close this capture session asynchronously.
*
* <p>Closing a session frees up the target output Surfaces of the session for reuse with either
diff --git a/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
index c7654c9e..374789c6 100644
--- a/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
+++ b/android/hardware/camera2/impl/CameraCaptureSessionImpl.java
@@ -314,6 +314,20 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession
}
@Override
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ synchronized (mDeviceImpl.mInterfaceLock) {
+ checkNotClosed();
+
+ if (DEBUG) {
+ Log.v(TAG, mIdString + "updateOutputConfiguration");
+ }
+
+ mDeviceImpl.updateOutputConfiguration(config);
+ }
+ }
+
+ @Override
public boolean isReprocessable() {
return mInput != null;
}
diff --git a/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java b/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
index fec7fd97..8c4dbfa5 100644
--- a/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
+++ b/android/hardware/camera2/impl/CameraConstrainedHighSpeedCaptureSessionImpl.java
@@ -235,6 +235,13 @@ public class CameraConstrainedHighSpeedCaptureSessionImpl
}
@Override
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ throw new UnsupportedOperationException("Constrained high speed session doesn't support"
+ + " this method");
+ }
+
+ @Override
public void close() {
mSessionImpl.close();
}
diff --git a/android/hardware/camera2/impl/CameraDeviceImpl.java b/android/hardware/camera2/impl/CameraDeviceImpl.java
index bfeb14de..6787d84b 100644
--- a/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -764,6 +764,24 @@ public class CameraDeviceImpl extends CameraDevice
}
}
+ public void updateOutputConfiguration(OutputConfiguration config)
+ throws CameraAccessException {
+ synchronized(mInterfaceLock) {
+ int streamId = -1;
+ for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+ if (config.getSurface() == mConfiguredOutputs.valueAt(i).getSurface()) {
+ streamId = mConfiguredOutputs.keyAt(i);
+ break;
+ }
+ }
+ if (streamId == -1) {
+ throw new IllegalArgumentException("Invalid output configuration");
+ }
+
+ mRemoteDevice.updateOutputConfiguration(streamId, config);
+ }
+ }
+
public void tearDown(Surface surface) throws CameraAccessException {
if (surface == null) throw new IllegalArgumentException("Surface is null");
diff --git a/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index 27087a2e..0978ff87 100644
--- a/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -215,6 +215,16 @@ public class ICameraDeviceUserWrapper {
}
}
+ public void updateOutputConfiguration(int streamId, OutputConfiguration config)
+ throws CameraAccessException {
+ try {
+ mRemoteDevice.updateOutputConfiguration(streamId, config);
+ } catch (Throwable t) {
+ CameraManager.throwAsPublicException(t);
+ throw new UnsupportedOperationException("Unexpected exception", t);
+ }
+ }
+
public void finalizeOutputConfigurations(int streamId, OutputConfiguration deferredConfig)
throws CameraAccessException {
try {
diff --git a/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 49d4096e..119cca8d 100644
--- a/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -646,6 +646,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser {
}
@Override
+ public void updateOutputConfiguration(int streamId, OutputConfiguration config) {
+ // TODO: b/63912484 implement updateOutputConfiguration.
+ }
+
+ @Override
public void waitUntilIdle() throws RemoteException {
if (DEBUG) {
Log.d(TAG, "waitUntilIdle called.");
diff --git a/android/hardware/camera2/params/OutputConfiguration.java b/android/hardware/camera2/params/OutputConfiguration.java
index 2b317d67..7409671f 100644
--- a/android/hardware/camera2/params/OutputConfiguration.java
+++ b/android/hardware/camera2/params/OutputConfiguration.java
@@ -42,6 +42,53 @@ 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.
*
+ * <p>There are several ways to instantiate, modify and use OutputConfigurations. The most common
+ * and recommended usage patterns are summarized in the following list:</p>
+ *<ul>
+ * <li>Passing a {@link Surface} to the constructor and using the OutputConfiguration instance as
+ * argument to {@link CameraDevice#createCaptureSessionByOutputConfigurations}. This is the most
+ * frequent usage and clients should consider it first before other more complicated alternatives.
+ * </li>
+ *
+ * <li>Passing only a surface source class as an argument to the constructor. This is usually
+ * followed by a call to create a capture session
+ * (see {@link CameraDevice#createCaptureSessionByOutputConfigurations} and a {@link Surface} add
+ * call {@link #addSurface} with a valid {@link Surface}. The sequence completes with
+ * {@link CameraCaptureSession#finalizeOutputConfigurations}. This is the deferred usage case which
+ * aims to enhance performance by allowing the resource-intensive capture session create call to
+ * execute in parallel with any {@link Surface} initialization, such as waiting for a
+ * {@link android.view.SurfaceView} to be ready as part of the UI initialization.</li>
+ *
+ * <li>The third and most complex usage pattern inlvolves surface sharing. Once instantiated an
+ * OutputConfiguration can be enabled for surface sharing via {@link #enableSurfaceSharing}. This
+ * must be done before creating a new capture session and enables calls to
+ * {@link CameraCaptureSession#updateOutputConfiguration}. An OutputConfiguration with enabled
+ * surface sharing can be modified via {@link #addSurface} or {@link #removeSurface}. The updates
+ * to this OutputConfiguration will only come into effect after
+ * {@link CameraCaptureSession#updateOutputConfiguration} returns without throwing exceptions.
+ * Such updates can be done as long as the session is active. Clients should always consider the
+ * additional requirements and limitations placed on the output surfaces (for more details see
+ * {@link #enableSurfaceSharing}, {@link #addSurface}, {@link #removeSurface},
+ * {@link CameraCaptureSession#updateOutputConfiguration}). A trade-off exists between additional
+ * complexity and flexibility. If exercised correctly surface sharing can switch between different
+ * output surfaces without interrupting any ongoing repeating capture requests. This saves time and
+ * can significantly improve the user experience.</li>
+ *
+ * <li>Surface sharing can be used in combination with deferred surfaces. The rules from both cases
+ * are combined and clients must call {@link #enableSurfaceSharing} before creating a capture
+ * session. Attach and/or remove output surfaces via {@link #addSurface}/{@link #removeSurface} and
+ * finalize the configuration using {@link CameraCaptureSession#finalizeOutputConfigurations}.
+ * {@link CameraCaptureSession#updateOutputConfiguration} can be called after the configuration
+ * finalize method returns without exceptions.</li>
+ *
+ * </ul>
+ *
+ * <p>Please note that surface sharing is currently only enabled for outputs that use the
+ * {@link ImageFormat#PRIVATE} format. This includes surface sources like
+ * {@link android.view.SurfaceView}, {@link android.media.MediaRecorder},
+ * {@link android.graphics.SurfaceTexture} and {@link android.media.ImageReader}, configured using
+ * the aforementioned format.</p>
+ *
* @see CameraDevice#createCaptureSessionByOutputConfigurations
*
*/
@@ -123,7 +170,7 @@ public final class OutputConfiguration implements Parcelable {
* {@link OutputConfiguration#addSurface} should not exceed this value.</p>
*
*/
- private static final int MAX_SURFACES_COUNT = 2;
+ private static final int MAX_SURFACES_COUNT = 4;
/**
* Create a new {@link OutputConfiguration} instance with a {@link Surface},
@@ -280,7 +327,10 @@ public final class OutputConfiguration implements Parcelable {
* <p>For advanced use cases, a camera application may require more streams than the combination
* guaranteed by {@link CameraDevice#createCaptureSession}. In this case, more than one
* compatible surface can be attached to an OutputConfiguration so that they map to one
- * camera stream, and the outputs share memory buffers when possible. </p>
+ * camera stream, and the outputs share memory buffers when possible. Due to buffer sharing
+ * clients should be careful when adding surface outputs that modify their input data. If such
+ * case exists, camera clients should have an additional mechanism to synchronize read and write
+ * access between individual consumers.</p>
*
* <p>Two surfaces are compatible in the below cases:</p>
*
@@ -301,9 +351,9 @@ public final class OutputConfiguration implements Parcelable {
* CameraDevice#createCaptureSessionByOutputConfigurations}. Calling this function after {@link
* CameraDevice#createCaptureSessionByOutputConfigurations} has no effect.</p>
*
- * <p>Up to 2 surfaces can be shared for an OutputConfiguration. The supported surfaces for
- * sharing must be of type SurfaceTexture, SurfaceView, MediaRecorder, MediaCodec, or
- * implementation defined ImageReader.</p>
+ * <p>Up to {@link #getMaxSharedSurfaceCount} surfaces can be shared for an OutputConfiguration.
+ * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView,
+ * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p>
*/
public void enableSurfaceSharing() {
mIsShared = true;
@@ -329,8 +379,10 @@ public final class OutputConfiguration implements Parcelable {
* <p> This function can be called before or after {@link
* CameraDevice#createCaptureSessionByOutputConfigurations}. If it's called after,
* the application must finalize the capture session with
- * {@link CameraCaptureSession#finalizeOutputConfigurations}.
- * </p>
+ * {@link CameraCaptureSession#finalizeOutputConfigurations}. It is possible to call this method
+ * after the output configurations have been finalized only in cases of enabled surface sharing
+ * see {@link #enableSurfaceSharing}. The modified output configuration must be updated with
+ * {@link CameraCaptureSession#updateOutputConfiguration}.</p>
*
* <p> If the OutputConfiguration was constructed with a deferred surface by {@link
* OutputConfiguration#OutputConfiguration(Size, Class)}, the added surface must be obtained
@@ -388,6 +440,31 @@ public final class OutputConfiguration implements Parcelable {
}
/**
+ * Remove a surface from this OutputConfiguration.
+ *
+ * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the
+ * OutputConfiguration. The only notable exception is the surface associated with
+ * the OutputConfigration see {@link #getSurface} which was passed as part of the constructor
+ * or was added first in the deferred case
+ * {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p>
+ *
+ * @param surface The surface to be removed.
+ *
+ * @throws IllegalArgumentException If the surface is associated with this OutputConfiguration
+ * (see {@link #getSurface}) or the surface didn't get added
+ * with {@link #addSurface}.
+ */
+ public void removeSurface(@NonNull Surface surface) {
+ if (getSurface() == surface) {
+ throw new IllegalArgumentException(
+ "Cannot remove surface associated with this output configuration");
+ }
+ if (!mSurfaces.remove(surface)) {
+ throw new IllegalArgumentException("Surface is not part of this output configuration");
+ }
+ }
+
+ /**
* Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
* instance.
*
@@ -447,6 +524,17 @@ public final class OutputConfiguration implements Parcelable {
}
/**
+ * Get the maximum supported shared {@link Surface} count.
+ *
+ * @return the maximum number of surfaces that can be added per each OutputConfiguration.
+ *
+ * @see #enableSurfaceSharing
+ */
+ public static int getMaxSharedSurfaceCount() {
+ return MAX_SURFACES_COUNT;
+ }
+
+ /**
* Get the {@link Surface} associated with this {@link OutputConfiguration}.
*
* If more than one surface is associated with this {@link OutputConfiguration}, return the
diff --git a/android/hardware/display/BrightnessChangeEvent.java b/android/hardware/display/BrightnessChangeEvent.java
new file mode 100644
index 00000000..fe24e32e
--- /dev/null
+++ b/android/hardware/display/BrightnessChangeEvent.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data about a brightness settings change.
+ * TODO make this SystemAPI
+ * @hide
+ */
+public final class BrightnessChangeEvent implements Parcelable {
+ /** Brightness in nits */
+ public int brightness;
+
+ /** Timestamp of the change {@see System.currentTimeMillis()} */
+ public long timeStamp;
+
+ /** Package name of focused activity when brightness was changed. */
+ public String packageName;
+
+ /** User id of of the user running when brightness was changed.
+ * @hide */
+ public int userId;
+
+ /** Lux values of recent sensor data */
+ public float[] luxValues;
+
+ /** Timestamps of the lux sensor readings {@see System.currentTimeMillis()} */
+ public long[] luxTimestamps;
+
+ /** Most recent battery level when brightness was changed or Float.NaN */
+ public float batteryLevel;
+
+ /** Color filter active to provide night mode */
+ public boolean nightMode;
+
+ /** If night mode color filter is active this will be the temperature in kelvin */
+ public int colorTemperature;
+
+ /** Brightness level before slider adjustment */
+ public int lastBrightness;
+
+ public BrightnessChangeEvent() {
+ }
+
+ private BrightnessChangeEvent(Parcel source) {
+ brightness = source.readInt();
+ timeStamp = source.readLong();
+ packageName = source.readString();
+ userId = source.readInt();
+ luxValues = source.createFloatArray();
+ luxTimestamps = source.createLongArray();
+ batteryLevel = source.readFloat();
+ nightMode = source.readBoolean();
+ colorTemperature = source.readInt();
+ lastBrightness = source.readInt();
+ }
+
+ public static final Creator<BrightnessChangeEvent> CREATOR =
+ new Creator<BrightnessChangeEvent>() {
+ public BrightnessChangeEvent createFromParcel(Parcel source) {
+ return new BrightnessChangeEvent(source);
+ }
+ public BrightnessChangeEvent[] newArray(int size) {
+ return new BrightnessChangeEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(brightness);
+ dest.writeLong(timeStamp);
+ dest.writeString(packageName);
+ dest.writeInt(userId);
+ dest.writeFloatArray(luxValues);
+ dest.writeLongArray(luxTimestamps);
+ dest.writeFloat(batteryLevel);
+ dest.writeBoolean(nightMode);
+ dest.writeInt(colorTemperature);
+ dest.writeInt(lastBrightness);
+ }
+}
diff --git a/android/hardware/display/DisplayManager.java b/android/hardware/display/DisplayManager.java
index b2af44ec..ef77d6e6 100644
--- a/android/hardware/display/DisplayManager.java
+++ b/android/hardware/display/DisplayManager.java
@@ -30,6 +30,7 @@ import android.view.Surface;
import android.view.WindowManagerPolicy;
import java.util.ArrayList;
+import java.util.List;
/**
* Manages the properties of attached displays.
@@ -615,6 +616,21 @@ public final class DisplayManager {
}
/**
+ * Fetch {@link BrightnessChangeEvent}s.
+ * @hide until we make it a system api.
+ */
+ public List<BrightnessChangeEvent> getBrightnessEvents() {
+ return mGlobal.getBrightnessEvents();
+ }
+
+ /**
+ * @hide STOPSHIP - remove when adaptive brightness accepts curves.
+ */
+ public void setBrightness(int brightness) {
+ mGlobal.setBrightness(brightness);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/android/hardware/display/DisplayManagerGlobal.java b/android/hardware/display/DisplayManagerGlobal.java
index a8a4eb67..d93d0e4e 100644
--- a/android/hardware/display/DisplayManagerGlobal.java
+++ b/android/hardware/display/DisplayManagerGlobal.java
@@ -17,6 +17,7 @@
package android.hardware.display;
import android.content.Context;
+import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Point;
import android.hardware.display.DisplayManager.DisplayListener;
@@ -37,6 +38,8 @@ import android.view.DisplayInfo;
import android.view.Surface;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Manager communication with the display manager service on behalf of
@@ -456,6 +459,33 @@ public final class DisplayManagerGlobal {
}
}
+ /**
+ * Retrieves brightness change events.
+ */
+ public List<BrightnessChangeEvent> getBrightnessEvents() {
+ try {
+ ParceledListSlice<BrightnessChangeEvent> events = mDm.getBrightnessEvents();
+ if (events == null) {
+ return Collections.emptyList();
+ }
+ return events.getList();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set brightness but don't add a BrightnessChangeEvent
+ * STOPSHIP remove when adaptive brightness accepts curves.
+ */
+ public void setBrightness(int brightness) {
+ try {
+ mDm.setBrightness(brightness);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, int event) {
diff --git a/android/hardware/location/ContextHubManager.java b/android/hardware/location/ContextHubManager.java
index 7cbb436c..24117278 100644
--- a/android/hardware/location/ContextHubManager.java
+++ b/android/hardware/location/ContextHubManager.java
@@ -271,6 +271,59 @@ public final class ContextHubManager {
throw new UnsupportedOperationException("TODO: Implement this");
}
+ /*
+ * Helper function to generate a stub for a non-query transaction callback.
+ *
+ * @param transaction the transaction to unblock when complete
+ *
+ * @return the callback
+ *
+ * @hide
+ */
+ private IContextHubTransactionCallback createTransactionCallback(
+ ContextHubTransaction<Void> transaction) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+ Log.e(TAG, "Received a query callback on a non-query request");
+ transaction.setResponse(new ContextHubTransaction.Response<Void>(
+ ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+ }
+
+ @Override
+ public void onTransactionComplete(int result) {
+ transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
+ }
+ };
+ }
+
+ /*
+ * Helper function to generate a stub for a query transaction callback.
+ *
+ * @param transaction the transaction to unblock when complete
+ *
+ * @return the callback
+ *
+ * @hide
+ */
+ private IContextHubTransactionCallback createQueryCallback(
+ ContextHubTransaction<List<NanoAppState>> transaction) {
+ return new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
+ transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+ result, nanoappList));
+ }
+
+ @Override
+ public void onTransactionComplete(int result) {
+ Log.e(TAG, "Received a non-query callback on a query request");
+ transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
+ ContextHubTransaction.TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE, null));
+ }
+ };
+ }
+
/**
* Loads a nanoapp at the specified Context Hub.
*
@@ -411,7 +464,7 @@ public final class ContextHubManager {
*
* @param callback the notification callback to register
* @param hubInfo the hub to attach this client to
- * @param handler the handler to invoke the callback, if null uses the current thread Looper
+ * @param handler the handler to invoke the callback, if null uses the main thread's Looper
*
* @return the registered client object
*
diff --git a/android/hardware/location/ContextHubTransaction.java b/android/hardware/location/ContextHubTransaction.java
index 4877d38b..a8569ef4 100644
--- a/android/hardware/location/ContextHubTransaction.java
+++ b/android/hardware/location/ContextHubTransaction.java
@@ -16,11 +16,16 @@
package android.hardware.location;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* A class describing a request sent to the Context Hub Service.
@@ -29,17 +34,15 @@ import java.util.concurrent.TimeUnit;
* through the ContextHubManager APIs. The caller can either retrieve the result
* synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
* asynchronously through a user-defined callback
- * ({@link #onComplete(ContextHubTransaction.Callback<T>, Handler)}).
- *
- * A transaction can be invalidated if the caller of the transaction is no longer active
- * and the reference to this object is lost, or if timeout period has passed in
- * {@link #waitForResponse(long, TimeUnit)}.
+ * ({@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}).
*
* @param <T> the type of the contents in the transaction response
*
* @hide
*/
public class ContextHubTransaction<T> {
+ private static final String TAG = "ContextHubTransaction";
+
/**
* Constants describing the type of a transaction through the Context Hub Service.
*/
@@ -68,7 +71,8 @@ public class ContextHubTransaction<T> {
TRANSACTION_FAILED_UNINITIALIZED,
TRANSACTION_FAILED_PENDING,
TRANSACTION_FAILED_AT_HUB,
- TRANSACTION_FAILED_TIMEOUT})
+ TRANSACTION_FAILED_TIMEOUT,
+ TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE})
public @interface Result {}
public static final int TRANSACTION_SUCCESS = 0;
/**
@@ -95,6 +99,10 @@ public class ContextHubTransaction<T> {
* Failure mode when the transaction has timed out.
*/
public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+ /**
+ * Failure mode when the transaction has failed internally at the service.
+ */
+ public static final int TRANSACTION_FAILED_SERVICE_INTERNAL_FAILURE = 7;
/**
* A class describing the response for a ContextHubTransaction.
@@ -146,11 +154,6 @@ public class ContextHubTransaction<T> {
}
/*
- * The unique identifier representing the transaction.
- */
- private int mTransactionId;
-
- /*
* The type of the transaction.
*/
@Type
@@ -171,8 +174,17 @@ public class ContextHubTransaction<T> {
*/
private ContextHubTransaction.Callback<T> mCallback = null;
- ContextHubTransaction(int id, @Type int type) {
- mTransactionId = id;
+ /*
+ * Synchronization latch used to block on response.
+ */
+ private final CountDownLatch mDoneSignal = new CountDownLatch(1);
+
+ /*
+ * true if the response has been set throught setResponse, false otherwise.
+ */
+ private boolean mIsResponseSet = false;
+
+ ContextHubTransaction(@Type int type) {
mTransactionType = type;
}
@@ -191,17 +203,26 @@ public class ContextHubTransaction<T> {
* for the transaction represented by this object by the Context Hub, or a
* specified timeout period has elapsed.
*
- * If the specified timeout has passed, the transaction represented by this object
- * is invalidated by the Context Hub Service (resulting in a timeout failure in the
- * response).
+ * If the specified timeout has passed, a TimeoutException will be thrown and the caller may
+ * retry the invocation of this method at a later time.
*
* @param timeout the timeout duration
* @param unit the unit of the timeout
*
* @return the transaction response
+ *
+ * @throws InterruptedException if the current thread is interrupted while waiting for response
+ * @throws TimeoutException if the timeout period has passed
*/
- public ContextHubTransaction.Response<T> waitForResponse(long timeout, TimeUnit unit) {
- throw new UnsupportedOperationException("TODO: Implement this");
+ public ContextHubTransaction.Response<T> waitForResponse(
+ long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ boolean success = mDoneSignal.await(timeout, unit);
+
+ if (!success) {
+ throw new TimeoutException("Timed out while waiting for transaction");
+ }
+
+ return mResponse;
}
/**
@@ -215,15 +236,100 @@ public class ContextHubTransaction<T> {
* will be immediately posted by the handler. If the transaction has been invalidated,
* the callback will never be invoked.
*
+ * A transaction can be invalidated if the process owning the transaction is no longer active
+ * and the reference to this object is lost.
+ *
+ * This method or {@link #setCallbackOnCompletecan(ContextHubTransaction.Callback)} can only be
+ * invoked once, or an IllegalStateException will be thrown.
+ *
* @param callback the callback to be invoked upon completion
* @param handler the handler to post the callback
+ *
+ * @throws IllegalStateException if this method is called multiple times
+ * @throws NullPointerException if the callback or handler is null
*/
- public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) {
- throw new UnsupportedOperationException("TODO: Implement this");
+ public void setCallbackOnComplete(
+ @NonNull ContextHubTransaction.Callback<T> callback, @NonNull Handler handler) {
+ synchronized (this) {
+ if (callback == null) {
+ throw new NullPointerException("Callback cannot be null");
+ }
+ if (handler == null) {
+ throw new NullPointerException("Handler cannot be null");
+ }
+ if (mCallback != null) {
+ throw new IllegalStateException(
+ "Cannot set ContextHubTransaction callback multiple times");
+ }
+
+ mCallback = callback;
+ mHandler = handler;
+
+ if (mDoneSignal.getCount() == 0) {
+ boolean callbackPosted = mHandler.post(() -> {
+ mCallback.onComplete(this, mResponse);
+ });
+
+ if (!callbackPosted) {
+ Log.e(TAG, "Failed to post callback to Handler");
+ }
+ }
+ }
}
- private void setResponse(ContextHubTransaction.Response<T> response) {
- mResponse = response;
- throw new UnsupportedOperationException("TODO: Unblock waitForResponse");
+ /**
+ * Sets a callback to be invoked when the transaction completes.
+ *
+ * Equivalent to {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+ * with the handler being that of the main thread's Looper.
+ *
+ * This method or {@link #setCallbackOnComplete(ContextHubTransaction.Callback, Handler)}
+ * can only be invoked once, or an IllegalStateException will be thrown.
+ *
+ * @param callback the callback to be invoked upon completion
+ *
+ * @throws IllegalStateException if this method is called multiple times
+ * @throws NullPointerException if the callback is null
+ */
+ public void setCallbackOnComplete(@NonNull ContextHubTransaction.Callback<T> callback) {
+ setCallbackOnComplete(callback, new Handler(Looper.getMainLooper()));
+ }
+
+ /**
+ * Sets the response of the transaction.
+ *
+ * This method should only be invoked by ContextHubManager as a result of a callback from
+ * the Context Hub Service indicating the response from a transaction. This method should not be
+ * invoked more than once.
+ *
+ * @param response the response to set
+ *
+ * @throws IllegalStateException if this method is invoked multiple times
+ * @throws NullPointerException if the response is null
+ */
+ void setResponse(ContextHubTransaction.Response<T> response) {
+ synchronized (this) {
+ if (response == null) {
+ throw new NullPointerException("Response cannot be null");
+ }
+ if (mIsResponseSet) {
+ throw new IllegalStateException(
+ "Cannot set response of ContextHubTransaction multiple times");
+ }
+
+ mResponse = response;
+ mIsResponseSet = true;
+
+ mDoneSignal.countDown();
+ if (mCallback != null) {
+ boolean callbackPosted = mHandler.post(() -> {
+ mCallback.onComplete(this, mResponse);
+ });
+
+ if (!callbackPosted) {
+ Log.e(TAG, "Failed to post callback to Handler");
+ }
+ }
+ }
}
}
diff --git a/android/hardware/location/NanoAppBinary.java b/android/hardware/location/NanoAppBinary.java
index 54542277..934e9e48 100644
--- a/android/hardware/location/NanoAppBinary.java
+++ b/android/hardware/location/NanoAppBinary.java
@@ -22,6 +22,7 @@ import android.util.Log;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Arrays;
/**
* @hide
@@ -57,7 +58,7 @@ public final class NanoAppBinary implements Parcelable {
private static final int EXPECTED_HEADER_VERSION = 1;
/*
- * The magic value expected in the header.
+ * The magic value expected in the header as defined in context_hub.h.
*/
private static final int EXPECTED_MAGIC_VALUE =
(((int) 'N' << 0) | ((int) 'A' << 8) | ((int) 'N' << 16) | ((int) 'O' << 24));
@@ -67,6 +68,17 @@ public final class NanoAppBinary implements Parcelable {
*/
private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN;
+ /*
+ * The size of the header in bytes as defined in context_hub.h.
+ */
+ private static final int HEADER_SIZE_BYTES = 40;
+
+ /*
+ * The bit fields for mFlags as defined in context_hub.h.
+ */
+ private static final int NANOAPP_SIGNED_FLAG_BIT = 0x1;
+ private static final int NANOAPP_ENCRYPTED_FLAG_BIT = 0x2;
+
public NanoAppBinary(byte[] appBinary) {
mNanoAppBinary = appBinary;
parseBinaryHeader();
@@ -111,11 +123,26 @@ public final class NanoAppBinary implements Parcelable {
/**
* @return the app binary byte array
*/
- public byte[] getNanoAppBinary() {
+ public byte[] getBinary() {
return mNanoAppBinary;
}
/**
+ * @return the app binary byte array without the leading header
+ *
+ * @throws IndexOutOfBoundsException if the nanoapp binary size is smaller than the header size
+ * @throws NullPointerException if the nanoapp binary is null
+ */
+ public byte[] getBinaryNoHeader() {
+ if (mNanoAppBinary.length < HEADER_SIZE_BYTES) {
+ throw new IndexOutOfBoundsException("NanoAppBinary binary byte size ("
+ + mNanoAppBinary.length + ") is less than header size (" + HEADER_SIZE_BYTES + ")");
+ }
+
+ return Arrays.copyOfRange(mNanoAppBinary, HEADER_SIZE_BYTES, mNanoAppBinary.length);
+ }
+
+ /**
* @return {@code true} if the header is valid, {@code false} otherwise
*/
public boolean hasValidHeader() {
@@ -164,6 +191,31 @@ public final class NanoAppBinary implements Parcelable {
return mTargetChreApiMinorVersion;
}
+ /**
+ * Returns the flags for the nanoapp as defined in context_hub.h.
+ *
+ * This method is meant to be used by the Context Hub Service.
+ *
+ * @return the flags for the nanoapp
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * @return {@code true} if the nanoapp binary is signed, {@code false} otherwise
+ */
+ public boolean isSigned() {
+ return (mFlags & NANOAPP_SIGNED_FLAG_BIT) != 0;
+ }
+
+ /**
+ * @return {@code true} if the nanoapp binary is encrypted, {@code false} otherwise
+ */
+ public boolean isEncrypted() {
+ return (mFlags & NANOAPP_ENCRYPTED_FLAG_BIT) != 0;
+ }
+
private NanoAppBinary(Parcel in) {
int binaryLength = in.readInt();
mNanoAppBinary = new byte[binaryLength];
diff --git a/android/hardware/sidekick/SidekickInternal.java b/android/hardware/sidekick/SidekickInternal.java
new file mode 100644
index 00000000..fe3550b2
--- /dev/null
+++ b/android/hardware/sidekick/SidekickInternal.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.sidekick;
+
+
+/**
+ * Sidekick local system service interface.
+ *
+ * @hide Only for use within the system server, and maybe by Clockwork Home.
+ */
+public abstract class SidekickInternal {
+
+ /**
+ * Tell Sidekick to reset back to newly-powered-on state.
+ *
+ * @return true on success (Sidekick is reset), false if Sidekick is not
+ * available (failed or not present). Either way, upon return Sidekick is
+ * guaranteed not to be controlling the display.
+ */
+ public abstract boolean reset();
+
+ /**
+ * Tell Sidekick it can start controlling the display.
+ *
+ * SidekickServer may choose not to actually control the display, if it's been told
+ * via other channels to leave the previous image on the display (same as SUSPEND in
+ * a non-Sidekick system).
+ *
+ * @param displayState - one of Display.STATE_DOZE_SUSPEND, Display.STATE_ON_SUSPEND
+ * @return true on success, false on failure (no sidekick available)
+ */
+ public abstract boolean startDisplayControl(int displayState);
+
+ /**
+ * Tell Sidekick it must stop controlling the display.
+ *
+ * No return code because this must always succeed - after return, Sidekick
+ * is guaranteed to not be controlling the display.
+ */
+ public abstract void endDisplayControl();
+
+}
diff --git a/android/hardware/usb/AccessoryFilter.java b/android/hardware/usb/AccessoryFilter.java
new file mode 100644
index 00000000..d9b7c5be
--- /dev/null
+++ b/android/hardware/usb/AccessoryFilter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class is used to describe a USB accessory.
+ * When used in HashMaps all values must be specified,
+ * but wildcards can be used for any of the fields in
+ * the package meta-data.
+ *
+ * @hide
+ */
+public class AccessoryFilter {
+ // USB accessory manufacturer (or null for unspecified)
+ public final String mManufacturer;
+ // USB accessory model (or null for unspecified)
+ public final String mModel;
+ // USB accessory version (or null for unspecified)
+ public final String mVersion;
+
+ public AccessoryFilter(String manufacturer, String model, String version) {
+ mManufacturer = manufacturer;
+ mModel = model;
+ mVersion = version;
+ }
+
+ public AccessoryFilter(UsbAccessory accessory) {
+ mManufacturer = accessory.getManufacturer();
+ mModel = accessory.getModel();
+ mVersion = accessory.getVersion();
+ }
+
+ public static AccessoryFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String manufacturer = null;
+ String model = null;
+ String version = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+
+ if ("manufacturer".equals(name)) {
+ manufacturer = value;
+ } else if ("model".equals(name)) {
+ model = value;
+ } else if ("version".equals(name)) {
+ version = value;
+ }
+ }
+ return new AccessoryFilter(manufacturer, model, version);
+ }
+
+ public void write(XmlSerializer serializer)throws IOException {
+ serializer.startTag(null, "usb-accessory");
+ if (mManufacturer != null) {
+ serializer.attribute(null, "manufacturer", mManufacturer);
+ }
+ if (mModel != null) {
+ serializer.attribute(null, "model", mModel);
+ }
+ if (mVersion != null) {
+ serializer.attribute(null, "version", mVersion);
+ }
+ serializer.endTag(null, "usb-accessory");
+ }
+
+ public boolean matches(UsbAccessory acc) {
+ if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
+ if (mModel != null && !acc.getModel().equals(mModel)) return false;
+ return !(mVersion != null && !acc.getVersion().equals(mVersion));
+ }
+
+ /**
+ * Is the accessories described {@code accessory} covered by this filter?
+ *
+ * @param accessory A filter describing the accessory
+ *
+ * @return {@code true} iff this the filter covers the accessory
+ */
+ public boolean contains(AccessoryFilter accessory) {
+ if (mManufacturer != null && !Objects.equals(accessory.mManufacturer, mManufacturer)) {
+ return false;
+ }
+ if (mModel != null && !Objects.equals(accessory.mModel, mModel)) return false;
+ return !(mVersion != null && !Objects.equals(accessory.mVersion, mVersion));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mManufacturer == null || mModel == null || mVersion == null) {
+ return false;
+ }
+ if (obj instanceof AccessoryFilter) {
+ AccessoryFilter filter = (AccessoryFilter)obj;
+ return (mManufacturer.equals(filter.mManufacturer) &&
+ mModel.equals(filter.mModel) &&
+ mVersion.equals(filter.mVersion));
+ }
+ if (obj instanceof UsbAccessory) {
+ UsbAccessory accessory = (UsbAccessory)obj;
+ return (mManufacturer.equals(accessory.getManufacturer()) &&
+ mModel.equals(accessory.getModel()) &&
+ mVersion.equals(accessory.getVersion()));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
+ (mModel == null ? 0 : mModel.hashCode()) ^
+ (mVersion == null ? 0 : mVersion.hashCode()));
+ }
+
+ @Override
+ public String toString() {
+ return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
+ "\", mModel=\"" + mModel +
+ "\", mVersion=\"" + mVersion + "\"]";
+ }
+}
diff --git a/android/hardware/usb/DeviceFilter.java b/android/hardware/usb/DeviceFilter.java
new file mode 100644
index 00000000..439c6297
--- /dev/null
+++ b/android/hardware/usb/DeviceFilter.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import android.util.Slog;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class is used to describe a USB device.
+ * When used in HashMaps all values must be specified,
+ * but wildcards can be used for any of the fields in
+ * the package meta-data.
+ *
+ * @hide
+ */
+public class DeviceFilter {
+ private static final String TAG = DeviceFilter.class.getSimpleName();
+
+ // USB Vendor ID (or -1 for unspecified)
+ public final int mVendorId;
+ // USB Product ID (or -1 for unspecified)
+ public final int mProductId;
+ // USB device or interface class (or -1 for unspecified)
+ public final int mClass;
+ // USB device subclass (or -1 for unspecified)
+ public final int mSubclass;
+ // USB device protocol (or -1 for unspecified)
+ public final int mProtocol;
+ // USB device manufacturer name string (or null for unspecified)
+ public final String mManufacturerName;
+ // USB device product name string (or null for unspecified)
+ public final String mProductName;
+ // USB device serial number string (or null for unspecified)
+ public final String mSerialNumber;
+
+ public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
+ String manufacturer, String product, String serialnum) {
+ mVendorId = vid;
+ mProductId = pid;
+ mClass = clasz;
+ mSubclass = subclass;
+ mProtocol = protocol;
+ mManufacturerName = manufacturer;
+ mProductName = product;
+ mSerialNumber = serialnum;
+ }
+
+ public DeviceFilter(UsbDevice device) {
+ mVendorId = device.getVendorId();
+ mProductId = device.getProductId();
+ mClass = device.getDeviceClass();
+ mSubclass = device.getDeviceSubclass();
+ mProtocol = device.getDeviceProtocol();
+ mManufacturerName = device.getManufacturerName();
+ mProductName = device.getProductName();
+ mSerialNumber = device.getSerialNumber();
+ }
+
+ public static DeviceFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int vendorId = -1;
+ int productId = -1;
+ int deviceClass = -1;
+ int deviceSubclass = -1;
+ int deviceProtocol = -1;
+ String manufacturerName = null;
+ String productName = null;
+ String serialNumber = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ // Attribute values are ints or strings
+ if ("manufacturer-name".equals(name)) {
+ manufacturerName = value;
+ } else if ("product-name".equals(name)) {
+ productName = value;
+ } else if ("serial-number".equals(name)) {
+ serialNumber = value;
+ } else {
+ int intValue;
+ int radix = 10;
+ if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
+ (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
+ // allow hex values starting with 0x or 0X
+ radix = 16;
+ value = value.substring(2);
+ }
+ try {
+ intValue = Integer.parseInt(value, radix);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "invalid number for field " + name, e);
+ continue;
+ }
+ if ("vendor-id".equals(name)) {
+ vendorId = intValue;
+ } else if ("product-id".equals(name)) {
+ productId = intValue;
+ } else if ("class".equals(name)) {
+ deviceClass = intValue;
+ } else if ("subclass".equals(name)) {
+ deviceSubclass = intValue;
+ } else if ("protocol".equals(name)) {
+ deviceProtocol = intValue;
+ }
+ }
+ }
+ return new DeviceFilter(vendorId, productId,
+ deviceClass, deviceSubclass, deviceProtocol,
+ manufacturerName, productName, serialNumber);
+ }
+
+ public void write(XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, "usb-device");
+ if (mVendorId != -1) {
+ serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
+ }
+ if (mProductId != -1) {
+ serializer.attribute(null, "product-id", Integer.toString(mProductId));
+ }
+ if (mClass != -1) {
+ serializer.attribute(null, "class", Integer.toString(mClass));
+ }
+ if (mSubclass != -1) {
+ serializer.attribute(null, "subclass", Integer.toString(mSubclass));
+ }
+ if (mProtocol != -1) {
+ serializer.attribute(null, "protocol", Integer.toString(mProtocol));
+ }
+ if (mManufacturerName != null) {
+ serializer.attribute(null, "manufacturer-name", mManufacturerName);
+ }
+ if (mProductName != null) {
+ serializer.attribute(null, "product-name", mProductName);
+ }
+ if (mSerialNumber != null) {
+ serializer.attribute(null, "serial-number", mSerialNumber);
+ }
+ serializer.endTag(null, "usb-device");
+ }
+
+ private boolean matches(int clasz, int subclass, int protocol) {
+ return ((mClass == -1 || clasz == mClass) &&
+ (mSubclass == -1 || subclass == mSubclass) &&
+ (mProtocol == -1 || protocol == mProtocol));
+ }
+
+ public boolean matches(UsbDevice device) {
+ if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
+ if (mProductId != -1 && device.getProductId() != mProductId) return false;
+ if (mManufacturerName != null && device.getManufacturerName() == null) return false;
+ if (mProductName != null && device.getProductName() == null) return false;
+ if (mSerialNumber != null && device.getSerialNumber() == null) return false;
+ if (mManufacturerName != null && device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) return false;
+ if (mProductName != null && device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) return false;
+ if (mSerialNumber != null && device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber())) return false;
+
+ // check device class/subclass/protocol
+ if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
+ device.getDeviceProtocol())) return true;
+
+ // if device doesn't match, check the interfaces
+ int count = device.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ UsbInterface intf = device.getInterface(i);
+ if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
+ intf.getInterfaceProtocol())) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the device described by {@code device} covered by this filter?
+ *
+ * @param device The device
+ *
+ * @return {@code true} iff this filter covers the {@code device}
+ */
+ public boolean contains(DeviceFilter device) {
+ // -1 and null means "match anything"
+
+ if (mVendorId != -1 && device.mVendorId != mVendorId) return false;
+ if (mProductId != -1 && device.mProductId != mProductId) return false;
+ if (mManufacturerName != null && !Objects.equals(mManufacturerName,
+ device.mManufacturerName)) {
+ return false;
+ }
+ if (mProductName != null && !Objects.equals(mProductName, device.mProductName)) {
+ return false;
+ }
+ if (mSerialNumber != null
+ && !Objects.equals(mSerialNumber, device.mSerialNumber)) {
+ return false;
+ }
+
+ // check device class/subclass/protocol
+ return matches(device.mClass, device.mSubclass, device.mProtocol);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mVendorId == -1 || mProductId == -1 ||
+ mClass == -1 || mSubclass == -1 || mProtocol == -1) {
+ return false;
+ }
+ if (obj instanceof DeviceFilter) {
+ DeviceFilter filter = (DeviceFilter)obj;
+
+ if (filter.mVendorId != mVendorId ||
+ filter.mProductId != mProductId ||
+ filter.mClass != mClass ||
+ filter.mSubclass != mSubclass ||
+ filter.mProtocol != mProtocol) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName == null) ||
+ (filter.mManufacturerName == null &&
+ mManufacturerName != null) ||
+ (filter.mProductName != null &&
+ mProductName == null) ||
+ (filter.mProductName == null &&
+ mProductName != null) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber == null) ||
+ (filter.mSerialNumber == null &&
+ mSerialNumber != null)) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName != null &&
+ !mManufacturerName.equals(filter.mManufacturerName)) ||
+ (filter.mProductName != null &&
+ mProductName != null &&
+ !mProductName.equals(filter.mProductName)) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber != null &&
+ !mSerialNumber.equals(filter.mSerialNumber))) {
+ return false;
+ }
+ return true;
+ }
+ if (obj instanceof UsbDevice) {
+ UsbDevice device = (UsbDevice)obj;
+ if (device.getVendorId() != mVendorId ||
+ device.getProductId() != mProductId ||
+ device.getDeviceClass() != mClass ||
+ device.getDeviceSubclass() != mSubclass ||
+ device.getDeviceProtocol() != mProtocol) {
+ return(false);
+ }
+ if ((mManufacturerName != null && device.getManufacturerName() == null) ||
+ (mManufacturerName == null && device.getManufacturerName() != null) ||
+ (mProductName != null && device.getProductName() == null) ||
+ (mProductName == null && device.getProductName() != null) ||
+ (mSerialNumber != null && device.getSerialNumber() == null) ||
+ (mSerialNumber == null && device.getSerialNumber() != null)) {
+ return(false);
+ }
+ if ((device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) ||
+ (device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) ||
+ (device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber()))) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (((mVendorId << 16) | mProductId) ^
+ ((mClass << 16) | (mSubclass << 8) | mProtocol));
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
+ ",mClass=" + mClass + ",mSubclass=" + mSubclass +
+ ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
+ ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
+ "]";
+ }
+}
diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java
index 996824d4..6ce96698 100644
--- a/android/hardware/usb/UsbManager.java
+++ b/android/hardware/usb/UsbManager.java
@@ -102,7 +102,7 @@ public class UsbManager {
"android.hardware.usb.action.USB_PORT_CHANGED";
/**
- * Activity intent sent when a USB device is attached.
+ * Activity intent sent when user attaches a USB device.
*
* This intent is sent when a USB device is attached to the USB bus when in host mode.
* <ul>
@@ -128,7 +128,7 @@ public class UsbManager {
"android.hardware.usb.action.USB_DEVICE_DETACHED";
/**
- * Activity intent sent when a USB accessory is attached.
+ * Activity intent sent when user attaches a USB accessory.
*
* <ul>
* <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
diff --git a/android/location/Location.java b/android/location/Location.java
index e7f903e8..c9d2f7f8 100644
--- a/android/location/Location.java
+++ b/android/location/Location.java
@@ -821,7 +821,7 @@ public class Location implements Parcelable {
* considered 1 standard deviation.
*
* <p>For example, if {@link #getAltitude()} returns 150, and
- * {@link #getVerticalAccuracyMeters()} ()} returns 20 then there is a 68% probability
+ * {@link #getVerticalAccuracyMeters()} returns 20 then there is a 68% probability
* of the true altitude being between 130 and 170 meters.
*
* <p>If this location does not have a vertical accuracy, then 0.0 is returned.
@@ -933,7 +933,7 @@ public class Location implements Parcelable {
* considered 1 standard deviation.
*
* <p>For example, if {@link #getBearing()} returns 60, and
- * {@link #getBearingAccuracyDegrees()} ()} returns 10, then there is a 68% probability of the
+ * {@link #getBearingAccuracyDegrees()} returns 10, then there is a 68% probability of the
* true bearing being between 50 and 70 degrees.
*
* <p>If this location does not have a bearing accuracy, then 0.0 is returned.
diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java
index 20405d3b..7afe267f 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -244,6 +244,7 @@ public final class AudioAttributes implements Parcelable {
SUPPRESSIBLE_USAGES.put(USAGE_GAME, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
}
/**
diff --git a/android/media/AudioManager.java b/android/media/AudioManager.java
index dab7632a..58976ca0 100644
--- a/android/media/AudioManager.java
+++ b/android/media/AudioManager.java
@@ -43,17 +43,16 @@ import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Pair;
+import android.util.Slog;
import android.view.KeyEvent;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -1966,9 +1965,28 @@ public class AudioManager {
*/
private boolean querySoundEffectsEnabled(int user) {
return Settings.System.getIntForUser(getContext().getContentResolver(),
- Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
+ Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0
+ && !areSystemSoundsZenModeBlocked(getContext());
}
+ private boolean areSystemSoundsZenModeBlocked(Context context) {
+ int zenMode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ZEN_MODE, 0);
+
+ switch (zenMode) {
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Settings.Global.ZEN_MODE_ALARMS:
+ return true;
+ case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ final NotificationManager noMan = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ return (noMan.getNotificationPolicy().priorityCategories
+ & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+ case Settings.Global.ZEN_MODE_OFF:
+ default:
+ return false;
+ }
+ }
/**
* Load Sound effects.
diff --git a/android/media/BufferingParams.java b/android/media/BufferingParams.java
index 681271b1..521e8975 100644
--- a/android/media/BufferingParams.java
+++ b/android/media/BufferingParams.java
@@ -26,170 +26,68 @@ import java.lang.annotation.RetentionPolicy;
/**
* Structure for source buffering management params.
*
- * Used by {@link MediaPlayer#getDefaultBufferingParams()},
- * {@link MediaPlayer#getBufferingParams()} and
+ * Used by {@link MediaPlayer#getBufferingParams()} and
* {@link MediaPlayer#setBufferingParams(BufferingParams)}
* to control source buffering behavior.
*
* <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering
* (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer}
- * is playing back source). {@link BufferingParams} includes mode and corresponding
- * watermarks for each stage of source buffering. The watermarks could be either size
- * based (in milliseconds), or time based (in kilobytes) or both, depending on the mode.
+ * is playing back source). {@link BufferingParams} includes corresponding marks for each
+ * stage of source buffering. The marks are time based (in milliseconds).
*
- * <p>There are 4 buffering modes: {@link #BUFFERING_MODE_NONE},
- * {@link #BUFFERING_MODE_TIME_ONLY}, {@link #BUFFERING_MODE_SIZE_ONLY} and
- * {@link #BUFFERING_MODE_TIME_THEN_SIZE}.
- * {@link MediaPlayer} source component has default buffering modes which can be queried
- * by calling {@link MediaPlayer#getDefaultBufferingParams()}.
- * Users should always use those default modes or their downsized version when trying to
- * change buffering params. For example, {@link #BUFFERING_MODE_TIME_THEN_SIZE} can be
- * downsized to {@link #BUFFERING_MODE_NONE}, {@link #BUFFERING_MODE_TIME_ONLY} or
- * {@link #BUFFERING_MODE_SIZE_ONLY}. But {@link #BUFFERING_MODE_TIME_ONLY} can not be
- * downsized to {@link #BUFFERING_MODE_SIZE_ONLY}.
+ * <p>{@link MediaPlayer} source component has default marks which can be queried by
+ * calling {@link MediaPlayer#getBufferingParams()} before any change is made by
+ * {@link MediaPlayer#setBufferingParams()}.
* <ul>
- * <li><strong>initial buffering stage:</strong> has one watermark which is used when
- * {@link MediaPlayer} is being prepared. When cached data amount exceeds this watermark,
- * {@link MediaPlayer} is prepared.</li>
- * <li><strong>rebuffering stage:</strong> has two watermarks, low and high, which are
- * used when {@link MediaPlayer} is playing back content.
+ * <li><strong>initial buffering:</strong> initialMarkMs is used when
+ * {@link MediaPlayer} is being prepared. When cached data amount exceeds this mark
+ * {@link MediaPlayer} is prepared. </li>
+ * <li><strong>rebuffering during playback:</strong> resumePlaybackMarkMs is used when
+ * {@link MediaPlayer} is playing back content.
* <ul>
- * <li> When cached data amount exceeds high watermark, {@link MediaPlayer} will pause
- * buffering. Buffering will resume when cache runs below some limit which could be low
- * watermark or some intermediate value decided by the source component.</li>
- * <li> When cached data amount runs below low watermark, {@link MediaPlayer} will paused
- * playback. Playback will resume when cached data amount exceeds high watermark
- * or reaches end of stream.</li>
- * </ul>
+ * <li> {@link MediaPlayer} has internal mark, namely pausePlaybackMarkMs, to decide when
+ * to pause playback if cached data amount runs low. This internal mark varies based on
+ * type of data source. </li>
+ * <li> When cached data amount exceeds resumePlaybackMarkMs, {@link MediaPlayer} will
+ * resume playback if it has been paused due to low cached data amount. The internal mark
+ * pausePlaybackMarkMs shall be less than resumePlaybackMarkMs. </li>
+ * <li> {@link MediaPlayer} has internal mark, namely pauseRebufferingMarkMs, to decide
+ * when to pause rebuffering. Apparently, this internal mark shall be no less than
+ * resumePlaybackMarkMs. </li>
+ * <li> {@link MediaPlayer} has internal mark, namely resumeRebufferingMarkMs, to decide
+ * when to resume buffering. This internal mark varies based on type of data source. This
+ * mark shall be larger than pausePlaybackMarkMs, and less than pauseRebufferingMarkMs.
+ * </li>
+ * </ul> </li>
* </ul>
* <p>Users should use {@link Builder} to change {@link BufferingParams}.
* @hide
*/
public final class BufferingParams implements Parcelable {
- /**
- * This mode indicates that source buffering is not supported.
- */
- public static final int BUFFERING_MODE_NONE = 0;
- /**
- * This mode indicates that only time based source buffering is supported. This means
- * the watermark(s) are time based.
- */
- public static final int BUFFERING_MODE_TIME_ONLY = 1;
- /**
- * This mode indicates that only size based source buffering is supported. This means
- * the watermark(s) are size based.
- */
- public static final int BUFFERING_MODE_SIZE_ONLY = 2;
- /**
- * This mode indicates that both time and size based source buffering are supported,
- * and time based calculation precedes size based. Size based calculation will be used
- * only when time information is not available from the source.
- */
- public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3;
-
- /** @hide */
- @IntDef(
- value = {
- BUFFERING_MODE_NONE,
- BUFFERING_MODE_TIME_ONLY,
- BUFFERING_MODE_SIZE_ONLY,
- BUFFERING_MODE_TIME_THEN_SIZE,
- }
- )
- @Retention(RetentionPolicy.SOURCE)
- public @interface BufferingMode {}
-
- private static final int BUFFERING_NO_WATERMARK = -1;
+ private static final int BUFFERING_NO_MARK = -1;
// params
- private int mInitialBufferingMode = BUFFERING_MODE_NONE;
- private int mRebufferingMode = BUFFERING_MODE_NONE;
-
- private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
- private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
+ private int mInitialMarkMs = BUFFERING_NO_MARK;
- private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+ private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
private BufferingParams() {
}
/**
- * Return the initial buffering mode used when {@link MediaPlayer} is being prepared.
- * @return one of the values that can be set in {@link Builder#setInitialBufferingMode(int)}
- */
- public int getInitialBufferingMode() {
- return mInitialBufferingMode;
- }
-
- /**
- * Return the rebuffering mode used when {@link MediaPlayer} is playing back source.
- * @return one of the values that can be set in {@link Builder#setRebufferingMode(int)}
- */
- public int getRebufferingMode() {
- return mRebufferingMode;
- }
-
- /**
- * Return the time based initial buffering watermark in milliseconds.
- * It is meaningful only when initial buffering mode obatined from
- * {@link #getInitialBufferingMode()} is time based.
- * @return time based initial buffering watermark in milliseconds
- */
- public int getInitialBufferingWatermarkMs() {
- return mInitialWatermarkMs;
- }
-
- /**
- * Return the size based initial buffering watermark in kilobytes.
- * It is meaningful only when initial buffering mode obatined from
- * {@link #getInitialBufferingMode()} is size based.
- * @return size based initial buffering watermark in kilobytes
+ * Return initial buffering mark in milliseconds.
+ * @return initial buffering mark in milliseconds
*/
- public int getInitialBufferingWatermarkKB() {
- return mInitialWatermarkKB;
+ public int getInitialMarkMs() {
+ return mInitialMarkMs;
}
/**
- * Return the time based low watermark in milliseconds for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is time based.
- * @return time based low watermark for rebuffering in milliseconds
+ * Return the mark in milliseconds for resuming playback.
+ * @return the mark for resuming playback in milliseconds
*/
- public int getRebufferingWatermarkLowMs() {
- return mRebufferingWatermarkLowMs;
- }
-
- /**
- * Return the time based high watermark in milliseconds for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is time based.
- * @return time based high watermark for rebuffering in milliseconds
- */
- public int getRebufferingWatermarkHighMs() {
- return mRebufferingWatermarkHighMs;
- }
-
- /**
- * Return the size based low watermark in kilobytes for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is size based.
- * @return size based low watermark for rebuffering in kilobytes
- */
- public int getRebufferingWatermarkLowKB() {
- return mRebufferingWatermarkLowKB;
- }
-
- /**
- * Return the size based high watermark in kilobytes for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is size based.
- * @return size based high watermark for rebuffering in kilobytes
- */
- public int getRebufferingWatermarkHighKB() {
- return mRebufferingWatermarkHighKB;
+ public int getResumePlaybackMarkMs() {
+ return mResumePlaybackMarkMs;
}
/**
@@ -200,27 +98,19 @@ public final class BufferingParams implements Parcelable {
* <pre class="prettyprint">
* BufferingParams myParams = mediaplayer.getDefaultBufferingParams();
* myParams = new BufferingParams.Builder(myParams)
- * .setInitialBufferingWatermarkMs(10000)
- * .build();
+ * .setInitialMarkMs(10000)
+ * .setResumePlaybackMarkMs(15000)
+ * .build();
* mediaplayer.setBufferingParams(myParams);
* </pre>
*/
public static class Builder {
- private int mInitialBufferingMode = BUFFERING_MODE_NONE;
- private int mRebufferingMode = BUFFERING_MODE_NONE;
-
- private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
- private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
-
- private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+ private int mInitialMarkMs = BUFFERING_NO_MARK;
+ private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
/**
* Constructs a new Builder with the defaults.
- * By default, both initial buffering mode and rebuffering mode are
- * {@link BufferingParams#BUFFERING_MODE_NONE}, and all watermarks are -1.
+ * By default, all marks are -1.
*/
public Builder() {
}
@@ -231,16 +121,8 @@ public final class BufferingParams implements Parcelable {
* in the new Builder.
*/
public Builder(BufferingParams bp) {
- mInitialBufferingMode = bp.mInitialBufferingMode;
- mRebufferingMode = bp.mRebufferingMode;
-
- mInitialWatermarkMs = bp.mInitialWatermarkMs;
- mInitialWatermarkKB = bp.mInitialWatermarkKB;
-
- mRebufferingWatermarkLowMs = bp.mRebufferingWatermarkLowMs;
- mRebufferingWatermarkHighMs = bp.mRebufferingWatermarkHighMs;
- mRebufferingWatermarkLowKB = bp.mRebufferingWatermarkLowKB;
- mRebufferingWatermarkHighKB = bp.mRebufferingWatermarkHighKB;
+ mInitialMarkMs = bp.mInitialMarkMs;
+ mResumePlaybackMarkMs = bp.mResumePlaybackMarkMs;
}
/**
@@ -250,179 +132,37 @@ public final class BufferingParams implements Parcelable {
* @return a new {@link BufferingParams} object
*/
public BufferingParams build() {
- if (isTimeBasedMode(mRebufferingMode)
- && mRebufferingWatermarkLowMs > mRebufferingWatermarkHighMs) {
- throw new IllegalStateException("Illegal watermark:"
- + mRebufferingWatermarkLowMs + " : " + mRebufferingWatermarkHighMs);
- }
- if (isSizeBasedMode(mRebufferingMode)
- && mRebufferingWatermarkLowKB > mRebufferingWatermarkHighKB) {
- throw new IllegalStateException("Illegal watermark:"
- + mRebufferingWatermarkLowKB + " : " + mRebufferingWatermarkHighKB);
- }
-
BufferingParams bp = new BufferingParams();
- bp.mInitialBufferingMode = mInitialBufferingMode;
- bp.mRebufferingMode = mRebufferingMode;
-
- bp.mInitialWatermarkMs = mInitialWatermarkMs;
- bp.mInitialWatermarkKB = mInitialWatermarkKB;
+ bp.mInitialMarkMs = mInitialMarkMs;
+ bp.mResumePlaybackMarkMs = mResumePlaybackMarkMs;
- bp.mRebufferingWatermarkLowMs = mRebufferingWatermarkLowMs;
- bp.mRebufferingWatermarkHighMs = mRebufferingWatermarkHighMs;
- bp.mRebufferingWatermarkLowKB = mRebufferingWatermarkLowKB;
- bp.mRebufferingWatermarkHighKB = mRebufferingWatermarkHighKB;
return bp;
}
- private boolean isTimeBasedMode(int mode) {
- return (mode == BUFFERING_MODE_TIME_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
- }
-
- private boolean isSizeBasedMode(int mode) {
- return (mode == BUFFERING_MODE_SIZE_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
- }
-
/**
- * Sets the initial buffering mode.
- * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
- * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * Sets the time based mark in milliseconds for initial buffering.
+ * @param markMs time based mark in milliseconds
* @return the same Builder instance.
*/
- public Builder setInitialBufferingMode(@BufferingMode int mode) {
- switch (mode) {
- case BUFFERING_MODE_NONE:
- case BUFFERING_MODE_TIME_ONLY:
- case BUFFERING_MODE_SIZE_ONLY:
- case BUFFERING_MODE_TIME_THEN_SIZE:
- mInitialBufferingMode = mode;
- break;
- default:
- throw new IllegalArgumentException("Illegal buffering mode " + mode);
- }
+ public Builder setInitialMarkMs(int markMs) {
+ mInitialMarkMs = markMs;
return this;
}
/**
- * Sets the rebuffering mode.
- * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
- * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * Sets the time based mark in milliseconds for resuming playback.
+ * @param markMs time based mark in milliseconds for resuming playback
* @return the same Builder instance.
*/
- public Builder setRebufferingMode(@BufferingMode int mode) {
- switch (mode) {
- case BUFFERING_MODE_NONE:
- case BUFFERING_MODE_TIME_ONLY:
- case BUFFERING_MODE_SIZE_ONLY:
- case BUFFERING_MODE_TIME_THEN_SIZE:
- mRebufferingMode = mode;
- break;
- default:
- throw new IllegalArgumentException("Illegal buffering mode " + mode);
- }
- return this;
- }
-
- /**
- * Sets the time based watermark in milliseconds for initial buffering.
- * @param watermarkMs time based watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setInitialBufferingWatermarkMs(int watermarkMs) {
- mInitialWatermarkMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the size based watermark in kilobytes for initial buffering.
- * @param watermarkKB size based watermark in kilobytes
- * @return the same Builder instance.
- */
- public Builder setInitialBufferingWatermarkKB(int watermarkKB) {
- mInitialWatermarkKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the time based low watermark in milliseconds for rebuffering.
- * @param watermarkMs time based low watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkLowMs(int watermarkMs) {
- mRebufferingWatermarkLowMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the time based high watermark in milliseconds for rebuffering.
- * @param watermarkMs time based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkHighMs(int watermarkMs) {
- mRebufferingWatermarkHighMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the size based low watermark in milliseconds for rebuffering.
- * @param watermarkKB size based low watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkLowKB(int watermarkKB) {
- mRebufferingWatermarkLowKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the size based high watermark in milliseconds for rebuffering.
- * @param watermarkKB size based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkHighKB(int watermarkKB) {
- mRebufferingWatermarkHighKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the time based low and high watermarks in milliseconds for rebuffering.
- * @param lowWatermarkMs time based low watermark in milliseconds
- * @param highWatermarkMs time based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarksMs(int lowWatermarkMs, int highWatermarkMs) {
- mRebufferingWatermarkLowMs = lowWatermarkMs;
- mRebufferingWatermarkHighMs = highWatermarkMs;
- return this;
- }
-
- /**
- * Sets the size based low and high watermarks in kilobytes for rebuffering.
- * @param lowWatermarkKB size based low watermark in kilobytes
- * @param highWatermarkKB size based high watermark in kilobytes
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarksKB(int lowWatermarkKB, int highWatermarkKB) {
- mRebufferingWatermarkLowKB = lowWatermarkKB;
- mRebufferingWatermarkHighKB = highWatermarkKB;
+ public Builder setResumePlaybackMarkMs(int markMs) {
+ mResumePlaybackMarkMs = markMs;
return this;
}
}
private BufferingParams(Parcel in) {
- mInitialBufferingMode = in.readInt();
- mRebufferingMode = in.readInt();
-
- mInitialWatermarkMs = in.readInt();
- mInitialWatermarkKB = in.readInt();
-
- mRebufferingWatermarkLowMs = in.readInt();
- mRebufferingWatermarkHighMs = in.readInt();
- mRebufferingWatermarkLowKB = in.readInt();
- mRebufferingWatermarkHighKB = in.readInt();
+ mInitialMarkMs = in.readInt();
+ mResumePlaybackMarkMs = in.readInt();
}
public static final Parcelable.Creator<BufferingParams> CREATOR =
@@ -446,15 +186,7 @@ public final class BufferingParams implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mInitialBufferingMode);
- dest.writeInt(mRebufferingMode);
-
- dest.writeInt(mInitialWatermarkMs);
- dest.writeInt(mInitialWatermarkKB);
-
- dest.writeInt(mRebufferingWatermarkLowMs);
- dest.writeInt(mRebufferingWatermarkHighMs);
- dest.writeInt(mRebufferingWatermarkLowKB);
- dest.writeInt(mRebufferingWatermarkHighKB);
+ dest.writeInt(mInitialMarkMs);
+ dest.writeInt(mResumePlaybackMarkMs);
}
}
diff --git a/android/media/ExifInterface.java b/android/media/ExifInterface.java
index ba41a7bd..91754162 100644
--- a/android/media/ExifInterface.java
+++ b/android/media/ExifInterface.java
@@ -2564,51 +2564,66 @@ public class ExifInterface {
});
}
+ String hasImage = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
String hasVideo = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
- final String METADATA_HAS_VIDEO_VALUE_YES = "yes";
- if (METADATA_HAS_VIDEO_VALUE_YES.equals(hasVideo)) {
- String width = retriever.extractMetadata(
+ String width = null;
+ String height = null;
+ String rotation = null;
+ final String METADATA_VALUE_YES = "yes";
+ // If the file has both image and video, prefer image info over video info.
+ // App querying ExifInterface is most likely using the bitmap path which
+ // picks the image first.
+ if (METADATA_VALUE_YES.equals(hasImage)) {
+ width = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
+ height = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
+ rotation = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
+ } else if (METADATA_VALUE_YES.equals(hasVideo)) {
+ width = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
- String height = retriever.extractMetadata(
+ height = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+ rotation = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ }
- if (width != null) {
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
- ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
- }
-
- if (height != null) {
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
- ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
- }
+ if (width != null) {
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
+ ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
+ }
- String rotation = retriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
- if (rotation != null) {
- int orientation = ExifInterface.ORIENTATION_NORMAL;
+ if (height != null) {
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
+ ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
+ }
- // all rotation angles in CW
- switch (Integer.parseInt(rotation)) {
- case 90:
- orientation = ExifInterface.ORIENTATION_ROTATE_90;
- break;
- case 180:
- orientation = ExifInterface.ORIENTATION_ROTATE_180;
- break;
- case 270:
- orientation = ExifInterface.ORIENTATION_ROTATE_270;
- break;
- }
+ if (rotation != null) {
+ int orientation = ExifInterface.ORIENTATION_NORMAL;
- mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
- ExifAttribute.createUShort(orientation, mExifByteOrder));
+ // all rotation angles in CW
+ switch (Integer.parseInt(rotation)) {
+ case 90:
+ orientation = ExifInterface.ORIENTATION_ROTATE_90;
+ break;
+ case 180:
+ orientation = ExifInterface.ORIENTATION_ROTATE_180;
+ break;
+ case 270:
+ orientation = ExifInterface.ORIENTATION_ROTATE_270;
+ break;
}
- if (DEBUG) {
- Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
- }
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
+ ExifAttribute.createUShort(orientation, mExifByteOrder));
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
}
} finally {
retriever.release();
diff --git a/android/media/MediaDrm.java b/android/media/MediaDrm.java
index 88b1c5ff..1feea890 100644
--- a/android/media/MediaDrm.java
+++ b/android/media/MediaDrm.java
@@ -994,7 +994,6 @@ public final class MediaDrm {
* {@link #PROPERTY_VENDOR}, {@link #PROPERTY_VERSION},
* {@link #PROPERTY_DESCRIPTION}, {@link #PROPERTY_ALGORITHMS}
*/
- /* FIXME this throws IllegalStateException for invalid property names */
@NonNull
public native String getPropertyString(@NonNull @StringProperty String propertyName);
@@ -1002,7 +1001,6 @@ public final class MediaDrm {
* Byte array property name: the device unique identifier is established during
* device provisioning and provides a means of uniquely identifying each device.
*/
- /* FIXME this throws IllegalStateException for invalid property names */
public static final String PROPERTY_DEVICE_UNIQUE_ID = "deviceUniqueId";
/** @hide */
diff --git a/android/media/MediaFormat.java b/android/media/MediaFormat.java
index ed5f7d84..c475e122 100644
--- a/android/media/MediaFormat.java
+++ b/android/media/MediaFormat.java
@@ -96,6 +96,19 @@ import java.util.Map;
* <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
* <tr><td>{@link #KEY_LANGUAGE}</td><td>String</td><td>The language of the content.</td></tr>
* </table>
+ *
+ * Image formats have the following keys:
+ * <table>
+ * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
+ * <tr><td>{@link #KEY_WIDTH}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_HEIGHT}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_COLOR_FORMAT}</td><td>Integer</td><td>set by the user
+ * for encoders, readable in the output format of decoders</b></td></tr>
+ * <tr><td>{@link #KEY_GRID_WIDTH}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_HEIGHT}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_ROWS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_COLS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * </table>
*/
public final class MediaFormat {
public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
@@ -126,6 +139,35 @@ public final class MediaFormat {
public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
/**
+ * MIME type for HEIF still image data encoded in HEVC.
+ *
+ * To decode such an image, {@link MediaCodec} decoder for
+ * {@ #MIMETYPE_VIDEO_HEVC} shall be used. The client needs to form
+ * the correct {@link #MediaFormat} based on additional information in
+ * the track format, and send it to {@link MediaCodec#configure}.
+ *
+ * The track's MediaFormat will come with {@link #KEY_WIDTH} and
+ * {@link #KEY_HEIGHT} keys, which describes the width and height
+ * of the image. If the image doesn't contain grid (i.e. none of
+ * {@link #KEY_GRID_WIDTH}, {@link #KEY_GRID_HEIGHT},
+ * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLS} are present}), the
+ * track will contain a single sample of coded data for the entire image,
+ * and the image width and height should be used to set up the decoder.
+ *
+ * If the image does come with grid, each sample from the track will
+ * contain one tile in the grid, of which the size is described by
+ * {@link #KEY_GRID_WIDTH} and {@link #KEY_GRID_HEIGHT}. This size
+ * (instead of {@link #KEY_WIDTH} and {@link #KEY_HEIGHT}) should be
+ * used to set up the decoder. The track contains {@link #KEY_GRID_ROWS}
+ * by {@link #KEY_GRID_COLS} samples in row-major, top-row first,
+ * left-to-right order. The output image should be reconstructed by
+ * first tiling the decoding results of the tiles in the correct order,
+ * then trimming (before rotation is applied) on the bottom and right
+ * side, if the tiled area is larger than the image width and height.
+ */
+ public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
+
+ /**
* MIME type for WebVTT subtitle data.
*/
public static final String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -232,6 +274,54 @@ public final class MediaFormat {
public static final String KEY_FRAME_RATE = "frame-rate";
/**
+ * A key describing the grid width of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+ * track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_ROWS
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_WIDTH = "grid-width";
+
+ /**
+ * A key describing the grid height of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+ * track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_ROWS
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_HEIGHT = "grid-height";
+
+ /**
+ * A key describing the number of grid rows in the content in a
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_COLS
+ */
+ public static final String KEY_GRID_ROWS = "grid-rows";
+
+ /**
+ * A key describing the number of grid columns in the content in a
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ *
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ *
+ * @see #KEY_GRID_WIDTH
+ * @see #KEY_GRID_HEIGHT
+ * @see #KEY_GRID_ROWS
+ */
+ public static final String KEY_GRID_COLS = "grid-cols";
+
+ /**
* A key describing the raw audio sample encoding/format.
*
* <p>The associated value is an integer, using one of the
diff --git a/android/media/MediaMetadataRetriever.java b/android/media/MediaMetadataRetriever.java
index 760cc49b..0b864018 100644
--- a/android/media/MediaMetadataRetriever.java
+++ b/android/media/MediaMetadataRetriever.java
@@ -47,7 +47,7 @@ public class MediaMetadataRetriever
// The field below is accessed by native methods
@SuppressWarnings("unused")
private long mNativeContext;
-
+
private static final int EMBEDDED_PICTURE_TYPE_ANY = 0xFFFF;
public MediaMetadataRetriever() {
@@ -58,7 +58,7 @@ public class MediaMetadataRetriever
* Sets the data source (file pathname) to use. Call this
* method before the rest of the methods in this class. This method may be
* time-consuming.
- *
+ *
* @param path The path of the input media file.
* @throws IllegalArgumentException If the path is invalid.
*/
@@ -113,7 +113,7 @@ public class MediaMetadataRetriever
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
- *
+ *
* @param fd the FileDescriptor for the file you want to play
* @param offset the offset into the file where the data to be played starts,
* in bytes. It must be non-negative
@@ -123,13 +123,13 @@ public class MediaMetadataRetriever
*/
public native void setDataSource(FileDescriptor fd, long offset, long length)
throws IllegalArgumentException;
-
+
/**
* Sets the data source (FileDescriptor) to use. It is the caller's
* responsibility to close the file descriptor. It is safe to do so as soon
* as this call returns. Call this method before the rest of the methods in
* this class. This method may be time-consuming.
- *
+ *
* @param fd the FileDescriptor for the file you want to play
* @throws IllegalArgumentException if the FileDescriptor is invalid
*/
@@ -138,11 +138,11 @@ public class MediaMetadataRetriever
// intentionally less than LONG_MAX
setDataSource(fd, 0, 0x7ffffffffffffffL);
}
-
+
/**
- * Sets the data source as a content Uri. Call this method before
+ * Sets the data source as a content Uri. Call this method before
* the rest of the methods in this class. This method may be time-consuming.
- *
+ *
* @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @throws IllegalArgumentException if the Uri is invalid
@@ -154,7 +154,7 @@ public class MediaMetadataRetriever
if (uri == null) {
throw new IllegalArgumentException();
}
-
+
String scheme = uri.getScheme();
if(scheme == null || scheme.equals("file")) {
setDataSource(uri.getPath());
@@ -213,12 +213,12 @@ public class MediaMetadataRetriever
/**
* Call this method after setDataSource(). This method retrieves the
* meta data value associated with the keyCode.
- *
+ *
* The keyCode currently supported is listed below as METADATA_XXX
* constants. With any other value, it returns a null pointer.
- *
+ *
* @param keyCode One of the constants listed below at the end of the class.
- * @return The meta data value associate with the given keyCode on success;
+ * @return The meta data value associate with the given keyCode on success;
* null on failure.
*/
public native String extractMetadata(int keyCode);
@@ -357,6 +357,109 @@ public class MediaMetadataRetriever
private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
/**
+ * This method retrieves a video frame by its index. It should only be called
+ * after {@link #setDataSource}.
+ *
+ * @param frameIndex 0-based index of the video frame. The frame index must be that of
+ * a valid frame. The total number of frames available for retrieval can be queried
+ * via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+ *
+ * @throws IllegalStateException if the container doesn't contain video or image sequences.
+ * @throws IllegalArgumentException if the requested frame index does not exist.
+ *
+ * @return A Bitmap containing the requested video frame, or null if the retrieval fails.
+ *
+ * @see #getFramesAtIndex(int, int)
+ */
+ public Bitmap getFrameAtIndex(int frameIndex) {
+ Bitmap[] bitmaps = getFramesAtIndex(frameIndex, 1);
+ if (bitmaps == null || bitmaps.length < 1) {
+ return null;
+ }
+ return bitmaps[0];
+ }
+
+ /**
+ * This method retrieves a consecutive set of video frames starting at the
+ * specified index. It should only be called after {@link #setDataSource}.
+ *
+ * If the caller intends to retrieve more than one consecutive video frames,
+ * this method is preferred over {@link #getFrameAtIndex(int)} for efficiency.
+ *
+ * @param frameIndex 0-based index of the first video frame to retrieve. The frame index
+ * must be that of a valid frame. The total number of frames available for retrieval
+ * can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+ * @param numFrames number of consecutive video frames to retrieve. Must be a positive
+ * value. The stream must contain at least numFrames frames starting at frameIndex.
+ *
+ * @throws IllegalStateException if the container doesn't contain video or image sequences.
+ * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the
+ * stream doesn't contain at least numFrames starting at frameIndex.
+
+ * @return An array of Bitmaps containing the requested video frames. The returned
+ * array could contain less frames than requested if the retrieval fails.
+ *
+ * @see #getFrameAtIndex(int)
+ */
+ public Bitmap[] getFramesAtIndex(int frameIndex, int numFrames) {
+ if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) {
+ throw new IllegalStateException("Does not contail video or image sequences");
+ }
+ int frameCount = Integer.parseInt(
+ extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+ if (frameIndex < 0 || numFrames < 1
+ || frameIndex >= frameCount
+ || frameIndex > frameCount - numFrames) {
+ throw new IllegalArgumentException("Invalid frameIndex or numFrames: "
+ + frameIndex + ", " + numFrames);
+ }
+ return _getFrameAtIndex(frameIndex, numFrames);
+ }
+ private native Bitmap[] _getFrameAtIndex(int frameIndex, int numFrames);
+
+ /**
+ * This method retrieves a still image by its index. It should only be called
+ * after {@link #setDataSource}.
+ *
+ * @param imageIndex 0-based index of the image, with negative value indicating
+ * the primary image.
+ * @throws IllegalStateException if the container doesn't contain still images.
+ * @throws IllegalArgumentException if the requested image does not exist.
+ *
+ * @return the requested still image, or null if the image cannot be retrieved.
+ *
+ * @see #getPrimaryImage
+ */
+ public Bitmap getImageAtIndex(int imageIndex) {
+ if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) {
+ throw new IllegalStateException("Does not contail still images");
+ }
+
+ String imageCount = extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT);
+ if (imageIndex >= Integer.parseInt(imageCount)) {
+ throw new IllegalArgumentException("Invalid image index: " + imageCount);
+ }
+
+ return _getImageAtIndex(imageIndex);
+ }
+
+ /**
+ * This method retrieves the primary image of the media content. It should only
+ * be called after {@link #setDataSource}.
+ *
+ * @return the primary image, or null if it cannot be retrieved.
+ *
+ * @throws IllegalStateException if the container doesn't contain still images.
+ *
+ * @see #getImageAtIndex(int)
+ */
+ public Bitmap getPrimaryImage() {
+ return getImageAtIndex(-1);
+ }
+
+ private native Bitmap _getImageAtIndex(int imageIndex);
+
+ /**
* Call this method after setDataSource(). This method finds the optional
* graphic or album/cover art associated associated with the data source. If
* there are more than one pictures, (any) one of them is returned.
@@ -572,5 +675,40 @@ public class MediaMetadataRetriever
* number.
*/
public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
+ /**
+ * If this key exists the media contains still image content.
+ */
+ public static final int METADATA_KEY_HAS_IMAGE = 26;
+ /**
+ * If the media contains still images, this key retrieves the number
+ * of still images.
+ */
+ public static final int METADATA_KEY_IMAGE_COUNT = 27;
+ /**
+ * If the media contains still images, this key retrieves the image
+ * index of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_PRIMARY = 28;
+ /**
+ * If the media contains still images, this key retrieves the width
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_WIDTH = 29;
+ /**
+ * If the media contains still images, this key retrieves the height
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_HEIGHT = 30;
+ /**
+ * If the media contains still images, this key retrieves the rotation
+ * of the primary image.
+ */
+ public static final int METADATA_KEY_IMAGE_ROTATION = 31;
+ /**
+ * If the media contains video and this key exists, it retrieves the
+ * total number of frames in the video sequence.
+ */
+ public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32;
+
// Add more here...
}
diff --git a/android/media/MediaPlayer.java b/android/media/MediaPlayer.java
index 62757e2e..649c091b 100644
--- a/android/media/MediaPlayer.java
+++ b/android/media/MediaPlayer.java
@@ -43,6 +43,7 @@ import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import android.util.Pair;
+import android.util.ArrayMap;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.widget.VideoView;
@@ -58,6 +59,7 @@ import android.media.SubtitleData;
import android.media.SubtitleTrack.RenderingWidget;
import android.media.SyncParams;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import libcore.io.IoBridge;
@@ -577,6 +579,7 @@ import java.util.Vector;
public class MediaPlayer extends PlayerBase
implements SubtitleController.Listener
, VolumeAutomation
+ , AudioRouting
{
/**
Constant to retrieve only the new metadata since the last
@@ -1417,6 +1420,155 @@ public class MediaPlayer extends PlayerBase
private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id);
+ //--------------------------------------------------------------------------
+ // Explicit Routing
+ //--------------------
+ private AudioDeviceInfo mPreferredDevice = null;
+
+ /**
+ * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+ * the output from this MediaPlayer.
+ * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
+ * If deviceInfo is null, default routing is restored.
+ * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+ * does not correspond to a valid audio device.
+ */
+ @Override
+ public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+ if (deviceInfo != null && !deviceInfo.isSink()) {
+ return false;
+ }
+ int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+ boolean status = native_setOutputDevice(preferredDeviceId);
+ if (status == true) {
+ synchronized (this) {
+ mPreferredDevice = deviceInfo;
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
+ * is not guaranteed to correspond to the actual device being used for playback.
+ */
+ @Override
+ public AudioDeviceInfo getPreferredDevice() {
+ synchronized (this) {
+ return mPreferredDevice;
+ }
+ }
+
+ /**
+ * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer
+ * Note: The query is only valid if the MediaPlayer is currently playing.
+ * If the player is not playing, the returned device can be null or correspond to previously
+ * selected device when the player was last active.
+ */
+ @Override
+ public AudioDeviceInfo getRoutedDevice() {
+ int deviceId = native_getRoutedDeviceId();
+ if (deviceId == 0) {
+ return null;
+ }
+ AudioDeviceInfo[] devices =
+ AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
+ for (int i = 0; i < devices.length; i++) {
+ if (devices[i].getId() == deviceId) {
+ return devices[i];
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
+ */
+ private void enableNativeRoutingCallbacksLocked(boolean enabled) {
+ if (mRoutingChangeListeners.size() == 0) {
+ native_enableDeviceCallback(enabled);
+ }
+ }
+
+ /**
+ * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+ * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+ * by an app to receive (re)routing notifications.
+ */
+ @GuardedBy("mRoutingChangeListeners")
+ private ArrayMap<AudioRouting.OnRoutingChangedListener,
+ NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this MediaPlayer.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the handler on the main looper will be used.
+ */
+ @Override
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ synchronized (mRoutingChangeListeners) {
+ if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+ enableNativeRoutingCallbacksLocked(true);
+ mRoutingChangeListeners.put(
+ listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
+ }
+ }
+ }
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ @Override
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mRoutingChangeListeners) {
+ if (mRoutingChangeListeners.containsKey(listener)) {
+ mRoutingChangeListeners.remove(listener);
+ enableNativeRoutingCallbacksLocked(false);
+ }
+ }
+ }
+
+ /**
+ * Helper class to handle the forwarding of native events to the appropriate listener
+ * (potentially) handled in a different thread
+ */
+ private class NativeRoutingEventHandlerDelegate {
+ private MediaPlayer mMediaPlayer;
+ private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
+ private Handler mHandler;
+
+ NativeRoutingEventHandlerDelegate(final MediaPlayer mediaPlayer,
+ final AudioRouting.OnRoutingChangedListener listener, Handler handler) {
+ mMediaPlayer = mediaPlayer;
+ mOnRoutingChangedListener = listener;
+ mHandler = handler != null ? handler : mEventHandler;
+ }
+
+ void notifyClient() {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mOnRoutingChangedListener != null) {
+ mOnRoutingChangedListener.onRoutingChanged(mMediaPlayer);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private native final boolean native_setOutputDevice(int deviceId);
+ private native final int native_getRoutedDeviceId();
+ private native final void native_enableDeviceCallback(boolean enabled);
+
/**
* Set the low-level power management behavior for this MediaPlayer. This
* can be used when the MediaPlayer is not playing through a SurfaceHolder
@@ -1546,21 +1698,9 @@ public class MediaPlayer extends PlayerBase
public native boolean isPlaying();
/**
- * Gets the default buffering management params.
- * Calling it only after {@code setDataSource} has been called.
- * Each type of data source might have different set of default params.
- *
- * @return the default buffering management params supported by the source component.
- * @throws IllegalStateException if the internal player engine has not been
- * initialized, or {@code setDataSource} has not been called.
- * @hide
- */
- @NonNull
- public native BufferingParams getDefaultBufferingParams();
-
- /**
* Gets the current buffering management params used by the source component.
* Calling it only after {@code setDataSource} has been called.
+ * Each type of data source might have different set of default params.
*
* @return the current buffering management params used by the source component.
* @throws IllegalStateException if the internal player engine has not been
@@ -1575,8 +1715,7 @@ public class MediaPlayer extends PlayerBase
* The object sets its internal BufferingParams to the input, except that the input is
* invalid or not supported.
* Call it only after {@code setDataSource} has been called.
- * Users should only use supported mode returned by {@link #getDefaultBufferingParams()}
- * or its downsized version as described in {@link BufferingParams}.
+ * The input is a hint to MediaPlayer.
*
* @param params the buffering management params.
*
@@ -3176,6 +3315,7 @@ public class MediaPlayer extends PlayerBase
private static final int MEDIA_SUBTITLE_DATA = 201;
private static final int MEDIA_META_DATA = 202;
private static final int MEDIA_DRM_INFO = 210;
+ private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000;
private TimeProvider mTimeProvider;
@@ -3414,6 +3554,16 @@ public class MediaPlayer extends PlayerBase
case MEDIA_NOP: // interface test message - ignore
break;
+ case MEDIA_AUDIO_ROUTING_CHANGED:
+ AudioManager.resetAudioPortGeneration();
+ synchronized (mRoutingChangeListeners) {
+ for (NativeRoutingEventHandlerDelegate delegate
+ : mRoutingChangeListeners.values()) {
+ delegate.notifyClient();
+ }
+ }
+ return;
+
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
diff --git a/android/net/ConnectivityManager.java b/android/net/ConnectivityManager.java
index d7ecc81f..8071e8b8 100644
--- a/android/net/ConnectivityManager.java
+++ b/android/net/ConnectivityManager.java
@@ -619,6 +619,35 @@ public class ConnectivityManager {
*/
public static final int NETID_UNSET = 0;
+ /**
+ * Private DNS Mode values.
+ *
+ * The "private_dns_mode" global setting stores a String value which is
+ * expected to be one of the following.
+ */
+
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OFF = "off";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
+ /**
+ * The default Private DNS mode.
+ *
+ * This may change from release to release or may become dependent upon
+ * the capabilities of the underlying platform.
+ *
+ * @hide
+ */
+ public static final String PRIVATE_DNS_DEFAULT_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
private final IConnectivityManager mService;
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
diff --git a/android/net/ConnectivityMetricsEvent.java b/android/net/ConnectivityMetricsEvent.java
index 46bb3467..394ac428 100644
--- a/android/net/ConnectivityMetricsEvent.java
+++ b/android/net/ConnectivityMetricsEvent.java
@@ -18,6 +18,7 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
+
import com.android.internal.util.BitUtils;
/**
@@ -80,7 +81,7 @@ public final class ConnectivityMetricsEvent implements Parcelable {
StringBuilder buffer = new StringBuilder("ConnectivityMetricsEvent(");
buffer.append(String.format("%tT.%tL", timestamp, timestamp));
if (netId != 0) {
- buffer.append(", ").append(netId);
+ buffer.append(", ").append("netId=").append(netId);
}
if (ifname != null) {
buffer.append(", ").append(ifname);
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
index 16b14523..64f8f39e 100644
--- a/android/net/IpSecAlgorithm.java
+++ b/android/net/IpSecAlgorithm.java
@@ -78,7 +78,11 @@ public final class IpSecAlgorithm implements Parcelable {
/**
* AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
*
- * <p>Valid lengths for this key are {128, 192, 256}.
+ * <p>Valid lengths for keying material are {160, 224, 288}.
+ *
+ * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key
+ * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per
+ * invocation with the same key.
*
* <p>Valid ICV (truncation) lengths are {64, 96, 128}.
*/
diff --git a/android/net/IpSecManager.java b/android/net/IpSecManager.java
index d7b32561..eccd5f47 100644
--- a/android/net/IpSecManager.java
+++ b/android/net/IpSecManager.java
@@ -136,7 +136,7 @@ public final class IpSecManager {
}
@Override
- protected void finalize() {
+ protected void finalize() throws Throwable {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
diff --git a/android/net/LocalSocketImpl.java b/android/net/LocalSocketImpl.java
index 05c8afb3..6e4a231b 100644
--- a/android/net/LocalSocketImpl.java
+++ b/android/net/LocalSocketImpl.java
@@ -16,18 +16,18 @@
package android.net;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.InputStream;
-import java.io.FileDescriptor;
-import java.net.SocketOptions;
-
import android.system.ErrnoException;
+import android.system.Int32Ref;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructLinger;
import android.system.StructTimeval;
-import android.util.MutableInt;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketOptions;
/**
* Socket implementation used for android.net.LocalSocket and
@@ -62,7 +62,7 @@ class LocalSocketImpl
FileDescriptor myFd = fd;
if (myFd == null) throw new IOException("socket closed");
- MutableInt avail = new MutableInt(0);
+ Int32Ref avail = new Int32Ref(0);
try {
Os.ioctlInt(myFd, OsConstants.FIONREAD, avail);
} catch (ErrnoException e) {
@@ -167,7 +167,7 @@ class LocalSocketImpl
if (myFd == null) throw new IOException("socket closed");
// Loop until the output buffer is empty.
- MutableInt pending = new MutableInt(0);
+ Int32Ref pending = new Int32Ref(0);
while (true) {
try {
// See linux/net/unix/af_unix.c
diff --git a/android/net/MacAddress.java b/android/net/MacAddress.java
new file mode 100644
index 00000000..f6a69bac
--- /dev/null
+++ b/android/net/MacAddress.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.BitUtils;
+
+import java.util.Arrays;
+import java.util.Random;
+import java.util.StringJoiner;
+
+/**
+ * Represents a mac address.
+ *
+ * @hide
+ */
+public final class MacAddress implements Parcelable {
+
+ private static final int ETHER_ADDR_LEN = 6;
+ private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
+
+ /** The broadcast mac address. */
+ public static final MacAddress BROADCAST_ADDRESS = new MacAddress(ETHER_ADDR_BROADCAST);
+
+ /** The zero mac address. */
+ public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);
+
+ /** Represents categories of mac addresses. */
+ public enum MacAddressType {
+ UNICAST,
+ MULTICAST,
+ BROADCAST;
+ }
+
+ private static final long VALID_LONG_MASK = BROADCAST_ADDRESS.mAddr;
+ private static final long LOCALLY_ASSIGNED_MASK = new MacAddress("2:0:0:0:0:0").mAddr;
+ private static final long MULTICAST_MASK = new MacAddress("1:0:0:0:0:0").mAddr;
+ private static final long OUI_MASK = new MacAddress("ff:ff:ff:0:0:0").mAddr;
+ private static final long NIC_MASK = new MacAddress("0:0:0:ff:ff:ff").mAddr;
+ private static final MacAddress BASE_ANDROID_MAC = new MacAddress("da:a1:19:0:0:0");
+
+ // Internal representation of the mac address as a single 8 byte long.
+ // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
+ // mac address are encoded in the 6 least significant bytes of the long, where the first
+ // byte of the array is mapped to the 3rd highest logical byte of the long, the second
+ // byte of the array is mapped to the 4th highest logical byte of the long, and so on.
+ private final long mAddr;
+
+ private MacAddress(long addr) {
+ mAddr = addr;
+ }
+
+ /** Creates a MacAddress for the given byte representation. */
+ public MacAddress(byte[] addr) {
+ this(longAddrFromByteAddr(addr));
+ }
+
+ /** Creates a MacAddress for the given string representation. */
+ public MacAddress(String addr) {
+ this(longAddrFromByteAddr(byteAddrFromStringAddr(addr)));
+ }
+
+ /** Returns the MacAddressType of this MacAddress. */
+ public MacAddressType addressType() {
+ if (equals(BROADCAST_ADDRESS)) {
+ return MacAddressType.BROADCAST;
+ }
+ if (isMulticastAddress()) {
+ return MacAddressType.MULTICAST;
+ }
+ return MacAddressType.UNICAST;
+ }
+
+ /** Returns true if this MacAddress corresponds to a multicast address. */
+ public boolean isMulticastAddress() {
+ return (mAddr & MULTICAST_MASK) != 0;
+ }
+
+ /** Returns true if this MacAddress corresponds to a locally assigned address. */
+ public boolean isLocallyAssigned() {
+ return (mAddr & LOCALLY_ASSIGNED_MASK) != 0;
+ }
+
+ /** Returns a byte array representation of this MacAddress. */
+ public byte[] toByteArray() {
+ return byteAddrFromLongAddr(mAddr);
+ }
+
+ @Override
+ public String toString() {
+ return stringAddrFromByteAddr(byteAddrFromLongAddr(mAddr));
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) ((mAddr >> 32) ^ mAddr);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mAddr);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<MacAddress> CREATOR =
+ new Parcelable.Creator<MacAddress>() {
+ public MacAddress createFromParcel(Parcel in) {
+ return new MacAddress(in.readLong());
+ }
+
+ public MacAddress[] newArray(int size) {
+ return new MacAddress[size];
+ }
+ };
+
+ /** Return true if the given byte array is not null and has the length of a mac address. */
+ public static boolean isMacAddress(byte[] addr) {
+ return addr != null && addr.length == ETHER_ADDR_LEN;
+ }
+
+ /**
+ * Return the MacAddressType of the mac address represented by the given byte array,
+ * or null if the given byte array does not represent an mac address.
+ */
+ public static MacAddressType macAddressType(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ return null;
+ }
+ return new MacAddress(addr).addressType();
+ }
+
+ /** DOCME */
+ public static byte[] byteAddrFromStringAddr(String addr) {
+ if (addr == null) {
+ throw new IllegalArgumentException("cannot convert the null String");
+ }
+ String[] parts = addr.split(":");
+ if (parts.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(addr + " was not a valid MAC address");
+ }
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ int x = Integer.valueOf(parts[i], 16);
+ if (x < 0 || 0xff < x) {
+ throw new IllegalArgumentException(addr + "was not a valid MAC address");
+ }
+ bytes[i] = (byte) x;
+ }
+ return bytes;
+ }
+
+ /** DOCME */
+ public static String stringAddrFromByteAddr(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ return null;
+ }
+ StringJoiner j = new StringJoiner(":");
+ for (byte b : addr) {
+ j.add(Integer.toHexString(BitUtils.uint8(b)));
+ }
+ return j.toString();
+ }
+
+ /** @hide */
+ public static byte[] byteAddrFromLongAddr(long addr) {
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ int index = ETHER_ADDR_LEN;
+ while (index-- > 0) {
+ bytes[index] = (byte) addr;
+ addr = addr >> 8;
+ }
+ return bytes;
+ }
+
+ /** @hide */
+ public static long longAddrFromByteAddr(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ throw new IllegalArgumentException(
+ Arrays.toString(addr) + " was not a valid MAC address");
+ }
+ long longAddr = 0;
+ for (byte b : addr) {
+ longAddr = (longAddr << 8) + BitUtils.uint8(b);
+ }
+ return longAddr;
+ }
+
+ /** @hide */
+ public static long longAddrFromStringAddr(String addr) {
+ if (addr == null) {
+ throw new IllegalArgumentException("cannot convert the null String");
+ }
+ String[] parts = addr.split(":");
+ if (parts.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(addr + " was not a valid MAC address");
+ }
+ long longAddr = 0;
+ int index = ETHER_ADDR_LEN;
+ while (index-- > 0) {
+ int x = Integer.valueOf(parts[index], 16);
+ if (x < 0 || 0xff < x) {
+ throw new IllegalArgumentException(addr + "was not a valid MAC address");
+ }
+ longAddr = x + (longAddr << 8);
+ }
+ return longAddr;
+ }
+
+ /** @hide */
+ public static String stringAddrFromLongAddr(long addr) {
+ addr = Long.reverseBytes(addr) >> 16;
+ StringJoiner j = new StringJoiner(":");
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ j.add(Integer.toHexString((byte) addr));
+ addr = addr >> 8;
+ }
+ return j.toString();
+ }
+
+ /**
+ * Returns a randomely generated mac address with the Android OUI value "DA-A1-19".
+ * The locally assigned bit is always set to 1.
+ */
+ public static MacAddress getRandomAddress() {
+ return getRandomAddress(BASE_ANDROID_MAC, new Random());
+ }
+
+ /**
+ * Returns a randomely generated mac address using the given Random object and the same
+ * OUI values as the given MacAddress. The locally assigned bit is always set to 1.
+ */
+ public static MacAddress getRandomAddress(MacAddress base, Random r) {
+ long longAddr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()) | LOCALLY_ASSIGNED_MASK;
+ return new MacAddress(longAddr);
+ }
+
+ // Convenience function for working around the lack of byte literals.
+ private static byte[] addr(int... in) {
+ if (in.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(Arrays.toString(in)
+ + " was not an array with length equal to " + ETHER_ADDR_LEN);
+ }
+ byte[] out = new byte[ETHER_ADDR_LEN];
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ out[i] = (byte) in[i];
+ }
+ return out;
+ }
+}
diff --git a/android/net/Network.java b/android/net/Network.java
index 3c868c39..903b602b 100644
--- a/android/net/Network.java
+++ b/android/net/Network.java
@@ -16,14 +16,14 @@
package android.net;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import libcore.net.http.Dns;
-import libcore.net.http.HttpURLConnectionFactory;
+import com.android.okhttp.internalandroidapi.Dns;
+import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -34,11 +34,12 @@ import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
-import java.net.UnknownHostException;
import java.net.URL;
import java.net.URLConnection;
+import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
+
import javax.net.SocketFactory;
/**
diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java
index db12dd97..ee75fd44 100644
--- a/android/net/NetworkCapabilities.java
+++ b/android/net/NetworkCapabilities.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.IntDef;
+import android.net.ConnectivityManager.NetworkCallback;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,15 +31,24 @@ import java.util.Objects;
import java.util.StringJoiner;
/**
- * This class represents the capabilities of a network. This is used both to specify
- * needs to {@link ConnectivityManager} and when inspecting a network.
- *
- * Note that this replaces the old {@link ConnectivityManager#TYPE_MOBILE} method
- * of network selection. Rather than indicate a need for Wi-Fi because an application
- * needs high bandwidth and risk obsolescence when a new, fast network appears (like LTE),
- * the application should specify it needs high bandwidth. Similarly if an application
- * needs an unmetered network for a bulk transfer it can specify that rather than assuming
- * all cellular based connections are metered and all Wi-Fi based connections are not.
+ * Representation of the capabilities of a network. This object serves two
+ * purposes:
+ * <ul>
+ * <li>An expression of the current capabilities of an active network, typically
+ * expressed through
+ * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
+ * or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
+ * <li>An expression of the future capabilities of a desired network, typically
+ * expressed through {@link NetworkRequest}.
+ * </ul>
+ * <p>
+ * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
+ * network selection. Rather than indicate a need for Wi-Fi because an
+ * application needs high bandwidth and risk obsolescence when a new, fast
+ * network appears (like LTE), the application should specify it needs high
+ * bandwidth. Similarly if an application needs an unmetered network for a bulk
+ * transfer it can specify that rather than assuming all cellular based
+ * connections are metered and all Wi-Fi based connections are not.
*/
public final class NetworkCapabilities implements Parcelable {
private static final String TAG = "NetworkCapabilities";
@@ -101,6 +111,7 @@ public final class NetworkCapabilities implements Parcelable {
NET_CAPABILITY_NOT_VPN,
NET_CAPABILITY_VALIDATED,
NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_NOT_ROAMING,
NET_CAPABILITY_FOREGROUND,
})
public @interface NetCapability { }
@@ -218,11 +229,16 @@ public final class NetworkCapabilities implements Parcelable {
public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17;
/**
+ * Indicates that this network is not roaming.
+ */
+ public static final int NET_CAPABILITY_NOT_ROAMING = 18;
+
+ /**
* Indicates that this network is available for use by apps, and not a network that is being
* kept up in the background to facilitate fast network switching.
* @hide
*/
- public static final int NET_CAPABILITY_FOREGROUND = 18;
+ public static final int NET_CAPABILITY_FOREGROUND = 19;
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_FOREGROUND;
@@ -237,6 +253,7 @@ public final class NetworkCapabilities implements Parcelable {
(1 << NET_CAPABILITY_TRUSTED) |
(1 << NET_CAPABILITY_VALIDATED) |
(1 << NET_CAPABILITY_CAPTIVE_PORTAL) |
+ (1 << NET_CAPABILITY_NOT_ROAMING) |
(1 << NET_CAPABILITY_FOREGROUND);
/**
@@ -316,6 +333,21 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Sets (or clears) the given capability on this {@link NetworkCapabilities}
+ * instance.
+ *
+ * @hide
+ */
+ public NetworkCapabilities setCapability(@NetCapability int capability, boolean value) {
+ if (value) {
+ addCapability(capability);
+ } else {
+ removeCapability(capability);
+ }
+ return this;
+ }
+
+ /**
* Gets all the capabilities set on this {@code NetworkCapability} instance.
*
* @return an array of capability values for this instance.
@@ -326,6 +358,15 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Sets all the capabilities set on this {@code NetworkCapability} instance.
+ *
+ * @hide
+ */
+ public void setCapabilities(@NetCapability int[] capabilities) {
+ mNetworkCapabilities = BitUtils.packBits(capabilities);
+ }
+
+ /**
* Tests for the presence of a capabilitity on this instance.
*
* @param capability the capabilities to be tested for.
@@ -515,6 +556,21 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Sets (or clears) the given transport on this {@link NetworkCapabilities}
+ * instance.
+ *
+ * @hide
+ */
+ public NetworkCapabilities setTransportType(@Transport int transportType, boolean value) {
+ if (value) {
+ addTransportType(transportType);
+ } else {
+ removeTransportType(transportType);
+ }
+ return this;
+ }
+
+ /**
* Gets all the transports set on this {@code NetworkCapability} instance.
*
* @return an array of transport type values for this instance.
@@ -525,6 +581,15 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Sets all the transports set on this {@code NetworkCapability} instance.
+ *
+ * @hide
+ */
+ public void setTransportTypes(@Transport int[] transportTypes) {
+ mTransportTypes = BitUtils.packBits(transportTypes);
+ }
+
+ /**
* Tests for the presence of a transport on this instance.
*
* @param transportType the transport type to be tested for.
@@ -549,12 +614,18 @@ public final class NetworkCapabilities implements Parcelable {
}
/**
+ * Value indicating that link bandwidth is unspecified.
+ * @hide
+ */
+ public static final int LINK_BANDWIDTH_UNSPECIFIED = 0;
+
+ /**
* Passive link bandwidth. This is a rough guide of the expected peak bandwidth
* for the first hop on the given transport. It is not measured, but may take into account
* link parameters (Radio technology, allocated channels, etc).
*/
- private int mLinkUpBandwidthKbps;
- private int mLinkDownBandwidthKbps;
+ private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
+ private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED;
/**
* Sets the upstream bandwidth for this network in Kbps. This always only refers to
@@ -571,8 +642,9 @@ public final class NetworkCapabilities implements Parcelable {
* @param upKbps the estimated first hop upstream (device to network) bandwidth.
* @hide
*/
- public void setLinkUpstreamBandwidthKbps(int upKbps) {
+ public NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) {
mLinkUpBandwidthKbps = upKbps;
+ return this;
}
/**
@@ -600,8 +672,9 @@ public final class NetworkCapabilities implements Parcelable {
* @param downKbps the estimated first hop downstream (network to device) bandwidth.
* @hide
*/
- public void setLinkDownstreamBandwidthKbps(int downKbps) {
+ public NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) {
mLinkDownBandwidthKbps = downKbps;
+ return this;
}
/**
@@ -628,6 +701,20 @@ public final class NetworkCapabilities implements Parcelable {
return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps &&
this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps);
}
+ /** @hide */
+ public static int minBandwidth(int a, int b) {
+ if (a == LINK_BANDWIDTH_UNSPECIFIED) {
+ return b;
+ } else if (b == LINK_BANDWIDTH_UNSPECIFIED) {
+ return a;
+ } else {
+ return Math.min(a, b);
+ }
+ }
+ /** @hide */
+ public static int maxBandwidth(int a, int b) {
+ return Math.max(a, b);
+ }
private NetworkSpecifier mNetworkSpecifier = null;
@@ -708,8 +795,9 @@ public final class NetworkCapabilities implements Parcelable {
* @param signalStrength the bearer-specific signal strength.
* @hide
*/
- public void setSignalStrength(int signalStrength) {
+ public NetworkCapabilities setSignalStrength(int signalStrength) {
mSignalStrength = signalStrength;
+ return this;
}
/**
@@ -968,6 +1056,7 @@ public final class NetworkCapabilities implements Parcelable {
case NET_CAPABILITY_NOT_VPN: return "NOT_VPN";
case NET_CAPABILITY_VALIDATED: return "VALIDATED";
case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL";
+ case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING";
case NET_CAPABILITY_FOREGROUND: return "FOREGROUND";
default: return Integer.toString(capability);
}
diff --git a/android/net/NetworkIdentity.java b/android/net/NetworkIdentity.java
index acd7b560..d3b35998 100644
--- a/android/net/NetworkIdentity.java
+++ b/android/net/NetworkIdentity.java
@@ -189,7 +189,8 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> {
String subscriberId = null;
String networkId = null;
- boolean roaming = false;
+ boolean roaming = !state.networkCapabilities.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
boolean metered = !state.networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
@@ -203,7 +204,6 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> {
}
subscriberId = state.subscriberId;
- roaming = state.networkInfo.isRoaming();
} else if (type == TYPE_WIFI) {
if (state.networkId != null) {
diff --git a/android/net/NetworkInfo.java b/android/net/NetworkInfo.java
index 818aa211..e6ad89a2 100644
--- a/android/net/NetworkInfo.java
+++ b/android/net/NetworkInfo.java
@@ -305,11 +305,17 @@ public class NetworkInfo implements Parcelable {
}
/**
- * Indicates whether the device is currently roaming on this network.
- * When {@code true}, it suggests that use of data on this network
- * may incur extra costs.
+ * Indicates whether the device is currently roaming on this network. When
+ * {@code true}, it suggests that use of data on this network may incur
+ * extra costs.
+ *
* @return {@code true} if roaming is in effect, {@code false} otherwise.
+ * @deprecated Callers should switch to checking
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING}
+ * instead, since that handles more complex situations, such as
+ * VPNs.
*/
+ @Deprecated
public boolean isRoaming() {
synchronized (this) {
return mIsRoaming;
@@ -318,6 +324,7 @@ public class NetworkInfo implements Parcelable {
/** {@hide} */
@VisibleForTesting
+ @Deprecated
public void setRoaming(boolean isRoaming) {
synchronized (this) {
mIsRoaming = isRoaming;
diff --git a/android/net/NetworkWatchlistManager.java b/android/net/NetworkWatchlistManager.java
new file mode 100644
index 00000000..42e43c8a
--- /dev/null
+++ b/android/net/NetworkWatchlistManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Class that manage network watchlist in system.
+ * @hide
+ */
+@SystemService(Context.NETWORK_WATCHLIST_SERVICE)
+public class NetworkWatchlistManager {
+
+ private static final String TAG = "NetworkWatchlistManager";
+ private static final String SHARED_MEMORY_TAG = "NETWORK_WATCHLIST_SHARED_MEMORY";
+
+ private final Context mContext;
+ private final INetworkWatchlistManager mNetworkWatchlistManager;
+
+ /**
+ * @hide
+ */
+ public NetworkWatchlistManager(Context context, INetworkWatchlistManager manager) {
+ mContext = context;
+ mNetworkWatchlistManager = manager;
+ }
+
+ /**
+ * @hide
+ */
+ public NetworkWatchlistManager(Context context) {
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mNetworkWatchlistManager = (INetworkWatchlistManager)
+ INetworkWatchlistManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_WATCHLIST_SERVICE));
+ }
+
+ /**
+ * Report network watchlist records if necessary.
+ *
+ * Watchlist report process will run summarize records into a single report, then the
+ * report will be processed by differential privacy framework and store it on disk.
+ *
+ * @hide
+ */
+ public void reportWatchlistIfNecessary() {
+ try {
+ mNetworkWatchlistManager.reportWatchlistIfNecessary();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot report records", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/android/net/SSLCertificateSocketFactory.java b/android/net/SSLCertificateSocketFactory.java
index 0b1569ca..4817813c 100644
--- a/android/net/SSLCertificateSocketFactory.java
+++ b/android/net/SSLCertificateSocketFactory.java
@@ -63,7 +63,12 @@ import javax.net.ssl.X509TrustManager;
* This implementation does check the server's certificate hostname, but only
* for createSocket variants that specify a hostname. When using methods that
* use {@link InetAddress} or which return an unconnected socket, you MUST
- * verify the server's identity yourself to ensure a secure connection.</p>
+ * verify the server's identity yourself to ensure a secure connection.
+ *
+ * Refer to
+ * <a href="https://developer.android.com/training/articles/security-gms-provider.html">
+ * Updating Your Security Provider to Protect Against SSL Exploits</a>
+ * for further information.</p>
*
* <p>One way to verify the server's identity is to use
* {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a
diff --git a/android/net/Uri.java b/android/net/Uri.java
index d5377c71..9edcc0e9 100644
--- a/android/net/Uri.java
+++ b/android/net/Uri.java
@@ -1066,7 +1066,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
return null;
}
- int end = authority.indexOf('@');
+ int end = authority.lastIndexOf('@');
return end == NOT_FOUND ? null : authority.substring(0, end);
}
@@ -1090,7 +1090,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
// Parse out user info and then port.
- int userInfoSeparator = authority.indexOf('@');
+ int userInfoSeparator = authority.lastIndexOf('@');
int portSeparator = authority.indexOf(':', userInfoSeparator);
String encodedHost = portSeparator == NOT_FOUND
@@ -1116,7 +1116,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
// Make sure we look for the port separtor *after* the user info
// separator. We have URLs with a ':' in the user info.
- int userInfoSeparator = authority.indexOf('@');
+ int userInfoSeparator = authority.lastIndexOf('@');
int portSeparator = authority.indexOf(':', userInfoSeparator);
if (portSeparator == NOT_FOUND) {
diff --git a/android/net/apf/ApfFilter.java b/android/net/apf/ApfFilter.java
index 5c2b66f6..31a1abb3 100644
--- a/android/net/apf/ApfFilter.java
+++ b/android/net/apf/ApfFilter.java
@@ -86,6 +86,14 @@ import libcore.io.IoBridge;
*/
public class ApfFilter {
+ // Helper class for specifying functional filter parameters.
+ public static class ApfConfiguration {
+ public ApfCapabilities apfCapabilities;
+ public boolean multicastFilter;
+ public boolean ieee802_3Filter;
+ public int[] ethTypeBlackList;
+ }
+
// Enums describing the outcome of receiving an RA packet.
private static enum ProcessRaResult {
MATCH, // Received RA matched a known RA
@@ -261,17 +269,16 @@ public class ApfFilter {
private int mIPv4PrefixLength;
@VisibleForTesting
- ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
- IpClient.Callback ipClientCallback, boolean multicastFilter,
- boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
- mApfCapabilities = apfCapabilities;
+ ApfFilter(ApfConfiguration config, NetworkInterface networkInterface,
+ IpClient.Callback ipClientCallback, IpConnectivityLog log) {
+ mApfCapabilities = config.apfCapabilities;
mIpClientCallback = ipClientCallback;
mNetworkInterface = networkInterface;
- mMulticastFilter = multicastFilter;
- mDrop802_3Frames = ieee802_3Filter;
+ mMulticastFilter = config.multicastFilter;
+ mDrop802_3Frames = config.ieee802_3Filter;
// Now fill the black list from the passed array
- mEthTypeBlackList = filterEthTypeBlackList(ethTypeBlackList);
+ mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
mMetricsLog = log;
@@ -1160,9 +1167,10 @@ public class ApfFilter {
* Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for packet
* filtering using APF programs.
*/
- public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
- NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
- boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
+ public static ApfFilter maybeCreate(ApfConfiguration config,
+ NetworkInterface networkInterface, IpClient.Callback ipClientCallback) {
+ if (config == null) return null;
+ ApfCapabilities apfCapabilities = config.apfCapabilities;
if (apfCapabilities == null || networkInterface == null) return null;
if (apfCapabilities.apfVersionSupported == 0) return null;
if (apfCapabilities.maximumApfProgramSize < 512) {
@@ -1178,8 +1186,7 @@ public class ApfFilter {
Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
return null;
}
- return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
- multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
+ return new ApfFilter(config, networkInterface, ipClientCallback, new IpConnectivityLog());
}
public synchronized void shutdown() {
diff --git a/android/net/ip/IpClient.java b/android/net/ip/IpClient.java
index 2359fab4..70983c86 100644
--- a/android/net/ip/IpClient.java
+++ b/android/net/ip/IpClient.java
@@ -310,12 +310,12 @@ public class IpClient extends StateMachine {
return this;
}
- public Builder withIPv6AddrGenModeEUI64() {
+ public Builder withRandomMacAddress() {
mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
return this;
}
- public Builder withIPv6AddrGenModeStablePrivacy() {
+ public Builder withStableMacAddress() {
mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
return this;
}
@@ -1429,15 +1429,15 @@ public class IpClient extends StateMachine {
@Override
public void enter() {
+ ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();
+ apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;
+ apfConfig.multicastFilter = mMulticastFiltering;
// Get the Configuration for ApfFilter from Context
- final boolean filter802_3Frames =
+ apfConfig.ieee802_3Filter =
mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
- final int[] ethTypeBlackList = mContext.getResources().getIntArray(
- R.array.config_apfEthTypeBlackList);
-
- mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
- mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+ apfConfig.ethTypeBlackList =
+ mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
+ mApfFilter = ApfFilter.maybeCreate(apfConfig, mNetworkInterface, mCallback);
// TODO: investigate the effects of any multicast filtering racing/interfering with the
// rest of this IP configuration startup.
if (mApfFilter == null) {
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index b12cb32c..38981453 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -88,16 +88,6 @@ public class IpManager extends IpClient {
return this;
}
@Override
- public Builder withIPv6AddrGenModeEUI64() {
- super.withIPv6AddrGenModeEUI64();
- return this;
- }
- @Override
- public Builder withIPv6AddrGenModeStablePrivacy() {
- super.withIPv6AddrGenModeStablePrivacy();
- return this;
- }
- @Override
public Builder withNetwork(Network network) {
super.withNetwork(network);
return this;
diff --git a/android/net/metrics/ConnectStats.java b/android/net/metrics/ConnectStats.java
index 2495cab1..b320b755 100644
--- a/android/net/metrics/ConnectStats.java
+++ b/android/net/metrics/ConnectStats.java
@@ -119,7 +119,8 @@ public class ConnectStats {
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("ConnectStats(").append(netId).append(", ");
+ StringBuilder builder =
+ new StringBuilder("ConnectStats(").append("netId=").append(netId).append(", ");
for (int t : BitUtils.unpackBits(transports)) {
builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
}
diff --git a/android/net/metrics/DefaultNetworkEvent.java b/android/net/metrics/DefaultNetworkEvent.java
index eb61c153..8ff8e4f3 100644
--- a/android/net/metrics/DefaultNetworkEvent.java
+++ b/android/net/metrics/DefaultNetworkEvent.java
@@ -20,44 +20,72 @@ import static android.net.ConnectivityManager.NETID_UNSET;
import android.net.NetworkCapabilities;
+import com.android.internal.util.BitUtils;
+
+import java.util.StringJoiner;
+
/**
* An event recorded by ConnectivityService when there is a change in the default network.
* {@hide}
*/
public class DefaultNetworkEvent {
- // The ID of the network that has become the new default or NETID_UNSET if none.
+ // The creation time in milliseconds of this DefaultNetworkEvent.
+ public final long creationTimeMs;
+ // The network ID of the network or NETID_UNSET if none.
public int netId = NETID_UNSET;
- // The list of transport types of the new default network, for example TRANSPORT_WIFI, as
- // defined in NetworkCapabilities.java.
- public int[] transportTypes = new int[0];
- // The ID of the network that was the default before or NETID_UNSET if none.
- public int prevNetId = NETID_UNSET;
- // Whether the previous network had IPv4/IPv6 connectivity.
- public boolean prevIPv4;
- public boolean prevIPv6;
+ // The list of transport types, as defined in NetworkCapabilities.java.
+ public int transports;
+ // The list of transport types of the last previous default network.
+ public int previousTransports;
+ // Whether the network has IPv4/IPv6 connectivity.
+ public boolean ipv4;
+ public boolean ipv6;
+ // The initial network score when this network became the default network.
+ public int initialScore;
+ // The initial network score when this network stopped being the default network.
+ public int finalScore;
+ // The total duration in milliseconds this network was the default network.
+ public long durationMs;
+ // The total duration in milliseconds this network was the default network and was validated.
+ public long validatedMs;
+
+ public DefaultNetworkEvent(long timeMs) {
+ creationTimeMs = timeMs;
+ }
+
+ /** Update the durationMs of this DefaultNetworkEvent for the given current time. */
+ public void updateDuration(long timeMs) {
+ durationMs = timeMs - creationTimeMs;
+ }
@Override
public String toString() {
- String prevNetwork = String.valueOf(prevNetId);
- String newNetwork = String.valueOf(netId);
- if (prevNetId != 0) {
- prevNetwork += ":" + ipSupport();
+ StringJoiner j = new StringJoiner(", ", "DefaultNetworkEvent(", ")");
+ j.add("netId=" + netId);
+ for (int t : BitUtils.unpackBits(transports)) {
+ j.add(NetworkCapabilities.transportNameOf(t));
+ }
+ j.add("ip=" + ipSupport());
+ if (initialScore > 0) {
+ j.add("initial_score=" + initialScore);
}
- if (netId != 0) {
- newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+ if (finalScore > 0) {
+ j.add("final_score=" + finalScore);
}
- return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+ j.add(String.format("duration=%.0fs", durationMs / 1000.0));
+ j.add(String.format("validation=%4.1f%%", (validatedMs * 100.0) / durationMs));
+ return j.toString();
}
private String ipSupport() {
- if (prevIPv4 && prevIPv6) {
+ if (ipv4 && ipv6) {
return "IPv4v6";
}
- if (prevIPv6) {
+ if (ipv6) {
return "IPv6";
}
- if (prevIPv4) {
+ if (ipv4) {
return "IPv4";
}
return "NONE";
diff --git a/android/net/metrics/DnsEvent.java b/android/net/metrics/DnsEvent.java
index 81b098bb..5aa705b0 100644
--- a/android/net/metrics/DnsEvent.java
+++ b/android/net/metrics/DnsEvent.java
@@ -85,7 +85,8 @@ final public class DnsEvent {
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("DnsEvent(").append(netId).append(", ");
+ StringBuilder builder =
+ new StringBuilder("DnsEvent(").append("netId=").append(netId).append(", ");
for (int t : BitUtils.unpackBits(transports)) {
builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
}
diff --git a/android/net/metrics/NetworkEvent.java b/android/net/metrics/NetworkEvent.java
index 4df3bf09..1999e78d 100644
--- a/android/net/metrics/NetworkEvent.java
+++ b/android/net/metrics/NetworkEvent.java
@@ -60,29 +60,25 @@ public final class NetworkEvent implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
- public final int netId;
public final @EventType int eventType;
public final long durationMs;
- public NetworkEvent(int netId, @EventType int eventType, long durationMs) {
- this.netId = netId;
+ public NetworkEvent(@EventType int eventType, long durationMs) {
this.eventType = eventType;
this.durationMs = durationMs;
}
- public NetworkEvent(int netId, @EventType int eventType) {
- this(netId, eventType, 0);
+ public NetworkEvent(@EventType int eventType) {
+ this(eventType, 0);
}
private NetworkEvent(Parcel in) {
- netId = in.readInt();
eventType = in.readInt();
durationMs = in.readLong();
}
@Override
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(netId);
out.writeInt(eventType);
out.writeLong(durationMs);
}
@@ -105,8 +101,8 @@ public final class NetworkEvent implements Parcelable {
@Override
public String toString() {
- return String.format("NetworkEvent(%d, %s, %dms)",
- netId, Decoder.constants.get(eventType), durationMs);
+ return String.format("NetworkEvent(%s, %dms)",
+ Decoder.constants.get(eventType), durationMs);
}
final static class Decoder {
diff --git a/android/net/metrics/WakeupEvent.java b/android/net/metrics/WakeupEvent.java
index cbf3fc8c..8f1a5c42 100644
--- a/android/net/metrics/WakeupEvent.java
+++ b/android/net/metrics/WakeupEvent.java
@@ -16,6 +16,10 @@
package android.net.metrics;
+import android.net.MacAddress;
+
+import java.util.StringJoiner;
+
/**
* An event logged when NFLOG notifies userspace of a wakeup packet for
* watched interfaces.
@@ -23,12 +27,35 @@ package android.net.metrics;
*/
public class WakeupEvent {
public String iface;
- public long timestampMs;
public int uid;
+ public int ethertype;
+ public byte[] dstHwAddr;
+ public String srcIp;
+ public String dstIp;
+ public int ipNextHeader;
+ public int srcPort;
+ public int dstPort;
+ public long timestampMs;
@Override
public String toString() {
- return String.format("WakeupEvent(%tT.%tL, %s, uid: %d)",
- timestampMs, timestampMs, iface, uid);
+ StringJoiner j = new StringJoiner(", ", "WakeupEvent(", ")");
+ j.add(String.format("%tT.%tL", timestampMs, timestampMs));
+ j.add(iface);
+ j.add("uid: " + Integer.toString(uid));
+ j.add("eth=0x" + Integer.toHexString(ethertype));
+ j.add("dstHw=" + MacAddress.stringAddrFromByteAddr(dstHwAddr));
+ if (ipNextHeader > 0) {
+ j.add("ipNxtHdr=" + ipNextHeader);
+ j.add("srcIp=" + srcIp);
+ j.add("dstIp=" + dstIp);
+ if (srcPort > -1) {
+ j.add("srcPort=" + srcPort);
+ }
+ if (dstPort > -1) {
+ j.add("dstPort=" + dstPort);
+ }
+ }
+ return j.toString();
}
}
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
index 97e83f96..1ba97771 100644
--- a/android/net/metrics/WakeupStats.java
+++ b/android/net/metrics/WakeupStats.java
@@ -16,8 +16,12 @@
package android.net.metrics;
+import android.net.MacAddress;
import android.os.Process;
import android.os.SystemClock;
+import android.util.SparseIntArray;
+
+import java.util.StringJoiner;
/**
* An event logged per interface and that aggregates WakeupEvents for that interface.
@@ -38,6 +42,13 @@ public class WakeupStats {
public long noUidWakeups = 0;
public long durationSec = 0;
+ public long l2UnicastCount = 0;
+ public long l2MulticastCount = 0;
+ public long l2BroadcastCount = 0;
+
+ public final SparseIntArray ethertypes = new SparseIntArray();
+ public final SparseIntArray ipNextHeaders = new SparseIntArray();
+
public WakeupStats(String iface) {
this.iface = iface;
}
@@ -68,20 +79,56 @@ public class WakeupStats {
}
break;
}
+
+ switch (MacAddress.macAddressType(ev.dstHwAddr)) {
+ case UNICAST:
+ l2UnicastCount++;
+ break;
+ case MULTICAST:
+ l2MulticastCount++;
+ break;
+ case BROADCAST:
+ l2BroadcastCount++;
+ break;
+ default:
+ break;
+ }
+
+ increment(ethertypes, ev.ethertype);
+ if (ev.ipNextHeader >= 0) {
+ increment(ipNextHeaders, ev.ipNextHeader);
+ }
}
@Override
public String toString() {
updateDuration();
- return new StringBuilder()
- .append("WakeupStats(").append(iface)
- .append(", total: ").append(totalWakeups)
- .append(", root: ").append(rootWakeups)
- .append(", system: ").append(systemWakeups)
- .append(", apps: ").append(applicationWakeups)
- .append(", non-apps: ").append(nonApplicationWakeups)
- .append(", no uid: ").append(noUidWakeups)
- .append(", ").append(durationSec).append("s)")
- .toString();
+ StringJoiner j = new StringJoiner(", ", "WakeupStats(", ")");
+ j.add(iface);
+ j.add("" + durationSec + "s");
+ j.add("total: " + totalWakeups);
+ j.add("root: " + rootWakeups);
+ j.add("system: " + systemWakeups);
+ j.add("apps: " + applicationWakeups);
+ j.add("non-apps: " + nonApplicationWakeups);
+ j.add("no uid: " + noUidWakeups);
+ j.add(String.format("l2 unicast/multicast/broadcast: %d/%d/%d",
+ l2UnicastCount, l2MulticastCount, l2BroadcastCount));
+ for (int i = 0; i < ethertypes.size(); i++) {
+ int eth = ethertypes.keyAt(i);
+ int count = ethertypes.valueAt(i);
+ j.add(String.format("ethertype 0x%x: %d", eth, count));
+ }
+ for (int i = 0; i < ipNextHeaders.size(); i++) {
+ int proto = ipNextHeaders.keyAt(i);
+ int count = ipNextHeaders.valueAt(i);
+ j.add(String.format("ipNxtHdr %d: %d", proto, count));
+ }
+ return j.toString();
+ }
+
+ private static void increment(SparseIntArray counters, int key) {
+ int newcount = counters.get(key, 0) + 1;
+ counters.put(key, newcount);
}
}
diff --git a/android/net/wifi/WifiInfo.java b/android/net/wifi/WifiInfo.java
index a367b231..bf8fed1c 100644
--- a/android/net/wifi/WifiInfo.java
+++ b/android/net/wifi/WifiInfo.java
@@ -348,6 +348,9 @@ public class WifiInfo implements Parcelable {
* quotation marks. Otherwise, it is returned as a string of hex digits. The
* SSID may be &lt;unknown ssid&gt; if there is no network currently connected,
* or if the caller has insufficient permissions to access the SSID.
+ *
+ * Prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}, this method
+ * always returned the SSID with no quotes around it.
* @return the SSID
*/
public String getSSID() {
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index c2959d5e..66fabf33 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -16,6 +16,7 @@
package android.net.wifi;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -1127,7 +1128,7 @@ public class WifiManager {
*/
private int addOrUpdateNetwork(WifiConfiguration config) {
try {
- return mService.addOrUpdateNetwork(config);
+ return mService.addOrUpdateNetwork(config, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1148,7 +1149,7 @@ public class WifiManager {
*/
public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
try {
- if (!mService.addOrUpdatePasspointConfiguration(config)) {
+ if (!mService.addOrUpdatePasspointConfiguration(config, mContext.getOpPackageName())) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1165,7 +1166,7 @@ public class WifiManager {
*/
public void removePasspointConfiguration(String fqdn) {
try {
- if (!mService.removePasspointConfiguration(fqdn)) {
+ if (!mService.removePasspointConfiguration(fqdn, mContext.getOpPackageName())) {
throw new IllegalArgumentException();
}
} catch (RemoteException e) {
@@ -1251,7 +1252,7 @@ public class WifiManager {
*/
public boolean removeNetwork(int netId) {
try {
- return mService.removeNetwork(netId);
+ return mService.removeNetwork(netId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1297,7 +1298,7 @@ public class WifiManager {
boolean success;
try {
- success = mService.enableNetwork(netId, attemptConnect);
+ success = mService.enableNetwork(netId, attemptConnect, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1323,7 +1324,7 @@ public class WifiManager {
*/
public boolean disableNetwork(int netId) {
try {
- return mService.disableNetwork(netId);
+ return mService.disableNetwork(netId, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1336,7 +1337,7 @@ public class WifiManager {
*/
public boolean disconnect() {
try {
- mService.disconnect();
+ mService.disconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1351,7 +1352,7 @@ public class WifiManager {
*/
public boolean reconnect() {
try {
- mService.reconnect();
+ mService.reconnect(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1366,7 +1367,7 @@ public class WifiManager {
*/
public boolean reassociate() {
try {
- mService.reassociate();
+ mService.reassociate(mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1739,7 +1740,7 @@ public class WifiManager {
@Deprecated
public boolean saveConfiguration() {
try {
- return mService.saveConfiguration();
+ return mService.saveConfiguration(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1748,13 +1749,12 @@ public class WifiManager {
/**
* Set the country code.
* @param countryCode country code in ISO 3166 format.
- * @param persist {@code true} if this needs to be remembered
*
* @hide
*/
- public void setCountryCode(String country, boolean persist) {
+ public void setCountryCode(@NonNull String country) {
try {
- mService.setCountryCode(country, persist);
+ mService.setCountryCode(country);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1803,18 +1803,14 @@ public class WifiManager {
/**
* Enable or disable Wi-Fi.
- *
- * Note: This method will return false if wifi cannot be enabled (e.g., an incompatible mode
- * where the user has enabled tethering or Airplane Mode).
- *
- * Applications need to have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
- * permission to toggle wifi. Callers without the permissions will trigger a
- * {@link java.lang.SecurityException}.
+ * <p>
+ * Applications must have the {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+ * permission to toggle wifi.
*
* @param enabled {@code true} to enable, {@code false} to disable.
- * @return {@code true} if the operation succeeds (or if the existing state
- * is the same as the requested state). False if wifi cannot be toggled on/off when the
- * request is made.
+ * @return {@code false} if the request cannot be satisfied; {@code true} indicates that wifi is
+ * either already in the requested state, or in progress toward the requested state.
+ * @throws {@link java.lang.SecurityException} if the caller is missing required permissions.
*/
public boolean setWifiEnabled(boolean enabled) {
try {
@@ -2060,7 +2056,7 @@ public class WifiManager {
}
mLOHSCallbackProxy = null;
try {
- mService.stopLocalOnlyHotspot();
+ mService.stopLocalOnlyHotspot(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2179,7 +2175,7 @@ public class WifiManager {
@RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
public boolean setWifiApConfiguration(WifiConfiguration wifiConfig) {
try {
- mService.setWifiApConfiguration(wifiConfig);
+ mService.setWifiApConfiguration(wifiConfig, mContext.getOpPackageName());
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -2951,7 +2947,7 @@ public class WifiManager {
public void disableEphemeralNetwork(String SSID) {
if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
try {
- mService.disableEphemeralNetwork(SSID);
+ mService.disableEphemeralNetwork(SSID, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2990,7 +2986,7 @@ public class WifiManager {
*/
public Messenger getWifiServiceMessenger() {
try {
- return mService.getWifiServiceMessenger();
+ return mService.getWifiServiceMessenger(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3441,6 +3437,7 @@ public class WifiManager {
* Set wifi verbose log. Called from developer settings.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
public void enableVerboseLogging (int verbose) {
try {
mService.enableVerboseLogging(verbose);
@@ -3519,7 +3516,7 @@ public class WifiManager {
*/
public void factoryReset() {
try {
- mService.factoryReset();
+ mService.factoryReset(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3546,7 +3543,7 @@ public class WifiManager {
*/
public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
try {
- return mService.setEnableAutoJoinWhenAssociated(enabled);
+ return mService.setEnableAutoJoinWhenAssociated(enabled, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java
index 7405e82e..c8aea3c4 100644
--- a/android/net/wifi/rtt/RangingResultCallback.java
+++ b/android/net/wifi/rtt/RangingResultCallback.java
@@ -36,7 +36,7 @@ import java.util.List;
*/
public abstract class RangingResultCallback {
/** @hide */
- @IntDef({STATUS_CODE_FAIL})
+ @IntDef({STATUS_CODE_FAIL, STATUS_CODE_FAIL_RTT_NOT_AVAILABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface RangingOperationStatus {
}
@@ -47,6 +47,14 @@ public abstract class RangingResultCallback {
public static final int STATUS_CODE_FAIL = 1;
/**
+ * A failure code for the whole ranging request operation. Indicates that the request failed due
+ * to RTT not being available - e.g. Wi-Fi was disabled. Use the
+ * {@link WifiRttManager#isAvailable()} and {@link WifiRttManager#ACTION_WIFI_RTT_STATE_CHANGED}
+ * to track RTT availability.
+ */
+ public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2;
+
+ /**
* Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
* devices specified in the request was attempted.
*
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
index 435bb377..128d6c91 100644
--- a/android/net/wifi/rtt/WifiRttManager.java
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -3,15 +3,19 @@ package android.net.wifi.rtt;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static android.Manifest.permission.CHANGE_WIFI_STATE;
+import static android.Manifest.permission.LOCATION_HARDWARE;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.WorkSource;
import android.util.Log;
import java.util.List;
@@ -22,11 +26,18 @@ import java.util.List;
* <p>
* The devices which can be ranged include:
* <li>Access Points (APs)
+ * <li>Wi-Fi Aware peers
* <p>
* Ranging requests are triggered using
* {@link #startRanging(RangingRequest, RangingResultCallback, Handler)}. Results (in case of
* successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
* callback.
+ * <p>
+ * Wi-Fi RTT may not be usable at some points, e.g. when Wi-Fi is disabled. To validate that
+ * the functionality is available use the {@link #isAvailable()} function. To track
+ * changes in RTT usability register for the {@link #ACTION_WIFI_RTT_STATE_CHANGED}
+ * broadcast. Note that this broadcast is not sticky - you should register for it and then
+ * check the above API to avoid a race condition.
*
* @hide RTT_API
*/
@@ -38,6 +49,18 @@ public class WifiRttManager {
private final Context mContext;
private final IWifiRttManager mService;
+ /**
+ * Broadcast intent action to indicate that the state of Wi-Fi RTT availability has changed.
+ * Use the {@link #isAvailable()} to query the current status.
+ * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
+ * the broadcast to check the current state of Wi-Fi RTT.
+ * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+ * components will be launched.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_RTT_STATE_CHANGED =
+ "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
+
/** @hide */
public WifiRttManager(Context context, IWifiRttManager service) {
mContext = context;
@@ -45,6 +68,22 @@ public class WifiRttManager {
}
/**
+ * Returns the current status of RTT API: whether or not RTT is available. To track
+ * changes in the state of RTT API register for the
+ * {@link #ACTION_WIFI_RTT_STATE_CHANGED} broadcast.
+ *
+ * @return A boolean indicating whether the app can use the RTT API at this time (true) or
+ * not (false).
+ */
+ public boolean isAvailable() {
+ try {
+ return mService.isAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
* Results will be returned in the {@link RangingResultCallback} set of callbacks.
*
@@ -58,21 +97,63 @@ public class WifiRttManager {
@RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
public void startRanging(RangingRequest request, RangingResultCallback callback,
@Nullable Handler handler) {
+ startRanging(null, request, callback, handler);
+ }
+
+ /**
+ * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
+ * Results will be returned in the {@link RangingResultCallback} set of callbacks.
+ *
+ * @param workSource A mechanism to specify an alternative work-source for the request.
+ * @param request A request specifying a set of devices whose distance measurements are
+ * requested.
+ * @param callback A callback for the result of the ranging request.
+ * @param handler The Handler on whose thread to execute the callbacks of the {@code
+ * callback} object. If a null is provided then the application's main thread
+ * will be used.
+ *
+ * @hide (@SystemApi)
+ */
+ @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE,
+ ACCESS_WIFI_STATE})
+ public void startRanging(@Nullable WorkSource workSource, RangingRequest request,
+ RangingResultCallback callback, @Nullable Handler handler) {
if (VDBG) {
- Log.v(TAG, "startRanging: request=" + request + ", callback=" + callback + ", handler="
- + handler);
+ Log.v(TAG, "startRanging: workSource=" + workSource + ", request=" + request
+ + ", callback=" + callback + ", handler=" + handler);
}
Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
Binder binder = new Binder();
try {
- mService.startRanging(binder, mContext.getOpPackageName(), request,
+ mService.startRanging(binder, mContext.getOpPackageName(), workSource, request,
new RttCallbackProxy(looper, callback));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+ /**
+ * Cancel all ranging requests for the specified work sources. The requests have been requested
+ * using {@link #startRanging(WorkSource, RangingRequest, RangingResultCallback, Handler)}.
+ *
+ * @param workSource The work-sources of the requesters.
+ *
+ * @hide (@SystemApi)
+ */
+ @RequiresPermission(allOf = {LOCATION_HARDWARE})
+ public void cancelRanging(WorkSource workSource) {
+ if (VDBG) {
+ Log.v(TAG, "cancelRanging: workSource=" + workSource);
+ }
+
+ try {
+ mService.cancelRanging(workSource);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static class RttCallbackProxy extends IRttCallback.Stub {
private final Handler mHandler;
private final RangingResultCallback mCallback;
diff --git a/android/os/BatteryManager.java b/android/os/BatteryManager.java
index f715f507..6e0f70c1 100644
--- a/android/os/BatteryManager.java
+++ b/android/os/BatteryManager.java
@@ -19,6 +19,7 @@ package android.os;
import android.annotation.SystemService;
import android.content.Context;
import android.hardware.health.V1_0.Constants;
+
import com.android.internal.app.IBatteryStats;
/**
@@ -33,39 +34,39 @@ public class BatteryManager {
* integer containing the current status constant.
*/
public static final String EXTRA_STATUS = "status";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the current health constant.
*/
public static final String EXTRA_HEALTH = "health";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* boolean indicating whether a battery is present.
*/
public static final String EXTRA_PRESENT = "present";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer field containing the current battery level, from 0 to
* {@link #EXTRA_SCALE}.
*/
public static final String EXTRA_LEVEL = "level";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the maximum battery level.
*/
public static final String EXTRA_SCALE = "scale";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the resource ID of a small status bar icon
* indicating the current battery state.
*/
public static final String EXTRA_ICON_SMALL = "icon-small";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer indicating whether the device is plugged in to a power
@@ -73,19 +74,19 @@ public class BatteryManager {
* types of power sources.
*/
public static final String EXTRA_PLUGGED = "plugged";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the current battery voltage level.
*/
public static final String EXTRA_VOLTAGE = "voltage";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* integer containing the current battery temperature.
*/
public static final String EXTRA_TEMPERATURE = "temperature";
-
+
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
* String describing the technology of the current battery.
@@ -216,6 +217,7 @@ public class BatteryManager {
*/
public static final int BATTERY_PROPERTY_STATUS = 6;
+ private final Context mContext;
private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -223,6 +225,7 @@ public class BatteryManager {
* @removed Was previously made visible by accident.
*/
public BatteryManager() {
+ mContext = null;
mBatteryStats = IBatteryStats.Stub.asInterface(
ServiceManager.getService(BatteryStats.SERVICE_NAME));
mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(
@@ -230,8 +233,10 @@ public class BatteryManager {
}
/** {@hide} */
- public BatteryManager(IBatteryStats batteryStats,
+ public BatteryManager(Context context,
+ IBatteryStats batteryStats,
IBatteryPropertiesRegistrar batteryPropertiesRegistrar) {
+ mContext = context;
mBatteryStats = batteryStats;
mBatteryPropertiesRegistrar = batteryPropertiesRegistrar;
}
@@ -278,16 +283,23 @@ public class BatteryManager {
}
/**
- * Return the value of a battery property of integer type. If the
- * platform does not provide the property queried, this value will
- * be Integer.MIN_VALUE.
+ * Return the value of a battery property of integer type.
*
* @param id identifier of the requested property
*
- * @return the property value, or Integer.MIN_VALUE if not supported.
+ * @return the property value. If the property is not supported or there is any other error,
+ * return (a) 0 if {@code targetSdkVersion < VERSION_CODES.P} or (b) Integer.MIN_VALUE
+ * if {@code targetSdkVersion >= VERSION_CODES.P}.
*/
public int getIntProperty(int id) {
- return (int)queryProperty(id);
+ long value = queryProperty(id);
+ if (value == Long.MIN_VALUE && mContext != null
+ && mContext.getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.P) {
+ return Integer.MIN_VALUE;
+ }
+
+ return (int) value;
}
/**
diff --git a/android/os/BatteryStatsInternal.java b/android/os/BatteryStatsInternal.java
new file mode 100644
index 00000000..b0436eb5
--- /dev/null
+++ b/android/os/BatteryStatsInternal.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+/**
+ * Battery stats local system service interface. This is used to pass internal data out of
+ * BatteryStatsImpl.
+ *
+ * @hide Only for use within Android OS.
+ */
+public abstract class BatteryStatsInternal {
+ /**
+ * Returns the wifi interfaces.
+ */
+ public abstract String[] getWifiIfaces();
+
+ /**
+ * Returns the mobile data interfaces.
+ */
+ public abstract String[] getMobileIfaces();
+}
diff --git a/android/os/Binder.java b/android/os/Binder.java
index 2bfb0138..b5bcd02c 100644
--- a/android/os/Binder.java
+++ b/android/os/Binder.java
@@ -193,6 +193,19 @@ public class Binder implements IBinder {
}
/**
+ * Reset the given interface back to the default blocking behavior,
+ * reverting any changes made by {@link #allowBlocking(IBinder)}.
+ *
+ * @hide
+ */
+ public static IBinder defaultBlocking(IBinder binder) {
+ if (binder instanceof BinderProxy) {
+ ((BinderProxy) binder).mWarnOnBlocking = sWarnOnBlocking;
+ }
+ return binder;
+ }
+
+ /**
* Inherit the current {@link #allowBlocking(IBinder)} value from one given
* interface to another.
*
diff --git a/android/os/Binder_Delegate.java b/android/os/Binder_Delegate.java
new file mode 100644
index 00000000..03596dee
--- /dev/null
+++ b/android/os/Binder_Delegate.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate overriding selected methods of android.os.Binder
+ *
+ * Through the layoutlib_create tool, selected methods of Binder have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Binder_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Binder_Delegate> sManager =
+ new DelegateManager<>(Binder_Delegate.class);
+ private static long sFinalizer = -1;
+
+ @LayoutlibDelegate
+ /*package*/ static long getNativeBBinderHolder() {
+ return sManager.addNewDelegate(new Binder_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long getNativeFinalizer() {
+ synchronized (Binder_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
+ }
+}
diff --git a/android/os/ConfigUpdate.java b/android/os/ConfigUpdate.java
index 13968779..94a44ec3 100644
--- a/android/os/ConfigUpdate.java
+++ b/android/os/ConfigUpdate.java
@@ -68,13 +68,6 @@ public final class ConfigUpdate {
= "android.intent.action.UPDATE_CT_LOGS";
/**
- * Update system wide timezone data.
- * @hide
- */
- @SystemApi
- public static final String ACTION_UPDATE_TZDATA = "android.intent.action.UPDATE_TZDATA";
-
- /**
* Update language detection model file.
* @hide
*/
diff --git a/android/os/Debug.java b/android/os/Debug.java
index 017c2134..2acf36fe 100644
--- a/android/os/Debug.java
+++ b/android/os/Debug.java
@@ -16,14 +16,16 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Context;
import android.util.Log;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.TypedProperties;
-import dalvik.bytecode.OpcodeInfo;
import dalvik.system.VMDebug;
import org.apache.harmony.dalvik.ddmc.Chunk;
@@ -48,8 +50,6 @@ import java.util.HashMap;
import java.util.Map;
-
-
/**
* Provides various debugging methods for Android applications, including
* tracing and allocation counts.
@@ -1959,13 +1959,7 @@ public final class Debug
*/
@Deprecated
public static class InstructionCount {
- private static final int NUM_INSTR =
- OpcodeInfo.MAXIMUM_PACKED_VALUE + 1;
-
- private int[] mCounts;
-
public InstructionCount() {
- mCounts = new int[NUM_INSTR];
}
/**
@@ -1975,13 +1969,7 @@ public final class Debug
* @return true if counting was started
*/
public boolean resetAndStart() {
- try {
- VMDebug.startInstructionCounting();
- VMDebug.resetInstructionCount();
- } catch (UnsupportedOperationException uoe) {
- return false;
- }
- return true;
+ return false;
}
/**
@@ -1989,13 +1977,7 @@ public final class Debug
* counting process.
*/
public boolean collect() {
- try {
- VMDebug.stopInstructionCounting();
- VMDebug.getInstructionCount(mCounts);
- } catch (UnsupportedOperationException uoe) {
- return false;
- }
- return true;
+ return false;
}
/**
@@ -2003,13 +1985,7 @@ public final class Debug
* all threads).
*/
public int globalTotal() {
- int count = 0;
-
- for (int i = 0; i < NUM_INSTR; i++) {
- count += mCounts[i];
- }
-
- return count;
+ return 0;
}
/**
@@ -2017,15 +1993,7 @@ public final class Debug
* executed globally.
*/
public int globalMethodInvocations() {
- int count = 0;
-
- for (int i = 0; i < NUM_INSTR; i++) {
- if (OpcodeInfo.isInvoke(i)) {
- count += mCounts[i];
- }
- }
-
- return count;
+ return 0;
}
}
@@ -2382,4 +2350,24 @@ public final class Debug
public static String getCaller() {
return getCaller(Thread.currentThread().getStackTrace(), 0);
}
+
+ /**
+ * Attach a library as a jvmti agent to the current runtime.
+ *
+ * @param library library containing the agent
+ * @param options options passed to the agent
+ *
+ * @throws IOException If the agent could not be attached
+ */
+ public static void attachJvmtiAgent(@NonNull String library, @Nullable String options)
+ throws IOException {
+ Preconditions.checkNotNull(library);
+ Preconditions.checkArgument(!library.contains("="));
+
+ if (options == null) {
+ VMDebug.attachAgent(library);
+ } else {
+ VMDebug.attachAgent(library + "=" + options);
+ }
+ }
}
diff --git a/android/os/Environment.java b/android/os/Environment.java
index 5b0e5bbc..f977c1de 100644
--- a/android/os/Environment.java
+++ b/android/os/Environment.java
@@ -836,7 +836,6 @@ public class Environment {
* physically removed.
*/
public static boolean isExternalStorageRemovable() {
- if (isStorageDisabled()) return false;
final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageRemovable(externalDir);
}
@@ -875,7 +874,6 @@ public class Environment {
* boolean)
*/
public static boolean isExternalStorageEmulated() {
- if (isStorageDisabled()) return false;
final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageEmulated(externalDir);
}
@@ -951,9 +949,6 @@ public class Environment {
return cur;
}
- private static boolean isStorageDisabled() {
- return SystemProperties.getBoolean("config.disable_storage", false);
- }
/**
* If the given path exists on emulated external storage, return the
diff --git a/android/os/FileUtils.java b/android/os/FileUtils.java
index 56d6e0a6..7c53ec19 100644
--- a/android/os/FileUtils.java
+++ b/android/os/FileUtils.java
@@ -320,8 +320,17 @@ public class FileUtils {
* is {@code filename}.
*/
public static void bytesToFile(String filename, byte[] content) throws IOException {
- try (FileOutputStream fos = new FileOutputStream(filename)) {
- fos.write(content);
+ if (filename.startsWith("/proc/")) {
+ final int oldMask = StrictMode.allowThreadDiskWritesMask();
+ try (FileOutputStream fos = new FileOutputStream(filename)) {
+ fos.write(content);
+ } finally {
+ StrictMode.setThreadPolicyMask(oldMask);
+ }
+ } else {
+ try (FileOutputStream fos = new FileOutputStream(filename)) {
+ fos.write(content);
+ }
}
}
diff --git a/android/os/HidlSupport.java b/android/os/HidlSupport.java
index 7dec4d72..3544ea1e 100644
--- a/android/os/HidlSupport.java
+++ b/android/os/HidlSupport.java
@@ -156,4 +156,27 @@ public class HidlSupport {
// Should not reach here.
throw new UnsupportedOperationException();
}
+
+ /**
+ * Test that two interfaces are equal. This is the Java equivalent to C++
+ * interfacesEqual function.
+ * This essentially calls .equals on the internal binder objects (via Binder()).
+ * - If both interfaces are proxies, asBinder() returns a {@link HwRemoteBinder}
+ * object, and they are compared in {@link HwRemoteBinder#equals}.
+ * - If both interfaces are stubs, asBinder() returns the object itself. By default,
+ * auto-generated IFoo.Stub does not override equals(), but an implementation can
+ * optionally override it, and {@code interfacesEqual} will use it here.
+ */
+ public static boolean interfacesEqual(IHwInterface lft, Object rgt) {
+ if (lft == rgt) {
+ return true;
+ }
+ if (lft == null || rgt == null) {
+ return false;
+ }
+ if (!(rgt instanceof IHwInterface)) {
+ return false;
+ }
+ return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder());
+ }
}
diff --git a/android/os/HwBinder.java b/android/os/HwBinder.java
index 270e63f4..5e2a0815 100644
--- a/android/os/HwBinder.java
+++ b/android/os/HwBinder.java
@@ -16,10 +16,10 @@
package android.os;
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
import libcore.util.NativeAllocationRegistry;
+import java.util.NoSuchElementException;
+
/** @hide */
public abstract class HwBinder implements IHwBinder {
private static final String TAG = "HwBinder";
@@ -46,9 +46,16 @@ public abstract class HwBinder implements IHwBinder {
public native final void registerService(String serviceName)
throws RemoteException;
- public static native final IHwBinder getService(
+ public static final IHwBinder getService(
String iface,
String serviceName)
+ throws RemoteException, NoSuchElementException {
+ return getService(iface, serviceName, false /* retry */);
+ }
+ public static native final IHwBinder getService(
+ String iface,
+ String serviceName,
+ boolean retry)
throws RemoteException, NoSuchElementException;
public static native final void configureRpcThreadpool(
diff --git a/android/os/HwBlob.java b/android/os/HwBlob.java
index 88226f0a..5e9b9ae3 100644
--- a/android/os/HwBlob.java
+++ b/android/os/HwBlob.java
@@ -43,6 +43,18 @@ public class HwBlob {
public native final double getDouble(long offset);
public native final String getString(long offset);
+ /**
+ The copyTo... methods copy the blob's data, starting from the given
+ byte offset, into the array. A total of "size" _elements_ are copied.
+ */
+ public native final void copyToBoolArray(long offset, boolean[] array, int size);
+ public native final void copyToInt8Array(long offset, byte[] array, int size);
+ public native final void copyToInt16Array(long offset, short[] array, int size);
+ public native final void copyToInt32Array(long offset, int[] array, int size);
+ public native final void copyToInt64Array(long offset, long[] array, int size);
+ public native final void copyToFloatArray(long offset, float[] array, int size);
+ public native final void copyToDoubleArray(long offset, double[] array, int size);
+
public native final void putBool(long offset, boolean x);
public native final void putInt8(long offset, byte x);
public native final void putInt16(long offset, short x);
@@ -52,6 +64,14 @@ public class HwBlob {
public native final void putDouble(long offset, double x);
public native final void putString(long offset, String x);
+ public native final void putBoolArray(long offset, boolean[] x);
+ public native final void putInt8Array(long offset, byte[] x);
+ public native final void putInt16Array(long offset, short[] x);
+ public native final void putInt32Array(long offset, int[] x);
+ public native final void putInt64Array(long offset, long[] x);
+ public native final void putFloatArray(long offset, float[] x);
+ public native final void putDoubleArray(long offset, double[] x);
+
public native final void putBlob(long offset, HwBlob blob);
public native final long handle();
diff --git a/android/os/HwRemoteBinder.java b/android/os/HwRemoteBinder.java
index 2f89ce62..a07e42c7 100644
--- a/android/os/HwRemoteBinder.java
+++ b/android/os/HwRemoteBinder.java
@@ -63,4 +63,9 @@ public class HwRemoteBinder implements IHwBinder {
}
private long mNativeContext;
+
+ @Override
+ public final native boolean equals(Object other);
+ @Override
+ public final native int hashCode();
}
diff --git a/android/os/LocaleList.java b/android/os/LocaleList.java
index 2dc3bebb..ca9cbec9 100644
--- a/android/os/LocaleList.java
+++ b/android/os/LocaleList.java
@@ -295,7 +295,11 @@ public final class LocaleList implements Parcelable {
return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale);
}
- private static boolean isPseudoLocale(Locale locale) {
+ /**
+ * Returns true if locale is a pseudo-locale, false otherwise.
+ * {@hide}
+ */
+ public static boolean isPseudoLocale(Locale locale) {
return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
}
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
index c2cf3967..10adb5a6 100644
--- a/android/os/Parcel.java
+++ b/android/os/Parcel.java
@@ -2020,8 +2020,6 @@ public final class Parcel {
@Deprecated
static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
- static native void clearFileDescriptor(FileDescriptor desc);
-
/**
* Read a byte value from the parcel at the current dataPosition().
*/
diff --git a/android/os/ParcelFileDescriptor.java b/android/os/ParcelFileDescriptor.java
index 7f588adb..7556f092 100644
--- a/android/os/ParcelFileDescriptor.java
+++ b/android/os/ParcelFileDescriptor.java
@@ -683,7 +683,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
throw new IllegalStateException("Already closed");
}
final int fd = getFd();
- Parcel.clearFileDescriptor(mFd);
+ mFd.setInt$(-1);
writeCommStatusAndClose(Status.DETACHED, null);
mClosed = true;
mGuard.close();
diff --git a/android/os/PowerManager.java b/android/os/PowerManager.java
index 7f4dee6e..dd4825ef 100644
--- a/android/os/PowerManager.java
+++ b/android/os/PowerManager.java
@@ -513,6 +513,53 @@ public final class PowerManager {
*/
public static final int SHUTDOWN_REASON_BATTERY_THERMAL = 6;
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ServiceType.GPS,
+ ServiceType.VIBRATION,
+ ServiceType.ANIMATION,
+ ServiceType.FULL_BACKUP,
+ ServiceType.KEYVALUE_BACKUP,
+ ServiceType.NETWORK_FIREWALL,
+ ServiceType.SCREEN_BRIGHTNESS,
+ ServiceType.SOUND,
+ ServiceType.BATTERY_STATS,
+ ServiceType.DATA_SAVER,
+ ServiceType.FORCE_ALL_APPS_STANDBY_JOBS,
+ ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS,
+ ServiceType.OPTIONAL_SENSORS,
+ })
+ public @interface ServiceType {
+ int NULL = 0;
+ int GPS = 1;
+ int VIBRATION = 2;
+ int ANIMATION = 3;
+ int FULL_BACKUP = 4;
+ int KEYVALUE_BACKUP = 5;
+ int NETWORK_FIREWALL = 6;
+ int SCREEN_BRIGHTNESS = 7;
+ int SOUND = 8;
+ int BATTERY_STATS = 9;
+ int DATA_SAVER = 10;
+
+ /**
+ * Whether the job scheduler should force app standby on all apps on battery saver or not.
+ */
+ int FORCE_ALL_APPS_STANDBY_JOBS = 11;
+
+ /**
+ * Whether the alarm manager should force app standby on all apps on battery saver or not.
+ */
+ int FORCE_ALL_APPS_STANDBY_ALARMS = 12;
+
+ /**
+ * Whether to disable non-essential sensors. (e.g. edge sensors.)
+ */
+ int OPTIONAL_SENSORS = 13;
+ }
+
final Context mContext;
final IPowerManager mService;
final Handler mHandler;
@@ -1055,15 +1102,14 @@ public final class PowerManager {
/**
* Get data about the battery saver mode for a specific service
- * @param serviceType unique key for the service, one of
- * {@link com.android.server.power.BatterySaverPolicy.ServiceType}
+ * @param serviceType unique key for the service, one of {@link ServiceType}
* @return Battery saver state data.
*
* @hide
* @see com.android.server.power.BatterySaverPolicy
* @see PowerSaveState
*/
- public PowerSaveState getPowerSaveState(int serviceType) {
+ public PowerSaveState getPowerSaveState(@ServiceType int serviceType) {
try {
return mService.getPowerSaveState(serviceType);
} catch (RemoteException e) {
diff --git a/android/os/PowerManagerInternal.java b/android/os/PowerManagerInternal.java
index a01b8ed2..77ac2651 100644
--- a/android/os/PowerManagerInternal.java
+++ b/android/os/PowerManagerInternal.java
@@ -18,6 +18,8 @@ package android.os;
import android.view.Display;
+import java.util.function.Consumer;
+
/**
* Power manager local system service interface.
*
@@ -125,6 +127,23 @@ public abstract class PowerManagerInternal {
public abstract void registerLowPowerModeObserver(LowPowerModeListener listener);
+ /**
+ * Same as {@link #registerLowPowerModeObserver} but can take a lambda.
+ */
+ public void registerLowPowerModeObserver(int serviceType, Consumer<PowerSaveState> listener) {
+ registerLowPowerModeObserver(new LowPowerModeListener() {
+ @Override
+ public int getServiceType() {
+ return serviceType;
+ }
+
+ @Override
+ public void onLowPowerModeChanged(PowerSaveState state) {
+ listener.accept(state);
+ }
+ });
+ }
+
public interface LowPowerModeListener {
int getServiceType();
void onLowPowerModeChanged(PowerSaveState state);
diff --git a/android/os/PowerSaveState.java b/android/os/PowerSaveState.java
index 7058a1dc..de1128df 100644
--- a/android/os/PowerSaveState.java
+++ b/android/os/PowerSaveState.java
@@ -27,7 +27,7 @@ public class PowerSaveState implements Parcelable {
/**
* Whether we should enable battery saver for this service.
*
- * @see com.android.server.power.BatterySaverPolicy.ServiceType
+ * @see com.android.server.power.BatterySaverPolicy
*/
public final boolean batterySaverEnabled;
/**
diff --git a/android/os/RemoteCallbackList.java b/android/os/RemoteCallbackList.java
index 2281fb6d..b9b9a18e 100644
--- a/android/os/RemoteCallbackList.java
+++ b/android/os/RemoteCallbackList.java
@@ -19,6 +19,7 @@ package android.os;
import android.util.ArrayMap;
import android.util.Slog;
+import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -399,6 +400,13 @@ public class RemoteCallbackList<E extends IInterface> {
}
}
+ /** @hide */
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size());
+ pw.print(prefix); pw.print("killed: "); pw.println(mKilled);
+ pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount);
+ }
+
private void logExcessiveCallbacks() {
final long size = mCallbacks.size();
final long TOO_MANY = 3000;
diff --git a/android/os/ShellCallback.java b/android/os/ShellCallback.java
index e7fe697f..ad9fbfbf 100644
--- a/android/os/ShellCallback.java
+++ b/android/os/ShellCallback.java
@@ -35,8 +35,9 @@ public class ShellCallback implements Parcelable {
IShellCallback mShellCallback;
class MyShellCallback extends IShellCallback.Stub {
- public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
- return onOpenOutputFile(path, seLinuxContext);
+ public ParcelFileDescriptor openFile(String path, String seLinuxContext,
+ String mode) {
+ return onOpenFile(path, seLinuxContext, mode);
}
}
@@ -48,23 +49,27 @@ public class ShellCallback implements Parcelable {
}
/**
- * Ask the shell to open a file for writing. This will truncate the file if it
- * already exists. It will create the file if it doesn't exist.
+ * Ask the shell to open a file. If opening for writing, will truncate the file if it
+ * already exists and will create the file if it doesn't exist.
* @param path Path of the file to be opened/created.
* @param seLinuxContext Optional SELinux context that must be allowed to have
* access to the file; if null, nothing is required.
+ * @param mode Mode to open file in: "r" for input/reading an existing file,
+ * "r+" for reading/writing an existing file, "w" for output/writing a new file (either
+ * creating or truncating an existing one), "w+" for reading/writing a new file (either
+ * creating or truncating an existing one).
*/
- public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
- if (DEBUG) Log.d(TAG, "openOutputFile " + this + ": mLocal=" + mLocal
+ public ParcelFileDescriptor openFile(String path, String seLinuxContext, String mode) {
+ if (DEBUG) Log.d(TAG, "openFile " + this + " mode=" + mode + ": mLocal=" + mLocal
+ " mShellCallback=" + mShellCallback);
if (mLocal) {
- return onOpenOutputFile(path, seLinuxContext);
+ return onOpenFile(path, seLinuxContext, mode);
}
if (mShellCallback != null) {
try {
- return mShellCallback.openOutputFile(path, seLinuxContext);
+ return mShellCallback.openFile(path, seLinuxContext, mode);
} catch (RemoteException e) {
Log.w(TAG, "Failure opening " + path, e);
}
@@ -72,7 +77,7 @@ public class ShellCallback implements Parcelable {
return null;
}
- public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+ public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext, String mode) {
return null;
}
diff --git a/android/os/ShellCommand.java b/android/os/ShellCommand.java
index 6223235e..d75219fd 100644
--- a/android/os/ShellCommand.java
+++ b/android/os/ShellCommand.java
@@ -226,10 +226,10 @@ public abstract class ShellCommand {
* Helper for just system services to ask the shell to open an output file.
* @hide
*/
- public ParcelFileDescriptor openOutputFileForSystem(String path) {
+ public ParcelFileDescriptor openFileForSystem(String path, String mode) {
try {
- ParcelFileDescriptor pfd = getShellCallback().openOutputFile(path,
- "u:r:system_server:s0");
+ ParcelFileDescriptor pfd = getShellCallback().openFile(path,
+ "u:r:system_server:s0", mode);
if (pfd != null) {
return pfd;
}
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index ee3e5bc9..f90604ab 100644
--- a/android/os/StrictMode.java
+++ b/android/os/StrictMode.java
@@ -16,6 +16,7 @@
package android.os;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
@@ -25,8 +26,26 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.net.TrafficStats;
import android.net.Uri;
+import android.os.strictmode.CleartextNetworkViolation;
+import android.os.strictmode.ContentUriWithoutPermissionViolation;
+import android.os.strictmode.CustomViolation;
+import android.os.strictmode.DiskReadViolation;
+import android.os.strictmode.DiskWriteViolation;
+import android.os.strictmode.FileUriExposedViolation;
+import android.os.strictmode.InstanceCountViolation;
+import android.os.strictmode.IntentReceiverLeakedViolation;
+import android.os.strictmode.LeakedClosableViolation;
+import android.os.strictmode.NetworkViolation;
+import android.os.strictmode.ResourceMismatchViolation;
+import android.os.strictmode.ServiceConnectionLeakedViolation;
+import android.os.strictmode.SqliteObjectLeakedViolation;
+import android.os.strictmode.UnbufferedIoViolation;
+import android.os.strictmode.UntaggedSocketViolation;
+import android.os.strictmode.Violation;
+import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Printer;
@@ -35,6 +54,7 @@ import android.util.Slog;
import android.view.IWindowManager;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.os.RuntimeInit;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -53,6 +73,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -140,6 +162,15 @@ public final class StrictMode {
*/
private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
+ /**
+ * Quick feature-flag that can be used to disable the defaults provided by {@link
+ * #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}.
+ */
+ private static final boolean DISABLE = false;
+
+ // Only apply VM penalties for the same violation at this interval.
+ private static final long MIN_VM_INTERVAL_MS = 1000;
+
// Only log a duplicate stack trace to the logs every second.
private static final long MIN_LOG_INTERVAL_MS = 1000;
@@ -156,36 +187,30 @@ public final class StrictMode {
// Byte 1: Thread-policy
/** @hide */
- @TestApi
- public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
+ @TestApi public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
/** @hide */
- @TestApi
- public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
+ @TestApi public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
/** @hide */
- @TestApi
- public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
+ @TestApi public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
/**
* For StrictMode.noteSlowCall()
*
* @hide
*/
- @TestApi
- public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
+ @TestApi public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
/**
* For StrictMode.noteResourceMismatch()
*
* @hide
*/
- @TestApi
- public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
+ @TestApi public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
/** @hide */
- @TestApi
- public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
+ @TestApi public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
private static final int ALL_THREAD_DETECT_BITS =
DETECT_DISK_WRITE
@@ -202,48 +227,40 @@ public final class StrictMode {
*
* @hide
*/
- @TestApi
- public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
/**
* Note, a "VM_" bit, not thread.
*
* @hide
*/
- @TestApi
- public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
/**
* Note, a "VM_" bit, not thread.
*
* @hide
*/
- @TestApi
- public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
+ @TestApi public static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
/** @hide */
@TestApi
public static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
/** @hide */
- @TestApi
- public static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
+ @TestApi public static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
private static final int ALL_VM_DETECT_BITS =
DETECT_VM_CURSOR_LEAKS
@@ -354,15 +371,33 @@ public final class StrictMode {
} else {
msg = "StrictMode policy violation:";
}
- if (info.hasStackTrace()) {
- Log.d(TAG, msg + " " + info.getStackTrace());
- } else {
- Log.d(TAG, msg + " missing stack trace!");
- }
+ Log.d(TAG, msg + " " + info.getStackTrace());
};
private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
+ private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
+ new ThreadLocal<>();
+ private static final ThreadLocal<Executor> sThreadViolationExecutor = new ThreadLocal<>();
+
+ /**
+ * When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+ * provided executor when a Thread violation occurs.
+ */
+ public interface OnThreadViolationListener {
+ /** Called on a thread policy violation. */
+ void onThreadViolation(Violation v);
+ }
+
+ /**
+ * When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+ * provided executor when a VM violation occurs.
+ */
+ public interface OnVmViolationListener {
+ /** Called on a VM policy violation. */
+ void onVmViolation(Violation v);
+ }
+
/** {@hide} */
@TestApi
public static void setViolationLogger(ViolationLogger listener) {
@@ -392,12 +427,16 @@ public final class StrictMode {
*/
public static final class ThreadPolicy {
/** The default, lax policy which doesn't catch anything. */
- public static final ThreadPolicy LAX = new ThreadPolicy(0);
+ public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);
final int mask;
+ final OnThreadViolationListener mListener;
+ final Executor mCallbackExecutor;
- private ThreadPolicy(int mask) {
+ private ThreadPolicy(int mask, OnThreadViolationListener listener, Executor executor) {
this.mask = mask;
+ mListener = listener;
+ mCallbackExecutor = executor;
}
@Override
@@ -425,6 +464,8 @@ public final class StrictMode {
*/
public static final class Builder {
private int mMask = 0;
+ private OnThreadViolationListener mListener;
+ private Executor mExecutor;
/**
* Create a Builder that detects nothing and has no violations. (but note that {@link
@@ -590,6 +631,20 @@ public final class StrictMode {
return enable(PENALTY_DROPBOX);
}
+ /**
+ * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+ * executor every violation.
+ */
+ public Builder penaltyListener(
+ @NonNull OnThreadViolationListener listener, @NonNull Executor executor) {
+ if (executor == null) {
+ throw new NullPointerException("executor must not be null");
+ }
+ mListener = listener;
+ mExecutor = executor;
+ return this;
+ }
+
private Builder enable(int bit) {
mMask |= bit;
return this;
@@ -609,7 +664,8 @@ public final class StrictMode {
public ThreadPolicy build() {
// If there are detection bits set but no violation bits
// set, enable simple logging.
- if (mMask != 0
+ if (mListener == null
+ && mMask != 0
&& (mMask
& (PENALTY_DEATH
| PENALTY_LOG
@@ -618,7 +674,7 @@ public final class StrictMode {
== 0) {
penaltyLog();
}
- return new ThreadPolicy(mMask);
+ return new ThreadPolicy(mMask, mListener, mExecutor);
}
}
}
@@ -630,19 +686,27 @@ public final class StrictMode {
*/
public static final class VmPolicy {
/** The default, lax policy which doesn't catch anything. */
- public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP);
+ public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);
final int mask;
+ final OnVmViolationListener mListener;
+ final Executor mCallbackExecutor;
// Map from class to max number of allowed instances in memory.
final HashMap<Class, Integer> classInstanceLimit;
- private VmPolicy(int mask, HashMap<Class, Integer> classInstanceLimit) {
+ private VmPolicy(
+ int mask,
+ HashMap<Class, Integer> classInstanceLimit,
+ OnVmViolationListener listener,
+ Executor executor) {
if (classInstanceLimit == null) {
throw new NullPointerException("classInstanceLimit == null");
}
this.mask = mask;
this.classInstanceLimit = classInstanceLimit;
+ mListener = listener;
+ mCallbackExecutor = executor;
}
@Override
@@ -670,6 +734,8 @@ public final class StrictMode {
*/
public static final class Builder {
private int mMask;
+ private OnVmViolationListener mListener;
+ private Executor mExecutor;
private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
@@ -683,6 +749,8 @@ public final class StrictMode {
mMask = base.mask;
mClassInstanceLimitNeedCow = true;
mClassInstanceLimit = base.classInstanceLimit;
+ mListener = base.mListener;
+ mExecutor = base.mCallbackExecutor;
}
/**
@@ -714,6 +782,11 @@ public final class StrictMode {
return enable(DETECT_VM_ACTIVITY_LEAKS);
}
+ /** @hide */
+ public Builder permitActivityLeaks() {
+ return disable(DETECT_VM_ACTIVITY_LEAKS);
+ }
+
/**
* Detect everything that's potentially suspect.
*
@@ -847,6 +920,11 @@ public final class StrictMode {
return enable(DETECT_VM_UNTAGGED_SOCKET);
}
+ /** @hide */
+ public Builder permitUntaggedSockets() {
+ return disable(DETECT_VM_UNTAGGED_SOCKET);
+ }
+
/**
* Crashes the whole process on violation. This penalty runs at the end of all enabled
* penalties so you'll still get your logging or other violations before the process
@@ -889,6 +967,19 @@ public final class StrictMode {
return enable(PENALTY_DROPBOX);
}
+ /**
+ * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+ */
+ public Builder penaltyListener(
+ @NonNull OnVmViolationListener listener, @NonNull Executor executor) {
+ if (executor == null) {
+ throw new NullPointerException("executor must not be null");
+ }
+ mListener = listener;
+ mExecutor = executor;
+ return this;
+ }
+
private Builder enable(int bit) {
mMask |= bit;
return this;
@@ -908,7 +999,8 @@ public final class StrictMode {
public VmPolicy build() {
// If there are detection bits set but no violation bits
// set, enable simple logging.
- if (mMask != 0
+ if (mListener == null
+ && mMask != 0
&& (mMask
& (PENALTY_DEATH
| PENALTY_LOG
@@ -919,7 +1011,9 @@ public final class StrictMode {
}
return new VmPolicy(
mMask,
- mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP);
+ mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP,
+ mListener,
+ mExecutor);
}
}
}
@@ -952,9 +1046,12 @@ public final class StrictMode {
*/
public static void setThreadPolicy(final ThreadPolicy policy) {
setThreadPolicyMask(policy.mask);
+ sThreadViolationListener.set(policy.mListener);
+ sThreadViolationExecutor.set(policy.mCallbackExecutor);
}
- private static void setThreadPolicyMask(final int policyMask) {
+ /** @hide */
+ public static void setThreadPolicyMask(final int policyMask) {
// In addition to the Java-level thread-local in Dalvik's
// BlockGuard, we also need to keep a native thread-local in
// Binder in order to propagate the value across Binder calls,
@@ -991,55 +1088,6 @@ public final class StrictMode {
CloseGuard.setEnabled(enabled);
}
- /** @hide */
- public static class StrictModeViolation extends BlockGuard.BlockGuardPolicyException {
- public StrictModeViolation(int policyState, int policyViolated, String message) {
- super(policyState, policyViolated, message);
- }
- }
-
- /** @hide */
- public static class StrictModeNetworkViolation extends StrictModeViolation {
- public StrictModeNetworkViolation(int policyMask) {
- super(policyMask, DETECT_NETWORK, null);
- }
- }
-
- /** @hide */
- private static class StrictModeDiskReadViolation extends StrictModeViolation {
- public StrictModeDiskReadViolation(int policyMask) {
- super(policyMask, DETECT_DISK_READ, null);
- }
- }
-
- /** @hide */
- private static class StrictModeDiskWriteViolation extends StrictModeViolation {
- public StrictModeDiskWriteViolation(int policyMask) {
- super(policyMask, DETECT_DISK_WRITE, null);
- }
- }
-
- /** @hide */
- private static class StrictModeCustomViolation extends StrictModeViolation {
- public StrictModeCustomViolation(int policyMask, String name) {
- super(policyMask, DETECT_CUSTOM, name);
- }
- }
-
- /** @hide */
- private static class StrictModeResourceMismatchViolation extends StrictModeViolation {
- public StrictModeResourceMismatchViolation(int policyMask, Object tag) {
- super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null);
- }
- }
-
- /** @hide */
- private static class StrictModeUnbufferedIOViolation extends StrictModeViolation {
- public StrictModeUnbufferedIOViolation(int policyMask) {
- super(policyMask, DETECT_UNBUFFERED_IO, null);
- }
- }
-
/**
* Returns the bitmask of the current thread's policy.
*
@@ -1056,7 +1104,10 @@ public final class StrictMode {
// introduce VmPolicy cleanly) but this isn't particularly
// optimal for users who might call this method often. This
// should be in a thread-local and not allocate on each call.
- return new ThreadPolicy(getThreadPolicyMask());
+ return new ThreadPolicy(
+ getThreadPolicyMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
}
/**
@@ -1069,12 +1120,20 @@ public final class StrictMode {
* end of a block
*/
public static ThreadPolicy allowThreadDiskWrites() {
+ return new ThreadPolicy(
+ allowThreadDiskWritesMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
+ }
+
+ /** @hide */
+ public static int allowThreadDiskWritesMask() {
int oldPolicyMask = getThreadPolicyMask();
int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_WRITE | DETECT_DISK_READ);
if (newPolicyMask != oldPolicyMask) {
setThreadPolicyMask(newPolicyMask);
}
- return new ThreadPolicy(oldPolicyMask);
+ return oldPolicyMask;
}
/**
@@ -1085,31 +1144,66 @@ public final class StrictMode {
* @return the old policy, to be passed to setThreadPolicy to restore the policy.
*/
public static ThreadPolicy allowThreadDiskReads() {
+ return new ThreadPolicy(
+ allowThreadDiskReadsMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
+ }
+
+ /** @hide */
+ public static int allowThreadDiskReadsMask() {
int oldPolicyMask = getThreadPolicyMask();
int newPolicyMask = oldPolicyMask & ~(DETECT_DISK_READ);
if (newPolicyMask != oldPolicyMask) {
setThreadPolicyMask(newPolicyMask);
}
- return new ThreadPolicy(oldPolicyMask);
+ return oldPolicyMask;
}
- // We don't want to flash the screen red in the system server
- // process, nor do we want to modify all the call sites of
- // conditionallyEnableDebugLogging() in the system server,
- // so instead we use this to determine if we are the system server.
- private static boolean amTheSystemServerProcess() {
- // Fast path. Most apps don't have the system server's UID.
- if (Process.myUid() != Process.SYSTEM_UID) {
- return false;
- }
+ private static ThreadPolicy allowThreadViolations() {
+ ThreadPolicy oldPolicy = getThreadPolicy();
+ setThreadPolicyMask(0);
+ return oldPolicy;
+ }
- // The settings app, though, has the system server's UID so
- // look up our stack to see if we came from the system server.
- Throwable stack = new Throwable();
- stack.fillInStackTrace();
- for (StackTraceElement ste : stack.getStackTrace()) {
- String clsName = ste.getClassName();
- if (clsName != null && clsName.startsWith("com.android.server.")) {
+ private static VmPolicy allowVmViolations() {
+ VmPolicy oldPolicy = getVmPolicy();
+ sVmPolicy = VmPolicy.LAX;
+ return oldPolicy;
+ }
+
+ /**
+ * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+ * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
+ * chase any {@link StrictMode} regressions by enabling detection when running on {@link
+ * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
+ *
+ * <p>Unbundled apps included in the system image are expected to detect and triage their own
+ * {@link StrictMode} issues separate from the OS release process, which is why we don't enable
+ * them here.
+ *
+ * @hide
+ */
+ public static boolean isBundledSystemApp(ApplicationInfo ai) {
+ if (ai == null || ai.packageName == null) {
+ // Probably system server
+ return true;
+ } else if (ai.isSystemApp()) {
+ // Ignore unbundled apps living in the wrong namespace
+ if (ai.packageName.equals("com.android.vending")
+ || ai.packageName.equals("com.android.chrome")) {
+ return false;
+ }
+
+ // Ignore bundled apps that are way too spammy
+ // STOPSHIP: burn this list down to zero
+ if (ai.packageName.equals("com.android.phone")) {
+ return false;
+ }
+
+ if (ai.packageName.equals("android")
+ || ai.packageName.startsWith("android.")
+ || ai.packageName.startsWith("com.android.")) {
return true;
}
}
@@ -1117,81 +1211,81 @@ public final class StrictMode {
}
/**
- * Enable DropBox logging for debug phone builds.
+ * Initialize default {@link ThreadPolicy} for the current thread.
*
* @hide
*/
- public static boolean conditionallyEnableDebugLogging() {
- boolean doFlashes =
- SystemProperties.getBoolean(VISUAL_PROPERTY, false) && !amTheSystemServerProcess();
- final boolean suppress = SystemProperties.getBoolean(DISABLE_PROPERTY, false);
-
- // For debug builds, log event loop stalls to dropbox for analysis.
- // Similar logic also appears in ActivityThread.java for system apps.
- if (!doFlashes && (Build.IS_USER || suppress)) {
- setCloseGuardEnabled(false);
- return false;
+ public static void initThreadDefaults(ApplicationInfo ai) {
+ final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
+ final int targetSdkVersion =
+ (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ // Starting in HC, we don't allow network usage on the main thread
+ if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
+ builder.detectNetwork();
+ builder.penaltyDeathOnNetwork();
+ }
+
+ if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
+ // Detect nothing extra
+ } else if (Build.IS_USERDEBUG) {
+ // Detect everything in bundled apps
+ if (isBundledSystemApp(ai)) {
+ builder.detectAll();
+ builder.penaltyDropBox();
+ if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) {
+ builder.penaltyFlashScreen();
+ }
+ }
+ } else if (Build.IS_ENG) {
+ // Detect everything in bundled apps
+ if (isBundledSystemApp(ai)) {
+ builder.detectAll();
+ builder.penaltyDropBox();
+ builder.penaltyLog();
+ builder.penaltyFlashScreen();
+ }
}
- // Eng builds have flashes on all the time. The suppression property
- // overrides this, so we force the behavior only after the short-circuit
- // check above.
- if (Build.IS_ENG) {
- doFlashes = true;
- }
+ setThreadPolicy(builder.build());
+ }
- // Thread policy controls BlockGuard.
- int threadPolicyMask =
- StrictMode.DETECT_DISK_WRITE
- | StrictMode.DETECT_DISK_READ
- | StrictMode.DETECT_NETWORK;
+ /**
+ * Initialize default {@link VmPolicy} for the current VM.
+ *
+ * @hide
+ */
+ public static void initVmDefaults(ApplicationInfo ai) {
+ final VmPolicy.Builder builder = new VmPolicy.Builder();
+ final int targetSdkVersion =
+ (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
- if (!Build.IS_USER) {
- threadPolicyMask |= StrictMode.PENALTY_DROPBOX;
- }
- if (doFlashes) {
- threadPolicyMask |= StrictMode.PENALTY_FLASH;
+ // Starting in N, we don't allow file:// Uri exposure
+ if (targetSdkVersion >= Build.VERSION_CODES.N) {
+ builder.detectFileUriExposure();
+ builder.penaltyDeathOnFileUriExposure();
}
- StrictMode.setThreadPolicyMask(threadPolicyMask);
-
- // VM Policy controls CloseGuard, detection of Activity leaks,
- // and instance counting.
- if (Build.IS_USER) {
- setCloseGuardEnabled(false);
- } else {
- VmPolicy.Builder policyBuilder = new VmPolicy.Builder().detectAll();
- if (!Build.IS_ENG) {
- // Activity leak detection causes too much slowdown for userdebug because of the
- // GCs.
- policyBuilder = policyBuilder.disable(DETECT_VM_ACTIVITY_LEAKS);
- }
- policyBuilder = policyBuilder.penaltyDropBox();
- if (Build.IS_ENG) {
- policyBuilder.penaltyLog();
- }
- // All core system components need to tag their sockets to aid
- // system health investigations
- if (android.os.Process.myUid() < android.os.Process.FIRST_APPLICATION_UID) {
- policyBuilder.enable(DETECT_VM_UNTAGGED_SOCKET);
- } else {
- policyBuilder.disable(DETECT_VM_UNTAGGED_SOCKET);
+ if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
+ // Detect nothing extra
+ } else if (Build.IS_USERDEBUG) {
+ // Detect everything in bundled apps (except activity leaks, which
+ // are expensive to track)
+ if (isBundledSystemApp(ai)) {
+ builder.detectAll();
+ builder.permitActivityLeaks();
+ builder.penaltyDropBox();
+ }
+ } else if (Build.IS_ENG) {
+ // Detect everything in bundled apps
+ if (isBundledSystemApp(ai)) {
+ builder.detectAll();
+ builder.penaltyDropBox();
+ builder.penaltyLog();
}
- setVmPolicy(policyBuilder.build());
- setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
}
- return true;
- }
- /**
- * Used by the framework to make network usage on the main thread a fatal error.
- *
- * @hide
- */
- public static void enableDeathOnNetwork() {
- int oldPolicy = getThreadPolicyMask();
- int newPolicy = oldPolicy | DETECT_NETWORK | PENALTY_DEATH_ON_NETWORK;
- setThreadPolicyMask(newPolicy);
+ setVmPolicy(builder.build());
}
/**
@@ -1205,7 +1299,9 @@ public final class StrictMode {
sVmPolicy.mask
| DETECT_VM_FILE_URI_EXPOSURE
| PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
- sVmPolicy.classInstanceLimit);
+ sVmPolicy.classInstanceLimit,
+ sVmPolicy.mListener,
+ sVmPolicy.mCallbackExecutor);
}
/**
@@ -1220,7 +1316,9 @@ public final class StrictMode {
sVmPolicy.mask
& ~(DETECT_VM_FILE_URI_EXPOSURE
| PENALTY_DEATH_ON_FILE_URI_EXPOSURE),
- sVmPolicy.classInstanceLimit);
+ sVmPolicy.classInstanceLimit,
+ sVmPolicy.mListener,
+ sVmPolicy.mCallbackExecutor);
}
/**
@@ -1308,9 +1406,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new DiskWriteViolation());
}
// Not part of BlockGuard.Policy; just part of StrictMode:
@@ -1321,10 +1417,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e =
- new StrictModeCustomViolation(mPolicyMask, name);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new CustomViolation(name));
}
// Not part of BlockGuard.Policy; just part of StrictMode:
@@ -1335,13 +1428,10 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e =
- new StrictModeResourceMismatchViolation(mPolicyMask, tag);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new ResourceMismatchViolation(tag));
}
- // Part of BlockGuard.Policy; just part of StrictMode:
+ // Not part of BlockGuard.Policy; just part of StrictMode:
public void onUnbufferedIO() {
if ((mPolicyMask & DETECT_UNBUFFERED_IO) == 0) {
return;
@@ -1349,10 +1439,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e =
- new StrictModeUnbufferedIOViolation(mPolicyMask);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new UnbufferedIoViolation());
}
// Part of BlockGuard.Policy interface:
@@ -1363,9 +1450,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new DiskReadViolation());
}
// Part of BlockGuard.Policy interface:
@@ -1379,9 +1464,7 @@ public final class StrictMode {
if (tooManyViolationsThisLoop()) {
return;
}
- BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);
- e.fillInStackTrace();
- startHandlingViolationException(e);
+ startHandlingViolationException(new NetworkViolation());
}
public void setPolicyMask(int policyMask) {
@@ -1393,8 +1476,8 @@ public final class StrictMode {
// has yet occurred). This sees if we're in an event loop
// thread and, if so, uses it to roughly measure how long the
// violation took.
- void startHandlingViolationException(BlockGuard.BlockGuardPolicyException e) {
- final ViolationInfo info = new ViolationInfo(e, e.getPolicy());
+ void startHandlingViolationException(Violation e) {
+ final ViolationInfo info = new ViolationInfo(e, mPolicyMask);
info.violationUptimeMillis = SystemClock.uptimeMillis();
handleViolationWithTimingAttempt(info);
}
@@ -1423,9 +1506,9 @@ public final class StrictMode {
//
// TODO: if in gather mode, ignore Looper.myLooper() and always
// go into this immediate mode?
- if (looper == null || (info.policy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
+ if (looper == null || (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
info.durationMillis = -1; // unknown (redundant, already set)
- handleViolation(info);
+ onThreadPolicyViolation(info);
return;
}
@@ -1443,7 +1526,7 @@ public final class StrictMode {
}
final IWindowManager windowManager =
- (info.policy & PENALTY_FLASH) != 0 ? sWindowManager.get() : null;
+ info.penaltyEnabled(PENALTY_FLASH) ? sWindowManager.get() : null;
if (windowManager != null) {
try {
windowManager.showStrictModeViolation(true);
@@ -1463,30 +1546,28 @@ public final class StrictMode {
THREAD_HANDLER
.get()
.postAtFrontOfQueue(
- new Runnable() {
- public void run() {
- long loopFinishTime = SystemClock.uptimeMillis();
-
- // Note: we do this early, before handling the
- // violation below, as handling the violation
- // may include PENALTY_DEATH and we don't want
- // to keep the red border on.
- if (windowManager != null) {
- try {
- windowManager.showStrictModeViolation(false);
- } catch (RemoteException unused) {
- }
+ () -> {
+ long loopFinishTime = SystemClock.uptimeMillis();
+
+ // Note: we do this early, before handling the
+ // violation below, as handling the violation
+ // may include PENALTY_DEATH and we don't want
+ // to keep the red border on.
+ if (windowManager != null) {
+ try {
+ windowManager.showStrictModeViolation(false);
+ } catch (RemoteException unused) {
}
+ }
- for (int n = 0; n < records.size(); ++n) {
- ViolationInfo v = records.get(n);
- v.violationNumThisLoop = n + 1;
- v.durationMillis =
- (int) (loopFinishTime - v.violationUptimeMillis);
- handleViolation(v);
- }
- records.clear();
+ for (int n = 0; n < records.size(); ++n) {
+ ViolationInfo v = records.get(n);
+ v.violationNumThisLoop = n + 1;
+ v.durationMillis =
+ (int) (loopFinishTime - v.violationUptimeMillis);
+ onThreadPolicyViolation(v);
}
+ records.clear();
});
}
@@ -1495,18 +1576,13 @@ public final class StrictMode {
// violation fired and now (after the violating code ran) due
// to people who push/pop temporary policy in regions of code,
// hence the policy being passed around.
- void handleViolation(final ViolationInfo info) {
- if (info == null || !info.hasStackTrace()) {
- Log.wtf(TAG, "unexpected null stacktrace");
- return;
- }
-
- if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.policy);
+ void onThreadPolicyViolation(final ViolationInfo info) {
+ if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; policy=" + info.mPolicy);
- if ((info.policy & PENALTY_GATHER) != 0) {
+ if (info.penaltyEnabled(PENALTY_GATHER)) {
ArrayList<ViolationInfo> violations = gatheredViolations.get();
if (violations == null) {
- violations = new ArrayList<ViolationInfo>(1);
+ violations = new ArrayList<>(1);
gatheredViolations.set(violations);
}
for (ViolationInfo previous : violations) {
@@ -1535,31 +1611,32 @@ public final class StrictMode {
long timeSinceLastViolationMillis =
lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
- if ((info.policy & PENALTY_LOG) != 0
+ if (info.penaltyEnabled(PENALTY_LOG)
&& timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
sLogger.log(info);
}
+ final Violation violation = info.mViolation;
+
// The violationMaskSubset, passed to ActivityManager, is a
// subset of the original StrictMode policy bitmask, with
// only the bit violated and penalty bits to be executed
// by the ActivityManagerService remaining set.
int violationMaskSubset = 0;
- if ((info.policy & PENALTY_DIALOG) != 0
+ if (info.penaltyEnabled(PENALTY_DIALOG)
&& timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
violationMaskSubset |= PENALTY_DIALOG;
}
- if ((info.policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) {
+ if (info.penaltyEnabled(PENALTY_DROPBOX) && lastViolationTime == 0) {
violationMaskSubset |= PENALTY_DROPBOX;
}
if (violationMaskSubset != 0) {
violationMaskSubset |= info.getViolationBit();
- final int savedPolicyMask = getThreadPolicyMask();
- final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
+ final boolean justDropBox = (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
if (justDropBox) {
// If all we're going to ask the activity manager
// to do is dropbox it (the common case during
@@ -1568,42 +1645,38 @@ public final class StrictMode {
// isn't always super fast, despite the implementation
// in the ActivityManager trying to be mostly async.
dropboxViolationAsync(violationMaskSubset, info);
- return;
+ } else {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
}
+ }
- // Normal synchronous call to the ActivityManager.
- try {
- // First, remove any policy before we call into the Activity Manager,
- // otherwise we'll infinite recurse as we try to log policy violations
- // to disk, thus violating policy, thus requiring logging, etc...
- // We restore the current policy below, in the finally block.
- setThreadPolicyMask(0);
-
- ActivityManager.getService()
- .handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
- }
- } finally {
- // Restore the policy.
- setThreadPolicyMask(savedPolicyMask);
- }
+ if ((info.getPolicyMask() & PENALTY_DEATH) != 0) {
+ throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
}
- if ((info.policy & PENALTY_DEATH) != 0) {
- executeDeathPenalty(info);
+ // penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the
+ // executor finishes before crashing.
+ final OnThreadViolationListener listener = sThreadViolationListener.get();
+ final Executor executor = sThreadViolationExecutor.get();
+ if (listener != null && executor != null) {
+ try {
+ executor.execute(
+ () -> {
+ // Lift violated policy to prevent infinite recursion.
+ ThreadPolicy oldPolicy = allowThreadViolations();
+ try {
+ listener.onThreadViolation(violation);
+ } finally {
+ setThreadPolicy(oldPolicy);
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ Log.e(TAG, "ThreadPolicy penaltyCallback failed", e);
+ }
}
}
}
- private static void executeDeathPenalty(ViolationInfo info) {
- throw new StrictModeViolation(info.policy, info.getViolationBit(), null);
- }
-
/**
* In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
* violations but not showing a dialog, not loggging, and not killing the process. In these
@@ -1622,33 +1695,44 @@ public final class StrictMode {
if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding);
- new Thread("callActivityManagerForStrictModeDropbox") {
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- try {
- IActivityManager am = ActivityManager.getService();
- if (am == null) {
- Log.d(TAG, "No activity manager; failed to Dropbox violation.");
- } else {
- am.handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- }
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException handling StrictMode violation", e);
- }
- }
- int outstanding = sDropboxCallsInFlight.decrementAndGet();
- if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstanding);
+ BackgroundThread.getHandler().post(() -> {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
+ int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
+ if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
+ });
+ }
+
+ private static void handleApplicationStrictModeViolation(int violationMaskSubset,
+ ViolationInfo info) {
+ final int oldMask = getThreadPolicyMask();
+ try {
+ // First, remove any policy before we call into the Activity Manager,
+ // otherwise we'll infinite recurse as we try to log policy violations
+ // to disk, thus violating policy, thus requiring logging, etc...
+ // We restore the current policy below, in the finally block.
+ setThreadPolicyMask(0);
+
+ IActivityManager am = ActivityManager.getService();
+ if (am == null) {
+ Log.w(TAG, "No activity manager; failed to Dropbox violation.");
+ } else {
+ am.handleApplicationStrictModeViolation(
+ RuntimeInit.getApplicationObject(), violationMaskSubset, info);
}
- }.start();
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ // System process is dead; ignore
+ } else {
+ Log.e(TAG, "RemoteException handling StrictMode violation", e);
+ }
+ } finally {
+ setThreadPolicyMask(oldMask);
+ }
}
private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
public void report(String message, Throwable allocationSite) {
- onVmPolicyViolation(allocationSite);
+ onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite));
}
}
@@ -1686,8 +1770,7 @@ public final class StrictMode {
int limit = policy.classInstanceLimit.get(klass);
long instances = instanceCounts[i];
if (instances > limit) {
- Throwable tr = new InstanceCountViolation(klass, instances, limit);
- onVmPolicyViolation(tr);
+ onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
}
}
}
@@ -1769,9 +1852,8 @@ public final class StrictMode {
* #setThreadPolicy}.
*/
public static void enableDefaults() {
- StrictMode.setThreadPolicy(
- new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+ setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
/** @hide */
@@ -1811,24 +1893,22 @@ public final class StrictMode {
/** @hide */
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
- Throwable t = new Throwable(message);
- t.setStackTrace(originStack.getStackTrace());
- onVmPolicyViolation(t);
+ onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack));
}
/** @hide */
public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
- onVmPolicyViolation(originStack);
+ onVmPolicyViolation(new WebViewMethodCalledOnWrongThreadViolation(originStack));
}
/** @hide */
public static void onIntentReceiverLeaked(Throwable originStack) {
- onVmPolicyViolation(originStack);
+ onVmPolicyViolation(new IntentReceiverLeakedViolation(originStack));
}
/** @hide */
public static void onServiceConnectionLeaked(Throwable originStack) {
- onVmPolicyViolation(originStack);
+ onVmPolicyViolation(new ServiceConnectionLeakedViolation(originStack));
}
/** @hide */
@@ -1837,19 +1917,13 @@ public final class StrictMode {
if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
throw new FileUriExposedException(message);
} else {
- onVmPolicyViolation(new Throwable(message));
+ onVmPolicyViolation(new FileUriExposedViolation(message));
}
}
/** @hide */
public static void onContentUriWithoutPermission(Uri uri, String location) {
- final String message =
- uri
- + " exposed beyond app through "
- + location
- + " without permission grant flags; did you forget"
- + " FLAG_GRANT_READ_URI_PERMISSION?";
- onVmPolicyViolation(new Throwable(message));
+ onVmPolicyViolation(new ContentUriWithoutPermissionViolation(uri, location));
}
/** @hide */
@@ -1881,33 +1955,28 @@ public final class StrictMode {
}
msg += HexDump.dumpHexString(firstPacket).trim() + " ";
final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
- onVmPolicyViolation(new Throwable(msg), forceDeath);
+ onVmPolicyViolation(new CleartextNetworkViolation(msg), forceDeath);
}
/** @hide */
- public static final String UNTAGGED_SOCKET_VIOLATION_MESSAGE =
- "Untagged socket detected; use"
- + " TrafficStats.setThreadSocketTag() to track all network usage";
-
- /** @hide */
public static void onUntaggedSocket() {
- onVmPolicyViolation(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
+ onVmPolicyViolation(new UntaggedSocketViolation());
}
// Map from VM violation fingerprint to uptime millis.
- private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
+ private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>();
/** @hide */
- public static void onVmPolicyViolation(Throwable originStack) {
+ public static void onVmPolicyViolation(Violation originStack) {
onVmPolicyViolation(originStack, false);
}
/** @hide */
- public static void onVmPolicyViolation(Throwable originStack, boolean forceDeath) {
+ public static void onVmPolicyViolation(Violation violation, boolean forceDeath) {
final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
- final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
+ final ViolationInfo info = new ViolationInfo(violation, sVmPolicy.mask);
// Erase stuff not relevant for process-wide violations
info.numAnimationsRunning = 0;
@@ -1916,60 +1985,36 @@ public final class StrictMode {
final Integer fingerprint = info.hashCode();
final long now = SystemClock.uptimeMillis();
- long lastViolationTime = 0;
+ long lastViolationTime;
long timeSinceLastViolationMillis = Long.MAX_VALUE;
synchronized (sLastVmViolationTime) {
if (sLastVmViolationTime.containsKey(fingerprint)) {
lastViolationTime = sLastVmViolationTime.get(fingerprint);
timeSinceLastViolationMillis = now - lastViolationTime;
}
- if (timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+ if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
sLastVmViolationTime.put(fingerprint, now);
}
}
-
- if (penaltyLog && sLogger != null) {
- sLogger.log(info);
+ if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
+ // Rate limit all penalties.
+ return;
}
- if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+
+ if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
sLogger.log(info);
}
int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicy.mask);
- if (penaltyDropbox && !penaltyDeath) {
- // Common case for userdebug/eng builds. If no death and
- // just dropboxing, we can do the ActivityManager call
- // asynchronously.
- dropboxViolationAsync(violationMaskSubset, info);
- return;
- }
-
- if (penaltyDropbox && lastViolationTime == 0) {
- // The violationMask, passed to ActivityManager, is a
- // subset of the original StrictMode policy bitmask, with
- // only the bit violated and penalty bits to be executed
- // by the ActivityManagerService remaining set.
- final int savedPolicyMask = getThreadPolicyMask();
- try {
- // First, remove any policy before we call into the Activity Manager,
- // otherwise we'll infinite recurse as we try to log policy violations
- // to disk, thus violating policy, thus requiring logging, etc...
- // We restore the current policy below, in the finally block.
- setThreadPolicyMask(0);
-
- ActivityManager.getService()
- .handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
- }
- } finally {
- // Restore the policy.
- setThreadPolicyMask(savedPolicyMask);
+ if (penaltyDropbox) {
+ if (penaltyDeath) {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
+ } else {
+ // Common case for userdebug/eng builds. If no death and
+ // just dropboxing, we can do the ActivityManager call
+ // asynchronously.
+ dropboxViolationAsync(violationMaskSubset, info);
}
}
@@ -1978,6 +2023,26 @@ public final class StrictMode {
Process.killProcess(Process.myPid());
System.exit(10);
}
+
+ // If penaltyDeath, we can't guarantee this callback finishes before the process dies for
+ // all executors. penaltyDeath supersedes penaltyCallback.
+ if (sVmPolicy.mListener != null && sVmPolicy.mCallbackExecutor != null) {
+ final OnVmViolationListener listener = sVmPolicy.mListener;
+ try {
+ sVmPolicy.mCallbackExecutor.execute(
+ () -> {
+ // Lift violated policy to prevent infinite recursion.
+ VmPolicy oldPolicy = allowVmViolations();
+ try {
+ listener.onVmViolation(violation);
+ } finally {
+ setVmPolicy(oldPolicy);
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ Log.e(TAG, "VmPolicy penaltyCallback failed", e);
+ }
+ }
}
/** Called from Parcel.writeNoException() */
@@ -1998,14 +2063,12 @@ public final class StrictMode {
gatheredViolations.set(null);
}
- private static class LogStackTrace extends Exception {}
-
/**
* Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, we here
* read back all the encoded violations.
*/
/* package */ static void readAndHandleBinderCallViolations(Parcel p) {
- LogStackTrace localCallSite = new LogStackTrace();
+ Throwable localCallSite = new Throwable();
final int policyMask = getThreadPolicyMask();
final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
@@ -2323,8 +2386,7 @@ public final class StrictMode {
long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
- Throwable tr = new InstanceCountViolation(klass, instances, limit);
- onVmPolicyViolation(tr);
+ onVmPolicyViolation(new InstanceCountViolation(klass, instances, limit));
}
}
@@ -2337,17 +2399,16 @@ public final class StrictMode {
@TestApi
public static final class ViolationInfo implements Parcelable {
/** Stack and violation details. */
- @Nullable private final Throwable mThrowable;
+ private final Violation mViolation;
- private final Deque<Throwable> mBinderStack = new ArrayDeque<>();
+ /** Path leading to a violation that occurred across binder. */
+ private final Deque<StackTraceElement[]> mBinderStack = new ArrayDeque<>();
/** Memoized stack trace of full violation. */
@Nullable private String mStackTrace;
- /** Memoized violation bit. */
- private int mViolationBit;
/** The strict mode policy mask at the time of violation. */
- public final int policy;
+ private final int mPolicy;
/** The wall time duration of the violation, when known. -1 when not known. */
public int durationMillis = -1;
@@ -2377,17 +2438,11 @@ public final class StrictMode {
/** If this is a instance count violation, the number of instances in memory, else -1. */
public long numInstances = -1;
- /** Create an uninitialized instance of ViolationInfo */
- public ViolationInfo() {
- mThrowable = null;
- policy = 0;
- }
-
/** Create an instance of ViolationInfo initialized from an exception. */
- public ViolationInfo(Throwable tr, int policy) {
- this.mThrowable = tr;
+ ViolationInfo(Violation tr, int policy) {
+ this.mViolation = tr;
+ this.mPolicy = policy;
violationUptimeMillis = SystemClock.uptimeMillis();
- this.policy = policy;
this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
Intent broadcastIntent = ActivityThread.getIntentBeingBroadcast();
if (broadcastIntent != null) {
@@ -2395,7 +2450,7 @@ public final class StrictMode {
}
ThreadSpanState state = sThisThreadSpanState.get();
if (tr instanceof InstanceCountViolation) {
- this.numInstances = ((InstanceCountViolation) tr).mInstances;
+ this.numInstances = ((InstanceCountViolation) tr).getNumberOfInstances();
}
synchronized (state) {
int spanActiveCount = state.mActiveSize;
@@ -2417,13 +2472,17 @@ public final class StrictMode {
/** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */
public String getStackTrace() {
- if (mThrowable != null && mStackTrace == null) {
+ if (mStackTrace == null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 256);
- mThrowable.printStackTrace(pw);
- for (Throwable t : mBinderStack) {
+ mViolation.printStackTrace(pw);
+ for (StackTraceElement[] traces : mBinderStack) {
pw.append("# via Binder call with stack:\n");
- t.printStackTrace(pw);
+ for (StackTraceElement traceElement : traces) {
+ pw.append("\tat ");
+ pw.append(traceElement.toString());
+ pw.append('\n');
+ }
}
pw.flush();
pw.close();
@@ -2439,29 +2498,31 @@ public final class StrictMode {
*/
@TestApi
public String getViolationDetails() {
- if (mThrowable != null) {
- return mThrowable.getMessage();
- } else {
- return "";
- }
+ return mViolation.getMessage();
}
/**
- * If this violation has a useful stack trace.
+ * Policy mask at time of violation.
*
* @hide
*/
- public boolean hasStackTrace() {
- return mThrowable != null;
+ @TestApi
+ public int getPolicyMask() {
+ return mPolicy;
+ }
+
+ boolean penaltyEnabled(int p) {
+ return (mPolicy & p) != 0;
}
/**
- * Add a {@link Throwable} from the current process that caused the underlying violation.
+ * Add a {@link Throwable} from the current process that caused the underlying violation. We
+ * only preserve the stack trace elements.
*
* @hide
*/
void addLocalStack(Throwable t) {
- mBinderStack.addFirst(t);
+ mBinderStack.addFirst(t.getStackTrace());
}
/**
@@ -2469,37 +2530,47 @@ public final class StrictMode {
*
* @hide
*/
- int getViolationBit() {
- if (mThrowable == null || mThrowable.getMessage() == null) {
- return 0;
- }
- if (mViolationBit != 0) {
- return mViolationBit;
- }
- String message = mThrowable.getMessage();
- int violationIndex = message.indexOf("violation=");
- if (violationIndex == -1) {
- return 0;
- }
- int numberStartIndex = violationIndex + "violation=".length();
- int numberEndIndex = message.indexOf(' ', numberStartIndex);
- if (numberEndIndex == -1) {
- numberEndIndex = message.length();
- }
- String violationString = message.substring(numberStartIndex, numberEndIndex);
- try {
- mViolationBit = Integer.parseInt(violationString);
- return mViolationBit;
- } catch (NumberFormatException e) {
- return 0;
- }
+ @TestApi
+ public int getViolationBit() {
+ if (mViolation instanceof DiskWriteViolation) {
+ return DETECT_DISK_WRITE;
+ } else if (mViolation instanceof DiskReadViolation) {
+ return DETECT_DISK_READ;
+ } else if (mViolation instanceof NetworkViolation) {
+ return DETECT_NETWORK;
+ } else if (mViolation instanceof CustomViolation) {
+ return DETECT_CUSTOM;
+ } else if (mViolation instanceof ResourceMismatchViolation) {
+ return DETECT_RESOURCE_MISMATCH;
+ } else if (mViolation instanceof UnbufferedIoViolation) {
+ return DETECT_UNBUFFERED_IO;
+ } else if (mViolation instanceof SqliteObjectLeakedViolation) {
+ return DETECT_VM_CURSOR_LEAKS;
+ } else if (mViolation instanceof LeakedClosableViolation) {
+ return DETECT_VM_CLOSABLE_LEAKS;
+ } else if (mViolation instanceof InstanceCountViolation) {
+ return DETECT_VM_INSTANCE_LEAKS;
+ } else if (mViolation instanceof IntentReceiverLeakedViolation) {
+ return DETECT_VM_REGISTRATION_LEAKS;
+ } else if (mViolation instanceof ServiceConnectionLeakedViolation) {
+ return DETECT_VM_REGISTRATION_LEAKS;
+ } else if (mViolation instanceof FileUriExposedViolation) {
+ return DETECT_VM_FILE_URI_EXPOSURE;
+ } else if (mViolation instanceof CleartextNetworkViolation) {
+ return DETECT_VM_CLEARTEXT_NETWORK;
+ } else if (mViolation instanceof ContentUriWithoutPermissionViolation) {
+ return DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION;
+ } else if (mViolation instanceof UntaggedSocketViolation) {
+ return DETECT_VM_UNTAGGED_SOCKET;
+ }
+ throw new IllegalStateException("missing violation bit");
}
@Override
public int hashCode() {
int result = 17;
- if (mThrowable != null) {
- result = 37 * result + mThrowable.hashCode();
+ if (mViolation != null) {
+ result = 37 * result + mViolation.hashCode();
}
if (numAnimationsRunning != 0) {
result *= 37;
@@ -2527,16 +2598,26 @@ public final class StrictMode {
* should be removed.
*/
public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
- mThrowable = (Throwable) in.readSerializable();
+ mViolation = (Violation) in.readSerializable();
int binderStackSize = in.readInt();
for (int i = 0; i < binderStackSize; i++) {
- mBinderStack.add((Throwable) in.readSerializable());
+ StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
+ for (int j = 0; j < traceElements.length; j++) {
+ StackTraceElement element =
+ new StackTraceElement(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readInt());
+ traceElements[j] = element;
+ }
+ mBinderStack.add(traceElements);
}
int rawPolicy = in.readInt();
if (unsetGatheringBit) {
- policy = rawPolicy & ~PENALTY_GATHER;
+ mPolicy = rawPolicy & ~PENALTY_GATHER;
} else {
- policy = rawPolicy;
+ mPolicy = rawPolicy;
}
durationMillis = in.readInt();
violationNumThisLoop = in.readInt();
@@ -2550,13 +2631,19 @@ public final class StrictMode {
/** Save a ViolationInfo instance to a parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeSerializable(mThrowable);
+ dest.writeSerializable(mViolation);
dest.writeInt(mBinderStack.size());
- for (Throwable t : mBinderStack) {
- dest.writeSerializable(t);
+ for (StackTraceElement[] traceElements : mBinderStack) {
+ dest.writeInt(traceElements.length);
+ for (StackTraceElement element : traceElements) {
+ dest.writeString(element.getClassName());
+ dest.writeString(element.getMethodName());
+ dest.writeString(element.getFileName());
+ dest.writeInt(element.getLineNumber());
+ }
}
int start = dest.dataPosition();
- dest.writeInt(policy);
+ dest.writeInt(mPolicy);
dest.writeInt(durationMillis);
dest.writeInt(violationNumThisLoop);
dest.writeInt(numAnimationsRunning);
@@ -2569,7 +2656,7 @@ public final class StrictMode {
Slog.d(
TAG,
"VIO: policy="
- + policy
+ + mPolicy
+ " dur="
+ durationMillis
+ " numLoop="
@@ -2588,10 +2675,8 @@ public final class StrictMode {
/** Dump a ViolationInfo instance to a Printer. */
public void dump(Printer pw, String prefix) {
- if (mThrowable != null) {
- pw.println(prefix + "stackTrace: " + getStackTrace());
- }
- pw.println(prefix + "policy: " + policy);
+ pw.println(prefix + "stackTrace: " + getStackTrace());
+ pw.println(prefix + "policy: " + mPolicy);
if (durationMillis != -1) {
pw.println(prefix + "durationMillis: " + durationMillis);
}
@@ -2635,27 +2720,6 @@ public final class StrictMode {
};
}
- // Dummy throwable, for now, since we don't know when or where the
- // leaked instances came from. We might in the future, but for
- // now we suppress the stack trace because it's useless and/or
- // misleading.
- private static class InstanceCountViolation extends Throwable {
- private final long mInstances;
- private final int mLimit;
-
- private static final StackTraceElement[] FAKE_STACK = {
- new StackTraceElement(
- "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
- };
-
- public InstanceCountViolation(Class klass, long instances, int limit) {
- super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
- setStackTrace(FAKE_STACK);
- mInstances = instances;
- mLimit = limit;
- }
- }
-
private static final class InstanceTracker {
private static final HashMap<Class<?>, Integer> sInstanceCounts =
new HashMap<Class<?>, Integer>();
diff --git a/android/os/SystemClock.java b/android/os/SystemClock.java
index b3d76d73..c52c22d6 100644
--- a/android/os/SystemClock.java
+++ b/android/os/SystemClock.java
@@ -16,12 +16,18 @@
package android.os;
+import android.annotation.NonNull;
import android.app.IAlarmManager;
import android.content.Context;
import android.util.Slog;
import dalvik.annotation.optimization.CriticalNative;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
/**
* Core timekeeping facilities.
*
@@ -168,6 +174,31 @@ public final class SystemClock {
native public static long uptimeMillis();
/**
+ * Return {@link Clock} that starts at system boot, not counting time spent
+ * in deep sleep.
+ */
+ public static @NonNull Clock uptimeMillisClock() {
+ return new Clock() {
+ @Override
+ public ZoneId getZone() {
+ return ZoneOffset.UTC;
+ }
+ @Override
+ public Clock withZone(ZoneId zone) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long millis() {
+ return SystemClock.uptimeMillis();
+ }
+ @Override
+ public Instant instant() {
+ return Instant.ofEpochMilli(millis());
+ }
+ };
+ }
+
+ /**
* Returns milliseconds since boot, including time spent in sleep.
*
* @return elapsed milliseconds since boot.
@@ -176,6 +207,31 @@ public final class SystemClock {
native public static long elapsedRealtime();
/**
+ * Return {@link Clock} that starts at system boot, including time spent in
+ * sleep.
+ */
+ public static @NonNull Clock elapsedRealtimeClock() {
+ return new Clock() {
+ @Override
+ public ZoneId getZone() {
+ return ZoneOffset.UTC;
+ }
+ @Override
+ public Clock withZone(ZoneId zone) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long millis() {
+ return SystemClock.elapsedRealtime();
+ }
+ @Override
+ public Instant instant() {
+ return Instant.ofEpochMilli(millis());
+ }
+ };
+ }
+
+ /**
* Returns nanoseconds since boot, including time spent in sleep.
*
* @return elapsed nanoseconds since boot.
diff --git a/android/os/TokenWatcher.java b/android/os/TokenWatcher.java
index 9b3a2d68..00333dad 100644
--- a/android/os/TokenWatcher.java
+++ b/android/os/TokenWatcher.java
@@ -16,17 +16,23 @@
package android.os;
+import android.util.Log;
+
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.WeakHashMap;
import java.util.Set;
-import android.util.Log;
+import java.util.WeakHashMap;
/**
- * Helper class that helps you use IBinder objects as reference counted
- * tokens. IBinders make good tokens because we find out when they are
- * removed
+ * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added
+ * to the collection by calling {@link #acquire}, and removed by calling {@link
+ * #release}. IBinders are also implicitly removed when they become weakly
+ * reachable. Each IBinder may be added at most once.
*
+ * The {@link #acquired} method is invoked by posting to the specified handler
+ * whenever the size of the watched collection becomes nonzero. The {@link
+ * #released} method is invoked on the specified handler whenever the size of
+ * the watched collection becomes zero.
*/
public abstract class TokenWatcher
{
@@ -59,15 +65,23 @@ public abstract class TokenWatcher
* Record that this token has been acquired. When acquire is called, and
* the current count is 0, the acquired method is called on the given
* handler.
- *
- * @param token An IBinder object. If this token has already been acquired,
- * no action is taken.
+ *
+ * Note that the same {@code token} can only be acquired once. If this
+ * {@code token} has already been acquired, no action is taken. The first
+ * subsequent call to {@link #release} will release this {@code token}
+ * immediately.
+ *
+ * @param token An IBinder object.
* @param tag A string used by the {@link #dump} method for debugging,
* to see who has references.
*/
public void acquire(IBinder token, String tag)
{
synchronized (mTokens) {
+ if (mTokens.containsKey(token)) {
+ return;
+ }
+
// explicitly checked to avoid bogus sendNotification calls because
// of the WeakHashMap and the GC
int oldSize = mTokens.size();
diff --git a/android/os/UpdateEngine.java b/android/os/UpdateEngine.java
index ee0b6230..c6149bed 100644
--- a/android/os/UpdateEngine.java
+++ b/android/os/UpdateEngine.java
@@ -67,6 +67,7 @@ public class UpdateEngine {
public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10;
public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11;
public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12;
+ public static final int UPDATED_BUT_NOT_ACTIVE = 52;
}
/**
diff --git a/android/os/UserHandle.java b/android/os/UserHandle.java
index e8ebf631..6381b56a 100644
--- a/android/os/UserHandle.java
+++ b/android/os/UserHandle.java
@@ -27,6 +27,8 @@ import java.io.PrintWriter;
* Representation of a user on the device.
*/
public final class UserHandle implements Parcelable {
+ // NOTE: keep logic in sync with system/core/libcutils/multiuser.c
+
/**
* @hide Range of uids allocated for a user.
*/
@@ -88,6 +90,19 @@ public final class UserHandle implements Parcelable {
*/
public static final boolean MU_ENABLED = true;
+ /** @hide */
+ public static final int ERR_GID = -1;
+ /** @hide */
+ public static final int AID_ROOT = android.os.Process.ROOT_UID;
+ /** @hide */
+ public static final int AID_APP_START = android.os.Process.FIRST_APPLICATION_UID;
+ /** @hide */
+ public static final int AID_APP_END = android.os.Process.LAST_APPLICATION_UID;
+ /** @hide */
+ public static final int AID_SHARED_GID_START = android.os.Process.FIRST_SHARED_APPLICATION_GID;
+ /** @hide */
+ public static final int AID_CACHE_GID_START = android.os.Process.FIRST_APPLICATION_CACHE_GID;
+
final int mHandle;
/**
@@ -197,13 +212,20 @@ public final class UserHandle implements Parcelable {
return getUid(userId, Process.SHARED_USER_GID);
}
- /**
- * Returns the shared app gid for a given uid or appId.
- * @hide
- */
- public static int getSharedAppGid(int id) {
- return Process.FIRST_SHARED_APPLICATION_GID + (id % PER_USER_RANGE)
- - Process.FIRST_APPLICATION_UID;
+ /** @hide */
+ public static int getSharedAppGid(int uid) {
+ return getSharedAppGid(getUserId(uid), getAppId(uid));
+ }
+
+ /** @hide */
+ public static int getSharedAppGid(int userId, int appId) {
+ if (appId >= AID_APP_START && appId <= AID_APP_END) {
+ return (appId - AID_APP_START) + AID_SHARED_GID_START;
+ } else if (appId >= AID_ROOT && appId <= AID_APP_START) {
+ return appId;
+ } else {
+ return -1;
+ }
}
/**
@@ -219,13 +241,18 @@ public final class UserHandle implements Parcelable {
return appId;
}
- /**
- * Returns the cache GID for a given UID or appId.
- * @hide
- */
- public static int getCacheAppGid(int id) {
- return Process.FIRST_APPLICATION_CACHE_GID + (id % PER_USER_RANGE)
- - Process.FIRST_APPLICATION_UID;
+ /** @hide */
+ public static int getCacheAppGid(int uid) {
+ return getCacheAppGid(getUserId(uid), getAppId(uid));
+ }
+
+ /** @hide */
+ public static int getCacheAppGid(int userId, int appId) {
+ if (appId >= AID_APP_START && appId <= AID_APP_END) {
+ return getUid(userId, (appId - AID_APP_START) + AID_CACHE_GID_START);
+ } else {
+ return -1;
+ }
}
/**
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index c54b72d4..22967af7 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -140,6 +140,18 @@ public class UserManager {
public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
/**
+ * Specifies if a user is disallowed from changing the device
+ * language. The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_LOCALE = "no_config_locale";
+
+ /**
* Specifies if a user is disallowed from installing applications.
* The default value is <code>false</code>.
*
@@ -792,6 +804,19 @@ public class UserManager {
public static final String DISALLOW_AUTOFILL = "no_autofill";
/**
+ * Specifies if user switching is blocked on the current user.
+ *
+ * <p> This restriction can only be set by the device owner, it will be applied to all users.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+
+ /**
* Application restriction key that is used to indicate the pending arrival
* of real restrictions for the app.
*
@@ -917,7 +942,7 @@ public class UserManager {
/**
* Returns whether switching users is currently allowed.
* <p>For instance switching users is not allowed if the current user is in a phone call,
- * or system user hasn't been unlocked yet
+ * system user hasn't been unlocked yet, or {@link #DISALLOW_USER_SWITCH} is set.
* @hide
*/
public boolean canSwitchUsers() {
@@ -927,7 +952,9 @@ public class UserManager {
boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
boolean inCall = TelephonyManager.getDefault().getCallState()
!= TelephonyManager.CALL_STATE_IDLE;
- return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall;
+ boolean isUserSwitchDisallowed = hasUserRestriction(DISALLOW_USER_SWITCH);
+ return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
+ && !isUserSwitchDisallowed;
}
/**
@@ -1022,12 +1049,22 @@ public class UserManager {
}
/**
- * Used to check if the user making this call is linked to another user. Linked users may have
+ * @hide
+ * @deprecated Use {@link #isRestrictedProfile()}
+ */
+ @Deprecated
+ public boolean isLinkedUser() {
+ return isRestrictedProfile();
+ }
+
+ /**
+ * Returns whether the caller is running as restricted profile. Restricted profile may have
* a reduced number of available apps, app restrictions and account restrictions.
* @return whether the user making this call is a linked user
* @hide
*/
- public boolean isLinkedUser() {
+ @SystemApi
+ public boolean isRestrictedProfile() {
try {
return mService.isRestricted();
} catch (RemoteException re) {
@@ -1048,6 +1085,20 @@ public class UserManager {
}
/**
+ * Returns whether the calling user has at least one restricted profile associated with it.
+ * @return
+ * @hide
+ */
+ @SystemApi
+ public boolean hasRestrictedProfiles() {
+ try {
+ return mService.hasRestrictedProfiles();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks if a user is a guest user.
* @return whether user is a guest user.
* @hide
@@ -1067,6 +1118,7 @@ public class UserManager {
return user != null && user.isGuest();
}
+
/**
* Checks if the calling app is running in a demo user. When running in a demo user,
* apps can be more helpful to the user, or explain their features in more detail.
@@ -2298,6 +2350,9 @@ public class UserManager {
if (!supportsMultipleUsers()) {
return false;
}
+ if (hasUserRestriction(DISALLOW_USER_SWITCH)) {
+ return false;
+ }
// If Demo Mode is on, don't show user switcher
if (isDeviceInDemoMode(mContext)) {
return false;
diff --git a/android/os/storage/StorageManager.java b/android/os/storage/StorageManager.java
index 6594cd07..0b007ddf 100644
--- a/android/os/storage/StorageManager.java
+++ b/android/os/storage/StorageManager.java
@@ -42,10 +42,12 @@ import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IVold;
+import android.os.IVoldTaskListener;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
+import android.os.PersistableBundle;
import android.os.ProxyFileDescriptorCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -87,7 +89,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -884,9 +888,32 @@ public class StorageManager {
}
/** {@hide} */
+ @Deprecated
public long benchmark(String volId) {
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ benchmark(volId, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ result.complete(extras);
+ }
+ });
+ try {
+ // Convert ms to ns
+ return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE) * 1000000;
+ } catch (Exception e) {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /** {@hide} */
+ public void benchmark(String volId, IVoldTaskListener listener) {
try {
- return mStorageManager.benchmark(volId);
+ mStorageManager.benchmark(volId, listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/os/strictmode/CleartextNetworkViolation.java
index e28e1b38..6a0d381d 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/android/os/strictmode/CleartextNetworkViolation.java
@@ -11,16 +11,13 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
+package android.os.strictmode;
-package android.telephony.ims.feature;
-
-/**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
- */
-
-public interface IRcsFeature {
+public final class CleartextNetworkViolation extends Violation {
+ /** @hide */
+ public CleartextNetworkViolation(String msg) {
+ super(msg);
+ }
}
diff --git a/android/os/strictmode/ContentUriWithoutPermissionViolation.java b/android/os/strictmode/ContentUriWithoutPermissionViolation.java
new file mode 100644
index 00000000..e78dc79d
--- /dev/null
+++ b/android/os/strictmode/ContentUriWithoutPermissionViolation.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+import android.net.Uri;
+
+public final class ContentUriWithoutPermissionViolation extends Violation {
+ /** @hide */
+ public ContentUriWithoutPermissionViolation(Uri uri, String location) {
+ super(
+ uri
+ + " exposed beyond app through "
+ + location
+ + " without permission grant flags; did you forget"
+ + " FLAG_GRANT_READ_URI_PERMISSION?");
+ }
+}
diff --git a/android/os/strictmode/CustomViolation.java b/android/os/strictmode/CustomViolation.java
new file mode 100644
index 00000000..d4ad0671
--- /dev/null
+++ b/android/os/strictmode/CustomViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class CustomViolation extends Violation {
+ /** @hide */
+ public CustomViolation(String name) {
+ super(name);
+ }
+}
diff --git a/android/os/strictmode/DiskReadViolation.java b/android/os/strictmode/DiskReadViolation.java
new file mode 100644
index 00000000..fad32dbf
--- /dev/null
+++ b/android/os/strictmode/DiskReadViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class DiskReadViolation extends Violation {
+ /** @hide */
+ public DiskReadViolation() {
+ super(null);
+ }
+}
diff --git a/android/os/strictmode/DiskWriteViolation.java b/android/os/strictmode/DiskWriteViolation.java
new file mode 100644
index 00000000..cb9ca381
--- /dev/null
+++ b/android/os/strictmode/DiskWriteViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class DiskWriteViolation extends Violation {
+ /** @hide */
+ public DiskWriteViolation() {
+ super(null);
+ }
+}
diff --git a/android/os/strictmode/FileUriExposedViolation.java b/android/os/strictmode/FileUriExposedViolation.java
new file mode 100644
index 00000000..e3e6f833
--- /dev/null
+++ b/android/os/strictmode/FileUriExposedViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class FileUriExposedViolation extends Violation {
+ /** @hide */
+ public FileUriExposedViolation(String msg) {
+ super(msg);
+ }
+}
diff --git a/android/os/strictmode/InstanceCountViolation.java b/android/os/strictmode/InstanceCountViolation.java
new file mode 100644
index 00000000..9ee2c8e5
--- /dev/null
+++ b/android/os/strictmode/InstanceCountViolation.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public class InstanceCountViolation extends Violation {
+ private final long mInstances;
+
+ private static final StackTraceElement[] FAKE_STACK = {
+ new StackTraceElement(
+ "android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
+ };
+
+ /** @hide */
+ public InstanceCountViolation(Class klass, long instances, int limit) {
+ super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
+ setStackTrace(FAKE_STACK);
+ mInstances = instances;
+ }
+
+ public long getNumberOfInstances() {
+ return mInstances;
+ }
+}
diff --git a/android/os/strictmode/IntentReceiverLeakedViolation.java b/android/os/strictmode/IntentReceiverLeakedViolation.java
new file mode 100644
index 00000000..f416c940
--- /dev/null
+++ b/android/os/strictmode/IntentReceiverLeakedViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class IntentReceiverLeakedViolation extends Violation {
+ /** @hide */
+ public IntentReceiverLeakedViolation(Throwable originStack) {
+ super(null);
+ setStackTrace(originStack.getStackTrace());
+ }
+}
diff --git a/android/os/strictmode/LeakedClosableViolation.java b/android/os/strictmode/LeakedClosableViolation.java
new file mode 100644
index 00000000..c795a6b8
--- /dev/null
+++ b/android/os/strictmode/LeakedClosableViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class LeakedClosableViolation extends Violation {
+ /** @hide */
+ public LeakedClosableViolation(String message, Throwable allocationSite) {
+ super(message);
+ initCause(allocationSite);
+ }
+}
diff --git a/android/os/strictmode/NetworkViolation.java b/android/os/strictmode/NetworkViolation.java
new file mode 100644
index 00000000..abcf009d
--- /dev/null
+++ b/android/os/strictmode/NetworkViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class NetworkViolation extends Violation {
+ /** @hide */
+ public NetworkViolation() {
+ super(null);
+ }
+}
diff --git a/android/os/strictmode/ResourceMismatchViolation.java b/android/os/strictmode/ResourceMismatchViolation.java
new file mode 100644
index 00000000..97c44993
--- /dev/null
+++ b/android/os/strictmode/ResourceMismatchViolation.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class ResourceMismatchViolation extends Violation {
+ /** @hide */
+ public ResourceMismatchViolation(Object tag) {
+ super(tag.toString());
+ }
+}
diff --git a/android/os/strictmode/ServiceConnectionLeakedViolation.java b/android/os/strictmode/ServiceConnectionLeakedViolation.java
new file mode 100644
index 00000000..2d6b58f0
--- /dev/null
+++ b/android/os/strictmode/ServiceConnectionLeakedViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class ServiceConnectionLeakedViolation extends Violation {
+ /** @hide */
+ public ServiceConnectionLeakedViolation(Throwable originStack) {
+ super(null);
+ setStackTrace(originStack.getStackTrace());
+ }
+}
diff --git a/android/os/strictmode/SqliteObjectLeakedViolation.java b/android/os/strictmode/SqliteObjectLeakedViolation.java
new file mode 100644
index 00000000..02002207
--- /dev/null
+++ b/android/os/strictmode/SqliteObjectLeakedViolation.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class SqliteObjectLeakedViolation extends Violation {
+
+ /** @hide */
+ public SqliteObjectLeakedViolation(String message, Throwable originStack) {
+ super(message);
+ initCause(originStack);
+ }
+}
diff --git a/android/os/strictmode/UnbufferedIoViolation.java b/android/os/strictmode/UnbufferedIoViolation.java
new file mode 100644
index 00000000..a5c326d1
--- /dev/null
+++ b/android/os/strictmode/UnbufferedIoViolation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+import android.os.StrictMode.ThreadPolicy.Builder;
+
+/**
+ * See #{@link Builder#detectUnbufferedIo()}
+ */
+public final class UnbufferedIoViolation extends Violation {
+ /** @hide */
+ public UnbufferedIoViolation() {
+ super(null);
+ }
+}
diff --git a/android/os/strictmode/UntaggedSocketViolation.java b/android/os/strictmode/UntaggedSocketViolation.java
new file mode 100644
index 00000000..836a8b9d
--- /dev/null
+++ b/android/os/strictmode/UntaggedSocketViolation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class UntaggedSocketViolation extends Violation {
+ /** @hide */
+ public static final String MESSAGE =
+ "Untagged socket detected; use"
+ + " TrafficStats.setThreadSocketTag() to track all network usage";
+
+ /** @hide */
+ public UntaggedSocketViolation() {
+ super(MESSAGE);
+ }
+}
diff --git a/android/os/strictmode/Violation.java b/android/os/strictmode/Violation.java
new file mode 100644
index 00000000..31c7d584
--- /dev/null
+++ b/android/os/strictmode/Violation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+/** Root class for all StrictMode violations. */
+public abstract class Violation extends Throwable {
+ Violation(String message) {
+ super(message);
+ }
+}
diff --git a/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java b/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
new file mode 100644
index 00000000..c328d147
--- /dev/null
+++ b/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.strictmode;
+
+public final class WebViewMethodCalledOnWrongThreadViolation extends Violation {
+ /** @hide */
+ public WebViewMethodCalledOnWrongThreadViolation(Throwable originStack) {
+ super(null);
+ setStackTrace(originStack.getStackTrace());
+ }
+}
diff --git a/android/preference/PreferenceFragment.java b/android/preference/PreferenceFragment.java
index 73fa01e5..4c556efa 100644
--- a/android/preference/PreferenceFragment.java
+++ b/android/preference/PreferenceFragment.java
@@ -23,7 +23,6 @@ import android.app.Fragment;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -105,7 +104,10 @@ import android.widget.TextView;
*
* @see Preference
* @see PreferenceScreen
+ *
+ * @deprecated Use {@link android.support.v7.preference.PreferenceFragmentCompat}
*/
+@Deprecated
public abstract class PreferenceFragment extends Fragment implements
PreferenceManager.OnPreferenceTreeClickListener {
@@ -146,7 +148,11 @@ public abstract class PreferenceFragment extends Fragment implements
* Interface that PreferenceFragment's containing activity should
* implement to be able to process preference items that wish to
* switch to a new fragment.
+ *
+ * @deprecated Use {@link
+ * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
*/
+ @Deprecated
public interface OnPreferenceStartFragmentCallback {
/**
* Called when the user has clicked on a Preference that has
diff --git a/android/provider/MediaStore.java b/android/provider/MediaStore.java
index 13e1e26b..32d68cd9 100644
--- a/android/provider/MediaStore.java
+++ b/android/provider/MediaStore.java
@@ -81,6 +81,13 @@ public final class MediaStore {
public static final String UNHIDE_CALL = "unhide";
/**
+ * The method name used by the media scanner service to reload all localized ringtone titles due
+ * to a locale change.
+ * @hide
+ */
+ public static final String RETRANSLATE_CALL = "update_titles";
+
+ /**
* This is for internal use by the media scanner only.
* Name of the (optional) Uri parameter that determines whether to skip deleting
* the file pointed to by the _data column, when deleting the database entry.
@@ -1358,6 +1365,18 @@ public final class MediaStore {
* @hide
*/
public static final String GENRE = "genre";
+
+ /**
+ * The resource URI of a localized title, if any
+ * <P>Type: TEXT</P>
+ * Conforms to this pattern:
+ * Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
+ * Authority: Package Name of ringtone title provider
+ * First Path Segment: Type of resource (must be "string")
+ * Second Path Segment: Resource ID of title
+ * @hide
+ */
+ public static final String TITLE_RESOURCE_URI = "title_resource_uri";
}
/**
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index 62f4bf58..6decc305 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -66,12 +66,14 @@ import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.speech.tts.TextToSpeech;
+import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.MemoryIntArray;
+import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
@@ -210,8 +212,13 @@ public final class Settings {
/** @hide */
public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
- /** @hide */
- public static final String EXTRA_SUB_ID = "sub_id";
+
+ /**
+ * An int extra specifying a subscription ID.
+ *
+ * @see android.telephony.SubscriptionInfo#getSubscriptionId
+ */
+ public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
/**
* Activity Action: Modify Airplane mode settings using a voice command.
@@ -915,6 +922,9 @@ public final class Settings {
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
+ * The subscription ID of the subscription for which available network operators should be
+ * displayed may be optionally specified with {@link #EXTRA_SUB_ID}.
+ * <p>
* Input: Nothing.
* <p>
* Output: Nothing.
@@ -1886,7 +1896,11 @@ public final class Settings {
arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
IContentProvider cp = mProviderHolder.getProvider(cr);
+ String prevValue = getStringForUser(cr, name, userHandle);
cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
+ String newValue = getStringForUser(cr, name, userHandle);
+ StatsLog.write(StatsLog.SETTING_CHANGED, name, value, newValue, prevValue, tag,
+ makeDefault ? 1 : 0, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
return false;
@@ -2100,6 +2114,9 @@ public final class Settings {
* functions for accessing individual settings entries.
*/
public static final class System extends NameValueTable {
+ // NOTE: If you add new settings here, be sure to add them to
+ // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoSystemSettingsLocked.
+
private static final float DEFAULT_FONT_SCALE = 1.0f;
/** @hide */
@@ -4549,6 +4566,9 @@ public final class Settings {
* APIs for those values, not modified directly by applications.
*/
public static final class Secure extends NameValueTable {
+ // NOTE: If you add new settings here, be sure to add them to
+ // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoSecureSettingsLocked.
+
/**
* The content:// style URL for this table
*/
@@ -5304,6 +5324,15 @@ public final class Settings {
public static final String AUTOFILL_SERVICE = "autofill_service";
/**
+ * Experimental autofill feature.
+ *
+ * <p>TODO(b/67867469): remove once feature is finished
+ * @hide
+ */
+ @TestApi
+ public static final String AUTOFILL_FEATURE_FIELD_DETECTION = "autofill_field_detection";
+
+ /**
* @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
*/
@Deprecated
@@ -7542,6 +7571,9 @@ public final class Settings {
* explicitly modify through the system UI or specialized APIs for those values.
*/
public static final class Global extends NameValueTable {
+ // NOTE: If you add new settings here, be sure to add them to
+ // com.android.providers.settings.SettingsProtoDumpUtil#dumpProtoGlobalSettingsLocked.
+
/**
* The content:// style URL for global secure settings items. Not public.
*/
@@ -8007,28 +8039,40 @@ public final class Settings {
public static final String HDMI_SYSTEM_AUDIO_CONTROL_ENABLED =
"hdmi_system_audio_control_enabled";
- /**
- * Whether TV will automatically turn on upon reception of the CEC command
- * &lt;Text View On&gt; or &lt;Image View On&gt;. (0 = false, 1 = true)
- * @hide
- */
- public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED =
- "hdmi_control_auto_wakeup_enabled";
+ /**
+ * Whether TV will automatically turn on upon reception of the CEC command
+ * &lt;Text View On&gt; or &lt;Image View On&gt;. (0 = false, 1 = true)
+ *
+ * @hide
+ */
+ public static final String HDMI_CONTROL_AUTO_WAKEUP_ENABLED =
+ "hdmi_control_auto_wakeup_enabled";
- /**
- * Whether TV will also turn off other CEC devices when it goes to standby mode.
- * (0 = false, 1 = true)
- * @hide
- */
- public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
- "hdmi_control_auto_device_off_enabled";
+ /**
+ * Whether TV will also turn off other CEC devices when it goes to standby mode.
+ * (0 = false, 1 = true)
+ *
+ * @hide
+ */
+ public static final String HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED =
+ "hdmi_control_auto_device_off_enabled";
- /**
- * The interval in milliseconds at which location requests will be throttled when they are
- * coming from the background.
- * @hide
- */
- public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
+ /**
+ * If <b>true</b>, enables out-of-the-box execution for priv apps.
+ * Default: false
+ * Values: 0 = false, 1 = true
+ *
+ * @hide
+ */
+ public static final String PRIV_APP_OOB_ENABLED = "priv_app_oob_enabled";
+
+ /**
+ * The interval in milliseconds at which location requests will be throttled when they are
+ * coming from the background.
+ *
+ * @hide
+ */
+ public static final String LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS =
"location_background_throttle_interval_ms";
/**
@@ -8488,6 +8532,13 @@ public final class Settings {
public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
"network_metered_multipath_preference";
+ /**
+ * Network watchlist last report time.
+ * @hide
+ */
+ public static final String NETWORK_WATCHLIST_LAST_REPORT_TIME =
+ "network_watchlist_last_report_time";
+
/**
* The thresholds of the wifi throughput badging (SD, HD etc.) as a comma-delimited list of
* colon-delimited key-value pairs. The key is the badging enum value defined in
@@ -9280,11 +9331,20 @@ public final class Settings {
public static final String DEFAULT_DNS_SERVER = "default_dns_server";
/**
- * Whether to disable DNS over TLS (boolean)
+ * The requested Private DNS mode (string), and an accompanying specifier (string).
+ *
+ * Currently, the specifier holds the chosen provider name when the mode requests
+ * a specific provider. It may be used to store the provider name even when the
+ * mode changes so that temporarily disabling and re-enabling the specific
+ * provider mode does not necessitate retyping the provider hostname.
*
* @hide
*/
- public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+ public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
/** {@hide} */
public static final String
@@ -9418,6 +9478,16 @@ public final class Settings {
public static final String BATTERY_SAVER_CONSTANTS = "battery_saver_constants";
/**
+ * Battery Saver device specific settings
+ * This is encoded as a key=value list, separated by commas.
+ * See {@link com.android.server.power.BatterySaverPolicy} for the details.
+ *
+ * @hide
+ */
+ public static final String BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS =
+ "battery_saver_device_specific_constants";
+
+ /**
* Battery anomaly detection specific settings
* This is encoded as a key=value list, separated by commas.
* wakeup_blacklisted_tags is a string, encoded as a set of tags, encoded via
@@ -10094,12 +10164,17 @@ public final class Settings {
public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
/**
- * Whether the Volte is enabled
+ * Whether the Volte is enabled. If this setting is not set then we use the Carrier Config
+ * value {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
* <p>
* Type: int (0 for false, 1 for true)
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#ENHANCED_4G_MODE_ENABLED}
+ * instead.
*/
- public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+ @Deprecated
+ public static final String ENHANCED_4G_MODE_ENABLED =
+ SubscriptionManager.ENHANCED_4G_MODE_ENABLED;
/**
* Whether VT (Video Telephony over IMS) is enabled
@@ -10107,8 +10182,10 @@ public final class Settings {
* Type: int (0 for false, 1 for true)
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#VT_IMS_ENABLED} instead.
*/
- public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+ @Deprecated
+ public static final String VT_IMS_ENABLED = SubscriptionManager.VT_IMS_ENABLED;
/**
* Whether WFC is enabled
@@ -10116,8 +10193,10 @@ public final class Settings {
* Type: int (0 for false, 1 for true)
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ENABLED} instead.
*/
- public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+ @Deprecated
+ public static final String WFC_IMS_ENABLED = SubscriptionManager.WFC_IMS_ENABLED;
/**
* WFC mode on home/non-roaming network.
@@ -10125,8 +10204,10 @@ public final class Settings {
* Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_MODE} instead.
*/
- public static final String WFC_IMS_MODE = "wfc_ims_mode";
+ @Deprecated
+ public static final String WFC_IMS_MODE = SubscriptionManager.WFC_IMS_MODE;
/**
* WFC mode on roaming network.
@@ -10134,8 +10215,11 @@ public final class Settings {
* Type: int - see {@link #WFC_IMS_MODE} for values
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_MODE}
+ * instead.
*/
- public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+ @Deprecated
+ public static final String WFC_IMS_ROAMING_MODE = SubscriptionManager.WFC_IMS_ROAMING_MODE;
/**
* Whether WFC roaming is enabled
@@ -10143,8 +10227,12 @@ public final class Settings {
* Type: int (0 for false, 1 for true)
*
* @hide
+ * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_ENABLED}
+ * instead
*/
- public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+ @Deprecated
+ public static final String WFC_IMS_ROAMING_ENABLED =
+ SubscriptionManager.WFC_IMS_ROAMING_ENABLED;
/**
* Whether user can enable/disable LTE as a preferred network. A carrier might control
@@ -10370,7 +10458,9 @@ public final class Settings {
DOCK_AUDIO_MEDIA_ENABLED,
ENCODED_SURROUND_OUTPUT,
LOW_POWER_MODE_TRIGGER_LEVEL,
- BLUETOOTH_ON
+ BLUETOOTH_ON,
+ PRIVATE_DNS_MODE,
+ PRIVATE_DNS_SPECIFIER
};
/** @hide */
@@ -10823,7 +10913,7 @@ public final class Settings {
/** User preferred subscriptions setting.
* This holds the details of the user selected subscription from the card and
- * the activation status. Each settings string have the coma separated values
+ * the activation status. Each settings string have the comma separated values
* iccId,appType,appId,activationStatus,3gppIndex,3gpp2Index
* @hide
*/
diff --git a/android/provider/Telephony.java b/android/provider/Telephony.java
index 216d28cb..d7b6142a 100644
--- a/android/provider/Telephony.java
+++ b/android/provider/Telephony.java
@@ -2828,6 +2828,26 @@ public final class Telephony {
* @hide
*/
public static final int CARRIER_DELETED_BUT_PRESENT_IN_XML = 6;
+
+ /**
+ * The owner of the APN.
+ * <p>Type: INTEGER</p>
+ * @hide
+ */
+ public static final String OWNED_BY = "owned_by";
+
+ /**
+ * Possible value for the OWNED_BY field.
+ * APN is owned by DPC.
+ * @hide
+ */
+ public static final int OWNED_BY_DPC = 0;
+ /**
+ * Possible value for the OWNED_BY field.
+ * APN is owned by other sources.
+ * @hide
+ */
+ public static final int OWNED_BY_OTHERS = 1;
}
/**
@@ -3273,4 +3293,69 @@ public final class Telephony {
*/
public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
}
+
+ /**
+ * Contains carrier identification information.
+ * @hide
+ */
+ public static final class CarrierIdentification implements BaseColumns {
+ /**
+ * Numeric operator ID (as String). {@code MCC + MNC}
+ * <P>Type: TEXT </P>
+ */
+ public static final String MCCMNC = "mccmnc";
+
+ /**
+ * Group id level 1 (as String).
+ * <P>Type: TEXT </P>
+ */
+ public static final String GID1 = "gid1";
+
+ /**
+ * Group id level 2 (as String).
+ * <P>Type: TEXT </P>
+ */
+ public static final String GID2 = "gid2";
+
+ /**
+ * Public Land Mobile Network name.
+ * <P>Type: TEXT </P>
+ */
+ public static final String PLMN = "plmn";
+
+ /**
+ * Prefix xpattern of IMSI (International Mobile Subscriber Identity).
+ * <P>Type: TEXT </P>
+ */
+ public static final String IMSI_PREFIX_XPATTERN = "imsi_prefix_xpattern";
+
+ /**
+ * Service Provider Name.
+ * <P>Type: TEXT </P>
+ */
+ public static final String SPN = "spn";
+
+ /**
+ * Prefer APN name.
+ * <P>Type: TEXT </P>
+ */
+ public static final String APN = "apn";
+
+ /**
+ * User facing carrier name.
+ * <P>Type: TEXT </P>
+ */
+ public static final String NAME = "carrier_name";
+
+ /**
+ * A unique carrier id
+ * <P>Type: INTEGER </P>
+ */
+ public static final String CID = "carrier_id";
+
+ /**
+ * The {@code content://} URI for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://carrier_identification");
+ }
}
diff --git a/android/security/KeyStore.java b/android/security/KeyStore.java
index 7e959a87..399dddd7 100644
--- a/android/security/KeyStore.java
+++ b/android/security/KeyStore.java
@@ -20,6 +20,7 @@ import android.app.ActivityThread;
import android.app.Application;
import android.app.KeyguardManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.IBinder;
@@ -53,7 +54,7 @@ import java.util.Locale;
public class KeyStore {
private static final String TAG = "KeyStore";
- // ResponseCodes
+ // ResponseCodes - see system/security/keystore/include/keystore/keystore.h
public static final int NO_ERROR = 1;
public static final int LOCKED = 2;
public static final int UNINITIALIZED = 3;
@@ -167,10 +168,14 @@ public class KeyStore {
public byte[] get(String key, int uid) {
try {
+ key = key != null ? key : "";
return mBinder.get(key, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
}
@@ -184,6 +189,9 @@ public class KeyStore {
public int insert(String key, byte[] value, int uid, int flags) {
try {
+ if (value == null) {
+ value = new byte[0];
+ }
return mBinder.insert(key, value, uid, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -227,6 +235,9 @@ public class KeyStore {
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
}
@@ -275,6 +286,7 @@ public class KeyStore {
*/
public boolean unlock(int userId, String password) {
try {
+ password = password != null ? password : "";
mError = mBinder.unlock(userId, password);
return mError == NO_ERROR;
} catch (RemoteException e) {
@@ -329,16 +341,25 @@ public class KeyStore {
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
+
}
public boolean verify(String key, byte[] data, byte[] signature) {
try {
+ signature = signature != null ? signature : new byte[0];
return mBinder.verify(key, data, signature) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return false;
}
+
}
public String grant(String key, int uid) {
@@ -431,6 +452,8 @@ public class KeyStore {
public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid,
int flags, KeyCharacteristics outCharacteristics) {
try {
+ entropy = entropy != null ? entropy : new byte[0];
+ args = args != null ? args : new KeymasterArguments();
return mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -446,6 +469,8 @@ public class KeyStore {
public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId,
int uid, KeyCharacteristics outCharacteristics) {
try {
+ clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
+ appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
return mBinder.getKeyCharacteristics(alias, clientId, appId, uid, outCharacteristics);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -477,6 +502,8 @@ public class KeyStore {
public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
KeymasterBlob appId, int uid) {
try {
+ clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
+ appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
return mBinder.exportKey(alias, format, clientId, appId, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -491,6 +518,8 @@ public class KeyStore {
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy, int uid) {
try {
+ args = args != null ? args : new KeymasterArguments();
+ entropy = entropy != null ? entropy : new byte[0];
return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -500,11 +529,15 @@ public class KeyStore {
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy) {
+ entropy = entropy != null ? entropy : new byte[0];
+ args = args != null ? args : new KeymasterArguments();
return begin(alias, purpose, pruneable, args, entropy, UID_SELF);
}
public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) {
try {
+ arguments = arguments != null ? arguments : new KeymasterArguments();
+ input = input != null ? input : new byte[0];
return mBinder.update(token, arguments, input);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -515,6 +548,9 @@ public class KeyStore {
public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature,
byte[] entropy) {
try {
+ arguments = arguments != null ? arguments : new KeymasterArguments();
+ entropy = entropy != null ? entropy : new byte[0];
+ signature = signature != null ? signature : new byte[0];
return mBinder.finish(token, arguments, signature, entropy);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -631,6 +667,12 @@ public class KeyStore {
public int attestKey(
String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
try {
+ if (params == null) {
+ params = new KeymasterArguments();
+ }
+ if (outChain == null) {
+ outChain = new KeymasterCertificateChain();
+ }
return mBinder.attestKey(alias, params, outChain);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -640,6 +682,12 @@ public class KeyStore {
public int attestDeviceIds(KeymasterArguments params, KeymasterCertificateChain outChain) {
try {
+ if (params == null) {
+ params = new KeymasterArguments();
+ }
+ if (outChain == null) {
+ outChain = new KeymasterCertificateChain();
+ }
return mBinder.attestDeviceIds(params, outChain);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -762,6 +810,10 @@ public class KeyStore {
}
private long getFingerprintOnlySid() {
+ final PackageManager packageManager = mContext.getPackageManager();
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ return 0;
+ }
FingerprintManager fingerprintManager = mContext.getSystemService(FingerprintManager.class);
if (fingerprintManager == null) {
return 0;
diff --git a/android/service/autofill/FieldsDetection.java b/android/service/autofill/FieldsDetection.java
new file mode 100644
index 00000000..550ecf68
--- /dev/null
+++ b/android/service/autofill/FieldsDetection.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.autofill;
+
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.autofill.AutofillId;
+
+/**
+ * Class by service to improve autofillable fields detection by tracking the meaning of fields
+ * manually edited by the user (when they match values provided by the service).
+ *
+ * TODO(b/67867469):
+ * - proper javadoc
+ * - unhide / remove testApi
+ * - add FieldsDetection management so service can set it just once and reference it in further
+ * calls to improve performance (and also API to refresh it)
+ * - rename to FieldsDetectionInfo or FieldClassification? (same for CTS tests)
+ * - add FieldsDetectionUnitTest once API is well-defined
+ * @hide
+ */
+@TestApi
+public final class FieldsDetection implements Parcelable {
+
+ private final AutofillId mFieldId;
+ private final String mRemoteId;
+ private final String mValue;
+
+ /**
+ * Creates a field detection for just one field / value pair.
+ *
+ * @param fieldId autofill id of the field in the screen.
+ * @param remoteId id used by the service to identify the field later.
+ * @param value field value known to the service.
+ *
+ * TODO(b/67867469):
+ * - proper javadoc
+ * - change signature to allow more fields / values / match methods
+ * - might also need to use a builder, where the constructor is the id for the fieldsdetector
+ * - might need id for values as well
+ * - add @NonNull / check it / add unit tests
+ * - make 'value' input more generic so it can accept distance-based match and other matches
+ * - throw exception if field value is less than X characters (somewhere between 7-10)
+ * - make sure to limit total number of fields to around 10 or so
+ * - use AutofillValue instead of String (so it can compare dates, for example)
+ */
+ public FieldsDetection(AutofillId fieldId, String remoteId, String value) {
+ mFieldId = fieldId;
+ mRemoteId = remoteId;
+ mValue = value;
+ }
+
+ /** @hide */
+ public AutofillId getFieldId() {
+ return mFieldId;
+ }
+
+ /** @hide */
+ public String getRemoteId() {
+ return mRemoteId;
+ }
+
+ /** @hide */
+ public String getValue() {
+ return mValue;
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ // Cannot disclose remoteId or value because they could contain PII
+ return new StringBuilder("FieldsDetection: [field=").append(mFieldId)
+ .append(", remoteId_length=").append(mRemoteId.length())
+ .append(", value_length=").append(mValue.length())
+ .append("]").toString();
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mFieldId, flags);
+ parcel.writeString(mRemoteId);
+ parcel.writeString(mValue);
+ }
+
+ public static final Parcelable.Creator<FieldsDetection> CREATOR =
+ new Parcelable.Creator<FieldsDetection>() {
+ @Override
+ public FieldsDetection createFromParcel(Parcel parcel) {
+ // TODO(b/67867469): remove comment below if it does not use a builder at the end
+ // Always go through the builder to ensure the data ingested by
+ // the system obeys the contract of the builder to avoid attacks
+ // using specially crafted parcels.
+ return new FieldsDetection(parcel.readParcelable(null), parcel.readString(),
+ parcel.readString());
+ }
+
+ @Override
+ public FieldsDetection[] newArray(int size) {
+ return new FieldsDetection[size];
+ }
+ };
+}
diff --git a/android/service/autofill/FillEventHistory.java b/android/service/autofill/FillEventHistory.java
index b1857b30..736d9ef4 100644
--- a/android/service/autofill/FillEventHistory.java
+++ b/android/service/autofill/FillEventHistory.java
@@ -19,6 +19,7 @@ package android.service.autofill;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.IntentSender;
import android.os.Bundle;
import android.os.Parcel;
@@ -164,6 +165,10 @@ public final class FillEventHistory implements Parcelable {
dest.writeStringList(event.mManuallyFilledDatasetIds.get(j));
}
}
+ dest.writeString(event.mDetectedRemoteId);
+ if (event.mDetectedRemoteId != null) {
+ dest.writeInt(event.mDetectedFieldScore);
+ }
}
}
}
@@ -226,6 +231,7 @@ public final class FillEventHistory implements Parcelable {
* <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
* contexts.
*/
+ // TODO(b/67867469): update with field detection behavior
public static final int TYPE_CONTEXT_COMMITTED = 4;
/** @hide */
@@ -253,6 +259,9 @@ public final class FillEventHistory implements Parcelable {
@Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
@Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
+ @Nullable private final String mDetectedRemoteId;
+ private final int mDetectedFieldScore;
+
/**
* Returns the type of the event.
*
@@ -355,6 +364,39 @@ public final class FillEventHistory implements Parcelable {
}
/**
+ * Gets the results of the last {@link FieldsDetection} request.
+ *
+ * @return map of edit-distance match ({@code 0} means full match,
+ * {@code 1} means 1 character different, etc...) by remote id (as set in the
+ * {@link FieldsDetection} constructor), or {@code null} if none of the user-input values
+ * matched the requested detection.
+ *
+ * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}, when the
+ * service requested {@link FillResponse.Builder#setFieldsDetection(FieldsDetection) fields
+ * detection}.
+ *
+ * TODO(b/67867469):
+ * - improve javadoc
+ * - refine score meaning (for example, should 1 be different of -1?)
+ * - mention when it's set
+ * - unhide
+ * - unhide / remove testApi
+ * - add @NonNull / check it / add unit tests
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull public Map<String, Integer> getDetectedFields() {
+ if (mDetectedRemoteId == null || mDetectedFieldScore == -1) {
+ return Collections.emptyMap();
+ }
+
+ final ArrayMap<String, Integer> map = new ArrayMap<>(1);
+ map.put(mDetectedRemoteId, mDetectedFieldScore);
+ return map;
+ }
+
+ /**
* Returns which fields were available on datasets provided by the service but manually
* entered by the user.
*
@@ -430,7 +472,6 @@ public final class FillEventHistory implements Parcelable {
* and belonged to datasets.
* @param manuallyFilledDatasetIds The ids of datasets that had values matching the
* respective entry on {@code manuallyFilledFieldIds}.
- *
* @throws IllegalArgumentException If the length of {@code changedFieldIds} and
* {@code changedDatasetIds} doesn't match.
* @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
@@ -438,13 +479,15 @@ public final class FillEventHistory implements Parcelable {
*
* @hide
*/
+ // TODO(b/67867469): document detection field parameters once stable
public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
@Nullable List<String> selectedDatasetIds,
@Nullable ArraySet<String> ignoredDatasetIds,
@Nullable ArrayList<AutofillId> changedFieldIds,
@Nullable ArrayList<String> changedDatasetIds,
@Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
- @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+ @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
+ @Nullable String detectedRemoteId, int detectedFieldScore) {
mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
"eventType");
mDatasetId = datasetId;
@@ -467,6 +510,8 @@ public final class FillEventHistory implements Parcelable {
}
mManuallyFilledFieldIds = manuallyFilledFieldIds;
mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
+ mDetectedRemoteId = detectedRemoteId;
+ mDetectedFieldScore = detectedFieldScore;
}
@Override
@@ -479,6 +524,8 @@ public final class FillEventHistory implements Parcelable {
+ ", changedDatasetsIds=" + mChangedDatasetIds
+ ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
+ ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+ + ", detectedRemoteId=" + mDetectedRemoteId
+ + ", detectedFieldScore=" + mDetectedFieldScore
+ "]";
}
}
@@ -514,11 +561,15 @@ public final class FillEventHistory implements Parcelable {
} else {
manuallyFilledDatasetIds = null;
}
+ final String detectedRemoteId = parcel.readString();
+ final int detectedFieldScore = detectedRemoteId == null ? -1
+ : parcel.readInt();
selection.addEvent(new Event(eventType, datasetId, clientState,
selectedDatasetIds, ignoredDatasets,
changedFieldIds, changedDatasetIds,
- manuallyFilledFieldIds, manuallyFilledDatasetIds));
+ manuallyFilledFieldIds, manuallyFilledDatasetIds,
+ detectedRemoteId, detectedFieldScore));
}
return selection;
}
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
index 2f6342af..4e6a8845 100644
--- a/android/service/autofill/FillResponse.java
+++ b/android/service/autofill/FillResponse.java
@@ -22,6 +22,7 @@ import static android.view.autofill.Helper.sDebug;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.Activity;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
@@ -75,6 +76,7 @@ public final class FillResponse implements Parcelable {
private final @Nullable AutofillId[] mAuthenticationIds;
private final @Nullable AutofillId[] mIgnoredIds;
private final long mDisableDuration;
+ private final @Nullable FieldsDetection mFieldsDetection;
private final int mFlags;
private int mRequestId;
@@ -87,6 +89,7 @@ public final class FillResponse implements Parcelable {
mAuthenticationIds = builder.mAuthenticationIds;
mIgnoredIds = builder.mIgnoredIds;
mDisableDuration = builder.mDisableDuration;
+ mFieldsDetection = builder.mFieldsDetection;
mFlags = builder.mFlags;
mRequestId = INVALID_REQUEST_ID;
}
@@ -132,6 +135,11 @@ public final class FillResponse implements Parcelable {
}
/** @hide */
+ public @Nullable FieldsDetection getFieldsDetection() {
+ return mFieldsDetection;
+ }
+
+ /** @hide */
public int getFlags() {
return mFlags;
}
@@ -167,6 +175,7 @@ public final class FillResponse implements Parcelable {
private AutofillId[] mAuthenticationIds;
private AutofillId[] mIgnoredIds;
private long mDisableDuration;
+ private FieldsDetection mFieldsDetection;
private int mFlags;
private boolean mDestroyed;
@@ -315,6 +324,25 @@ public final class FillResponse implements Parcelable {
}
/**
+ * TODO(b/67867469):
+ * - javadoc it
+ * - javadoc how to check results
+ * - unhide
+ * - unhide / remove testApi
+ * - throw exception (and document) if response has datasets or saveinfo
+ * - throw exception (and document) if id on fieldsDetection is ignored
+ *
+ * @hide
+ */
+ @TestApi
+ public Builder setFieldsDetection(@NonNull FieldsDetection fieldsDetection) {
+ throwIfDestroyed();
+ throwIfDisableAutofillCalled();
+ mFieldsDetection = Preconditions.checkNotNull(fieldsDetection);
+ return this;
+ }
+
+ /**
* Sets flags changing the response behavior.
*
* @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
@@ -365,7 +393,8 @@ public final class FillResponse implements Parcelable {
if (duration <= 0) {
throw new IllegalArgumentException("duration must be greater than 0");
}
- if (mAuthentication != null || mDatasets != null || mSaveInfo != null) {
+ if (mAuthentication != null || mDatasets != null || mSaveInfo != null
+ || mFieldsDetection != null) {
throw new IllegalStateException("disableAutofill() must be the only method called");
}
@@ -388,11 +417,11 @@ public final class FillResponse implements Parcelable {
*/
public FillResponse build() {
throwIfDestroyed();
-
if (mAuthentication == null && mDatasets == null && mSaveInfo == null
- && mDisableDuration == 0) {
- throw new IllegalStateException("need to provide at least one DataSet or a "
- + "SaveInfo or an authentication with a presentation or disable autofill");
+ && mDisableDuration == 0 && mFieldsDetection == null) {
+ throw new IllegalStateException("need to provide: at least one DataSet, or a "
+ + "SaveInfo, or an authentication with a presentation, "
+ + "or a FieldsDetection, or disable autofill");
}
mDestroyed = true;
return new FillResponse(this);
@@ -430,6 +459,7 @@ public final class FillResponse implements Parcelable {
.append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
.append(", disableDuration=").append(mDisableDuration)
.append(", flags=").append(mFlags)
+ .append(", fieldDetection=").append(mFieldsDetection)
.append("]")
.toString();
}
@@ -453,6 +483,7 @@ public final class FillResponse implements Parcelable {
parcel.writeParcelable(mPresentation, flags);
parcel.writeParcelableArray(mIgnoredIds, flags);
parcel.writeLong(mDisableDuration);
+ parcel.writeParcelable(mFieldsDetection, flags);
parcel.writeInt(mFlags);
parcel.writeInt(mRequestId);
}
@@ -488,6 +519,10 @@ public final class FillResponse implements Parcelable {
if (disableDuration > 0) {
builder.disableAutofill(disableDuration);
}
+ final FieldsDetection fieldsDetection = parcel.readParcelable(null);
+ if (fieldsDetection != null) {
+ builder.setFieldsDetection(fieldsDetection);
+ }
builder.setFlags(parcel.readInt());
final FillResponse response = builder.build();
diff --git a/android/service/dreams/DreamService.java b/android/service/dreams/DreamService.java
index 6a15aded..2a245d04 100644
--- a/android/service/dreams/DreamService.java
+++ b/android/service/dreams/DreamService.java
@@ -680,8 +680,8 @@ public class DreamService extends Service implements Window.Callback {
*
* @return The screen state to use while dozing, such as {@link Display#STATE_ON},
* {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
- * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
- * behavior.
+ * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+ * for the default behavior.
*
* @see #setDozeScreenState
* @hide For use by system UI components only.
@@ -700,12 +700,18 @@ public class DreamService extends Service implements Window.Callback {
* perform transitions between states while dozing to conserve power and
* achieve various effects.
* </p><p>
- * It is recommended that the state be set to {@link Display#STATE_DOZE_SUSPEND}
- * once the dream has completely finished drawing and before it releases its wakelock
- * to allow the display hardware to be fully suspended. While suspended, the
- * display will preserve its on-screen contents or hand off control to dedicated
- * doze hardware if the devices supports it. If the doze suspend state is
- * used, the dream must make sure to set the mode back
+ * Some devices will have dedicated hardware ("Sidekick") to animate
+ * the display content while the CPU sleeps. If the dream and the hardware support
+ * this, {@link Display#STATE_ON_SUSPEND} or {@link Display#STATE_DOZE_SUSPEND}
+ * will switch control to the Sidekick.
+ * </p><p>
+ * If not using Sidekick, it is recommended that the state be set to
+ * {@link Display#STATE_DOZE_SUSPEND} once the dream has completely
+ * finished drawing and before it releases its wakelock
+ * to allow the display hardware to be fully suspended. While suspended,
+ * the display will preserve its on-screen contents.
+ * </p><p>
+ * If the doze suspend state is used, the dream must make sure to set the mode back
* to {@link Display#STATE_DOZE} or {@link Display#STATE_ON} before drawing again
* since the display updates may be ignored and not seen by the user otherwise.
* </p><p>
@@ -716,8 +722,8 @@ public class DreamService extends Service implements Window.Callback {
*
* @param state The screen state to use while dozing, such as {@link Display#STATE_ON},
* {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
- * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
- * behavior.
+ * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+ * for the default behavior.
*
* @hide For use by system UI components only.
*/
diff --git a/android/service/euicc/EuiccService.java b/android/service/euicc/EuiccService.java
index 0c2e4b7a..cd233b83 100644
--- a/android/service/euicc/EuiccService.java
+++ b/android/service/euicc/EuiccService.java
@@ -97,6 +97,10 @@ public abstract class EuiccService extends Service {
public static final String ACTION_RESOLVE_NO_PRIVILEGES =
"android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
+ /** Ask the user to input carrier confirmation code. */
+ public static final String ACTION_RESOLVE_CONFIRMATION_CODE =
+ "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
+
/** Intent extra set for resolution requests containing the package name of the calling app. */
public static final String EXTRA_RESOLUTION_CALLING_PACKAGE =
"android.service.euicc.extra.RESOLUTION_CALLING_PACKAGE";
@@ -105,6 +109,8 @@ public abstract class EuiccService extends Service {
public static final int RESULT_OK = 0;
/** Result code indicating that an active SIM must be deactivated to perform the operation. */
public static final int RESULT_MUST_DEACTIVATE_SIM = -1;
+ /** Result code indicating that the user must input a carrier confirmation code. */
+ public static final int RESULT_NEED_CONFIRMATION_CODE = -2;
// New predefined codes should have negative values.
/** Start of implementation-specific error results. */
@@ -119,10 +125,13 @@ public abstract class EuiccService extends Service {
RESOLUTION_ACTIONS = new ArraySet<>();
RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM);
RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_NO_PRIVILEGES);
+ RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE);
}
/** Boolean extra for resolution actions indicating whether the user granted consent. */
public static final String RESOLUTION_EXTRA_CONSENT = "consent";
+ /** String extra for resolution actions indicating the carrier confirmation code. */
+ public static final String RESOLUTION_EXTRA_CONFIRMATION_CODE = "confirmation_code";
private final IEuiccService.Stub mStubWrapper;
diff --git a/android/service/notification/ConditionProviderService.java b/android/service/notification/ConditionProviderService.java
index 3e992ec3..6fc689ab 100644
--- a/android/service/notification/ConditionProviderService.java
+++ b/android/service/notification/ConditionProviderService.java
@@ -18,6 +18,8 @@ package android.service.notification;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Service;
import android.content.ComponentName;
@@ -56,6 +58,8 @@ import android.util.Log;
* &lt;/meta-data>
* &lt;/service></pre>
*
+ * <p> Condition providers cannot be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
*/
public abstract class ConditionProviderService extends Service {
private final String TAG = ConditionProviderService.class.getSimpleName()
@@ -197,7 +201,11 @@ public abstract class ConditionProviderService extends Service {
return mProvider;
}
- private boolean isBound() {
+ /**
+ * @hide
+ */
+ @TestApi
+ public boolean isBound() {
if (mProvider == null) {
Log.w(TAG, "Condition provider service not yet bound.");
return false;
diff --git a/android/service/notification/NotificationListenerService.java b/android/service/notification/NotificationListenerService.java
index 08d3118b..dac663e7 100644
--- a/android/service/notification/NotificationListenerService.java
+++ b/android/service/notification/NotificationListenerService.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Notification.Builder;
@@ -82,6 +83,8 @@ import java.util.List;
* method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
* or after {@link #onListenerDisconnected()}.
* </p>
+ * <p> Notification listeners cannot get notification access or be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
*/
public abstract class NotificationListenerService extends Service {
diff --git a/android/service/voice/VoiceInteractionSession.java b/android/service/voice/VoiceInteractionSession.java
index 625dd9eb..cd177c42 100644
--- a/android/service/voice/VoiceInteractionSession.java
+++ b/android/service/voice/VoiceInteractionSession.java
@@ -16,6 +16,8 @@
package android.service.voice;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
import android.annotation.Nullable;
import android.app.Activity;
import android.app.Dialog;
@@ -46,7 +48,6 @@ import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -63,8 +64,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
/**
* 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
@@ -110,16 +109,6 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
*/
public static final int SHOW_SOURCE_ACTIVITY = 1<<4;
- // Keys for Bundle values
- /** @hide */
- public static final String KEY_DATA = "data";
- /** @hide */
- public static final String KEY_STRUCTURE = "structure";
- /** @hide */
- public static final String KEY_CONTENT = "content";
- /** @hide */
- public static final String KEY_RECEIVER_EXTRAS = "receiverExtras";
-
final Context mContext;
final HandlerCaller mHandlerCaller;
@@ -1423,9 +1412,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
public void setContentView(View view) {
ensureWindowCreated();
mContentFrame.removeAllViews();
- mContentFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
+ mContentFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentFrame.requestApplyInsets();
}
diff --git a/android/support/LibraryGroups.java b/android/support/LibraryGroups.java
new file mode 100644
index 00000000..feaefbc6
--- /dev/null
+++ b/android/support/LibraryGroups.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support;
+
+/**
+ * The list of maven group names of all the libraries in this project.
+ */
+public class LibraryGroups {
+ public static final String SUPPORT = "com.android.support";
+ public static final String ROOM = "android.arch.persistence.room";
+ public static final String PERSISTENCE = "android.arch.persistence";
+ public static final String LIFECYCLE = "android.arch.lifecycle";
+ public static final String ARCH_CORE = "android.arch.core";
+ public static final String PAGING = "android.arch.paging";
+ public static final String NAVIGATION = "android.arch.navigation";
+}
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index 2f5730a2..efa0cbae 100644
--- a/android/support/LibraryVersions.java
+++ b/android/support/LibraryVersions.java
@@ -28,7 +28,7 @@ public class LibraryVersions {
/**
* Version code for flatfoot 1.0 projects (room, lifecycles)
*/
- private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0-rc1");
+ private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0");
/**
* Version code for Room
diff --git a/android/support/SourceJarTaskHelper.java b/android/support/SourceJarTaskHelper.java
new file mode 100644
index 00000000..9fbd1dba
--- /dev/null
+++ b/android/support/SourceJarTaskHelper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support;
+
+import com.android.build.gradle.LibraryExtension;
+import com.android.builder.core.BuilderConstants;
+
+import org.gradle.api.Project;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.bundling.Jar;
+
+/**
+ * Helper class to handle creation of source jars.
+ */
+public class SourceJarTaskHelper {
+ /**
+ * Sets up a source jar task for an Android library project.
+ */
+ public static void setUpAndroidProject(Project project, LibraryExtension extension) {
+ // Create sources jar for release builds
+ extension.getLibraryVariants().all(libraryVariant -> {
+ if (!libraryVariant.getBuildType().getName().equals(BuilderConstants.RELEASE)) {
+ return; // Skip non-release builds.
+ }
+
+ Jar sourceJar = project.getTasks().create("sourceJarRelease", Jar.class);
+ sourceJar.setPreserveFileTimestamps(false);
+ sourceJar.setClassifier("sources");
+ sourceJar.from(extension.getSourceSets().findByName("main").getJava().getSrcDirs());
+ project.getArtifacts().add("archives", sourceJar);
+ });
+ }
+
+ /**
+ * Sets up a source jar task for a Java library project.
+ */
+ public static void setUpJavaProject(Project project) {
+ Jar sourceJar = project.getTasks().create("sourceJar", Jar.class);
+ sourceJar.setPreserveFileTimestamps(false);
+ sourceJar.setClassifier("sources");
+ JavaPluginConvention convention =
+ project.getConvention().getPlugin(JavaPluginConvention.class);
+ sourceJar.from(convention.getSourceSets().findByName("main").getAllSource().getSrcDirs());
+ project.getArtifacts().add("archives", sourceJar);
+ }
+}
diff --git a/android/support/car/drawer/CarDrawerActivity.java b/android/support/car/drawer/CarDrawerActivity.java
index 7100218a..f46c652b 100644
--- a/android/support/car/drawer/CarDrawerActivity.java
+++ b/android/support/car/drawer/CarDrawerActivity.java
@@ -46,7 +46,7 @@ import android.view.ViewGroup;
*
* <p>The rootAdapter can implement nested-navigation, in its click-handling, by passing the
* CarDrawerAdapter for the next level to
- * {@link CarDrawerController#switchToAdapter(CarDrawerAdapter)}.
+ * {@link CarDrawerController#pushAdapter(CarDrawerAdapter)}.
*
* <p>Any Activity's based on this class need to set their theme to CarDrawerActivityTheme or a
* derivative.
diff --git a/android/support/car/drawer/CarDrawerController.java b/android/support/car/drawer/CarDrawerController.java
index 4d9f4e99..7b23714c 100644
--- a/android/support/car/drawer/CarDrawerController.java
+++ b/android/support/car/drawer/CarDrawerController.java
@@ -19,16 +19,19 @@ package android.support.car.drawer;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.support.annotation.AnimRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.car.R;
import android.support.car.widget.PagedListView;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
+import android.view.animation.AnimationUtils;
import android.widget.ProgressBar;
import java.util.Stack;
@@ -39,13 +42,21 @@ import java.util.Stack;
* navigation.
*/
public class CarDrawerController {
+ /** An animation for when a user navigates into a submenu. */
+ @AnimRes
+ private static final int DRILL_DOWN_ANIM = R.anim.fade_in_trans_right_layout_anim;
+
+ /** An animation for when a user navigates up (when the back button is pressed). */
+ @AnimRes
+ private static final int NAVIGATE_UP_ANIM = R.anim.fade_in_trans_left_layout_anim;
+
/** The amount that the drawer has been opened before its color should be switched. */
private static final float COLOR_SWITCH_SLIDE_OFFSET = 0.25f;
/**
* A representation of the hierarchy of navigation being displayed in the list. The ordering of
* this stack is the order that the user has visited each level. When the user navigates up,
- * the adapters are poopped from this list.
+ * the adapters are popped from this list.
*/
private final Stack<CarDrawerAdapter> mAdapterStack = new Stack<>();
@@ -78,16 +89,14 @@ public class CarDrawerController {
ActionBarDrawerToggle drawerToggle) {
mToolbar = toolbar;
mContext = drawerLayout.getContext();
-
+ mDrawerToggle = drawerToggle;
mDrawerLayout = drawerLayout;
mDrawerContent = drawerLayout.findViewById(R.id.drawer_content);
mDrawerList = drawerLayout.findViewById(R.id.drawer_list);
mDrawerList.setMaxPages(PagedListView.ItemCap.UNLIMITED);
-
mProgressBar = drawerLayout.findViewById(R.id.drawer_progress);
- mDrawerToggle = drawerToggle;
setupDrawerToggling();
}
@@ -104,7 +113,13 @@ public class CarDrawerController {
return;
}
- mAdapterStack.push(rootAdapter);
+ // The root adapter is always the last item in the stack.
+ if (mAdapterStack.size() > 0) {
+ mAdapterStack.set(0, rootAdapter);
+ } else {
+ mAdapterStack.push(rootAdapter);
+ }
+
setToolbarTitleFrom(rootAdapter);
mDrawerList.setAdapter(rootAdapter);
}
@@ -120,10 +135,11 @@ public class CarDrawerController {
*
* @param adapter Adapter for next level of content in the drawer.
*/
- public final void switchToAdapter(CarDrawerAdapter adapter) {
+ public final void pushAdapter(CarDrawerAdapter adapter) {
mAdapterStack.peek().setTitleChangeListener(null);
mAdapterStack.push(adapter);
- switchToAdapterInternal(adapter);
+ setDisplayAdapter(adapter);
+ runLayoutAnimation(DRILL_DOWN_ANIM);
}
/** Close the drawer. */
@@ -264,15 +280,15 @@ public class CarDrawerController {
}
/**
- * Sets the navigation drawer's title to be the one supplied by the given adapter and updates
- * the navigation drawer list with the adapter's contents.
+ * Sets the given adapter as the one displaying the current contents of the drawer.
+ *
+ * <p>The drawer's title will also be derived from the given adapter.
*/
- private void switchToAdapterInternal(CarDrawerAdapter adapter) {
+ private void setDisplayAdapter(CarDrawerAdapter adapter) {
setToolbarTitleFrom(adapter);
// NOTE: We don't use swapAdapter() since different levels in the Drawer may switch between
// car_drawer_list_item_normal, car_drawer_list_item_small and car_list_empty layouts.
mDrawerList.getRecyclerView().setAdapter(adapter);
- scrollToPosition(0);
}
/**
@@ -290,7 +306,8 @@ public class CarDrawerController {
CarDrawerAdapter adapter = mAdapterStack.pop();
adapter.setTitleChangeListener(null);
adapter.cleanup();
- switchToAdapterInternal(mAdapterStack.peek());
+ setDisplayAdapter(mAdapterStack.peek());
+ runLayoutAnimation(NAVIGATE_UP_ANIM);
return true;
}
@@ -301,6 +318,18 @@ public class CarDrawerController {
adapter.setTitleChangeListener(null);
adapter.cleanup();
}
- switchToAdapterInternal(mAdapterStack.peek());
+ setDisplayAdapter(mAdapterStack.peek());
+ runLayoutAnimation(NAVIGATE_UP_ANIM);
+ }
+
+ /**
+ * Runs the given layout animation on the PagedListView. Running this animation will also
+ * refresh the contents of the list.
+ */
+ private void runLayoutAnimation(@AnimRes int animation) {
+ RecyclerView recyclerView = mDrawerList.getRecyclerView();
+ recyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, animation));
+ recyclerView.getAdapter().notifyDataSetChanged();
+ recyclerView.scheduleLayoutAnimation();
}
}
diff --git a/android/support/car/utils/ColumnCalculator.java b/android/support/car/utils/ColumnCalculator.java
index 96e081b9..fa5dd432 100644
--- a/android/support/car/utils/ColumnCalculator.java
+++ b/android/support/car/utils/ColumnCalculator.java
@@ -65,7 +65,7 @@ public class ColumnCalculator {
private ColumnCalculator(Context context) {
Resources res = context.getResources();
- int marginSize = res.getDimensionPixelSize(R.dimen.car_screen_margin_size);
+ int marginSize = res.getDimensionPixelSize(R.dimen.car_margin);
mGutterSize = res.getDimensionPixelSize(R.dimen.car_screen_gutter_size);
mNumOfColumns = res.getInteger(R.integer.car_screen_num_of_columns);
diff --git a/android/support/car/widget/CarItemAnimator.java b/android/support/car/widget/CarItemAnimator.java
index 4dd32127..ef22c484 100644
--- a/android/support/car/widget/CarItemAnimator.java
+++ b/android/support/car/widget/CarItemAnimator.java
@@ -22,9 +22,9 @@ import android.support.v7.widget.RecyclerView;
/** {@link DefaultItemAnimator} with a few minor changes where it had undesired behavior. */
public class CarItemAnimator extends DefaultItemAnimator {
- private final CarLayoutManager mLayoutManager;
+ private final PagedLayoutManager mLayoutManager;
- public CarItemAnimator(CarLayoutManager layoutManager) {
+ public CarItemAnimator(PagedLayoutManager layoutManager) {
mLayoutManager = layoutManager;
}
diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java
index 2684c58a..bb9cb71a 100644
--- a/android/support/car/widget/CarRecyclerView.java
+++ b/android/support/car/widget/CarRecyclerView.java
@@ -26,7 +26,7 @@ import android.view.View;
import android.view.ViewGroup;
/**
- * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate.
+ * Custom {@link RecyclerView} that helps {@link PagedLayoutManager} properly fling and paginate.
*
* <p>It also has the ability to fade children as they scroll off screen that can be set with {@link
* #setFadeLastItem(boolean)}.
@@ -57,7 +57,7 @@ public class CarRecyclerView extends RecyclerView {
@Override
public boolean fling(int velocityX, int velocityY) {
mWasFlingCalledForGesture = true;
- return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
+ return ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
}
@Override
@@ -69,7 +69,7 @@ public class CarRecyclerView extends RecyclerView {
int action = e.getActionMasked();
if (action == MotionEvent.ACTION_UP) {
if (!mWasFlingCalledForGesture) {
- ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
+ ((PagedLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
}
mWasFlingCalledForGesture = false;
}
@@ -102,7 +102,7 @@ public class CarRecyclerView extends RecyclerView {
* number of items that fit completely on the screen.
*/
public void pageUp() {
- CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+ PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager();
int pageUpPosition = lm.getPageUpPosition();
if (pageUpPosition == -1) {
return;
@@ -116,7 +116,7 @@ public class CarRecyclerView extends RecyclerView {
* number of items that fit completely on the screen.
*/
public void pageDown() {
- CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+ PagedLayoutManager lm = (PagedLayoutManager) getLayoutManager();
int pageDownPosition = lm.getPageDownPosition();
if (pageDownPosition == -1) {
return;
diff --git a/android/support/car/widget/CarLayoutManager.java b/android/support/car/widget/PagedLayoutManager.java
index d0d3a9e1..c4f469a3 100644
--- a/android/support/car/widget/CarLayoutManager.java
+++ b/android/support/car/widget/PagedLayoutManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@ package android.support.car.widget;
import android.content.Context;
import android.graphics.PointF;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
@@ -46,9 +48,9 @@ import java.util.ArrayList;
*
* <ol>
* <li>In a normal ListView, when views reach the top of the list, they are clipped. In
- * CarLayoutManager, views have the option of flying off of the top of the screen as the next
- * row settles in to place. This functionality can be enabled or disabled with {@link
- * #setOffsetRows(boolean)}.
+ * PagedLayoutManager, views have the option of flying off of the top of the screen as the
+ * next row settles in to place. This functionality can be enabled or disabled with
+ * {@link #setOffsetRows(boolean)}.
* <li>Standard list physics is disabled. Instead, when the user scrolls, it will settle on the
* next page.
* <li>Items can scroll past the bottom edge of the screen. This helps with pagination so that the
@@ -57,8 +59,8 @@ import java.util.ArrayList;
*
* This LayoutManger should be used with {@link CarRecyclerView}.
*/
-public class CarLayoutManager extends RecyclerView.LayoutManager {
- private static final String TAG = "CarLayoutManager";
+public class PagedLayoutManager extends RecyclerView.LayoutManager {
+ private static final String TAG = "PagedLayoutManager";
/**
* Any fling below the threshold will just scroll to the top fully visible row. The units is
@@ -166,7 +168,7 @@ public class CarLayoutManager extends RecyclerView.LayoutManager {
/** Set the anchor to the following position on the next layout pass. */
private int mPendingScrollPosition = -1;
- public CarLayoutManager(Context context) {
+ public PagedLayoutManager(Context context) {
mContext = context;
}
@@ -919,6 +921,55 @@ public class CarLayoutManager extends RecyclerView.LayoutManager {
return mLowerPageBreakPosition;
}
+ @Override
+ public Parcelable onSaveInstanceState() {
+ SavedState savedState = new SavedState();
+ savedState.mFirstChildPosition = getFirstFullyVisibleChildPosition();
+ return savedState;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof SavedState) {
+ scrollToPosition(((SavedState) state).mFirstChildPosition);
+ }
+ }
+
+ /** The state that will be saved across configuration changes. */
+ static class SavedState implements Parcelable {
+ /** The position of the first visible child view in the list. */
+ int mFirstChildPosition;
+
+ SavedState() {}
+
+ private SavedState(Parcel in) {
+ mFirstChildPosition = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFirstChildPosition);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
/**
* Layout the anchor row. The anchor row is the first fully visible row.
*
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
index 46527001..4695c45c 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -23,7 +23,11 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -33,6 +37,7 @@ import android.support.car.R;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -41,13 +46,19 @@ import android.widget.FrameLayout;
/**
* Custom {@link android.support.v7.widget.RecyclerView} that displays a list of items that
* resembles a {@link android.widget.ListView} but also has page up and page down arrows on the
- * right side.
+ * left side.
*/
public class PagedListView extends FrameLayout {
/** Default maximum number of clicks allowed on a list */
public static final int DEFAULT_MAX_CLICKS = 6;
/**
+ * Value to pass to {@link #setMaxPages(int)} to indicate there is no restriction on the
+ * maximum number of pages to show.
+ */
+ public static final int UNLIMITED_PAGES = -1;
+
+ /**
* The amount of time after settling to wait before autoscrolling to the next page when the user
* holds down a pagination button.
*/
@@ -57,7 +68,7 @@ public class PagedListView extends FrameLayout {
private static final int INVALID_RESOURCE_ID = -1;
protected final CarRecyclerView mRecyclerView;
- protected final CarLayoutManager mLayoutManager;
+ protected final PagedLayoutManager mLayoutManager;
protected final Handler mHandler = new Handler();
private final boolean mScrollBarEnabled;
private final PagedScrollBarView mScrollBarView;
@@ -65,8 +76,8 @@ public class PagedListView extends FrameLayout {
private int mRowsPerPage = -1;
protected RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
- /** Maximum number of pages to show. Values < 0 show all pages. */
- private int mMaxPages = -1;
+ /** Maximum number of pages to show. */
+ private int mMaxPages;
protected OnScrollListener mOnScrollListener;
@@ -115,8 +126,6 @@ public class PagedListView extends FrameLayout {
* the item in position 20 instead, for position 1 it will show the item in position 21 instead
* and so on.
*/
- // TODO(b/28003781): ItemPositionOffset and ItemCap interfaces should be merged once
- // we enable AlphaJump outside drawer.
public interface ItemPositionOffset {
/** Sets the position offset for the adapter. */
void setPositionOffset(int positionOffset);
@@ -151,7 +160,7 @@ public class PagedListView extends FrameLayout {
mMaxPages = getDefaultMaxPages();
- mLayoutManager = new CarLayoutManager(context);
+ mLayoutManager = new PagedLayoutManager(context);
mLayoutManager.setOffsetRows(offsetRows);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener);
@@ -162,7 +171,7 @@ public class PagedListView extends FrameLayout {
if (offsetScrollBar) {
MarginLayoutParams params = (MarginLayoutParams) mRecyclerView.getLayoutParams();
params.setMarginStart(getResources().getDimensionPixelSize(
- R.dimen.car_screen_margin_size));
+ R.dimen.car_margin));
params.setMarginEnd(
a.getDimensionPixelSize(R.styleable.PagedListView_listEndMargin, 0));
mRecyclerView.setLayoutParams(params);
@@ -180,6 +189,11 @@ public class PagedListView extends FrameLayout {
dividerStartId, dividerEndId));
}
+ int itemSpacing = a.getDimensionPixelSize(R.styleable.PagedListView_itemSpacing, 0);
+ if (itemSpacing > 0) {
+ mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
+ }
+
// Set this to true so that this view consumes clicks events and views underneath
// don't receive this click event. Without this it's possible to click places in the
// view that don't capture the event, and as a result, elements visually hidden consume
@@ -212,6 +226,16 @@ public class PagedListView extends FrameLayout {
}
});
+ Drawable upButtonIcon = a.getDrawable(R.styleable.PagedListView_upButtonIcon);
+ if (upButtonIcon != null) {
+ setUpButtonIcon(upButtonIcon);
+ }
+
+ Drawable downButtonIcon = a.getDrawable(R.styleable.PagedListView_downButtonIcon);
+ if (downButtonIcon != null) {
+ setDownButtonIcon(downButtonIcon);
+ }
+
mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE);
// Modify the layout the Scroll Bar is not visible.
@@ -236,7 +260,7 @@ public class PagedListView extends FrameLayout {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
// The user has interacted with the list using touch. All movements will now paginate
// the list.
- mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_PAGE);
+ mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_PAGE);
}
return super.onInterceptTouchEvent(e);
}
@@ -246,7 +270,7 @@ public class PagedListView extends FrameLayout {
super.requestChildFocus(child, focused);
// The user has interacted with the list using the controller. Movements through the list
// will now be one row at a time.
- mLayoutManager.setRowOffsetMode(CarLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL);
+ mLayoutManager.setRowOffsetMode(PagedLayoutManager.ROW_OFFSET_MODE_INDIVIDUAL);
}
/**
@@ -312,19 +336,25 @@ public class PagedListView extends FrameLayout {
mHandler.post(mUpdatePaginationRunnable);
}
+ /** Sets the icon to be used for the up button. */
+ public void setUpButtonIcon(Drawable icon) {
+ mScrollBarView.setUpButtonIcon(icon);
+ }
+
+ /** Sets the icon to be used for the down button. */
+ public void setDownButtonIcon(Drawable icon) {
+ mScrollBarView.setDownButtonIcon(icon);
+ }
+
/**
* Sets the adapter for the list.
*
- * <p>It <em>must</em> implement {@link ItemCap}, otherwise, will throw an {@link
- * IllegalArgumentException}.
+ * <p>The given Adapter can implement {@link ItemCap} if it wishes to control the behavior of
+ * a max number of items. Otherwise, methods in the PagedListView to limit the content, such as
+ * {@link #setMaxPages(int)}, will do nothing.
*/
public void setAdapter(
@NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
- if (!(adapter instanceof ItemCap)) {
- throw new IllegalArgumentException("ERROR: adapter ["
- + adapter.getClass().getCanonicalName() + "] MUST implement ItemCap");
- }
-
mAdapter = adapter;
mRecyclerView.setAdapter(adapter);
updateMaxItems();
@@ -333,7 +363,7 @@ public class PagedListView extends FrameLayout {
/** @hide */
@RestrictTo(LIBRARY_GROUP)
@NonNull
- public CarLayoutManager getLayoutManager() {
+ public PagedLayoutManager getLayoutManager() {
return mLayoutManager;
}
@@ -345,15 +375,19 @@ public class PagedListView extends FrameLayout {
/**
* Sets the maximum number of the pages that can be shown in the PagedListView. The size of a
- * page is defined as the number of items that fit completely on the screen at once.
+ * page is defined as the number of items that fit completely on the screen at once.
+ *
+ * <p>Passing {@link #UNLIMITED_PAGES} will remove any restrictions on a maximum number
+ * of pages.
+ *
+ * <p>Note that for any restriction on maximum pages to work, the adapter passed to this
+ * PagedListView needs to implement {@link ItemCap}.
*
- * @param maxPages The maximum number of pages that fit on the screen. Should be positive.
+ * @param maxPages The maximum number of pages that fit on the screen. Should be positive or
+ * {@link #UNLIMITED_PAGES}.
*/
public void setMaxPages(int maxPages) {
- if (maxPages < 0) {
- return;
- }
- mMaxPages = maxPages;
+ mMaxPages = Math.max(UNLIMITED_PAGES, maxPages);
updateMaxItems();
}
@@ -362,7 +396,8 @@ public class PagedListView extends FrameLayout {
* {@link #setMaxPages(int)}. If that method has not been called, then this value should match
* the default value.
*
- * @return The maximum number of pages to be shown.
+ * @return The maximum number of pages to be shown or {@link #UNLIMITED_PAGES} if there is
+ * no limit.
*/
public int getMaxPages() {
return mMaxPages;
@@ -370,7 +405,7 @@ public class PagedListView extends FrameLayout {
/**
* Gets the number of rows per page. Default value of mRowsPerPage is -1. If the first child of
- * CarLayoutManager is null or the height of the first child is 0, it will return 1.
+ * PagedLayoutManager is null or the height of the first child is 0, it will return 1.
*/
public int getRowsPerPage() {
return mRowsPerPage;
@@ -422,6 +457,32 @@ public class PagedListView extends FrameLayout {
}
/**
+ * Sets spacing between each item in the list. The spacing will not be added before the first
+ * item and after the last.
+ *
+ * @param itemSpacing the spacing between each item.
+ */
+ public void setItemSpacing(int itemSpacing) {
+ ItemSpacingDecoration existing = null;
+ for (int i = 0, count = mRecyclerView.getItemDecorationCount(); i < count; i++) {
+ RecyclerView.ItemDecoration itemDecoration = mRecyclerView.getItemDecorationAt(i);
+ if (itemDecoration instanceof ItemSpacingDecoration) {
+ existing = (ItemSpacingDecoration) itemDecoration;
+ break;
+ }
+ }
+
+ if (itemSpacing == 0 && existing != null) {
+ mRecyclerView.removeItemDecoration(existing);
+ } else if (existing == null) {
+ mRecyclerView.addItemDecoration(new ItemSpacingDecoration(itemSpacing));
+ } else {
+ existing.setItemSpacing(itemSpacing);
+ }
+ mRecyclerView.invalidateItemDecorations();
+ }
+
+ /**
* Adds an {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} to this
* PagedListView.
*
@@ -520,6 +581,7 @@ public class PagedListView extends FrameLayout {
return;
}
mDefaultMaxPages = newDefault;
+ resetMaxPages();
}
/** Returns the default number of pages the list should have */
@@ -646,8 +708,15 @@ public class PagedListView extends FrameLayout {
return;
}
- final int originalCount = mAdapter.getItemCount();
+ // Ensure mRowsPerPage regardless of if the adapter implements ItemCap.
updateRowsPerPage();
+
+ // If the adapter does not implement ItemCap, then the max items on it cannot be updated.
+ if (!(mAdapter instanceof ItemCap)) {
+ return;
+ }
+
+ final int originalCount = mAdapter.getItemCount();
((ItemCap) mAdapter).setMaxItems(calculateMaxItemCount());
final int newCount = mAdapter.getItemCount();
if (newCount == originalCount) {
@@ -683,6 +752,78 @@ public class PagedListView extends FrameLayout {
}
}
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ SavedState savedState = new SavedState(super.onSaveInstanceState());
+ savedState.mLayoutManagerState = mLayoutManager.onSaveInstanceState();
+ return savedState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState savedState = (SavedState) state;
+ mLayoutManager.onRestoreInstanceState(savedState.mLayoutManagerState);
+ super.onRestoreInstanceState(savedState.getSuperState());
+ }
+
+ @Override
+ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+ // There is the possibility of multiple PagedListViews on a page. This means that the ids
+ // of the child Views of PagedListView are no longer unique, and onSaveInstanceState()
+ // cannot be used. As a result, PagedListViews needs to manually dispatch the instance
+ // states. Call dispatchFreezeSelfOnly() so that no child views have onSaveInstanceState()
+ // called by the system.
+ dispatchFreezeSelfOnly(container);
+ }
+
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ // Prevent onRestoreInstanceState() from being called on child Views. Instead, PagedListView
+ // will manually handle passing the state. See the comment in dispatchSaveInstanceState()
+ // for more information.
+ dispatchThawSelfOnly(container);
+ }
+
+ /** The state that will be saved across configuration changes. */
+ private static class SavedState extends BaseSavedState {
+ /** The state of the {@link #mLayoutManager} of this PagedListView. */
+ Parcelable mLayoutManagerState;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ mLayoutManagerState =
+ in.readParcelable(PagedLayoutManager.SavedState.class.getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeParcelable(mLayoutManagerState, flags);
+ }
+
+ public static final ClassLoaderCreator<SavedState> CREATOR =
+ new ClassLoaderCreator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel source, ClassLoader loader) {
+ return new SavedState(source);
+ }
+
+ @Override
+ public SavedState createFromParcel(Parcel source) {
+ return createFromParcel(source, null /* loader */);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
new RecyclerView.OnScrollListener() {
@Override
@@ -766,16 +907,50 @@ public class PagedListView extends FrameLayout {
}
/**
+ * A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will add spacing
+ * between each item in the RecyclerView that it is added to.
+ */
+ private static class ItemSpacingDecoration extends RecyclerView.ItemDecoration {
+
+ private int mHalfItemSpacing;
+
+ private ItemSpacingDecoration(int itemSpacing) {
+ mHalfItemSpacing = itemSpacing / 2;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ // Skip top offset for first item and bottom offset for last.
+ int position = parent.getChildAdapterPosition(view);
+ if (position > 0) {
+ outRect.top = mHalfItemSpacing;
+ }
+ if (position < state.getItemCount() - 1) {
+ outRect.bottom = mHalfItemSpacing;
+ }
+ }
+
+ /**
+ * @param itemSpacing sets spacing between each item.
+ */
+ public void setItemSpacing(int itemSpacing) {
+ mHalfItemSpacing = itemSpacing / 2;
+ }
+ }
+
+ /**
* A {@link android.support.v7.widget.RecyclerView.ItemDecoration} that will draw a dividing
* line between each item in the RecyclerView that it is added to.
*/
- public static class DividerDecoration extends RecyclerView.ItemDecoration {
+ private static class DividerDecoration extends RecyclerView.ItemDecoration {
private final Context mContext;
private final Paint mPaint;
private final int mDividerHeight;
private final int mDividerStartMargin;
@IdRes private final int mDividerStartId;
- @IdRes private final int mDvidierEndId;
+ @IdRes private final int mDividerEndId;
/**
* @param dividerStartMargin The start offset of the dividing line. This offset will be
@@ -792,7 +967,7 @@ public class PagedListView extends FrameLayout {
mContext = context;
mDividerStartMargin = dividerStartMargin;
mDividerStartId = dividerStartId;
- mDvidierEndId = dividerEndId;
+ mDividerEndId = dividerEndId;
Resources res = context.getResources();
mPaint = new Paint();
@@ -807,16 +982,20 @@ public class PagedListView extends FrameLayout {
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
- for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++) {
+ // Draw a divider line between each item. No need to draw the line for the last item.
+ for (int i = 0, childCount = parent.getChildCount(); i < childCount - 1; i++) {
View container = parent.getChildAt(i);
+ View nextContainer = parent.getChildAt(i + 1);
+ int spacing = nextContainer.getTop() - container.getBottom();
+
View startChild =
mDividerStartId != INVALID_RESOURCE_ID
? container.findViewById(mDividerStartId)
: container;
View endChild =
- mDvidierEndId != INVALID_RESOURCE_ID
- ? container.findViewById(mDvidierEndId)
+ mDividerEndId != INVALID_RESOURCE_ID
+ ? container.findViewById(mDividerEndId)
: container;
if (startChild == null || endChild == null) {
@@ -825,14 +1004,24 @@ public class PagedListView extends FrameLayout {
int left = mDividerStartMargin + startChild.getLeft();
int right = endChild.getRight();
- int bottom = container.getBottom();
+ int bottom = container.getBottom() + spacing / 2 + mDividerHeight / 2;
int top = bottom - mDividerHeight;
- // Draw a divider line between each item. No need to draw the line for the last
- // item.
- if (i != childCount - 1) {
- c.drawRect(left, top, right, bottom, mPaint);
- }
+ c.drawRect(left, top, right, bottom, mPaint);
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ // Skip top offset for first item and bottom offset for last.
+ int position = parent.getChildAdapterPosition(view);
+ if (position > 0) {
+ outRect.top = mDividerHeight / 2;
+ }
+ if (position < state.getItemCount() - 1) {
+ outRect.bottom = mDividerHeight / 2;
}
}
}
diff --git a/android/support/car/widget/PagedScrollBarView.java b/android/support/car/widget/PagedScrollBarView.java
index 125b354c..1c46b5d4 100644
--- a/android/support/car/widget/PagedScrollBarView.java
+++ b/android/support/car/widget/PagedScrollBarView.java
@@ -18,6 +18,7 @@ package android.support.car.widget;
import android.content.Context;
import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
import android.support.car.R;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
@@ -98,6 +99,16 @@ public class PagedScrollBarView extends FrameLayout
return true;
}
+ /** Sets the icon to be used for the up button. */
+ public void setUpButtonIcon(Drawable icon) {
+ mUpButton.setImageDrawable(icon);
+ }
+
+ /** Sets the icon to be used for the down button. */
+ public void setDownButtonIcon(Drawable icon) {
+ mDownButton.setImageDrawable(icon);
+ }
+
/**
* Sets the listener that will be notified when the up and down buttons have been pressed.
*
@@ -119,7 +130,7 @@ public class PagedScrollBarView extends FrameLayout
/** Sets the range, offset and extent of the scroll bar. See {@link View}. */
public void setParameters(int range, int offset, int extent, boolean animate) {
- // This method is where we take the computed parameters from the CarLayoutManager and
+ // This method is where we take the computed parameters from the PagedLayoutManager and
// render it within the specified constraints ({@link #mMaxThumbLength} and
// {@link #mMinThumbLength}).
final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom();
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
deleted file mode 100644
index a18bcf32..00000000
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.mediacompat.testlib;
-
-/**
- * Constants used for sending intent between client and service apps.
- */
-public class IntentConstants {
- public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
- "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
- public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
- "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
- public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD =
- "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD";
- public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD =
- "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD";
- public static final String KEY_METHOD_ID = "method_id";
- public static final String KEY_ARGUMENT = "argument";
- public static final String KEY_SESSION_TOKEN = "session_token";
-}
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
index 95be1621..cbdccc1b 100644
--- a/android/support/mediacompat/testlib/MediaSessionConstants.java
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -40,7 +40,6 @@ public class MediaSessionConstants {
public static final int SET_RATING_TYPE = 117;
public static final String TEST_SESSION_TAG = "test-session-tag";
- public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
public static final String TEST_KEY = "test-key";
public static final String TEST_VALUE = "test-val";
public static final String TEST_SESSION_EVENT = "test-session-event";
diff --git a/android/support/mediacompat/testlib/VersionConstants.java b/android/support/mediacompat/testlib/VersionConstants.java
new file mode 100644
index 00000000..6533ee17
--- /dev/null
+++ b/android/support/mediacompat/testlib/VersionConstants.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib;
+
+/**
+ * Constants for getting support library version information.
+ */
+public class VersionConstants {
+ public static final String KEY_CLIENT_VERSION = "client_version";
+ public static final String KEY_SERVICE_VERSION = "service_version";
+}
diff --git a/android/support/mediacompat/testlib/util/IntentUtil.java b/android/support/mediacompat/testlib/util/IntentUtil.java
new file mode 100644
index 00000000..bbf97524
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/IntentUtil.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+
+/**
+ * Methods and constants used for sending intent between client and service apps.
+ */
+public class IntentUtil {
+
+ public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName(
+ "android.support.mediacompat.service.test",
+ "android.support.mediacompat.service.ServiceBroadcastReceiver");
+ public static final ComponentName CLIENT_RECEIVER_COMPONENT_NAME = new ComponentName(
+ "android.support.mediacompat.client.test",
+ "android.support.mediacompat.client.ClientBroadcastReceiver");
+
+ public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
+ "android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
+ public static final String ACTION_CALL_MEDIA_SESSION_METHOD =
+ "android.support.mediacompat.service.action.CALL_MEDIA_SESSION_METHOD";
+ public static final String ACTION_CALL_MEDIA_CONTROLLER_METHOD =
+ "android.support.mediacompat.client.action.CALL_MEDIA_CONTROLLER_METHOD";
+ public static final String ACTION_CALL_TRANSPORT_CONTROLS_METHOD =
+ "android.support.mediacompat.client.action.CALL_TRANSPORT_CONTROLS_METHOD";
+
+ public static final String KEY_METHOD_ID = "method_id";
+ public static final String KEY_ARGUMENT = "argument";
+ public static final String KEY_SESSION_TOKEN = "session_token";
+
+ /**
+ * Calls a method of MediaBrowserService. Used by client app.
+ */
+ public static void callMediaBrowserServiceMethod(int methodId, Object arg, Context context) {
+ Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
+ intent.setAction(ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD);
+ if (Build.VERSION.SDK_INT >= 16) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ context.sendBroadcast(intent);
+ }
+
+ /**
+ * Calls a method of MediaSession. Used by client app.
+ */
+ public static void callMediaSessionMethod(int methodId, Object arg, Context context) {
+ Intent intent = createIntent(SERVICE_RECEIVER_COMPONENT_NAME, methodId, arg);
+ intent.setAction(ACTION_CALL_MEDIA_SESSION_METHOD);
+ if (Build.VERSION.SDK_INT >= 16) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ context.sendBroadcast(intent);
+ }
+
+ /**
+ * Calls a method of MediaController. Used by service app.
+ */
+ public static void callMediaControllerMethod(
+ int methodId, Object arg, Context context, Parcelable token) {
+ Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg);
+ intent.setAction(ACTION_CALL_MEDIA_CONTROLLER_METHOD);
+ intent.putExtra(KEY_SESSION_TOKEN, token);
+ if (Build.VERSION.SDK_INT >= 16) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ context.sendBroadcast(intent);
+ }
+
+ /**
+ * Calls a method of TransportControls. Used by service app.
+ */
+ public static void callTransportControlsMethod(
+ int methodId, Object arg, Context context, Parcelable token) {
+ Intent intent = createIntent(CLIENT_RECEIVER_COMPONENT_NAME, methodId, arg);
+ intent.setAction(ACTION_CALL_TRANSPORT_CONTROLS_METHOD);
+ intent.putExtra(KEY_SESSION_TOKEN, token);
+ if (Build.VERSION.SDK_INT >= 16) {
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+ context.sendBroadcast(intent);
+ }
+
+ private static Intent createIntent(ComponentName componentName, int methodId, Object arg) {
+ Intent intent = new Intent();
+ intent.setComponent(componentName);
+ intent.putExtra(KEY_METHOD_ID, methodId);
+
+ if (arg instanceof String) {
+ intent.putExtra(KEY_ARGUMENT, (String) arg);
+ } else if (arg instanceof Integer) {
+ intent.putExtra(KEY_ARGUMENT, (int) arg);
+ } else if (arg instanceof Long) {
+ intent.putExtra(KEY_ARGUMENT, (long) arg);
+ } else if (arg instanceof Boolean) {
+ intent.putExtra(KEY_ARGUMENT, (boolean) arg);
+ } else if (arg instanceof Parcelable) {
+ intent.putExtra(KEY_ARGUMENT, (Parcelable) arg);
+ } else if (arg instanceof ArrayList<?>) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(KEY_ARGUMENT, (ArrayList<? extends Parcelable>) arg);
+ intent.putExtras(bundle);
+ } else if (arg instanceof Bundle) {
+ Bundle bundle = new Bundle();
+ bundle.putBundle(KEY_ARGUMENT, (Bundle) arg);
+ intent.putExtras(bundle);
+ }
+ return intent;
+ }
+}
diff --git a/android/support/mediacompat/testlib/util/PollingCheck.java b/android/support/mediacompat/testlib/util/PollingCheck.java
new file mode 100644
index 00000000..3412da02
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/PollingCheck.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import junit.framework.Assert;
+
+/**
+ * Utility used for testing that allows to poll for a certain condition to happen within a timeout.
+ * (Copied from testutils/src/main/java/android/support/testutils/PollingCheck.java.)
+ */
+public abstract class PollingCheck {
+ private static final long DEFAULT_TIMEOUT = 3000;
+ private static final long TIME_SLICE = 50;
+ private final long mTimeout;
+
+ /**
+ * The condition that the PollingCheck should use to proceed successfully.
+ */
+ public interface PollingCheckCondition {
+ /**
+ * @return Whether the polling condition has been met.
+ */
+ boolean canProceed();
+ }
+
+ public PollingCheck(long timeout) {
+ mTimeout = timeout;
+ }
+
+ protected abstract boolean check();
+
+ /**
+ * Start running the polling check.
+ */
+ public void run() {
+ if (check()) {
+ return;
+ }
+
+ long timeout = mTimeout;
+ while (timeout > 0) {
+ try {
+ Thread.sleep(TIME_SLICE);
+ } catch (InterruptedException e) {
+ Assert.fail("unexpected InterruptedException");
+ }
+
+ if (check()) {
+ return;
+ }
+
+ timeout -= TIME_SLICE;
+ }
+
+ Assert.fail("unexpected timeout");
+ }
+
+ /**
+ * Instantiate and start polling for a given condition with a default 3000ms timeout.
+ * @param condition The condition to check for success.
+ */
+ public static void waitFor(final PollingCheckCondition condition) {
+ new PollingCheck(DEFAULT_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return condition.canProceed();
+ }
+ }.run();
+ }
+
+ /**
+ * Instantiate and start polling for a given condition.
+ * @param timeout Time out in ms
+ * @param condition The condition to check for success.
+ */
+ public static void waitFor(long timeout, final PollingCheckCondition condition) {
+ new PollingCheck(timeout) {
+ @Override
+ protected boolean check() {
+ return condition.canProceed();
+ }
+ }.run();
+ }
+}
diff --git a/android/support/mediacompat/testlib/util/TestUtil.java b/android/support/mediacompat/testlib/util/TestUtil.java
new file mode 100644
index 00000000..d105510c
--- /dev/null
+++ b/android/support/mediacompat/testlib/util/TestUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.mediacompat.testlib.util;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+
+import android.os.Bundle;
+
+/**
+ * Utility methods used for testing.
+ */
+public final class TestUtil {
+
+ /**
+ * Asserts that two Bundles are equal.
+ */
+ public static void assertBundleEquals(Bundle expected, Bundle observed) {
+ if (expected == null || observed == null) {
+ assertSame(expected, observed);
+ }
+ assertEquals(expected.size(), observed.size());
+ for (String key : expected.keySet()) {
+ assertEquals(expected.get(key), observed.get(key));
+ }
+ }
+}
diff --git a/android/support/text/emoji/widget/EmojiAppCompatEditText.java b/android/support/text/emoji/widget/EmojiAppCompatEditText.java
index 87c17c20..0ae4ea04 100644
--- a/android/support/text/emoji/widget/EmojiAppCompatEditText.java
+++ b/android/support/text/emoji/widget/EmojiAppCompatEditText.java
@@ -21,6 +21,7 @@ import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.support.text.emoji.EmojiCompat;
import android.support.v7.widget.AppCompatEditText;
+import android.text.method.KeyListener;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -67,8 +68,11 @@ public class EmojiAppCompatEditText extends AppCompatEditText {
}
@Override
- public void setKeyListener(android.text.method.KeyListener input) {
- super.setKeyListener(getEmojiEditTextHelper().getKeyListener(input));
+ public void setKeyListener(@Nullable KeyListener keyListener) {
+ if (keyListener != null) {
+ keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+ }
+ super.setKeyListener(keyListener);
}
@Override
diff --git a/android/support/text/emoji/widget/EmojiEditText.java b/android/support/text/emoji/widget/EmojiEditText.java
index a0e8a69e..70ca7a66 100644
--- a/android/support/text/emoji/widget/EmojiEditText.java
+++ b/android/support/text/emoji/widget/EmojiEditText.java
@@ -21,6 +21,7 @@ import android.os.Build;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.support.text.emoji.EmojiCompat;
+import android.text.method.KeyListener;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -73,8 +74,11 @@ public class EmojiEditText extends EditText {
}
@Override
- public void setKeyListener(android.text.method.KeyListener keyListener) {
- super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
+ public void setKeyListener(@Nullable KeyListener keyListener) {
+ if (keyListener != null) {
+ keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+ }
+ super.setKeyListener(keyListener);
}
@Override
diff --git a/android/support/text/emoji/widget/EmojiExtractEditText.java b/android/support/text/emoji/widget/EmojiExtractEditText.java
index ca1868e2..2e4d3caa 100644
--- a/android/support/text/emoji/widget/EmojiExtractEditText.java
+++ b/android/support/text/emoji/widget/EmojiExtractEditText.java
@@ -27,6 +27,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.text.emoji.EmojiCompat;
import android.support.text.emoji.EmojiSpan;
+import android.text.method.KeyListener;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -81,8 +82,11 @@ public class EmojiExtractEditText extends ExtractEditText {
}
@Override
- public void setKeyListener(android.text.method.KeyListener keyListener) {
- super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
+ public void setKeyListener(@Nullable KeyListener keyListener) {
+ if (keyListener != null) {
+ keyListener = getEmojiEditTextHelper().getKeyListener(keyListener);
+ }
+ super.setKeyListener(keyListener);
}
@Override
diff --git a/android/support/transition/Transition.java b/android/support/transition/Transition.java
index 04cc57bd..9c198a94 100644
--- a/android/support/transition/Transition.java
+++ b/android/support/transition/Transition.java
@@ -1017,7 +1017,7 @@ public abstract class Transition implements Cloneable {
*/
@NonNull
public Transition addTarget(@IdRes int targetId) {
- if (targetId > 0) {
+ if (targetId != 0) {
mTargetIds.add(targetId);
}
return this;
@@ -1107,7 +1107,7 @@ public abstract class Transition implements Cloneable {
*/
@NonNull
public Transition removeTarget(@IdRes int targetId) {
- if (targetId > 0) {
+ if (targetId != 0) {
mTargetIds.remove((Integer) targetId);
}
return this;
diff --git a/android/support/v17/leanback/app/BaseFragment.java b/android/support/v17/leanback/app/BaseFragment.java
index bdb213f2..ea460111 100644
--- a/android/support/v17/leanback/app/BaseFragment.java
+++ b/android/support/v17/leanback/app/BaseFragment.java
@@ -31,7 +31,9 @@ import android.view.ViewTreeObserver;
/**
* Base class for leanback Fragments. This class is not intended to be subclassed by apps.
+ * @deprecated use {@link BaseSupportFragment}
*/
+@Deprecated
@SuppressWarnings("FragmentNotInstantiable")
public class BaseFragment extends BrandedFragment {
diff --git a/android/support/v17/leanback/app/BaseRowFragment.java b/android/support/v17/leanback/app/BaseRowFragment.java
index 2d79f3e1..97a5b848 100644
--- a/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/android/support/v17/leanback/app/BaseRowFragment.java
@@ -34,7 +34,9 @@ import android.view.ViewGroup;
/**
* An internal base class for a fragment containing a list of rows.
+ * @deprecated use {@link BaseRowSupportFragment}
*/
+@Deprecated
abstract class BaseRowFragment extends Fragment {
private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
private ObjectAdapter mAdapter;
@@ -164,8 +166,10 @@ abstract class BaseRowFragment extends Fragment {
* Set the presenter selector used to create and bind views.
*/
public final void setPresenterSelector(PresenterSelector presenterSelector) {
- mPresenterSelector = presenterSelector;
- updateAdapter();
+ if (mPresenterSelector != presenterSelector) {
+ mPresenterSelector = presenterSelector;
+ updateAdapter();
+ }
}
/**
@@ -180,8 +184,10 @@ abstract class BaseRowFragment extends Fragment {
* @param rowsAdapter Adapter that represents list of rows.
*/
public final void setAdapter(ObjectAdapter rowsAdapter) {
- mAdapter = rowsAdapter;
- updateAdapter();
+ if (mAdapter != rowsAdapter) {
+ mAdapter = rowsAdapter;
+ updateAdapter();
+ }
}
/**
diff --git a/android/support/v17/leanback/app/BaseRowSupportFragment.java b/android/support/v17/leanback/app/BaseRowSupportFragment.java
index dba78daf..6a477ab0 100644
--- a/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ b/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -161,8 +161,10 @@ abstract class BaseRowSupportFragment extends Fragment {
* Set the presenter selector used to create and bind views.
*/
public final void setPresenterSelector(PresenterSelector presenterSelector) {
- mPresenterSelector = presenterSelector;
- updateAdapter();
+ if (mPresenterSelector != presenterSelector) {
+ mPresenterSelector = presenterSelector;
+ updateAdapter();
+ }
}
/**
@@ -177,8 +179,10 @@ abstract class BaseRowSupportFragment extends Fragment {
* @param rowsAdapter Adapter that represents list of rows.
*/
public final void setAdapter(ObjectAdapter rowsAdapter) {
- mAdapter = rowsAdapter;
- updateAdapter();
+ if (mAdapter != rowsAdapter) {
+ mAdapter = rowsAdapter;
+ updateAdapter();
+ }
}
/**
diff --git a/android/support/v17/leanback/app/BrandedFragment.java b/android/support/v17/leanback/app/BrandedFragment.java
index 1f6ad299..415c13e0 100644
--- a/android/support/v17/leanback/app/BrandedFragment.java
+++ b/android/support/v17/leanback/app/BrandedFragment.java
@@ -33,7 +33,9 @@ import android.view.ViewGroup;
/**
* Fragment class for managing search and branding using a view that implements
* {@link TitleViewAdapter.Provider}.
+ * @deprecated use {@link BrandedSupportFragment}
*/
+@Deprecated
public class BrandedFragment extends Fragment {
// BUNDLE attribute for title is showing
diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java
index ae31c4fb..c561ea99 100644
--- a/android/support/v17/leanback/app/BrowseFragment.java
+++ b/android/support/v17/leanback/app/BrowseFragment.java
@@ -81,7 +81,9 @@ import java.util.Map;
* The recommended theme to use with a BrowseFragment is
* {@link android.support.v17.leanback.R.style#Theme_Leanback_Browse}.
* </p>
+ * @deprecated use {@link BrowseSupportFragment}
*/
+@Deprecated
public class BrowseFragment extends BaseFragment {
// BUNDLE attribute for saving header show/hide status when backstack is used:
@@ -203,7 +205,9 @@ public class BrowseFragment extends BaseFragment {
/**
* Listener for transitions between browse headers and rows.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public static class BrowseTransitionListener {
/**
* Callback when headers transition starts.
@@ -267,7 +271,9 @@ public class BrowseFragment extends BaseFragment {
/**
* Possible set of actions that {@link BrowseFragment} exposes to clients. Custom
* fragments can interact with {@link BrowseFragment} using this interface.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public interface FragmentHost {
/**
* Fragments are required to invoke this callback once their view is created
@@ -376,7 +382,9 @@ public class BrowseFragment extends BaseFragment {
* and provide that through {@link MainFragmentAdapterRegistry}.
* {@link MainFragmentAdapter} implementation can supply any fragment and override
* just those interactions that makes sense.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public static class MainFragmentAdapter<T extends Fragment> {
private boolean mScalingEnabled;
private final T mFragment;
@@ -466,7 +474,9 @@ public class BrowseFragment extends BaseFragment {
* Interface to be implemented by all fragments for providing an instance of
* {@link MainFragmentAdapter}. Both {@link RowsFragment} and custom fragment provided
* against {@link PageRow} will need to implement this interface.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public interface MainFragmentAdapterProvider {
/**
* Returns an instance of {@link MainFragmentAdapter} that {@link BrowseFragment}
@@ -478,7 +488,9 @@ public class BrowseFragment extends BaseFragment {
/**
* Interface to be implemented by {@link RowsFragment} and its subclasses for providing
* an instance of {@link MainFragmentRowsAdapter}.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public interface MainFragmentRowsAdapterProvider {
/**
* Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseFragment}
@@ -491,7 +503,9 @@ public class BrowseFragment extends BaseFragment {
* This is used to pass information to {@link RowsFragment} or its subclasses.
* {@link BrowseFragment} uses this interface to pass row based interaction events to
* the target fragment.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public static class MainFragmentRowsAdapter<T extends Fragment> {
private final T mFragment;
@@ -570,14 +584,27 @@ public class BrowseFragment extends BaseFragment {
}
boolean oldIsPageRow = mIsPageRow;
+ Object oldPageRow = mPageRow;
mIsPageRow = mCanShowHeaders && item instanceof PageRow;
+ mPageRow = mIsPageRow ? item : null;
boolean swap;
if (mMainFragment == null) {
swap = true;
} else {
if (oldIsPageRow) {
- swap = true;
+ if (mIsPageRow) {
+ if (oldPageRow == null) {
+ // fragment is restored, page row object not yet set, so just set the
+ // mPageRow object and there is no need to replace the fragment
+ swap = false;
+ } else {
+ // swap if page row object changes
+ swap = oldPageRow != mPageRow;
+ }
+ } else {
+ swap = true;
+ }
} else {
swap = mIsPageRow;
}
@@ -590,37 +617,45 @@ public class BrowseFragment extends BaseFragment {
"Fragment must implement MainFragmentAdapterProvider");
}
- mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
- .getMainFragmentAdapter();
- mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
- if (!mIsPageRow) {
- if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
- mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
- .getMainFragmentRowsAdapter();
- } else {
- mMainFragmentRowsAdapter = null;
- }
- mIsPageRow = mMainFragmentRowsAdapter == null;
- } else {
- mMainFragmentRowsAdapter = null;
- }
+ setMainFragmentAdapter();
}
return swap;
}
+ void setMainFragmentAdapter() {
+ mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment)
+ .getMainFragmentAdapter();
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+ if (!mIsPageRow) {
+ if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+ setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment)
+ .getMainFragmentRowsAdapter());
+ } else {
+ setMainFragmentRowsAdapter(null);
+ }
+ mIsPageRow = mMainFragmentRowsAdapter == null;
+ } else {
+ setMainFragmentRowsAdapter(null);
+ }
+ }
+
/**
* Factory class responsible for creating fragment given the current item. {@link ListRow}
* should return {@link RowsFragment} or its subclass whereas {@link PageRow}
* can return any fragment class.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public abstract static class FragmentFactory<T extends Fragment> {
public abstract T createFragment(Object row);
}
/**
* FragmentFactory implementation for {@link ListRow}.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public static class ListRowFragmentFactory extends FragmentFactory<RowsFragment> {
@Override
public RowsFragment createFragment(Object row) {
@@ -634,7 +669,9 @@ public class BrowseFragment extends BaseFragment {
* handling {@link ListRow}. Developers can override that and also if they want to
* use custom fragment, they can register a custom {@link FragmentFactory}
* against {@link PageRow}.
+ * @deprecated use {@link BrowseSupportFragment}
*/
+ @Deprecated
public final static class MainFragmentAdapterRegistry {
private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap<>();
private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
@@ -678,7 +715,8 @@ public class BrowseFragment extends BaseFragment {
MainFragmentAdapter mMainFragmentAdapter;
Fragment mMainFragment;
HeadersFragment mHeadersFragment;
- private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+ MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+ ListRowDataAdapter mMainFragmentListRowDataAdapter;
private ObjectAdapter mAdapter;
private PresenterSelector mAdapterPresenter;
@@ -701,6 +739,7 @@ public class BrowseFragment extends BaseFragment {
private int mSelectedPosition = -1;
private float mScaleFactor;
boolean mIsPageRow;
+ Object mPageRow;
private PresenterSelector mHeaderPresenterSelector;
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -820,11 +859,45 @@ public class BrowseFragment extends BaseFragment {
return;
}
+ updateMainFragmentRowsAdapter();
+ mHeadersFragment.setAdapter(mAdapter);
+ }
+
+ void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+ if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+ return;
+ }
+ // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter
if (mMainFragmentRowsAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(
- adapter == null ? null : new ListRowDataAdapter(adapter));
+ // RowsFragment cannot change click/select listeners after view created.
+ // The main fragment and adapter should be GCed as long as there is no reference from
+ // BrowseFragment to it.
+ mMainFragmentRowsAdapter.setAdapter(null);
+ }
+ mMainFragmentRowsAdapter = mainFragmentRowsAdapter;
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+ new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+ mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+ // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter
+ updateMainFragmentRowsAdapter();
+ }
+
+ /**
+ * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter.
+ * It also clears old mMainFragmentListRowDataAdapter.
+ */
+ void updateMainFragmentRowsAdapter() {
+ if (mMainFragmentListRowDataAdapter != null) {
+ mMainFragmentListRowDataAdapter.detach();
+ mMainFragmentListRowDataAdapter = null;
+ }
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentListRowDataAdapter = mAdapter == null
+ ? null : new ListRowDataAdapter(mAdapter);
+ mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter);
}
- mHeadersFragment.setAdapter(adapter);
}
public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
@@ -1144,7 +1217,8 @@ public class BrowseFragment extends BaseFragment {
@Override
public void onDestroyView() {
- mMainFragmentRowsAdapter = null;
+ setMainFragmentRowsAdapter(null);
+ mPageRow = null;
mMainFragmentAdapter = null;
mMainFragment = null;
mHeadersFragment = null;
@@ -1198,26 +1272,17 @@ public class BrowseFragment extends BaseFragment {
mHeadersFragment = (HeadersFragment) getChildFragmentManager()
.findFragmentById(R.id.browse_headers_dock);
mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
- mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
- .getMainFragmentAdapter();
- mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
mIsPageRow = savedInstanceState != null
&& savedInstanceState.getBoolean(IS_PAGE_ROW, false);
+ // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is
+ // the case for restoring, later if setSelection() triggers a createMainFragment(),
+ // should not create fragment.
mSelectedPosition = savedInstanceState != null
? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
- if (!mIsPageRow) {
- if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
- mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
- .getMainFragmentRowsAdapter();
- } else {
- mMainFragmentRowsAdapter = null;
- }
- } else {
- mMainFragmentRowsAdapter = null;
- }
+ setMainFragmentAdapter();
}
mHeadersFragment.setHeadersGone(!mCanShowHeaders);
@@ -1242,8 +1307,6 @@ public class BrowseFragment extends BaseFragment {
mScaleFrameLayout.setPivotX(0);
mScaleFrameLayout.setPivotY(mContainerListAlignTop);
- setupMainFragment();
-
if (mBrandColorSet) {
mHeadersFragment.setBackgroundColor(mBrandColor);
}
@@ -1270,17 +1333,6 @@ public class BrowseFragment extends BaseFragment {
return root;
}
- private void setupMainFragment() {
- if (mMainFragmentRowsAdapter != null) {
- if (mAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
- }
- mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
- new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
- mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
- }
- }
-
void createHeadersTransition() {
mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
mShowingHeaders
@@ -1470,10 +1522,10 @@ public class BrowseFragment extends BaseFragment {
};
void onRowSelected(int position) {
- if (position != mSelectedPosition) {
- mSetSelectionRunnable.post(
- position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
- }
+ // even position is same, it could be data changed, always post selection runnable
+ // to possibly swap main fragment.
+ mSetSelectionRunnable.post(
+ position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
}
void setSelection(int position, boolean smooth) {
@@ -1500,7 +1552,6 @@ public class BrowseFragment extends BaseFragment {
if (createMainFragment(mAdapter, position)) {
swapToMainFragment();
expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
- setupMainFragment();
}
}
diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java
index 4a2502a8..c28064ca 100644
--- a/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -567,14 +567,27 @@ public class BrowseSupportFragment extends BaseSupportFragment {
}
boolean oldIsPageRow = mIsPageRow;
+ Object oldPageRow = mPageRow;
mIsPageRow = mCanShowHeaders && item instanceof PageRow;
+ mPageRow = mIsPageRow ? item : null;
boolean swap;
if (mMainFragment == null) {
swap = true;
} else {
if (oldIsPageRow) {
- swap = true;
+ if (mIsPageRow) {
+ if (oldPageRow == null) {
+ // fragment is restored, page row object not yet set, so just set the
+ // mPageRow object and there is no need to replace the fragment
+ swap = false;
+ } else {
+ // swap if page row object changes
+ swap = oldPageRow != mPageRow;
+ }
+ } else {
+ swap = true;
+ }
} else {
swap = mIsPageRow;
}
@@ -587,25 +600,29 @@ public class BrowseSupportFragment extends BaseSupportFragment {
"Fragment must implement MainFragmentAdapterProvider");
}
- mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
- .getMainFragmentAdapter();
- mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
- if (!mIsPageRow) {
- if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
- mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
- .getMainFragmentRowsAdapter();
- } else {
- mMainFragmentRowsAdapter = null;
- }
- mIsPageRow = mMainFragmentRowsAdapter == null;
- } else {
- mMainFragmentRowsAdapter = null;
- }
+ setMainFragmentAdapter();
}
return swap;
}
+ void setMainFragmentAdapter() {
+ mMainFragmentAdapter = ((MainFragmentAdapterProvider) mMainFragment)
+ .getMainFragmentAdapter();
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+ if (!mIsPageRow) {
+ if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+ setMainFragmentRowsAdapter(((MainFragmentRowsAdapterProvider) mMainFragment)
+ .getMainFragmentRowsAdapter());
+ } else {
+ setMainFragmentRowsAdapter(null);
+ }
+ mIsPageRow = mMainFragmentRowsAdapter == null;
+ } else {
+ setMainFragmentRowsAdapter(null);
+ }
+ }
+
/**
* Factory class responsible for creating fragment given the current item. {@link ListRow}
* should return {@link RowsSupportFragment} or its subclass whereas {@link PageRow}
@@ -675,7 +692,8 @@ public class BrowseSupportFragment extends BaseSupportFragment {
MainFragmentAdapter mMainFragmentAdapter;
Fragment mMainFragment;
HeadersSupportFragment mHeadersSupportFragment;
- private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+ MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+ ListRowDataAdapter mMainFragmentListRowDataAdapter;
private ObjectAdapter mAdapter;
private PresenterSelector mAdapterPresenter;
@@ -698,6 +716,7 @@ public class BrowseSupportFragment extends BaseSupportFragment {
private int mSelectedPosition = -1;
private float mScaleFactor;
boolean mIsPageRow;
+ Object mPageRow;
private PresenterSelector mHeaderPresenterSelector;
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -817,11 +836,45 @@ public class BrowseSupportFragment extends BaseSupportFragment {
return;
}
+ updateMainFragmentRowsAdapter();
+ mHeadersSupportFragment.setAdapter(mAdapter);
+ }
+
+ void setMainFragmentRowsAdapter(MainFragmentRowsAdapter mainFragmentRowsAdapter) {
+ if (mainFragmentRowsAdapter == mMainFragmentRowsAdapter) {
+ return;
+ }
+ // first clear previous mMainFragmentRowsAdapter and set a new mMainFragmentRowsAdapter
+ if (mMainFragmentRowsAdapter != null) {
+ // RowsFragment cannot change click/select listeners after view created.
+ // The main fragment and adapter should be GCed as long as there is no reference from
+ // BrowseSupportFragment to it.
+ mMainFragmentRowsAdapter.setAdapter(null);
+ }
+ mMainFragmentRowsAdapter = mainFragmentRowsAdapter;
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+ new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+ mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+ // second update mMainFragmentListRowDataAdapter set on mMainFragmentRowsAdapter
+ updateMainFragmentRowsAdapter();
+ }
+
+ /**
+ * Update mMainFragmentListRowDataAdapter and set it on mMainFragmentRowsAdapter.
+ * It also clears old mMainFragmentListRowDataAdapter.
+ */
+ void updateMainFragmentRowsAdapter() {
+ if (mMainFragmentListRowDataAdapter != null) {
+ mMainFragmentListRowDataAdapter.detach();
+ mMainFragmentListRowDataAdapter = null;
+ }
if (mMainFragmentRowsAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(
- adapter == null ? null : new ListRowDataAdapter(adapter));
+ mMainFragmentListRowDataAdapter = mAdapter == null
+ ? null : new ListRowDataAdapter(mAdapter);
+ mMainFragmentRowsAdapter.setAdapter(mMainFragmentListRowDataAdapter);
}
- mHeadersSupportFragment.setAdapter(adapter);
}
public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
@@ -1141,7 +1194,8 @@ public class BrowseSupportFragment extends BaseSupportFragment {
@Override
public void onDestroyView() {
- mMainFragmentRowsAdapter = null;
+ setMainFragmentRowsAdapter(null);
+ mPageRow = null;
mMainFragmentAdapter = null;
mMainFragment = null;
mHeadersSupportFragment = null;
@@ -1195,26 +1249,17 @@ public class BrowseSupportFragment extends BaseSupportFragment {
mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
.findFragmentById(R.id.browse_headers_dock);
mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
- mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
- .getMainFragmentAdapter();
- mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
mIsPageRow = savedInstanceState != null
&& savedInstanceState.getBoolean(IS_PAGE_ROW, false);
+ // mPageRow object is unable to restore, if its null and mIsPageRow is true, this is
+ // the case for restoring, later if setSelection() triggers a createMainFragment(),
+ // should not create fragment.
mSelectedPosition = savedInstanceState != null
? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
- if (!mIsPageRow) {
- if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
- mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
- .getMainFragmentRowsAdapter();
- } else {
- mMainFragmentRowsAdapter = null;
- }
- } else {
- mMainFragmentRowsAdapter = null;
- }
+ setMainFragmentAdapter();
}
mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
@@ -1239,8 +1284,6 @@ public class BrowseSupportFragment extends BaseSupportFragment {
mScaleFrameLayout.setPivotX(0);
mScaleFrameLayout.setPivotY(mContainerListAlignTop);
- setupMainFragment();
-
if (mBrandColorSet) {
mHeadersSupportFragment.setBackgroundColor(mBrandColor);
}
@@ -1267,17 +1310,6 @@ public class BrowseSupportFragment extends BaseSupportFragment {
return root;
}
- private void setupMainFragment() {
- if (mMainFragmentRowsAdapter != null) {
- if (mAdapter != null) {
- mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(mAdapter));
- }
- mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
- new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
- mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
- }
- }
-
void createHeadersTransition() {
mHeadersTransition = TransitionHelper.loadTransition(getContext(),
mShowingHeaders
@@ -1467,10 +1499,10 @@ public class BrowseSupportFragment extends BaseSupportFragment {
};
void onRowSelected(int position) {
- if (position != mSelectedPosition) {
- mSetSelectionRunnable.post(
- position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
- }
+ // even position is same, it could be data changed, always post selection runnable
+ // to possibly swap main fragment.
+ mSetSelectionRunnable.post(
+ position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
}
void setSelection(int position, boolean smooth) {
@@ -1497,7 +1529,6 @@ public class BrowseSupportFragment extends BaseSupportFragment {
if (createMainFragment(mAdapter, position)) {
swapToMainFragment();
expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
- setupMainFragment();
}
}
diff --git a/android/support/v17/leanback/app/DetailsFragment.java b/android/support/v17/leanback/app/DetailsFragment.java
index 36559637..18934f45 100644
--- a/android/support/v17/leanback/app/DetailsFragment.java
+++ b/android/support/v17/leanback/app/DetailsFragment.java
@@ -91,7 +91,9 @@ import java.lang.ref.WeakReference;
* DetailsFragment can use {@link DetailsFragmentBackgroundController} to add a parallax drawable
* background and embedded video playing fragment.
* </p>
+ * @deprecated use {@link DetailsSupportFragment}
*/
+@Deprecated
public class DetailsFragment extends BaseFragment {
static final String TAG = "DetailsFragment";
static boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
index 223b8ef2..25ed723e 100644
--- a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
+++ b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
@@ -107,7 +107,9 @@ import android.app.Fragment;
* {@link #onCreateGlueHost()}.
* </p>
*
+ * @deprecated use {@link DetailsSupportFragmentBackgroundController}
*/
+@Deprecated
public class DetailsFragmentBackgroundController {
final DetailsFragment mFragment;
diff --git a/android/support/v17/leanback/app/ErrorFragment.java b/android/support/v17/leanback/app/ErrorFragment.java
index 2896d0f4..eda0de16 100644
--- a/android/support/v17/leanback/app/ErrorFragment.java
+++ b/android/support/v17/leanback/app/ErrorFragment.java
@@ -32,7 +32,9 @@ import android.widget.TextView;
/**
* A fragment for displaying an error indication.
+ * @deprecated use {@link ErrorSupportFragment}
*/
+@Deprecated
public class ErrorFragment extends BrandedFragment {
private ViewGroup mErrorFrame;
diff --git a/android/support/v17/leanback/app/GuidedStepFragment.java b/android/support/v17/leanback/app/GuidedStepFragment.java
index 2b7f2d0d..9be350d8 100644
--- a/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -27,6 +27,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DiffCallback;
import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
@@ -140,7 +141,9 @@ import java.util.List;
* @see GuidanceStylist.Guidance
* @see GuidedAction
* @see GuidedActionsStylist
+ * @deprecated use {@link GuidedStepSupportFragment}
*/
+@Deprecated
public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener {
private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
@@ -806,6 +809,8 @@ public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.
/**
* Sets the list of GuidedActions that the user may take in this fragment.
+ * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}.
+ *
* @param actions The list of GuidedActions for this fragment.
*/
public void setActions(List<GuidedAction> actions) {
@@ -816,6 +821,18 @@ public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.
}
/**
+ * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default
+ * GuidedStepFragment uses
+ * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}.
+ * Sets it to null if app wants to refresh the whole list.
+ *
+ * @param diffCallback DiffCallback used in {@link #setActions(List)}.
+ */
+ public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+ mAdapter.setDiffCallback(diffCallback);
+ }
+
+ /**
* Notify an action has changed and update its UI.
* @param position Position of the GuidedAction in array.
*/
diff --git a/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index aeb2d334..e276d076 100644
--- a/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -24,6 +24,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DiffCallback;
import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
@@ -803,6 +804,8 @@ public class GuidedStepSupportFragment extends Fragment implements GuidedActionA
/**
* Sets the list of GuidedActions that the user may take in this fragment.
+ * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}.
+ *
* @param actions The list of GuidedActions for this fragment.
*/
public void setActions(List<GuidedAction> actions) {
@@ -813,6 +816,18 @@ public class GuidedStepSupportFragment extends Fragment implements GuidedActionA
}
/**
+ * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default
+ * GuidedStepSupportFragment uses
+ * {@link android.support.v17.leanback.widget.GuidedActionDiffCallback}.
+ * Sets it to null if app wants to refresh the whole list.
+ *
+ * @param diffCallback DiffCallback used in {@link #setActions(List)}.
+ */
+ public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+ mAdapter.setDiffCallback(diffCallback);
+ }
+
+ /**
* Notify an action has changed and update its UI.
* @param position Position of the GuidedAction in array.
*/
diff --git a/android/support/v17/leanback/app/HeadersFragment.java b/android/support/v17/leanback/app/HeadersFragment.java
index dd037d2f..08780a50 100644
--- a/android/support/v17/leanback/app/HeadersFragment.java
+++ b/android/support/v17/leanback/app/HeadersFragment.java
@@ -52,12 +52,16 @@ import android.widget.FrameLayout;
* </ul>
* Use {@link #setPresenterSelector(PresenterSelector)} in subclass constructor to customize
* Presenters. App may override {@link BrowseFragment#onCreateHeadersFragment()}.
+ * @deprecated use {@link HeadersSupportFragment}
*/
+@Deprecated
public class HeadersFragment extends BaseRowFragment {
/**
* Interface definition for a callback to be invoked when a header item is clicked.
+ * @deprecated use {@link HeadersSupportFragment}
*/
+ @Deprecated
public interface OnHeaderClickedListener {
/**
* Called when a header item has been clicked.
@@ -70,7 +74,9 @@ public class HeadersFragment extends BaseRowFragment {
/**
* Interface definition for a callback to be invoked when a header item is selected.
+ * @deprecated use {@link HeadersSupportFragment}
*/
+ @Deprecated
public interface OnHeaderViewSelectedListener {
/**
* Called when a header item has been selected.
diff --git a/android/support/v17/leanback/app/ListRowDataAdapter.java b/android/support/v17/leanback/app/ListRowDataAdapter.java
index f9af12f3..03d948be 100644
--- a/android/support/v17/leanback/app/ListRowDataAdapter.java
+++ b/android/support/v17/leanback/app/ListRowDataAdapter.java
@@ -13,6 +13,7 @@ import android.support.v17.leanback.widget.Row;
* thinks there are items even though they're invisible. This class takes care of filtering out
* the invisible rows at the end. In case the data inside the adapter changes, it adjusts the
* bounds to reflect the latest data.
+ * {@link #detach()} must be called to release DataObserver from Adapter.
*/
class ListRowDataAdapter extends ObjectAdapter {
public static final int ON_ITEM_RANGE_CHANGED = 2;
@@ -22,6 +23,7 @@ class ListRowDataAdapter extends ObjectAdapter {
private final ObjectAdapter mAdapter;
int mLastVisibleRowIndex;
+ final DataObserver mDataObserver;
public ListRowDataAdapter(ObjectAdapter adapter) {
super(adapter.getPresenterSelector());
@@ -34,10 +36,20 @@ class ListRowDataAdapter extends ObjectAdapter {
// operation. To handle this case, we use QueueBasedDataObserver which forces
// recyclerview to do a full data refresh after each update operation.
if (adapter.isImmediateNotifySupported()) {
- mAdapter.registerObserver(new SimpleDataObserver());
+ mDataObserver = new SimpleDataObserver();
} else {
- mAdapter.registerObserver(new QueueBasedDataObserver());
+ mDataObserver = new QueueBasedDataObserver();
}
+ attach();
+ }
+
+ void detach() {
+ mAdapter.unregisterObserver(mDataObserver);
+ }
+
+ void attach() {
+ initialize();
+ mAdapter.registerObserver(mDataObserver);
}
void initialize() {
diff --git a/android/support/v17/leanback/app/OnboardingFragment.java b/android/support/v17/leanback/app/OnboardingFragment.java
index b69d5a72..f352c413 100644
--- a/android/support/v17/leanback/app/OnboardingFragment.java
+++ b/android/support/v17/leanback/app/OnboardingFragment.java
@@ -154,7 +154,9 @@ import java.util.List;
* @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
* @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
* @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
+ * @deprecated use {@link OnboardingSupportFragment}
*/
+@Deprecated
abstract public class OnboardingFragment extends Fragment {
private static final String TAG = "OnboardingF";
private static final boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/PlaybackFragment.java b/android/support/v17/leanback/app/PlaybackFragment.java
index 33e787c3..e2e6be48 100644
--- a/android/support/v17/leanback/app/PlaybackFragment.java
+++ b/android/support/v17/leanback/app/PlaybackFragment.java
@@ -81,7 +81,9 @@ import android.view.animation.AccelerateInterpolator;
* {@link #setControlsOverlayAutoHideEnabled(boolean)} upon play/pause. The auto hiding timer will
* be cancelled upon {@link #tickle()} triggered by input event.
* </p>
+ * @deprecated use {@link PlaybackSupportFragment}
*/
+@Deprecated
public class PlaybackFragment extends Fragment {
static final String BUNDLE_CONTROL_VISIBLE_ON_CREATEVIEW = "controlvisible_oncreateview";
@@ -181,7 +183,9 @@ public class PlaybackFragment extends Fragment {
* Listener allowing the application to receive notification of fade in and/or fade out
* completion events.
* @hide
+ * @deprecated use {@link PlaybackSupportFragment}
*/
+ @Deprecated
public static class OnFadeCompleteListener {
public void onFadeInComplete() {
}
diff --git a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
index 4a9d10f8..9e342fdb 100644
--- a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
@@ -30,7 +30,9 @@ import android.view.View;
/**
* {@link PlaybackGlueHost} implementation
* the interaction between this class and {@link PlaybackFragment}.
+ * @deprecated use {@link PlaybackSupportFragmentGlueHost}
*/
+@Deprecated
public class PlaybackFragmentGlueHost extends PlaybackGlueHost implements PlaybackSeekUi {
private final PlaybackFragment mFragment;
diff --git a/android/support/v17/leanback/app/RowsFragment.java b/android/support/v17/leanback/app/RowsFragment.java
index a008ad60..aa346bd9 100644
--- a/android/support/v17/leanback/app/RowsFragment.java
+++ b/android/support/v17/leanback/app/RowsFragment.java
@@ -53,7 +53,9 @@ import java.util.ArrayList;
* of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
* of {@link RowPresenter}.
* </p>
+ * @deprecated use {@link RowsSupportFragment}
*/
+@Deprecated
public class RowsFragment extends BaseRowFragment implements
BrowseFragment.MainFragmentRowsAdapterProvider,
BrowseFragment.MainFragmentAdapterProvider {
@@ -634,7 +636,9 @@ public class RowsFragment extends BaseRowFragment implements
* The adapter that RowsFragment implements
* BrowseFragment.MainFragmentRowsAdapter.
* @see #getMainFragmentRowsAdapter().
+ * @deprecated use {@link RowsSupportFragment}
*/
+ @Deprecated
public static class MainFragmentRowsAdapter
extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
diff --git a/android/support/v17/leanback/app/SearchFragment.java b/android/support/v17/leanback/app/SearchFragment.java
index 2154ff28..00f2cca8 100644
--- a/android/support/v17/leanback/app/SearchFragment.java
+++ b/android/support/v17/leanback/app/SearchFragment.java
@@ -66,7 +66,9 @@ import java.util.List;
* not when fragment is restored from an instance state. Activity may manually
* call {@link #startRecognition()}, typically in onNewIntent().
* </p>
+ * @deprecated use {@link SearchSupportFragment}
*/
+@Deprecated
public class SearchFragment extends Fragment {
static final String TAG = SearchFragment.class.getSimpleName();
static final boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/VerticalGridFragment.java b/android/support/v17/leanback/app/VerticalGridFragment.java
index 5bc52ff5..bff3dbab 100644
--- a/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -39,7 +39,9 @@ import android.view.ViewGroup;
*
* <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
* an {@link ObjectAdapter}.
+ * @deprecated use {@link VerticalGridSupportFragment}
*/
+@Deprecated
public class VerticalGridFragment extends BaseFragment {
static final String TAG = "VerticalGF";
static boolean DEBUG = false;
diff --git a/android/support/v17/leanback/app/VideoFragment.java b/android/support/v17/leanback/app/VideoFragment.java
index 1b2b8d07..e4d75f30 100644
--- a/android/support/v17/leanback/app/VideoFragment.java
+++ b/android/support/v17/leanback/app/VideoFragment.java
@@ -27,7 +27,9 @@ import android.view.ViewGroup;
/**
* Subclass of {@link PlaybackFragment} that is responsible for providing a {@link SurfaceView}
* and rendering video.
+ * @deprecated use {@link VideoSupportFragment}
*/
+@Deprecated
public class VideoFragment extends PlaybackFragment {
static final int SURFACE_NOT_CREATED = 0;
static final int SURFACE_CREATED = 1;
diff --git a/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
index d123676f..546e581c 100644
--- a/android/support/v17/leanback/app/VideoFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
@@ -24,7 +24,9 @@ import android.view.SurfaceHolder;
/**
* {@link PlaybackGlueHost} implementation
* the interaction between {@link PlaybackGlue} and {@link VideoFragment}.
+ * @deprecated use {@link VideoSupportFragmentGlueHost}
*/
+@Deprecated
public class VideoFragmentGlueHost extends PlaybackFragmentGlueHost
implements SurfaceHolderGlueHost {
private final VideoFragment mFragment;
diff --git a/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 00bc073d..2dcf51f7 100644
--- a/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -225,6 +225,8 @@ public class ArrayObjectAdapter extends ObjectAdapter {
return true;
}
+ ListUpdateCallback mListUpdateCallback;
+
/**
* Set a new item list to adapter. The DiffUtil will compute the difference and dispatch it to
* specified position.
@@ -280,39 +282,43 @@ public class ArrayObjectAdapter extends ObjectAdapter {
mItems.addAll(itemList);
// dispatch diff result
- diffResult.dispatchUpdatesTo(new ListUpdateCallback() {
-
- @Override
- public void onInserted(int position, int count) {
- if (DEBUG) {
- Log.d(TAG, "onInserted");
+ if (mListUpdateCallback == null) {
+ mListUpdateCallback = new ListUpdateCallback() {
+
+ @Override
+ public void onInserted(int position, int count) {
+ if (DEBUG) {
+ Log.d(TAG, "onInserted");
+ }
+ notifyItemRangeInserted(position, count);
}
- notifyItemRangeInserted(position, count);
- }
- @Override
- public void onRemoved(int position, int count) {
- if (DEBUG) {
- Log.d(TAG, "onRemoved");
+ @Override
+ public void onRemoved(int position, int count) {
+ if (DEBUG) {
+ Log.d(TAG, "onRemoved");
+ }
+ notifyItemRangeRemoved(position, count);
}
- notifyItemRangeRemoved(position, count);
- }
- @Override
- public void onMoved(int fromPosition, int toPosition) {
- if (DEBUG) {
- Log.d(TAG, "onMoved");
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ if (DEBUG) {
+ Log.d(TAG, "onMoved");
+ }
+ notifyItemMoved(fromPosition, toPosition);
}
- notifyItemMoved(fromPosition, toPosition);
- }
- @Override
- public void onChanged(int position, int count, Object payload) {
- if (DEBUG) {
- Log.d(TAG, "onChanged");
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ if (DEBUG) {
+ Log.d(TAG, "onChanged");
+ }
+ notifyItemRangeChanged(position, count, payload);
}
- notifyItemRangeChanged(position, count, payload);
- }
- });
+ };
+ }
+ diffResult.dispatchUpdatesTo(mListUpdateCallback);
+ mOldItems.clear();
}
}
diff --git a/android/support/v17/leanback/widget/BaseGridView.java b/android/support/v17/leanback/widget/BaseGridView.java
index f4e01c0b..2ebec47e 100644
--- a/android/support/v17/leanback/widget/BaseGridView.java
+++ b/android/support/v17/leanback/widget/BaseGridView.java
@@ -1134,7 +1134,7 @@ public abstract class BaseGridView extends RecyclerView {
@Override
public void scrollToPosition(int position) {
// dont abort the animateOut() animation, just record the position
- if (mLayoutManager.mIsSlidingChildViews) {
+ if (mLayoutManager.isSlidingChildViews()) {
mLayoutManager.setSelectionWithSub(position, 0, 0);
return;
}
@@ -1144,7 +1144,7 @@ public abstract class BaseGridView extends RecyclerView {
@Override
public void smoothScrollToPosition(int position) {
// dont abort the animateOut() animation, just record the position
- if (mLayoutManager.mIsSlidingChildViews) {
+ if (mLayoutManager.isSlidingChildViews()) {
mLayoutManager.setSelectionWithSub(position, 0, 0);
return;
}
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index dded0715..d7020e91 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -217,9 +217,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = getTargetPosition();
}
if (hasFocus()) {
- mInSelection = true;
+ mFlag |= PF_IN_SELECTION;
targetView.requestFocus();
- mInSelection = false;
+ mFlag &= ~PF_IN_SELECTION;
}
dispatchChildSelected();
dispatchChildSelectedAndPositioned();
@@ -320,9 +320,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
if (newSelected != null && hasFocus()) {
- mInSelection = true;
+ mFlag |= PF_IN_SELECTION;
newSelected.requestFocus();
- mInSelection = false;
+ mFlag &= ~PF_IN_SELECTION;
}
}
@@ -355,7 +355,8 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (mPendingMoves == 0) {
return null;
}
- int direction = (mReverseFlowPrimary ? mPendingMoves > 0 : mPendingMoves < 0)
+ int direction = ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+ ? mPendingMoves > 0 : mPendingMoves < 0)
? -1 : 1;
if (mOrientation == HORIZONTAL) {
return new PointF(direction, 0);
@@ -386,10 +387,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// effect smooth scrolling too over to bind an item view then drag the item view back.
final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
- // Represents whether child views are temporarily sliding out
- boolean mIsSlidingChildViews;
- boolean mLayoutEatenInSliding;
-
String getTag() {
return TAG + ":" + mBaseGridView.getId();
}
@@ -444,15 +441,101 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private static final Rect sTempRect = new Rect();
- boolean mInLayout;
- private boolean mInScroll;
- boolean mInFastRelayout;
+ // 2 bits mask is for 3 STAGEs: 0, PF_STAGE_LAYOUT or PF_STAGE_SCROLL.
+ static final int PF_STAGE_MASK = 0x3;
+ static final int PF_STAGE_LAYOUT = 0x1;
+ static final int PF_STAGE_SCROLL = 0x2;
+
+ // Flag for "in fast relayout", determined by layoutInit() result.
+ static final int PF_FAST_RELAYOUT = 1 << 2;
+
+ // Flag for the selected item being updated in fast relayout.
+ static final int PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION = 1 << 3;
/**
* During full layout pass, when GridView had focus: onLayoutChildren will
* skip non-focusable child and adjust mFocusPosition.
*/
- boolean mInLayoutSearchFocus;
- boolean mInSelection = false;
+ static final int PF_IN_LAYOUT_SEARCH_FOCUS = 1 << 4;
+
+ // flag to prevent reentry if it's already processing selection request.
+ static final int PF_IN_SELECTION = 1 << 5;
+
+ // Represents whether child views are temporarily sliding out
+ static final int PF_SLIDING = 1 << 6;
+ static final int PF_LAYOUT_EATEN_IN_SLIDING = 1 << 7;
+
+ /**
+ * Force a full layout under certain situations. E.g. Rows change, jump to invisible child.
+ */
+ static final int PF_FORCE_FULL_LAYOUT = 1 << 8;
+
+ /**
+ * True if layout is enabled.
+ */
+ static final int PF_LAYOUT_ENABLED = 1 << 9;
+
+ /**
+ * Flag controlling whether the current/next layout should
+ * be updating the secondary size of rows.
+ */
+ static final int PF_ROW_SECONDARY_SIZE_REFRESH = 1 << 10;
+
+ /**
+ * Allow DPAD key to navigate out at the front of the View (where position = 0),
+ * default is false.
+ */
+ static final int PF_FOCUS_OUT_FRONT = 1 << 11;
+
+ /**
+ * Allow DPAD key to navigate out at the end of the view, default is false.
+ */
+ static final int PF_FOCUS_OUT_END = 1 << 12;
+
+ static final int PF_FOCUS_OUT_MASKS = PF_FOCUS_OUT_FRONT | PF_FOCUS_OUT_END;
+
+ /**
+ * Allow DPAD key to navigate out of second axis.
+ * default is true.
+ */
+ static final int PF_FOCUS_OUT_SIDE_START = 1 << 13;
+
+ /**
+ * Allow DPAD key to navigate out of second axis.
+ */
+ static final int PF_FOCUS_OUT_SIDE_END = 1 << 14;
+
+ static final int PF_FOCUS_OUT_SIDE_MASKS = PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END;
+
+ /**
+ * True if focus search is disabled.
+ */
+ static final int PF_FOCUS_SEARCH_DISABLED = 1 << 15;
+
+ /**
+ * True if prune child, might be disabled during transition.
+ */
+ static final int PF_PRUNE_CHILD = 1 << 16;
+
+ /**
+ * True if scroll content, might be disabled during transition.
+ */
+ static final int PF_SCROLL_ENABLED = 1 << 17;
+
+ /**
+ * Set to true for RTL layout in horizontal orientation
+ */
+ static final int PF_REVERSE_FLOW_PRIMARY = 1 << 18;
+
+ /**
+ * Set to true for RTL layout in vertical orientation
+ */
+ static final int PF_REVERSE_FLOW_SECONDARY = 1 << 19;
+
+ static final int PF_REVERSE_FLOW_MASK = PF_REVERSE_FLOW_PRIMARY | PF_REVERSE_FLOW_SECONDARY;
+
+ int mFlag = PF_LAYOUT_ENABLED
+ | PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END
+ | PF_PRUNE_CHILD | PF_SCROLL_ENABLED;
private OnChildSelectedListener mChildSelectedListener = null;
@@ -493,16 +576,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private int mPrimaryScrollExtra;
/**
- * Force a full layout under certain situations. E.g. Rows change, jump to invisible child.
- */
- private boolean mForceFullLayout;
-
- /**
- * True if layout is enabled.
- */
- private boolean mLayoutEnabled = true;
-
- /**
* override child visibility
*/
@Visibility
@@ -535,12 +608,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private int[] mRowSizeSecondary;
/**
- * Flag controlling whether the current/next layout should
- * be updating the secondary size of rows.
- */
- private boolean mRowSecondarySizeRefresh;
-
- /**
* The maximum measured size of the view.
*/
private int mMaxSizeSecondary;
@@ -605,58 +672,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private int mExtraLayoutSpace;
/**
- * Allow DPAD key to navigate out at the front of the View (where position = 0),
- * default is false.
- */
- private boolean mFocusOutFront;
-
- /**
- * Allow DPAD key to navigate out at the end of the view, default is false.
- */
- private boolean mFocusOutEnd;
-
- /**
- * Allow DPAD key to navigate out of second axis.
- * default is true.
- */
- private boolean mFocusOutSideStart = true;
-
- /**
- * Allow DPAD key to navigate out of second axis.
- */
- private boolean mFocusOutSideEnd = true;
-
- /**
- * True if focus search is disabled.
- */
- private boolean mFocusSearchDisabled;
-
- /**
- * True if prune child, might be disabled during transition.
- */
- private boolean mPruneChild = true;
-
- /**
- * True if scroll content, might be disabled during transition.
- */
- private boolean mScrollEnabled = true;
-
- /**
* Temporary variable: an int array of length=2.
*/
static int[] sTwoInts = new int[2];
/**
- * Set to true for RTL layout in horizontal orientation
- */
- boolean mReverseFlowPrimary = false;
-
- /**
- * Set to true for RTL layout in vertical orientation
- */
- private boolean mReverseFlowSecondary = false;
-
- /**
* Temporaries used for measuring.
*/
private int[] mMeasuredDimension = new int[2];
@@ -685,24 +705,21 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
mWindowAlignment.setOrientation(orientation);
mItemAlignment.setOrientation(orientation);
- mForceFullLayout = true;
+ mFlag |= PF_FORCE_FULL_LAYOUT;
}
public void onRtlPropertiesChanged(int layoutDirection) {
- boolean reversePrimary, reverseSecondary;
+ final int flags;
if (mOrientation == HORIZONTAL) {
- reversePrimary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
- reverseSecondary = false;
+ flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_PRIMARY : 0;
} else {
- reverseSecondary = layoutDirection == View.LAYOUT_DIRECTION_RTL;
- reversePrimary = false;
+ flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_SECONDARY : 0;
}
- if (mReverseFlowPrimary == reversePrimary && mReverseFlowSecondary == reverseSecondary) {
+ if ((mFlag & PF_REVERSE_FLOW_MASK) == flags) {
return;
}
- mReverseFlowPrimary = reversePrimary;
- mReverseFlowSecondary = reverseSecondary;
- mForceFullLayout = true;
+ mFlag = (mFlag & ~PF_REVERSE_FLOW_MASK) | flags;
+ mFlag |= PF_FORCE_FULL_LAYOUT;
mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
}
@@ -775,13 +792,15 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
- mFocusOutFront = throughFront;
- mFocusOutEnd = throughEnd;
+ mFlag = (mFlag & ~PF_FOCUS_OUT_MASKS)
+ | (throughFront ? PF_FOCUS_OUT_FRONT : 0)
+ | (throughEnd ? PF_FOCUS_OUT_END : 0);
}
public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) {
- mFocusOutSideStart = throughStart;
- mFocusOutSideEnd = throughEnd;
+ mFlag = (mFlag & ~PF_FOCUS_OUT_SIDE_MASKS)
+ | (throughStart ? PF_FOCUS_OUT_SIDE_START : 0)
+ | (throughEnd ? PF_FOCUS_OUT_SIDE_END : 0);
}
public void setNumRows(int numRows) {
@@ -971,7 +990,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// layout warning.
// If not in layout, we may be scrolling in which case the child layout request will be
// eaten by recyclerview. Post a requestLayout.
- if (!mInLayout && !mBaseGridView.isLayoutRequested()) {
+ if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && !mBaseGridView.isLayoutRequested()) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
if (getChildAt(i).isLayoutRequested()) {
@@ -1177,19 +1196,19 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mSubFocusPosition = 0;
}
if (!mState.didStructureChange() && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
- && !mForceFullLayout && mGrid.getNumRows() == mNumRows) {
+ && (mFlag & PF_FORCE_FULL_LAYOUT) == 0 && mGrid.getNumRows() == mNumRows) {
updateScrollController();
updateSecondaryScrollLimits();
mGrid.setSpacing(mSpacingPrimary);
return true;
} else {
- mForceFullLayout = false;
+ mFlag &= ~PF_FORCE_FULL_LAYOUT;
if (mGrid == null || mNumRows != mGrid.getNumRows()
- || mReverseFlowPrimary != mGrid.isReversedFlow()) {
+ || ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) != mGrid.isReversedFlow()) {
mGrid = Grid.createGrid(mNumRows);
mGrid.setProvider(mGridProvider);
- mGrid.setReversedFlow(mReverseFlowPrimary);
+ mGrid.setReversedFlow((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0);
}
initScrollController();
updateSecondaryScrollLimits();
@@ -1216,7 +1235,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
int start = 0;
// Iterate from left to right, which is a different index traversal
// in RTL flow
- if (mReverseFlowSecondary) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
for (int i = mNumRows-1; i > rowIndex; i--) {
start += getRowSizeSecondary(i) + mSpacingSecondary;
}
@@ -1229,7 +1248,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
private int getSizeSecondary() {
- int rightmostIndex = mReverseFlowSecondary ? 0 : mNumRows - 1;
+ int rightmostIndex = (mFlag & PF_REVERSE_FLOW_SECONDARY) != 0 ? 0 : mNumRows - 1;
return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
}
@@ -1366,8 +1385,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
* Checks if we need to update row secondary sizes.
*/
private void updateRowSecondarySizeRefresh() {
- mRowSecondarySizeRefresh = processRowSizeSecondary(false);
- if (mRowSecondarySizeRefresh) {
+ mFlag = (mFlag & ~PF_ROW_SECONDARY_SIZE_REFRESH)
+ | (processRowSizeSecondary(false) ? PF_ROW_SECONDARY_SIZE_REFRESH : 0);
+ if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
forceRequestLayout();
}
@@ -1599,7 +1619,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout();
}
int subindex = getSubPositionByView(v, v.findFocus());
- if (!mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
// when we are appending item during scroll pass and the item's position
// matches the mFocusPosition, we should signal a childSelected event.
// However if we are still running PendingMoveSmoothScroller, we defer and
@@ -1610,20 +1630,20 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
&& mPendingMoveSmoothScroller == null) {
dispatchChildSelected();
}
- } else if (!mInFastRelayout) {
+ } else if ((mFlag & PF_FAST_RELAYOUT) == 0) {
// fastRelayout will dispatch event at end of onLayoutChildren().
// For full layout, two situations here:
// 1. mInLayoutSearchFocus is false, dispatchChildSelected() at mFocusPosition.
// 2. mInLayoutSearchFocus is true: dispatchChildSelected() on first child
// equal to or after mFocusPosition that can take focus.
- if (!mInLayoutSearchFocus && index == mFocusPosition
+ if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) == 0 && index == mFocusPosition
&& subindex == mSubFocusPosition) {
dispatchChildSelected();
- } else if (mInLayoutSearchFocus && index >= mFocusPosition
+ } else if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) != 0 && index >= mFocusPosition
&& v.hasFocusable()) {
mFocusPosition = index;
mSubFocusPosition = subindex;
- mInLayoutSearchFocus = false;
+ mFlag &= ~PF_IN_LAYOUT_SEARCH_FOCUS;
dispatchChildSelected();
}
}
@@ -1663,7 +1683,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (!mState.isPreLayout()) {
updateScrollLimits();
}
- if (!mInLayout && mPendingMoveSmoothScroller != null) {
+ if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && mPendingMoveSmoothScroller != null) {
mPendingMoveSmoothScroller.consumePendingMovesAfterLayout();
}
if (mChildLaidOutListener != null) {
@@ -1677,7 +1697,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
public void removeItem(int index) {
if (TRACE) TraceCompat.beginSection("removeItem");
View v = findViewByPosition(index - mPositionDeltaInPreLayout);
- if (mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
detachAndScrapView(v, mRecycler);
} else {
removeAndRecycleView(v, mRecycler);
@@ -1688,7 +1708,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
@Override
public int getEdge(int index) {
View v = findViewByPosition(index - mPositionDeltaInPreLayout);
- return mReverseFlowPrimary ? getViewMax(v) : getViewMin(v);
+ return (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? getViewMax(v) : getViewMin(v);
}
@Override
@@ -1705,7 +1725,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
}
final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
- final int horizontalGravity = (mReverseFlowPrimary || mReverseFlowSecondary)
+ final int horizontalGravity = (mFlag & PF_REVERSE_FLOW_MASK) != 0
? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK,
View.LAYOUT_DIRECTION_RTL)
: mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
@@ -1781,16 +1801,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
private void removeInvisibleViewsAtEnd() {
- if (mPruneChild && !mIsSlidingChildViews) {
- mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
- mReverseFlowPrimary ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
+ if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
+ mGrid.removeInvisibleItemsAtEnd(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+ ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
}
}
private void removeInvisibleViewsAtFront() {
- if (mPruneChild && !mIsSlidingChildViews) {
- mGrid.removeInvisibleItemsAtFront(mFocusPosition,
- mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace: -mExtraLayoutSpace);
+ if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
+ mGrid.removeInvisibleItemsAtFront(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+ ? mSizePrimary + mExtraLayoutSpace : -mExtraLayoutSpace);
}
}
@@ -1799,16 +1819,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
void slideIn() {
- if (mIsSlidingChildViews) {
- mIsSlidingChildViews = false;
+ if ((mFlag & PF_SLIDING) != 0) {
+ mFlag &= ~PF_SLIDING;
if (mFocusPosition >= 0) {
scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra);
} else {
- mLayoutEatenInSliding = false;
+ mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
requestLayout();
}
- if (mLayoutEatenInSliding) {
- mLayoutEatenInSliding = false;
+ if ((mFlag & PF_LAYOUT_EATEN_IN_SLIDING) != 0) {
+ mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) {
mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
@@ -1838,7 +1858,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
} else {
- if (mReverseFlowPrimary) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
distance = getWidth();
if (getChildCount() > 0) {
int start = getChildAt(0).getRight();
@@ -1861,14 +1881,18 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
return distance;
}
+ boolean isSlidingChildViews() {
+ return (mFlag & PF_SLIDING) != 0;
+ }
+
/**
* Temporarily slide out child and block layout and scroll requests.
*/
void slideOut() {
- if (mIsSlidingChildViews) {
+ if ((mFlag & PF_SLIDING) != 0) {
return;
}
- mIsSlidingChildViews = true;
+ mFlag |= PF_SLIDING;
if (getChildCount() == 0) {
return;
}
@@ -1886,13 +1910,13 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
private void appendVisibleItems() {
- mGrid.appendVisibleItems(mReverseFlowPrimary
+ mGrid.appendVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
: mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
}
private void prependVisibleItems() {
- mGrid.prependVisibleItems(mReverseFlowPrimary
+ mGrid.prependVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
: -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
}
@@ -1907,6 +1931,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
final int childCount = getChildCount();
int position = mGrid.getFirstVisibleIndex();
int index = 0;
+ mFlag &= ~PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
for (; index < childCount; index++, position++) {
View view = getChildAt(index);
// We don't hit fastRelayout() if State.didStructure() is true, but prelayout may add
@@ -1932,6 +1957,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (lp.viewNeedsUpdate()) {
+ mFlag |= PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
detachAndScrapView(view, mRecycler);
view = getViewForPosition(position);
addView(view, index);
@@ -1960,7 +1986,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
detachAndScrapView(v, mRecycler);
}
mGrid.invalidateItemsAfter(position);
- if (mPruneChild) {
+ if ((mFlag & PF_PRUNE_CHILD) != 0) {
// in regular prune child mode, we just append items up to edge limit
appendVisibleItems();
if (mFocusPosition >= 0 && mFocusPosition <= savedLastPos) {
@@ -2108,7 +2134,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
Log.v(getTag(), "layoutChildren start numRows " + mNumRows
+ " inPreLayout " + state.isPreLayout()
+ " didStructureChange " + state.didStructureChange()
- + " mForceFullLayout " + mForceFullLayout);
+ + " mForceFullLayout " + ((mFlag & PF_FORCE_FULL_LAYOUT) != 0));
Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
}
@@ -2121,20 +2147,20 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
return;
}
- if (mIsSlidingChildViews) {
+ if ((mFlag & PF_SLIDING) != 0) {
// if there is already children, delay the layout process until slideIn(), if it's
// first time layout children: scroll them offscreen at end of onLayoutChildren()
if (getChildCount() > 0) {
- mLayoutEatenInSliding = true;
+ mFlag |= PF_LAYOUT_EATEN_IN_SLIDING;
return;
}
}
- if (!mLayoutEnabled) {
+ if ((mFlag & PF_LAYOUT_ENABLED) == 0) {
discardLayoutInfo();
removeAndRecycleAllViews(recycler);
return;
}
- mInLayout = true;
+ mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_LAYOUT;
saveContext(recycler, state);
if (state.isPreLayout()) {
@@ -2172,7 +2198,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
appendVisibleItems();
prependVisibleItems();
}
- mInLayout = false;
+ mFlag &= ~PF_STAGE_MASK;
leaveContext();
if (DEBUG) Log.v(getTag(), "layoutChildren end");
return;
@@ -2206,13 +2232,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
deltaSecondary = state.getRemainingScrollHorizontal();
deltaPrimary = state.getRemainingScrollVertical();
}
- if (mInFastRelayout = layoutInit()) {
+ if (layoutInit()) {
+ mFlag |= PF_FAST_RELAYOUT;
// If grid view is empty, we will start from mFocusPosition
mGrid.setStart(mFocusPosition);
fastRelayout();
} else {
+ mFlag &= ~PF_FAST_RELAYOUT;
// layoutInit() has detached all views, so start from scratch
- mInLayoutSearchFocus = hadFocus;
+ mFlag = (mFlag & ~PF_IN_LAYOUT_SEARCH_FOCUS)
+ | (hadFocus ? PF_IN_LAYOUT_SEARCH_FOCUS : 0);
int startFromPosition, endPos;
if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
|| mFocusPosition < firstVisibleIndex)) {
@@ -2270,27 +2299,30 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
Log.d(getTag(), sw.toString());
}
- if (mRowSecondarySizeRefresh) {
- mRowSecondarySizeRefresh = false;
+ if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
+ mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
} else {
updateRowSecondarySizeRefresh();
}
- // For fastRelayout, only dispatch event when focus position changes.
- if (mInFastRelayout && (mFocusPosition != savedFocusPos || mSubFocusPosition
- != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView)) {
+ // For fastRelayout, only dispatch event when focus position changes or selected item
+ // being updated.
+ if ((mFlag & PF_FAST_RELAYOUT) != 0 && (mFocusPosition != savedFocusPos || mSubFocusPosition
+ != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView
+ || (mFlag & PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION) != 0)) {
dispatchChildSelected();
- } else if (!mInFastRelayout && mInLayoutSearchFocus) {
+ } else if ((mFlag & (PF_FAST_RELAYOUT | PF_IN_LAYOUT_SEARCH_FOCUS))
+ == PF_IN_LAYOUT_SEARCH_FOCUS) {
// For full layout we dispatchChildSelected() in createItem() unless searched all
// children and found none is focusable then dispatchChildSelected() here.
dispatchChildSelected();
}
dispatchChildSelectedAndPositioned();
- if (mIsSlidingChildViews) {
+ if ((mFlag & PF_SLIDING) != 0) {
scrollDirectionPrimary(getSlideOutDistance());
}
- mInLayout = false;
+ mFlag &= ~PF_STAGE_MASK;
leaveContext();
if (DEBUG) Log.v(getTag(), "layoutChildren end");
}
@@ -2324,11 +2356,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
@Override
public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
- if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+ if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
return 0;
}
saveContext(recycler, state);
- mInScroll = true;
+ mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
int result;
if (mOrientation == HORIZONTAL) {
result = scrollDirectionPrimary(dx);
@@ -2336,17 +2368,17 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
result = scrollDirectionSecondary(dx);
}
leaveContext();
- mInScroll = false;
+ mFlag &= ~PF_STAGE_MASK;
return result;
}
@Override
public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
- if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+ if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
return 0;
}
- mInScroll = true;
+ mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
saveContext(recycler, state);
int result;
if (mOrientation == VERTICAL) {
@@ -2355,7 +2387,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
result = scrollDirectionSecondary(dy);
}
leaveContext();
- mInScroll = false;
+ mFlag &= ~PF_STAGE_MASK;
return result;
}
@@ -2367,7 +2399,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// 2. During onLayoutChildren(), it may compensate the remaining scroll delta,
// we should honor the request regardless if it goes over minScroll / maxScroll.
// (see b/64931938 testScrollAndRemove and testScrollAndRemoveSample1)
- if (!mIsSlidingChildViews && !mInLayout) {
+ if ((mFlag & PF_SLIDING) == 0 && (mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
if (da > 0) {
if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
@@ -2389,7 +2421,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
return 0;
}
offsetChildrenPrimary(-da);
- if (mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
updateScrollLimits();
if (TRACE) TraceCompat.endSection();
return da;
@@ -2398,7 +2430,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
int childCount = getChildCount();
boolean updated;
- if (mReverseFlowPrimary ? da > 0 : da < 0) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
prependVisibleItems();
} else {
appendVisibleItems();
@@ -2407,7 +2439,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
childCount = getChildCount();
if (TRACE) TraceCompat.beginSection("remove");
- if (mReverseFlowPrimary ? da > 0 : da < 0) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
removeInvisibleViewsAtEnd();
} else {
removeInvisibleViewsAtFront();
@@ -2476,7 +2508,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
int highVisiblePos, lowVisiblePos;
int highMaxPos, lowMinPos;
- if (!mReverseFlowPrimary) {
+ if ((mFlag & PF_REVERSE_FLOW_PRIMARY) == 0) {
highVisiblePos = mGrid.getLastVisibleIndex();
highMaxPos = mState.getItemCount() - 1;
lowVisiblePos = mGrid.getFirstVisibleIndex();
@@ -2614,14 +2646,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// scrollToView() is based on Adapter position. Only call scrollToView() when item
// is still valid.
if (view != null && getAdapterPositionByView(view) == position) {
- mInSelection = true;
+ mFlag |= PF_IN_SELECTION;
scrollToView(view, smooth);
- mInSelection = false;
+ mFlag &= ~PF_IN_SELECTION;
} else {
mFocusPosition = position;
mSubFocusPosition = subposition;
mFocusPositionOffset = Integer.MIN_VALUE;
- if (!mLayoutEnabled || mIsSlidingChildViews) {
+ if ((mFlag & PF_LAYOUT_ENABLED) == 0 || (mFlag & PF_SLIDING) != 0) {
return;
}
if (smooth) {
@@ -2637,7 +2669,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mSubFocusPosition = 0;
}
} else {
- mForceFullLayout = true;
+ mFlag |= PF_FORCE_FULL_LAYOUT;
requestLayout();
}
}
@@ -2654,7 +2686,8 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
final int firstChildPos = getPosition(getChildAt(0));
// TODO We should be able to deduce direction from bounds of current and target
// focus, rather than making assumptions about positions and directionality
- final boolean isStart = mReverseFlowPrimary ? targetPosition > firstChildPos
+ final boolean isStart = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
+ ? targetPosition > firstChildPos
: targetPosition < firstChildPos;
final int direction = isStart ? -1 : 1;
if (mOrientation == HORIZONTAL) {
@@ -2788,14 +2821,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
@Override
public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
- if (mFocusSearchDisabled) {
+ if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
return true;
}
if (getAdapterPositionByView(child) == NO_POSITION) {
// This is could be the last view in DISAPPEARING animation.
return true;
}
- if (!mInLayout && !mInSelection && !mInScroll) {
+ if ((mFlag & (PF_STAGE_MASK | PF_IN_SELECTION)) == 0) {
scrollToView(child, focused, true);
}
return true;
@@ -2865,7 +2898,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
*/
private void scrollToView(View view, View childView, boolean smooth, int extraDelta,
int extraDeltaSecondary) {
- if (mIsSlidingChildViews) {
+ if ((mFlag & PF_SLIDING) != 0) {
return;
}
int newFocusPosition = getAdapterPositionByView(view);
@@ -2874,7 +2907,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = newFocusPosition;
mSubFocusPosition = newSubFocusPosition;
mFocusPositionOffset = 0;
- if (!mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
dispatchChildSelected();
}
if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
@@ -2889,7 +2922,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// by setSelection())
view.requestFocus();
}
- if (!mScrollEnabled && smooth) {
+ if ((mFlag & PF_SCROLL_ENABLED) == 0 && smooth) {
return;
}
if (getScrollPosition(view, childView, sTwoInts)
@@ -3007,7 +3040,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
- if (mInLayout) {
+ if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
scrollDirectionPrimary(scrollPrimary);
scrollDirectionSecondary(scrollSecondary);
} else {
@@ -3030,22 +3063,23 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
public void setPruneChild(boolean pruneChild) {
- if (mPruneChild != pruneChild) {
- mPruneChild = pruneChild;
- if (mPruneChild) {
+ if (((mFlag & PF_PRUNE_CHILD) != 0) != pruneChild) {
+ mFlag = (mFlag & ~PF_PRUNE_CHILD) | (pruneChild ? PF_PRUNE_CHILD : 0);
+ if (pruneChild) {
requestLayout();
}
}
}
public boolean getPruneChild() {
- return mPruneChild;
+ return (mFlag & PF_PRUNE_CHILD) != 0;
}
public void setScrollEnabled(boolean scrollEnabled) {
- if (mScrollEnabled != scrollEnabled) {
- mScrollEnabled = scrollEnabled;
- if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
+ if (((mFlag & PF_SCROLL_ENABLED) != 0) != scrollEnabled) {
+ mFlag = (mFlag & ~PF_SCROLL_ENABLED) | (scrollEnabled ? PF_SCROLL_ENABLED : 0);
+ if (((mFlag & PF_SCROLL_ENABLED) != 0)
+ && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
&& mFocusPosition != NO_POSITION) {
scrollToSelection(mFocusPosition, mSubFocusPosition,
true, mPrimaryScrollExtra);
@@ -3054,7 +3088,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
public boolean isScrollEnabled() {
- return mScrollEnabled;
+ return (mFlag & PF_SCROLL_ENABLED) != 0;
}
private int findImmediateChildIndex(View view) {
@@ -3088,16 +3122,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
void setFocusSearchDisabled(boolean disabled) {
- mFocusSearchDisabled = disabled;
+ mFlag = (mFlag & ~PF_FOCUS_SEARCH_DISABLED) | (disabled ? PF_FOCUS_SEARCH_DISABLED : 0);
}
boolean isFocusSearchDisabled() {
- return mFocusSearchDisabled;
+ return (mFlag & PF_FOCUS_SEARCH_DISABLED) != 0;
}
@Override
public View onInterceptFocusSearch(View focused, int direction) {
- if (mFocusSearchDisabled) {
+ if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
return focused;
}
@@ -3132,27 +3166,27 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
int movement = getMovement(direction);
final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
if (movement == NEXT_ITEM) {
- if (isScroll || !mFocusOutEnd) {
+ if (isScroll || (mFlag & PF_FOCUS_OUT_END) == 0) {
result = focused;
}
- if (mScrollEnabled && !hasCreatedLastItem()) {
+ if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedLastItem()) {
processPendingMovement(true);
result = focused;
}
} else if (movement == PREV_ITEM) {
- if (isScroll || !mFocusOutFront) {
+ if (isScroll || (mFlag & PF_FOCUS_OUT_FRONT) == 0) {
result = focused;
}
- if (mScrollEnabled && !hasCreatedFirstItem()) {
+ if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedFirstItem()) {
processPendingMovement(false);
result = focused;
}
} else if (movement == NEXT_ROW) {
- if (isScroll || !mFocusOutSideEnd) {
+ if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_END) == 0) {
result = focused;
}
} else if (movement == PREV_ROW) {
- if (isScroll || !mFocusOutSideStart) {
+ if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_START) == 0) {
result = focused;
}
}
@@ -3191,7 +3225,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
@Override
public boolean onAddFocusables(RecyclerView recyclerView,
ArrayList<View> views, int direction, int focusableMode) {
- if (mFocusSearchDisabled) {
+ if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
return true;
}
// If this viewgroup or one of its children currently has focus then we
@@ -3423,10 +3457,10 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (mOrientation == HORIZONTAL) {
switch(direction) {
case View.FOCUS_LEFT:
- movement = (!mReverseFlowPrimary) ? PREV_ITEM : NEXT_ITEM;
+ movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? PREV_ITEM : NEXT_ITEM;
break;
case View.FOCUS_RIGHT:
- movement = (!mReverseFlowPrimary) ? NEXT_ITEM : PREV_ITEM;
+ movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? NEXT_ITEM : PREV_ITEM;
break;
case View.FOCUS_UP:
movement = PREV_ROW;
@@ -3438,10 +3472,10 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
} else if (mOrientation == VERTICAL) {
switch(direction) {
case View.FOCUS_LEFT:
- movement = (!mReverseFlowSecondary) ? PREV_ROW : NEXT_ROW;
+ movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? PREV_ROW : NEXT_ROW;
break;
case View.FOCUS_RIGHT:
- movement = (!mReverseFlowSecondary) ? NEXT_ROW : PREV_ROW;
+ movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? NEXT_ROW : PREV_ROW;
break;
case View.FOCUS_UP:
movement = PREV_ITEM;
@@ -3497,12 +3531,12 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
private void discardLayoutInfo() {
mGrid = null;
mRowSizeSecondary = null;
- mRowSecondarySizeRefresh = false;
+ mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
}
public void setLayoutEnabled(boolean layoutEnabled) {
- if (mLayoutEnabled != layoutEnabled) {
- mLayoutEnabled = layoutEnabled;
+ if (((mFlag & PF_LAYOUT_ENABLED) != 0) != layoutEnabled) {
+ mFlag = (mFlag & ~PF_LAYOUT_ENABLED) | (layoutEnabled ? PF_LAYOUT_ENABLED : 0);
requestLayout();
}
}
@@ -3592,7 +3626,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = loadingState.index;
mFocusPositionOffset = 0;
mChildrenStates.loadFromBundle(loadingState.childStates);
- mForceFullLayout = true;
+ mFlag |= PF_FORCE_FULL_LAYOUT;
requestLayout();
if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
}
@@ -3699,9 +3733,9 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (newSelected != null) {
if (preventScroll) {
if (hasFocus()) {
- mInSelection = true;
+ mFlag |= PF_IN_SELECTION;
newSelected.requestFocus();
- mInSelection = false;
+ mFlag &= ~PF_IN_SELECTION;
}
mFocusPosition = focusPosition;
mSubFocusPosition = 0;
@@ -3717,11 +3751,11 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
AccessibilityNodeInfoCompat info) {
saveContext(recycler, state);
int count = state.getItemCount();
- if (mScrollEnabled && count > 1 && !isItemFullyVisible(0)) {
+ if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(0)) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
info.setScrollable(true);
}
- if (mScrollEnabled && count > 1 && !isItemFullyVisible(count - 1)) {
+ if ((mFlag & PF_SCROLL_ENABLED) != 0 && count > 1 && !isItemFullyVisible(count - 1)) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
info.setScrollable(true);
}
diff --git a/android/support/v17/leanback/widget/GuidedActionAdapter.java b/android/support/v17/leanback/widget/GuidedActionAdapter.java
index 5b755f5e..51b29e21 100644
--- a/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ b/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -15,7 +15,9 @@ package android.support.v17.leanback.widget;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.Log;
@@ -103,6 +105,7 @@ public class GuidedActionAdapter extends RecyclerView.Adapter {
private ClickListener mClickListener;
final GuidedActionsStylist mStylist;
GuidedActionAdapterGroup mGroup;
+ DiffCallback<GuidedAction> mDiffCallback;
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
@@ -145,20 +148,78 @@ public class GuidedActionAdapter extends RecyclerView.Adapter {
mActionOnFocusListener = new ActionOnFocusListener(focusListener);
mActionEditListener = new ActionEditListener();
mIsSubAdapter = isSubAdapter;
+ if (!isSubAdapter) {
+ mDiffCallback = GuidedActionDiffCallback.getInstance();
+ }
+ }
+
+ /**
+ * Change DiffCallback used in {@link #setActions(List)}. Set to null for firing a
+ * general {@link #notifyDataSetChanged()}.
+ *
+ * @param diffCallback
+ */
+ public void setDiffCallback(DiffCallback<GuidedAction> diffCallback) {
+ mDiffCallback = diffCallback;
}
/**
- * Sets the list of actions managed by this adapter.
+ * Sets the list of actions managed by this adapter. Use {@link #setDiffCallback(DiffCallback)}
+ * to change DiffCallback.
* @param actions The list of actions to be managed.
*/
- public void setActions(List<GuidedAction> actions) {
+ public void setActions(final List<GuidedAction> actions) {
if (!mIsSubAdapter) {
mStylist.collapseAction(false);
}
mActionOnFocusListener.unFocus();
- mActions.clear();
- mActions.addAll(actions);
- notifyDataSetChanged();
+ if (mDiffCallback != null) {
+ // temporary variable used for DiffCallback
+ final List<GuidedAction> oldActions = new ArrayList();
+ oldActions.addAll(mActions);
+
+ // update items.
+ mActions.clear();
+ mActions.addAll(actions);
+
+ DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
+ @Override
+ public int getOldListSize() {
+ return oldActions.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return mActions.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ return mDiffCallback.areItemsTheSame(oldActions.get(oldItemPosition),
+ mActions.get(newItemPosition));
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ return mDiffCallback.areContentsTheSame(oldActions.get(oldItemPosition),
+ mActions.get(newItemPosition));
+ }
+
+ @Nullable
+ @Override
+ public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+ return mDiffCallback.getChangePayload(oldActions.get(oldItemPosition),
+ mActions.get(newItemPosition));
+ }
+ });
+
+ // dispatch diff result
+ diffResult.dispatchUpdatesTo(this);
+ } else {
+ mActions.clear();
+ mActions.addAll(actions);
+ notifyDataSetChanged();
+ }
}
/**
diff --git a/android/support/v17/leanback/widget/GuidedActionDiffCallback.java b/android/support/v17/leanback/widget/GuidedActionDiffCallback.java
new file mode 100644
index 00000000..d4d4d77a
--- /dev/null
+++ b/android/support/v17/leanback/widget/GuidedActionDiffCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
+/**
+ * DiffCallback used for GuidedActions, see {@link
+ * android.support.v17.leanback.app.GuidedStepSupportFragment#setActionsDiffCallback(DiffCallback)}.
+ */
+public class GuidedActionDiffCallback extends DiffCallback<GuidedAction> {
+
+ static final GuidedActionDiffCallback sInstance = new GuidedActionDiffCallback();
+
+ /**
+ * Returns the singleton GuidedActionDiffCallback.
+ * @return The singleton GuidedActionDiffCallback.
+ */
+ public static final GuidedActionDiffCallback getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public boolean areItemsTheSame(@NonNull GuidedAction oldItem, @NonNull GuidedAction newItem) {
+ if (oldItem == null) {
+ return newItem == null;
+ } else if (newItem == null) {
+ return false;
+ }
+ return oldItem.getId() == newItem.getId();
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull GuidedAction oldItem,
+ @NonNull GuidedAction newItem) {
+ if (oldItem == null) {
+ return newItem == null;
+ } else if (newItem == null) {
+ return false;
+ }
+ return oldItem.getCheckSetId() == newItem.getCheckSetId()
+ && oldItem.mActionFlags == newItem.mActionFlags
+ && TextUtils.equals(oldItem.getTitle(), newItem.getTitle())
+ && TextUtils.equals(oldItem.getDescription(), newItem.getDescription())
+ && oldItem.getInputType() == newItem.getInputType()
+ && TextUtils.equals(oldItem.getEditTitle(), newItem.getEditTitle())
+ && TextUtils.equals(oldItem.getEditDescription(), newItem.getEditDescription())
+ && oldItem.getEditInputType() == newItem.getEditInputType()
+ && oldItem.getDescriptionEditInputType() == newItem.getDescriptionEditInputType();
+ }
+}
diff --git a/android/support/v17/leanback/widget/ObjectAdapter.java b/android/support/v17/leanback/widget/ObjectAdapter.java
index 535f81b4..d411f9e7 100644
--- a/android/support/v17/leanback/widget/ObjectAdapter.java
+++ b/android/support/v17/leanback/widget/ObjectAdapter.java
@@ -13,7 +13,10 @@
*/
package android.support.v17.leanback.widget;
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
import android.database.Observable;
+import android.support.annotation.RestrictTo;
/**
* Base class adapter to be used in leanback activities. Provides access to a data model and is
@@ -132,6 +135,10 @@ public abstract class ObjectAdapter {
mObservers.get(i).onItemMoved(positionStart, toPosition);
}
}
+
+ boolean hasObserver() {
+ return mObservers.size() > 0;
+ }
}
private final DataObservable mObservable = new DataObservable();
@@ -207,6 +214,14 @@ public abstract class ObjectAdapter {
}
/**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public final boolean hasObserver() {
+ return mObservable.hasObserver();
+ }
+
+ /**
* Unregisters all DataObservers for this ObjectAdapter.
*/
public final void unregisterAllObservers() {
diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java
index 614ff351..78161a87 100644
--- a/android/support/v4/app/FragmentActivity.java
+++ b/android/support/v4/app/FragmentActivity.java
@@ -536,7 +536,7 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ markFragmentsCreated();
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
@@ -591,7 +591,7 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
super.onStop();
mStopped = true;
- markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ markFragmentsCreated();
mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
mFragments.dispatchStop();
@@ -970,18 +970,30 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
}
}
- private static void markState(FragmentManager manager, Lifecycle.State state) {
+ private void markFragmentsCreated() {
+ boolean reiterate;
+ do {
+ reiterate = markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ } while (reiterate);
+ }
+
+ private static boolean markState(FragmentManager manager, Lifecycle.State state) {
+ boolean hadNotMarked = false;
Collection<Fragment> fragments = manager.getFragments();
for (Fragment fragment : fragments) {
if (fragment == null) {
continue;
}
- fragment.mLifecycleRegistry.markState(state);
+ if (fragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
+ fragment.mLifecycleRegistry.markState(state);
+ hadNotMarked = true;
+ }
FragmentManager childFragmentManager = fragment.peekChildFragmentManager();
if (childFragmentManager != null) {
- markState(childFragmentManager, state);
+ hadNotMarked |= markState(childFragmentManager, state);
}
}
+ return hadNotMarked;
}
}
diff --git a/android/support/v4/graphics/TypefaceCompat.java b/android/support/v4/graphics/TypefaceCompat.java
index 3c55df62..734f1837 100644
--- a/android/support/v4/graphics/TypefaceCompat.java
+++ b/android/support/v4/graphics/TypefaceCompat.java
@@ -35,7 +35,6 @@ import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.provider.FontsContractCompat;
import android.support.v4.provider.FontsContractCompat.FontInfo;
import android.support.v4.util.LruCache;
-
/**
* Helper for accessing features in {@link Typeface}.
* @hide
@@ -46,7 +45,9 @@ public class TypefaceCompat {
private static final TypefaceCompatImpl sTypefaceCompatImpl;
static {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ sTypefaceCompatImpl = new TypefaceCompatApi28Impl();
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
sTypefaceCompatImpl = new TypefaceCompatApi26Impl();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
&& TypefaceCompatApi24Impl.isUsable()) {
diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index 1b55a2e0..00e31a1a 100644
--- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -60,76 +60,69 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
"createFromFamiliesWithDefault";
private static final String FREEZE_METHOD = "freeze";
private static final String ABORT_CREATION_METHOD = "abortCreation";
- private static final Class sFontFamily;
- private static final Constructor sFontFamilyCtor;
- private static final Method sAddFontFromAssetManager;
- private static final Method sAddFontFromBuffer;
- private static final Method sFreeze;
- private static final Method sAbortCreation;
- private static final Method sCreateFromFamiliesWithDefault;
private static final int RESOLVE_BY_FONT_TABLE = -1;
- static {
- Class fontFamilyClass;
+ protected final Class mFontFamily;
+ protected final Constructor mFontFamilyCtor;
+ protected final Method mAddFontFromAssetManager;
+ protected final Method mAddFontFromBuffer;
+ protected final Method mFreeze;
+ protected final Method mAbortCreation;
+ protected final Method mCreateFromFamiliesWithDefault;
+
+ public TypefaceCompatApi26Impl() {
+ Class fontFamily;
Constructor fontFamilyCtor;
- Method addFontMethod;
- Method addFromBufferMethod;
- Method freezeMethod;
- Method abortCreationMethod;
- Method createFromFamiliesWithDefaultMethod;
+ Method addFontFromAssetManager;
+ Method addFontFromBuffer;
+ Method freeze;
+ Method abortCreation;
+ Method createFromFamiliesWithDefault;
try {
- fontFamilyClass = Class.forName(FONT_FAMILY_CLASS);
- fontFamilyCtor = fontFamilyClass.getConstructor();
- addFontMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
- AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
- Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
- addFromBufferMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
- ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
- Integer.TYPE);
- freezeMethod = fontFamilyClass.getMethod(FREEZE_METHOD);
- abortCreationMethod = fontFamilyClass.getMethod(ABORT_CREATION_METHOD);
- Object familyArray = Array.newInstance(fontFamilyClass, 1);
- createFromFamiliesWithDefaultMethod =
- Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
- familyArray.getClass(), Integer.TYPE, Integer.TYPE);
- createFromFamiliesWithDefaultMethod.setAccessible(true);
+ fontFamily = obtainFontFamily();
+ fontFamilyCtor = obtainFontFamilyCtor(fontFamily);
+ addFontFromAssetManager = obtainAddFontFromAssetManagerMethod(fontFamily);
+ addFontFromBuffer = obtainAddFontFromBufferMethod(fontFamily);
+ freeze = obtainFreezeMethod(fontFamily);
+ abortCreation = obtainAbortCreationMethod(fontFamily);
+ createFromFamiliesWithDefault = obtainCreateFromFamiliesWithDefaultMethod(fontFamily);
} catch (ClassNotFoundException | NoSuchMethodException e) {
Log.e(TAG, "Unable to collect necessary methods for class " + e.getClass().getName(),
e);
- fontFamilyClass = null;
+ fontFamily = null;
fontFamilyCtor = null;
- addFontMethod = null;
- addFromBufferMethod = null;
- freezeMethod = null;
- abortCreationMethod = null;
- createFromFamiliesWithDefaultMethod = null;
+ addFontFromAssetManager = null;
+ addFontFromBuffer = null;
+ freeze = null;
+ abortCreation = null;
+ createFromFamiliesWithDefault = null;
}
- sFontFamilyCtor = fontFamilyCtor;
- sFontFamily = fontFamilyClass;
- sAddFontFromAssetManager = addFontMethod;
- sAddFontFromBuffer = addFromBufferMethod;
- sFreeze = freezeMethod;
- sAbortCreation = abortCreationMethod;
- sCreateFromFamiliesWithDefault = createFromFamiliesWithDefaultMethod;
+ mFontFamily = fontFamily;
+ mFontFamilyCtor = fontFamilyCtor;
+ mAddFontFromAssetManager = addFontFromAssetManager;
+ mAddFontFromBuffer = addFontFromBuffer;
+ mFreeze = freeze;
+ mAbortCreation = abortCreation;
+ mCreateFromFamiliesWithDefault = createFromFamiliesWithDefault;
}
/**
- * Returns true if API26 implementation is usable.
+ * Returns true if all the necessary methods were found.
*/
- private static boolean isFontFamilyPrivateAPIAvailable() {
- if (sAddFontFromAssetManager == null) {
+ private boolean isFontFamilyPrivateAPIAvailable() {
+ if (mAddFontFromAssetManager == null) {
Log.w(TAG, "Unable to collect necessary private methods. "
+ "Fallback to legacy implementation.");
}
- return sAddFontFromAssetManager != null;
+ return mAddFontFromAssetManager != null;
}
/**
* Create a new FontFamily instance
*/
- private static Object newFamily() {
+ private Object newFamily() {
try {
- return sFontFamilyCtor.newInstance();
+ return mFontFamilyCtor.newInstance();
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
@@ -139,10 +132,10 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
* Call FontFamily#addFontFromAssetManager(AssetManager mgr, String path, int cookie,
* boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes)
*/
- private static boolean addFontFromAssetManager(Context context, Object family, String fileName,
+ private boolean addFontFromAssetManager(Context context, Object family, String fileName,
int ttcIndex, int weight, int style) {
try {
- final Boolean result = (Boolean) sAddFontFromAssetManager.invoke(family,
+ final Boolean result = (Boolean) mAddFontFromAssetManager.invoke(family,
context.getAssets(), fileName, 0 /* cookie */, false /* isAsset */, ttcIndex,
weight, style, null /* axes */);
return result.booleanValue();
@@ -155,10 +148,10 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
* Call FontFamily#addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
* int weight, int italic)
*/
- private static boolean addFontFromBuffer(Object family, ByteBuffer buffer,
+ private boolean addFontFromBuffer(Object family, ByteBuffer buffer,
int ttcIndex, int weight, int style) {
try {
- final Boolean result = (Boolean) sAddFontFromBuffer.invoke(family,
+ final Boolean result = (Boolean) mAddFontFromBuffer.invoke(family,
buffer, ttcIndex, null /* axes */, weight, style);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
@@ -167,14 +160,14 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
}
/**
- * Call static method Typeface#createFromFamiliesWithDefault(
+ * Call method Typeface#createFromFamiliesWithDefault(
* FontFamily[] families, int weight, int italic)
*/
- private static Typeface createFromFamiliesWithDefault(Object family) {
+ protected Typeface createFromFamiliesWithDefault(Object family) {
try {
- Object familyArray = Array.newInstance(sFontFamily, 1);
+ Object familyArray = Array.newInstance(mFontFamily, 1);
Array.set(familyArray, 0, family);
- return (Typeface) sCreateFromFamiliesWithDefault.invoke(null /* static method */,
+ return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
familyArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
@@ -184,9 +177,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
/**
* Call FontFamily#freeze()
*/
- private static boolean freeze(Object family) {
+ private boolean freeze(Object family) {
try {
- Boolean result = (Boolean) sFreeze.invoke(family);
+ Boolean result = (Boolean) mFreeze.invoke(family);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
@@ -196,9 +189,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
/**
* Call FontFamily#abortCreation()
*/
- private static boolean abortCreation(Object family) {
+ private boolean abortCreation(Object family) {
try {
- Boolean result = (Boolean) sAbortCreation.invoke(family);
+ Boolean result = (Boolean) mAbortCreation.invoke(family);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
@@ -299,4 +292,47 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
}
return createFromFamiliesWithDefault(fontFamily);
}
+
+ // The following getters retrieve by reflection the Typeface methods, belonging to the
+ // framework code, which will be invoked. Since the definitions of these methods can change
+ // across different API versions, inheriting classes should override these getters in order to
+ // reflect the method definitions in the API versions they represent.
+ //===========================================================================================
+ protected Class obtainFontFamily() throws ClassNotFoundException {
+ return Class.forName(FONT_FAMILY_CLASS);
+ }
+
+ protected Constructor obtainFontFamilyCtor(Class fontFamily) throws NoSuchMethodException {
+ return fontFamily.getConstructor();
+ }
+
+ protected Method obtainAddFontFromAssetManagerMethod(Class fontFamily)
+ throws NoSuchMethodException {
+ return fontFamily.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
+ AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
+ Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
+ }
+
+ protected Method obtainAddFontFromBufferMethod(Class fontFamily) throws NoSuchMethodException {
+ return fontFamily.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
+ ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
+ Integer.TYPE);
+ }
+
+ protected Method obtainFreezeMethod(Class fontFamily) throws NoSuchMethodException {
+ return fontFamily.getMethod(FREEZE_METHOD);
+ }
+
+ protected Method obtainAbortCreationMethod(Class fontFamily) throws NoSuchMethodException {
+ return fontFamily.getMethod(ABORT_CREATION_METHOD);
+ }
+
+ protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+ throws NoSuchMethodException {
+ Object familyArray = Array.newInstance(fontFamily, 1);
+ Method m = Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+ familyArray.getClass(), Integer.TYPE, Integer.TYPE);
+ m.setAccessible(true);
+ return m;
+ }
}
diff --git a/android/support/v4/graphics/TypefaceCompatApi28Impl.java b/android/support/v4/graphics/TypefaceCompatApi28Impl.java
new file mode 100644
index 00000000..baa2ce67
--- /dev/null
+++ b/android/support/v4/graphics/TypefaceCompatApi28Impl.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Typeface;
+import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Implementation of the Typeface compat methods for API 28 and above.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+@RequiresApi(28)
+public class TypefaceCompatApi28Impl extends TypefaceCompatApi26Impl {
+ private static final String TAG = "TypefaceCompatApi28Impl";
+
+ private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD =
+ "createFromFamiliesWithDefault";
+ private static final int RESOLVE_BY_FONT_TABLE = -1;
+ private static final String DEFAULT_FAMILY = "sans-serif";
+
+ /**
+ * Call method Typeface#createFromFamiliesWithDefault(
+ * FontFamily[] families, String fallbackName, int weight, int italic)
+ */
+ @Override
+ protected Typeface createFromFamiliesWithDefault(Object family) {
+ try {
+ Object familyArray = Array.newInstance(mFontFamily, 1);
+ Array.set(familyArray, 0, family);
+ return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
+ familyArray, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
+ throws NoSuchMethodException {
+ Object familyArray = Array.newInstance(fontFamily, 1);
+ Method m = Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
+ familyArray.getClass(), String.class, Integer.TYPE, Integer.TYPE);
+ m.setAccessible(true);
+ return m;
+ }
+}
diff --git a/android/support/v4/media/MediaBrowserCompat.java b/android/support/v4/media/MediaBrowserCompat.java
index 85f5a512..7adf7d78 100644
--- a/android/support/v4/media/MediaBrowserCompat.java
+++ b/android/support/v4/media/MediaBrowserCompat.java
@@ -676,17 +676,15 @@ public final class MediaBrowserCompat {
WeakReference<Subscription> mSubscriptionRef;
public SubscriptionCallback() {
+ mToken = new Binder();
if (Build.VERSION.SDK_INT >= 26) {
mSubscriptionCallbackObj =
MediaBrowserCompatApi26.createSubscriptionCallback(new StubApi26());
- mToken = null;
} else if (Build.VERSION.SDK_INT >= 21) {
mSubscriptionCallbackObj =
MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
- mToken = new Binder();
} else {
mSubscriptionCallbackObj = null;
- mToken = new Binder();
}
}
@@ -1958,22 +1956,30 @@ public final class MediaBrowserCompat {
@Override
public void subscribe(@NonNull String parentId, @Nullable Bundle options,
@NonNull SubscriptionCallback callback) {
- if (options == null) {
- MediaBrowserCompatApi21.subscribe(
- mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ if (mServiceBinderWrapper == null) {
+ if (options == null) {
+ MediaBrowserCompatApi21.subscribe(
+ mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ } else {
+ MediaBrowserCompatApi26.subscribe(
+ mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+ }
} else {
- MediaBrowserCompatApi26.subscribe(
- mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+ super.subscribe(parentId, options, callback);
}
}
@Override
public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
- if (callback == null) {
- MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ if (mServiceBinderWrapper == null) {
+ if (callback == null) {
+ MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ } else {
+ MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
+ callback.mSubscriptionCallbackObj);
+ }
} else {
- MediaBrowserCompatApi26.unsubscribe(mBrowserObj, parentId,
- callback.mSubscriptionCallbackObj);
+ super.unsubscribe(parentId, callback);
}
}
}
diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java
index 53b111ab..debc66e8 100644
--- a/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -422,11 +422,15 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override
public void notifyChildrenChanged(final String parentId, final Bundle options) {
- if (options == null) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ if (mMessenger == null) {
+ if (options == null) {
+ MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ } else {
+ MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
+ options);
+ }
} else {
- MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
- options);
+ super.notifyChildrenChanged(parentId, options);
}
}
diff --git a/android/support/v4/media/MediaMetadataCompat.java b/android/support/v4/media/MediaMetadataCompat.java
index 3ddf255c..00f16cb3 100644
--- a/android/support/v4/media/MediaMetadataCompat.java
+++ b/android/support/v4/media/MediaMetadataCompat.java
@@ -365,10 +365,12 @@ public final class MediaMetadataCompat implements Parcelable {
MediaMetadataCompat(Bundle bundle) {
mBundle = new Bundle(bundle);
+ mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader());
}
MediaMetadataCompat(Parcel in) {
mBundle = in.readBundle();
+ mBundle.setClassLoader(MediaMetadataCompat.class.getClassLoader());
}
/**
diff --git a/android/support/v4/view/ViewCompat.java b/android/support/v4/view/ViewCompat.java
index 34a198a1..204a1218 100644
--- a/android/support/v4/view/ViewCompat.java
+++ b/android/support/v4/view/ViewCompat.java
@@ -1356,7 +1356,7 @@ public class ViewCompat {
// after applying the tint
Drawable background = view.getBackground();
boolean hasTint = (view.getBackgroundTintList() != null)
- && (view.getBackgroundTintMode() != null);
+ || (view.getBackgroundTintMode() != null);
if ((background != null) && hasTint) {
if (background.isStateful()) {
background.setState(view.getDrawableState());
@@ -1375,7 +1375,7 @@ public class ViewCompat {
// after applying the tint
Drawable background = view.getBackground();
boolean hasTint = (view.getBackgroundTintList() != null)
- && (view.getBackgroundTintMode() != null);
+ || (view.getBackgroundTintMode() != null);
if ((background != null) && hasTint) {
if (background.isStateful()) {
background.setState(view.getDrawableState());
diff --git a/android/support/v7/app/AppCompatDelegateImplV9.java b/android/support/v7/app/AppCompatDelegateImplV9.java
index 056e33e3..5b53401c 100644
--- a/android/support/v7/app/AppCompatDelegateImplV9.java
+++ b/android/support/v7/app/AppCompatDelegateImplV9.java
@@ -1001,7 +1001,26 @@ class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
- mAppCompatViewInflater = new AppCompatViewInflater();
+ TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
+ String viewInflaterClassName =
+ a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
+ if ((viewInflaterClassName == null)
+ || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
+ // Either default class name or set explicitly to null. In both cases
+ // create the base inflater (no reflection)
+ mAppCompatViewInflater = new AppCompatViewInflater();
+ } else {
+ try {
+ Class viewInflaterClass = Class.forName(viewInflaterClassName);
+ mAppCompatViewInflater =
+ (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
+ .newInstance();
+ } catch (Throwable t) {
+ Log.i(TAG, "Failed to instantiate custom view inflater "
+ + viewInflaterClassName + ". Falling back to default.", t);
+ mAppCompatViewInflater = new AppCompatViewInflater();
+ }
+ }
}
boolean inheritContext = false;
diff --git a/android/support/v7/app/AppCompatViewInflater.java b/android/support/v7/app/AppCompatViewInflater.java
index 54d01bce..87a1a3c7 100644
--- a/android/support/v7/app/AppCompatViewInflater.java
+++ b/android/support/v7/app/AppCompatViewInflater.java
@@ -51,14 +51,12 @@ import java.lang.reflect.Method;
import java.util.Map;
/**
- * This class is responsible for manually inflating our tinted widgets which are used on devices
- * running {@link android.os.Build.VERSION_CODES#KITKAT KITKAT} or below. As such, this class
- * should only be used when running on those devices.
+ * This class is responsible for manually inflating our tinted widgets.
* <p>This class two main responsibilities: the first is to 'inject' our tinted views in place of
* the framework versions in layout inflation; the second is backport the {@code android:theme}
* functionality for any inflated widgets. This include theme inheritance from its parent.
*/
-class AppCompatViewInflater {
+public class AppCompatViewInflater {
private static final Class<?>[] sConstructorSignature = new Class[]{
Context.class, AttributeSet.class};
@@ -77,7 +75,7 @@ class AppCompatViewInflater {
private final Object[] mConstructorArgs = new Object[2];
- public final View createView(View parent, final String name, @NonNull Context context,
+ final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
@@ -100,44 +98,63 @@ class AppCompatViewInflater {
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
- view = new AppCompatTextView(context, attrs);
+ view = createTextView(context, attrs);
+ verifyNotNull(view, name);
break;
case "ImageView":
- view = new AppCompatImageView(context, attrs);
+ view = createImageView(context, attrs);
+ verifyNotNull(view, name);
break;
case "Button":
- view = new AppCompatButton(context, attrs);
+ view = createButton(context, attrs);
+ verifyNotNull(view, name);
break;
case "EditText":
- view = new AppCompatEditText(context, attrs);
+ view = createEditText(context, attrs);
+ verifyNotNull(view, name);
break;
case "Spinner":
- view = new AppCompatSpinner(context, attrs);
+ view = createSpinner(context, attrs);
+ verifyNotNull(view, name);
break;
case "ImageButton":
- view = new AppCompatImageButton(context, attrs);
+ view = createImageButton(context, attrs);
+ verifyNotNull(view, name);
break;
case "CheckBox":
- view = new AppCompatCheckBox(context, attrs);
+ view = createCheckBox(context, attrs);
+ verifyNotNull(view, name);
break;
case "RadioButton":
- view = new AppCompatRadioButton(context, attrs);
+ view = createRadioButton(context, attrs);
+ verifyNotNull(view, name);
break;
case "CheckedTextView":
- view = new AppCompatCheckedTextView(context, attrs);
+ view = createCheckedTextView(context, attrs);
+ verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
- view = new AppCompatAutoCompleteTextView(context, attrs);
+ view = createAutoCompleteTextView(context, attrs);
+ verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
- view = new AppCompatMultiAutoCompleteTextView(context, attrs);
+ view = createMultiAutoCompleteTextView(context, attrs);
+ verifyNotNull(view, name);
break;
case "RatingBar":
- view = new AppCompatRatingBar(context, attrs);
+ view = createRatingBar(context, attrs);
+ verifyNotNull(view, name);
break;
case "SeekBar":
- view = new AppCompatSeekBar(context, attrs);
+ view = createSeekBar(context, attrs);
+ verifyNotNull(view, name);
break;
+ default:
+ // The fallback that allows extending class to take over view inflation
+ // for other tags. Note that we don't check that the result is not-null.
+ // That allows the custom inflater path to fall back on the default one
+ // later in this method.
+ view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
@@ -154,6 +171,85 @@ class AppCompatViewInflater {
return view;
}
+ @NonNull
+ protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
+ return new AppCompatTextView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatImageView createImageView(Context context, AttributeSet attrs) {
+ return new AppCompatImageView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatButton createButton(Context context, AttributeSet attrs) {
+ return new AppCompatButton(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatEditText createEditText(Context context, AttributeSet attrs) {
+ return new AppCompatEditText(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatSpinner createSpinner(Context context, AttributeSet attrs) {
+ return new AppCompatSpinner(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatImageButton createImageButton(Context context, AttributeSet attrs) {
+ return new AppCompatImageButton(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatCheckBox createCheckBox(Context context, AttributeSet attrs) {
+ return new AppCompatCheckBox(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatRadioButton createRadioButton(Context context, AttributeSet attrs) {
+ return new AppCompatRadioButton(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatCheckedTextView createCheckedTextView(Context context, AttributeSet attrs) {
+ return new AppCompatCheckedTextView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatAutoCompleteTextView createAutoCompleteTextView(Context context,
+ AttributeSet attrs) {
+ return new AppCompatAutoCompleteTextView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatMultiAutoCompleteTextView createMultiAutoCompleteTextView(Context context,
+ AttributeSet attrs) {
+ return new AppCompatMultiAutoCompleteTextView(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatRatingBar createRatingBar(Context context, AttributeSet attrs) {
+ return new AppCompatRatingBar(context, attrs);
+ }
+
+ @NonNull
+ protected AppCompatSeekBar createSeekBar(Context context, AttributeSet attrs) {
+ return new AppCompatSeekBar(context, attrs);
+ }
+
+ private void verifyNotNull(View view, String name) {
+ if (view == null) {
+ throw new IllegalStateException(this.getClass().getName()
+ + " asked to inflate view for <" + name + ">, but returned null");
+ }
+ }
+
+ @Nullable
+ protected View createView(Context context, String name, AttributeSet attrs) {
+ return null;
+ }
+
private View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
@@ -165,14 +261,14 @@ class AppCompatViewInflater {
if (-1 == name.indexOf('.')) {
for (int i = 0; i < sClassPrefixList.length; i++) {
- final View view = createView(context, name, sClassPrefixList[i]);
+ final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
if (view != null) {
return view;
}
}
return null;
} else {
- return createView(context, name, null);
+ return createViewByPrefix(context, name, null);
}
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
@@ -209,7 +305,7 @@ class AppCompatViewInflater {
a.recycle();
}
- private View createView(Context context, String name, String prefix)
+ private View createViewByPrefix(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
diff --git a/android/support/v7/util/SortedList.java b/android/support/v7/util/SortedList.java
index c62d0ce8..af000a1e 100644
--- a/android/support/v7/util/SortedList.java
+++ b/android/support/v7/util/SortedList.java
@@ -16,6 +16,8 @@
package android.support.v7.util;
+import android.support.annotation.Nullable;
+
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
@@ -315,7 +317,8 @@ public class SortedList<T> {
newDataStart++;
mOldDataStart++;
if (!mCallback.areContentsTheSame(oldItem, newItem)) {
- mCallback.onChanged(mMergedSize - 1, 1);
+ mCallback.onChanged(mMergedSize - 1, 1,
+ mCallback.getChangePayload(oldItem, newItem));
}
} else {
// Old item is lower than or equal to (but not the same as the new). Output it.
@@ -401,7 +404,7 @@ public class SortedList<T> {
return index;
} else {
mData[index] = item;
- mCallback.onChanged(index, 1);
+ mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
return index;
}
}
@@ -488,13 +491,13 @@ public class SortedList<T> {
if (cmp == 0) {
mData[index] = item;
if (contentsChanged) {
- mCallback.onChanged(index, 1);
+ mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
}
return;
}
}
if (contentsChanged) {
- mCallback.onChanged(index, 1);
+ mCallback.onChanged(index, 1, mCallback.getChangePayload(existing, item));
}
// TODO this done in 1 pass to avoid shifting twice.
removeItemAtIndex(index, false);
@@ -741,6 +744,28 @@ public class SortedList<T> {
* @return True if the two items represent the same object or false if they are different.
*/
abstract public boolean areItemsTheSame(T2 item1, T2 item2);
+
+ /**
+ * When {@link #areItemsTheSame(T2, T2)} returns {@code true} for two items and
+ * {@link #areContentsTheSame(T2, T2)} returns false for them, {@link Callback} calls this
+ * method to get a payload about the change.
+ * <p>
+ * For example, if you are using {@link Callback} with
+ * {@link android.support.v7.widget.RecyclerView}, you can return the particular field that
+ * changed in the item and your
+ * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
+ * information to run the correct animation.
+ * <p>
+ * Default implementation returns {@code null}.
+ *
+ * @param item1 The first item to check.
+ * @param item2 The second item to check.
+ * @return A payload object that represents the changes between the two items.
+ */
+ @Nullable
+ public Object getChangePayload(T2 item1, T2 item2) {
+ return null;
+ }
}
/**
@@ -801,6 +826,11 @@ public class SortedList<T> {
}
@Override
+ public void onChanged(int position, int count, Object payload) {
+ mBatchingListUpdateCallback.onChanged(position, count, payload);
+ }
+
+ @Override
public boolean areContentsTheSame(T2 oldItem, T2 newItem) {
return mWrappedCallback.areContentsTheSame(oldItem, newItem);
}
@@ -810,6 +840,12 @@ public class SortedList<T> {
return mWrappedCallback.areItemsTheSame(item1, item2);
}
+ @Nullable
+ @Override
+ public Object getChangePayload(T2 item1, T2 item2) {
+ return mWrappedCallback.getChangePayload(item1, item2);
+ }
+
/**
* This method dispatches any pending event notifications to the wrapped Callback.
* You <b>must</b> always call this method after you are done with editing the SortedList.
diff --git a/android/support/v7/util/SortedListBatchedCallbackTest.java b/android/support/v7/util/SortedListBatchedCallbackTest.java
index 3ace2178..bc50415d 100644
--- a/android/support/v7/util/SortedListBatchedCallbackTest.java
+++ b/android/support/v7/util/SortedListBatchedCallbackTest.java
@@ -50,6 +50,16 @@ public class SortedListBatchedCallbackTest {
}
@Test
+ public void onChangeWithPayload() {
+ final Object payload = 7;
+ mBatchedCallback.onChanged(1, 2, payload);
+ verifyZeroInteractions(mMockCallback);
+ mBatchedCallback.dispatchLastEvent();
+ verify(mMockCallback).onChanged(1, 2, payload);
+ verifyNoMoreInteractions(mMockCallback);
+ }
+
+ @Test
public void onRemoved() {
mBatchedCallback.onRemoved(2, 3);
verifyZeroInteractions(mMockCallback);
diff --git a/android/support/v7/util/SortedListTest.java b/android/support/v7/util/SortedListTest.java
index da3c9572..47d2ac0f 100644
--- a/android/support/v7/util/SortedListTest.java
+++ b/android/support/v7/util/SortedListTest.java
@@ -16,6 +16,7 @@
package android.support.v7.util;
+import android.support.annotation.Nullable;
import android.support.test.filters.SmallTest;
import junit.framework.TestCase;
@@ -41,6 +42,8 @@ public class SortedListTest extends TestCase {
List<Pair> mRemovals = new ArrayList<Pair>();
List<Pair> mMoves = new ArrayList<Pair>();
List<Pair> mUpdates = new ArrayList<Pair>();
+ private boolean mPayloadChanges = false;
+ List<PayloadChange> mPayloadUpdates = new ArrayList<>();
private SortedList.Callback<Item> mCallback;
InsertedCallback<Item> mInsertedCallback;
ChangedCallback<Item> mChangedCallback;
@@ -97,6 +100,15 @@ public class SortedListTest extends TestCase {
}
@Override
+ public void onChanged(int position, int count, Object payload) {
+ if (mPayloadChanges) {
+ mPayloadUpdates.add(new PayloadChange(position, count, payload));
+ } else {
+ onChanged(position, count);
+ }
+ }
+
+ @Override
public boolean areContentsTheSame(Item oldItem, Item newItem) {
return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data;
}
@@ -105,6 +117,15 @@ public class SortedListTest extends TestCase {
public boolean areItemsTheSame(Item item1, Item item2) {
return item1.id == item2.id;
}
+
+ @Nullable
+ @Override
+ public Object getChangePayload(Item item1, Item item2) {
+ if (mPayloadChanges) {
+ return item2.data;
+ }
+ return null;
+ }
};
mInsertedCallback = null;
mChangedCallback = null;
@@ -705,6 +726,76 @@ public class SortedListTest extends TestCase {
assertTrue(mAdditions.contains(new Pair(0, 6)));
}
+ @Test
+ public void testAddExistingItemCallsChangeWithPayload() {
+ mList.addAll(
+ new Item(1, 10),
+ new Item(2, 20),
+ new Item(3, 30)
+ );
+ mPayloadChanges = true;
+
+ // add an item with the same id but a new data field i.e. send an update
+ final Item twoUpdate = new Item(2, 20);
+ twoUpdate.data = 1337;
+ mList.add(twoUpdate);
+ assertEquals(1, mPayloadUpdates.size());
+ final PayloadChange update = mPayloadUpdates.get(0);
+ assertEquals(1, update.position);
+ assertEquals(1, update.count);
+ assertEquals(1337, update.payload);
+ assertEquals(3, size());
+ }
+
+ @Test
+ public void testUpdateItemCallsChangeWithPayload() {
+ mList.addAll(
+ new Item(1, 10),
+ new Item(2, 20),
+ new Item(3, 30)
+ );
+ mPayloadChanges = true;
+
+ // add an item with the same id but a new data field i.e. send an update
+ final Item twoUpdate = new Item(2, 20);
+ twoUpdate.data = 1337;
+ mList.updateItemAt(1, twoUpdate);
+ assertEquals(1, mPayloadUpdates.size());
+ final PayloadChange update = mPayloadUpdates.get(0);
+ assertEquals(1, update.position);
+ assertEquals(1, update.count);
+ assertEquals(1337, update.payload);
+ assertEquals(3, size());
+ assertEquals(1337, mList.get(1).data);
+ }
+
+ @Test
+ public void testAddMultipleExistingItemCallsChangeWithPayload() {
+ mList.addAll(
+ new Item(1, 10),
+ new Item(2, 20),
+ new Item(3, 30)
+ );
+ mPayloadChanges = true;
+
+ // add two items with the same ids but a new data fields i.e. send two updates
+ final Item twoUpdate = new Item(2, 20);
+ twoUpdate.data = 222;
+ final Item threeUpdate = new Item(3, 30);
+ threeUpdate.data = 333;
+ mList.addAll(twoUpdate, threeUpdate);
+ assertEquals(2, mPayloadUpdates.size());
+ final PayloadChange update1 = mPayloadUpdates.get(0);
+ assertEquals(1, update1.position);
+ assertEquals(1, update1.count);
+ assertEquals(222, update1.payload);
+ final PayloadChange update2 = mPayloadUpdates.get(1);
+ assertEquals(2, update2.position);
+ assertEquals(1, update2.count);
+ assertEquals(333, update2.payload);
+ assertEquals(3, size());
+ }
+
private int size() {
return mList.size();
}
@@ -821,4 +912,37 @@ public class SortedListTest extends TestCase {
return result;
}
}
+
+ private static final class PayloadChange {
+ public final int position;
+ public final int count;
+ public final Object payload;
+
+ PayloadChange(int position, int count, Object payload) {
+ this.position = position;
+ this.count = count;
+ this.payload = payload;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ PayloadChange payloadChange = (PayloadChange) o;
+
+ if (position != payloadChange.position) return false;
+ if (count != payloadChange.count) return false;
+ return payload != null ? payload.equals(payloadChange.payload)
+ : payloadChange.payload == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = position;
+ result = 31 * result + count;
+ result = 31 * result + (payload != null ? payload.hashCode() : 0);
+ return result;
+ }
+ }
} \ No newline at end of file
diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java
index fa6196f5..b8ce82a4 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -213,9 +213,9 @@ class AppCompatTextHelper {
if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
|| a.hasValue(R.styleable.TextAppearance_fontFamily)) {
mFontTypeface = null;
- int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
- ? R.styleable.TextAppearance_android_fontFamily
- : R.styleable.TextAppearance_fontFamily;
+ int fontFamilyId = a.hasValue(R.styleable.TextAppearance_fontFamily)
+ ? R.styleable.TextAppearance_fontFamily
+ : R.styleable.TextAppearance_android_fontFamily;
if (!context.isRestricted()) {
final WeakReference<TextView> textViewWeak = new WeakReference<>(mView);
ResourcesCompat.FontCallback replyCallback = new ResourcesCompat.FontCallback() {
diff --git a/android/support/v7/widget/TooltipCompatHandler.java b/android/support/v7/widget/TooltipCompatHandler.java
index 5ce1f8b3..63a61982 100644
--- a/android/support/v7/widget/TooltipCompatHandler.java
+++ b/android/support/v7/widget/TooltipCompatHandler.java
@@ -66,6 +66,10 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
private TooltipPopup mPopup;
private boolean mFromTouch;
+ // The handler currently scheduled to show a tooltip, triggered by a hover
+ // (there can be only one).
+ private static TooltipCompatHandler sPendingHandler;
+
// The handler currently showing a tooltip (there can be only one).
private static TooltipCompatHandler sActiveHandler;
@@ -76,6 +80,15 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
* @param tooltipText the tooltip text
*/
public static void setTooltipText(View view, CharSequence tooltipText) {
+ // The code below is not attempting to update the tooltip text
+ // for a pending or currently active tooltip, because it may lead
+ // to updating the wrong tooltip in in some rare cases (e.g. when
+ // action menu item views are recycled). Instead, the tooltip is
+ // canceled/hidden. This might still be the wrong tooltip,
+ // but hiding a wrong tooltip is less disruptive UX.
+ if (sPendingHandler != null && sPendingHandler.mAnchor == view) {
+ setPendingHandler(null);
+ }
if (TextUtils.isEmpty(tooltipText)) {
if (sActiveHandler != null && sActiveHandler.mAnchor == view) {
sActiveHandler.hide();
@@ -119,8 +132,7 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
if (mAnchor.isEnabled() && mPopup == null) {
mAnchorX = (int) event.getX();
mAnchorY = (int) event.getY();
- mAnchor.removeCallbacks(mShowRunnable);
- mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+ setPendingHandler(this);
}
break;
case MotionEvent.ACTION_HOVER_EXIT:
@@ -145,6 +157,7 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
if (!ViewCompat.isAttachedToWindow(mAnchor)) {
return;
}
+ setPendingHandler(null);
if (sActiveHandler != null) {
sActiveHandler.hide();
}
@@ -180,7 +193,27 @@ class TooltipCompatHandler implements View.OnLongClickListener, View.OnHoverList
Log.e(TAG, "sActiveHandler.mPopup == null");
}
}
- mAnchor.removeCallbacks(mShowRunnable);
+ if (sPendingHandler == this) {
+ setPendingHandler(null);
+ }
mAnchor.removeCallbacks(mHideRunnable);
}
+
+ private static void setPendingHandler(TooltipCompatHandler handler) {
+ if (sPendingHandler != null) {
+ sPendingHandler.cancelPendingShow();
+ }
+ sPendingHandler = handler;
+ if (sPendingHandler != null) {
+ sPendingHandler.scheduleShow();
+ }
+ }
+
+ private void scheduleShow() {
+ mAnchor.postDelayed(mShowRunnable, ViewConfiguration.getLongPressTimeout());
+ }
+
+ private void cancelPendingShow() {
+ mAnchor.removeCallbacks(mShowRunnable);
+ }
}
diff --git a/android/support/v7/widget/util/SortedListAdapterCallback.java b/android/support/v7/widget/util/SortedListAdapterCallback.java
index 4921541b..a1203a69 100644
--- a/android/support/v7/widget/util/SortedListAdapterCallback.java
+++ b/android/support/v7/widget/util/SortedListAdapterCallback.java
@@ -56,4 +56,9 @@ public abstract class SortedListAdapterCallback<T2> extends SortedList.Callback<
public void onChanged(int position, int count) {
mAdapter.notifyItemRangeChanged(position, count);
}
+
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ mAdapter.notifyItemRangeChanged(position, count, payload);
+ }
}
diff --git a/android/support/wear/ambient/AmbientDelegate.java b/android/support/wear/ambient/AmbientDelegate.java
index 49012908..8e96a020 100644
--- a/android/support/wear/ambient/AmbientDelegate.java
+++ b/android/support/wear/ambient/AmbientDelegate.java
@@ -19,14 +19,12 @@ import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.util.Log;
import com.google.android.wearable.compat.WearableActivityController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.lang.reflect.Method;
/**
* Provides compatibility for ambient mode.
@@ -146,19 +144,6 @@ final class AmbientDelegate {
}
/**
- * Sets whether this activity's task should be moved to the front when the system exits ambient
- * mode. If true, the activity's task may be moved to the front if it was the last activity to
- * be running when ambient started, depending on how much time the system spent in ambient mode.
- */
- void setAutoResumeEnabled(boolean enabled) {
- if (mWearableController != null) {
- if (hasSetAutoResumeEnabledMethod()) {
- mWearableController.setAutoResumeEnabled(enabled);
- }
- }
- }
-
- /**
* @return {@code true} if the activity is currently in ambient.
*/
boolean isAmbient() {
@@ -177,31 +162,4 @@ final class AmbientDelegate {
mWearableController.dump(prefix, fd, writer, args);
}
}
-
- private boolean hasSetAutoResumeEnabledMethod() {
- if (!sInitAutoResumeEnabledMethod) {
- sInitAutoResumeEnabledMethod = true;
- try {
- Method method =
- WearableActivityController.class
- .getDeclaredMethod("setAutoResumeEnabled", boolean.class);
- // Proguard is sneaky -- it will actually rewrite strings it finds in addition to
- // function names. Therefore add a "." prefix to the method name check to ensure the
- // function was not renamed by proguard.
- if (!(".setAutoResumeEnabled".equals("." + method.getName()))) {
- throw new NoSuchMethodException();
- }
- sHasAutoResumeEnabledMethod = true;
- } catch (NoSuchMethodException e) {
- Log.w(
- "WearableActivity",
- "Could not find a required method for auto-resume "
- + "support, likely due to proguard optimization. Please add "
- + "com.google.android.wearable:wearable jar to the list of library "
- + "jars for your project");
- sHasAutoResumeEnabledMethod = false;
- }
- }
- return sHasAutoResumeEnabledMethod;
- }
}
diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java
index db53dfc1..5db93830 100644
--- a/android/support/wear/ambient/AmbientMode.java
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -38,13 +38,13 @@ import java.io.PrintWriter;
* It should be called with an {@link Activity} as an argument and that {@link Activity} will then
* be able to receive ambient lifecycle events through an {@link AmbientCallback}. The
* {@link Activity} will also receive a {@link AmbientController} object from the attachment which
- * can be used to query the current status of the ambient mode, or toggle simple settings.
+ * can be used to query the current status of the ambient mode.
* An example of how to attach {@link AmbientMode} to your {@link Activity} and use
* the {@link AmbientController} can be found below:
* <p>
* <pre class="prettyprint">{@code
* AmbientMode.AmbientController controller = AmbientMode.attachAmbientSupport(this);
- * controller.setAutoResumeEnabled(true);
+ * boolean isAmbient = controller.isAmbient();
* }</pre>
*/
public final class AmbientMode extends Fragment {
@@ -117,7 +117,7 @@ public final class AmbientMode extends Fragment {
* Called when the system is updating the display for ambient mode. Activities may use this
* opportunity to update or invalidate views.
*/
- public void onUpdateAmbient() {};
+ public void onUpdateAmbient() {}
/**
* Called when an activity should exit ambient mode. This event is sent while an activity is
@@ -126,7 +126,7 @@ public final class AmbientMode extends Fragment {
* <p><em>Derived classes must call through to the super class's implementation of this
* method. If they do not, an exception will be thrown.</em>
*/
- public void onExitAmbient() {};
+ public void onExitAmbient() {}
}
private final AmbientDelegate.AmbientCallback mCallback =
@@ -220,7 +220,7 @@ public final class AmbientMode extends Fragment {
* @param activity the activity to attach ambient support to. This activity has to also
* implement {@link AmbientCallbackProvider}
* @return the associated {@link AmbientController} which can be used to query the state of
- * ambient mode and toggle simple settings related to it.
+ * ambient mode.
*/
public static <T extends Activity & AmbientCallbackProvider> AmbientController
attachAmbientSupport(T activity) {
@@ -251,9 +251,8 @@ public final class AmbientMode extends Fragment {
/**
* A class for interacting with the ambient mode on a wearable device. This class can be used to
- * query the current state of ambient mode and to enable or disable certain settings.
- * An instance of this class is returned to the user when they attach their {@link Activity}
- * to {@link AmbientMode}.
+ * query the current state of ambient mode. An instance of this class is returned to the user
+ * when they attach their {@link Activity} to {@link AmbientMode}.
*/
public final class AmbientController {
private static final String TAG = "AmbientController";
diff --git a/android/support/wear/ambient/SharedLibraryVersion.java b/android/support/wear/ambient/SharedLibraryVersion.java
index cd90a3b7..9421d9ee 100644
--- a/android/support/wear/ambient/SharedLibraryVersion.java
+++ b/android/support/wear/ambient/SharedLibraryVersion.java
@@ -16,7 +16,6 @@
package android.support.wear.ambient;
import android.os.Build;
-import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
import com.google.android.wearable.WearableSharedLib;
@@ -24,10 +23,7 @@ import com.google.android.wearable.WearableSharedLib;
/**
* Internal class which can be used to determine the version of the wearable shared library that is
* available on the current device.
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
final class SharedLibraryVersion {
private SharedLibraryVersion() {
diff --git a/android/support/wear/ambient/WearableControllerProvider.java b/android/support/wear/ambient/WearableControllerProvider.java
index 1682dc0e..4b6ae8ee 100644
--- a/android/support/wear/ambient/WearableControllerProvider.java
+++ b/android/support/wear/ambient/WearableControllerProvider.java
@@ -28,7 +28,7 @@ import java.lang.reflect.Method;
*
* @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
public class WearableControllerProvider {
private static final String TAG = "WearableControllerProvider";
diff --git a/android/support/wear/internal/widget/ResourcesUtil.java b/android/support/wear/internal/widget/ResourcesUtil.java
index f23a6889..8ba3adf0 100644
--- a/android/support/wear/internal/widget/ResourcesUtil.java
+++ b/android/support/wear/internal/widget/ResourcesUtil.java
@@ -26,7 +26,7 @@ import android.support.annotation.RestrictTo.Scope;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public final class ResourcesUtil {
/**
diff --git a/android/support/wear/internal/widget/drawer/MultiPagePresenter.java b/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
index ad56048b..4a7ce667 100644
--- a/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
+++ b/android/support/wear/internal/widget/drawer/MultiPagePresenter.java
@@ -28,7 +28,7 @@ import android.support.wear.widget.drawer.WearableNavigationDrawerView.WearableN
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class MultiPagePresenter extends WearableNavigationDrawerPresenter {
private final Ui mUi;
diff --git a/android/support/wear/internal/widget/drawer/MultiPageUi.java b/android/support/wear/internal/widget/drawer/MultiPageUi.java
index 90568451..0ba2f5d1 100644
--- a/android/support/wear/internal/widget/drawer/MultiPageUi.java
+++ b/android/support/wear/internal/widget/drawer/MultiPageUi.java
@@ -16,6 +16,7 @@
package android.support.wear.internal.widget.drawer;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
@@ -37,7 +38,7 @@ import android.widget.TextView;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class MultiPageUi implements MultiPagePresenter.Ui {
private static final String TAG = "MultiPageUi";
@@ -62,13 +63,8 @@ public class MultiPageUi implements MultiPagePresenter.Ui {
final View content = inflater.inflate(R.layout.ws_navigation_drawer_view, drawer,
false /* attachToRoot */);
- mNavigationPager =
- (ViewPager) content
- .findViewById(R.id.ws_navigation_drawer_view_pager);
- mPageIndicatorView =
- (PageIndicatorView)
- content.findViewById(
- R.id.ws_navigation_drawer_page_indicator);
+ mNavigationPager = content.findViewById(R.id.ws_navigation_drawer_view_pager);
+ mPageIndicatorView = content.findViewById(R.id.ws_navigation_drawer_page_indicator);
drawer.setDrawerContent(content);
}
@@ -132,8 +128,9 @@ public class MultiPageUi implements MultiPagePresenter.Ui {
mAdapter = adapter;
}
+ @NonNull
@Override
- public Object instantiateItem(ViewGroup container, int position) {
+ public Object instantiateItem(@NonNull ViewGroup container, int position) {
// Do not attach to root in the inflate method. The view needs to returned at the end
// of this method. Attaching to root will cause view to point to container instead.
final View view =
@@ -141,17 +138,17 @@ public class MultiPageUi implements MultiPagePresenter.Ui {
.inflate(R.layout.ws_navigation_drawer_item_view, container, false);
container.addView(view);
final ImageView iconView =
- (ImageView) view
- .findViewById(R.id.ws_navigation_drawer_item_icon);
+ view.findViewById(R.id.ws_navigation_drawer_item_icon);
final TextView textView =
- (TextView) view.findViewById(R.id.ws_navigation_drawer_item_text);
+ view.findViewById(R.id.ws_navigation_drawer_item_text);
iconView.setImageDrawable(mAdapter.getItemDrawable(position));
textView.setText(mAdapter.getItemText(position));
return view;
}
@Override
- public void destroyItem(ViewGroup container, int position, Object object) {
+ public void destroyItem(@NonNull ViewGroup container, int position,
+ @NonNull Object object) {
container.removeView((View) object);
}
@@ -161,12 +158,12 @@ public class MultiPageUi implements MultiPagePresenter.Ui {
}
@Override
- public int getItemPosition(Object object) {
+ public int getItemPosition(@NonNull Object object) {
return POSITION_NONE;
}
@Override
- public boolean isViewFromObject(View view, Object object) {
+ public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
}
diff --git a/android/support/wear/internal/widget/drawer/SinglePagePresenter.java b/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
index d90b5891..42cc7d06 100644
--- a/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
+++ b/android/support/wear/internal/widget/drawer/SinglePagePresenter.java
@@ -29,7 +29,7 @@ import android.support.wear.widget.drawer.WearableNavigationDrawerView.WearableN
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class SinglePagePresenter extends WearableNavigationDrawerPresenter {
private static final long DRAWER_CLOSE_DELAY_MS = 500;
diff --git a/android/support/wear/internal/widget/drawer/SinglePageUi.java b/android/support/wear/internal/widget/drawer/SinglePageUi.java
index f3a42904..ffc966f0 100644
--- a/android/support/wear/internal/widget/drawer/SinglePageUi.java
+++ b/android/support/wear/internal/widget/drawer/SinglePageUi.java
@@ -38,7 +38,7 @@ import android.widget.Toast;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class SinglePageUi implements SinglePagePresenter.Ui {
@IdRes
@@ -111,11 +111,10 @@ public class SinglePageUi implements SinglePagePresenter.Ui {
R.layout.ws_single_page_nav_drawer_peek_view, mDrawer,
false /* attachToRoot */);
- mTextView = (TextView) content.findViewById(R.id.ws_nav_drawer_text);
+ mTextView = content.findViewById(R.id.ws_nav_drawer_text);
mSinglePageImageViews = new CircledImageView[count];
for (int i = 0; i < count; i++) {
- mSinglePageImageViews[i] = (CircledImageView) content
- .findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
+ mSinglePageImageViews[i] = content.findViewById(SINGLE_PAGE_BUTTON_IDS[i]);
mSinglePageImageViews[i].setOnClickListener(new OnSelectedClickHandler(i, mPresenter));
mSinglePageImageViews[i].setCircleHidden(true);
}
diff --git a/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java b/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
index 1c8c4fb7..df108aac 100644
--- a/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
+++ b/android/support/wear/internal/widget/drawer/WearableNavigationDrawerPresenter.java
@@ -30,7 +30,7 @@ import java.util.Set;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public abstract class WearableNavigationDrawerPresenter {
private final Set<OnItemSelectedListener> mOnItemSelectedListeners = new HashSet<>();
diff --git a/android/support/wear/utils/MetadataConstants.java b/android/support/wear/utils/MetadataConstants.java
index 5be9c523..c7335c2d 100644
--- a/android/support/wear/utils/MetadataConstants.java
+++ b/android/support/wear/utils/MetadataConstants.java
@@ -15,16 +15,13 @@
*/
package android.support.wear.utils;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.os.Build;
/**
* Constants for android wear apps which are related to manifest meta-data.
*/
-@TargetApi(Build.VERSION_CODES.N)
public class MetadataConstants {
// Constants for standalone apps. //
diff --git a/android/support/wear/widget/BezierSCurveInterpolator.java b/android/support/wear/widget/BezierSCurveInterpolator.java
index 131bae84..9c56a83d 100644
--- a/android/support/wear/widget/BezierSCurveInterpolator.java
+++ b/android/support/wear/widget/BezierSCurveInterpolator.java
@@ -17,8 +17,6 @@
package android.support.wear.widget;
import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.os.Build;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
@@ -27,8 +25,7 @@ import android.support.annotation.RestrictTo.Scope;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
+@RestrictTo(Scope.LIBRARY)
class BezierSCurveInterpolator implements TimeInterpolator {
/**
diff --git a/android/support/wear/widget/BoxInsetLayout.java b/android/support/wear/widget/BoxInsetLayout.java
index ba35f2c9..a8b13814 100644
--- a/android/support/wear/widget/BoxInsetLayout.java
+++ b/android/support/wear/widget/BoxInsetLayout.java
@@ -20,7 +20,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -111,21 +110,6 @@ public class BoxInsetLayout extends ViewGroup {
}
@Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- insets = super.onApplyWindowInsets(insets);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- final boolean round = insets.isRound();
- if (round != mIsRound) {
- mIsRound = round;
- requestLayout();
- }
- mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
- insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
- }
- return insets;
- }
-
- @Override
public void setForeground(Drawable drawable) {
super.setForeground(drawable);
mForegroundDrawable = drawable;
@@ -145,14 +129,10 @@ public class BoxInsetLayout extends ViewGroup {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- requestApplyInsets();
- } else {
- mIsRound = getResources().getConfiguration().isScreenRound();
- WindowInsets insets = getRootWindowInsets();
- mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
- insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
- }
+ mIsRound = getResources().getConfiguration().isScreenRound();
+ WindowInsets insets = getRootWindowInsets();
+ mInsets.set(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom());
}
@Override
@@ -413,7 +393,7 @@ public class BoxInsetLayout extends ViewGroup {
public static class LayoutParams extends FrameLayout.LayoutParams {
/** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@IntDef({BOX_NONE, BOX_LEFT, BOX_TOP, BOX_RIGHT, BOX_BOTTOM, BOX_ALL})
@Retention(RetentionPolicy.SOURCE)
public @interface BoxedEdges {}
diff --git a/android/support/wear/widget/CircledImageView.java b/android/support/wear/widget/CircledImageView.java
index 03ed8c98..c441dd57 100644
--- a/android/support/wear/widget/CircledImageView.java
+++ b/android/support/wear/widget/CircledImageView.java
@@ -19,7 +19,6 @@ package android.support.wear.widget;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -32,7 +31,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.Px;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
@@ -47,8 +45,7 @@ import java.util.Objects;
*
* @hide
*/
-@TargetApi(Build.VERSION_CODES.M)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class CircledImageView extends View {
private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();
@@ -133,13 +130,9 @@ public class CircledImageView extends View {
if (mDrawable != null && mDrawable.getConstantState() != null) {
// The provided Drawable may be used elsewhere, so make a mutable clone before setTint()
// or setAlpha() is called on it.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- mDrawable =
- mDrawable.getConstantState()
- .newDrawable(context.getResources(), context.getTheme());
- } else {
- mDrawable = mDrawable.getConstantState().newDrawable(context.getResources());
- }
+ mDrawable =
+ mDrawable.getConstantState()
+ .newDrawable(context.getResources(), context.getTheme());
mDrawable = mDrawable.mutate();
}
diff --git a/android/support/wear/widget/CurvingLayoutCallback.java b/android/support/wear/widget/CurvingLayoutCallback.java
index 275f1f8b..5e88a8c4 100644
--- a/android/support/wear/widget/CurvingLayoutCallback.java
+++ b/android/support/wear/widget/CurvingLayoutCallback.java
@@ -113,7 +113,7 @@ public class CurvingLayoutCallback extends WearableLinearLayoutManager.LayoutCal
*/
public void adjustAnchorOffsetXY(View child, float[] anchorOffsetXY) {
return;
- };
+ }
@VisibleForTesting
void setRound(boolean isScreenRound) {
diff --git a/android/support/wear/widget/ProgressDrawable.java b/android/support/wear/widget/ProgressDrawable.java
index 08e8ec2e..28e05708 100644
--- a/android/support/wear/widget/ProgressDrawable.java
+++ b/android/support/wear/widget/ProgressDrawable.java
@@ -19,14 +19,12 @@ package android.support.wear.widget;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
import android.util.Property;
@@ -37,8 +35,7 @@ import android.view.animation.LinearInterpolator;
*
* @hide
*/
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class ProgressDrawable extends Drawable {
private static final Property<ProgressDrawable, Integer> LEVEL =
diff --git a/android/support/wear/widget/RoundedDrawable.java b/android/support/wear/widget/RoundedDrawable.java
index fd09a878..300b6dd6 100644
--- a/android/support/wear/widget/RoundedDrawable.java
+++ b/android/support/wear/widget/RoundedDrawable.java
@@ -15,7 +15,6 @@
*/
package android.support.wear.widget;
-import android.annotation.TargetApi;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -29,7 +28,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -76,7 +74,6 @@ import java.util.Objects;
* app:radius="dimension"
* app:clipEnabled="boolean" /&gt;</pre>
*/
-@TargetApi(Build.VERSION_CODES.N)
public class RoundedDrawable extends Drawable {
@VisibleForTesting
diff --git a/android/support/wear/widget/ScrollManager.java b/android/support/wear/widget/ScrollManager.java
index 8155f62d..e01a2710 100644
--- a/android/support/wear/widget/ScrollManager.java
+++ b/android/support/wear/widget/ScrollManager.java
@@ -16,11 +16,8 @@
package android.support.wear.widget;
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.annotation.TargetApi;
-import android.os.Build;
import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -30,8 +27,7 @@ import android.view.VelocityTracker;
*
* @hide
*/
-@TargetApi(Build.VERSION_CODES.M)
-@RestrictTo(LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class ScrollManager {
// One second in milliseconds.
private static final int ONE_SEC_IN_MS = 1000;
diff --git a/android/support/wear/widget/SimpleAnimatorListener.java b/android/support/wear/widget/SimpleAnimatorListener.java
index a60b0bd2..3a1e56b9 100644
--- a/android/support/wear/widget/SimpleAnimatorListener.java
+++ b/android/support/wear/widget/SimpleAnimatorListener.java
@@ -29,7 +29,7 @@ import android.support.annotation.RestrictTo.Scope;
* @hide Hidden until this goes through review
*/
@RequiresApi(Build.VERSION_CODES.KITKAT_WATCH)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class SimpleAnimatorListener implements Animator.AnimatorListener {
private boolean mWasCanceled;
diff --git a/android/support/wear/widget/SwipeDismissLayout.java b/android/support/wear/widget/SwipeDismissLayout.java
index 6e7a6f36..33da79c2 100644
--- a/android/support/wear/widget/SwipeDismissLayout.java
+++ b/android/support/wear/widget/SwipeDismissLayout.java
@@ -16,12 +16,11 @@
package android.support.wear.widget;
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.annotation.RestrictTo.Scope;
import android.support.annotation.UiThread;
import android.util.AttributeSet;
import android.util.Log;
@@ -40,7 +39,7 @@ import android.widget.FrameLayout;
*
* @hide
*/
-@RestrictTo(LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
@UiThread
class SwipeDismissLayout extends FrameLayout {
private static final String TAG = "SwipeDismissLayout";
diff --git a/android/support/wear/widget/WearableRecyclerView.java b/android/support/wear/widget/WearableRecyclerView.java
index 5cacdfcb..1425e68b 100644
--- a/android/support/wear/widget/WearableRecyclerView.java
+++ b/android/support/wear/widget/WearableRecyclerView.java
@@ -16,11 +16,9 @@
package android.support.wear.widget;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Point;
-import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.support.wear.R;
@@ -35,7 +33,6 @@ import android.view.ViewTreeObserver;
*
* @see #setCircularScrollingGestureEnabled(boolean)
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableRecyclerView extends RecyclerView {
private static final String TAG = "WearableRecyclerView";
diff --git a/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java b/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
index f1cb640d..e9b2a40a 100644
--- a/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/AbsListViewFlingWatcher.java
@@ -32,7 +32,7 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class AbsListViewFlingWatcher implements FlingWatcher, OnScrollListener {
private final FlingListener mListener;
diff --git a/android/support/wear/widget/drawer/FlingWatcherFactory.java b/android/support/wear/widget/drawer/FlingWatcherFactory.java
index 3fe84c63..2fdfa13c 100644
--- a/android/support/wear/widget/drawer/FlingWatcherFactory.java
+++ b/android/support/wear/widget/drawer/FlingWatcherFactory.java
@@ -33,7 +33,7 @@ import java.util.WeakHashMap;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class FlingWatcherFactory {
/**
diff --git a/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java b/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
index ca95ab22..4c0e5c8c 100644
--- a/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/NestedScrollViewFlingWatcher.java
@@ -38,7 +38,7 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class NestedScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener {
static final int MAX_WAIT_TIME_MS = 100;
diff --git a/android/support/wear/widget/drawer/PageIndicatorView.java b/android/support/wear/widget/drawer/PageIndicatorView.java
index 99c7c09f..1285f725 100644
--- a/android/support/wear/widget/drawer/PageIndicatorView.java
+++ b/android/support/wear/widget/drawer/PageIndicatorView.java
@@ -54,7 +54,7 @@ import java.util.concurrent.TimeUnit;
* @hide
*/
@RequiresApi(Build.VERSION_CODES.M)
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
public class PageIndicatorView extends View implements OnPageChangeListener {
private static final String TAG = "Dots";
diff --git a/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java b/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
index 7570fae5..7916875e 100644
--- a/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/RecyclerViewFlingWatcher.java
@@ -31,7 +31,7 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class RecyclerViewFlingWatcher extends OnScrollListener implements FlingWatcher {
private final FlingListener mListener;
diff --git a/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java b/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
index f0b973be..5154e7bb 100644
--- a/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
+++ b/android/support/wear/widget/drawer/ScrollViewFlingWatcher.java
@@ -38,7 +38,7 @@ import java.lang.ref.WeakReference;
*
* @hide
*/
-@RestrictTo(Scope.LIBRARY_GROUP)
+@RestrictTo(Scope.LIBRARY)
class ScrollViewFlingWatcher implements FlingWatcher, OnScrollChangeListener {
static final int MAX_WAIT_TIME_MS = 100;
diff --git a/android/support/wear/widget/drawer/WearableActionDrawerMenu.java b/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
index 158467dc..092ac72d 100644
--- a/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
+++ b/android/support/wear/widget/drawer/WearableActionDrawerMenu.java
@@ -16,12 +16,10 @@
package android.support.wear.widget.drawer;
-import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.Nullable;
import android.view.ActionProvider;
import android.view.ContextMenu;
@@ -34,7 +32,6 @@ import android.view.View;
import java.util.ArrayList;
import java.util.List;
-@TargetApi(Build.VERSION_CODES.M)
/* package */ class WearableActionDrawerMenu implements Menu {
private final Context mContext;
diff --git a/android/support/wear/widget/drawer/WearableActionDrawerView.java b/android/support/wear/widget/drawer/WearableActionDrawerView.java
index 8154e6b9..99cd4ff6 100644
--- a/android/support/wear/widget/drawer/WearableActionDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableActionDrawerView.java
@@ -16,12 +16,10 @@
package android.support.wear.widget.drawer;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -38,6 +36,7 @@ import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -75,7 +74,6 @@ import java.util.Objects;
* <p>For {@link MenuItem}, setting and getting the title and icon, {@link MenuItem#getItemId}, and
* {@link MenuItem#setOnMenuItemClickListener} are implemented.
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableActionDrawerView extends WearableDrawerView {
private static final String TAG = "WearableActionDrawer";
@@ -140,12 +138,8 @@ public class WearableActionDrawerView extends WearableDrawerView {
View peekView = layoutInflater.inflate(R.layout.ws_action_drawer_peek_view,
getPeekContainer(), false /* attachToRoot */);
setPeekContent(peekView);
- mPeekActionIcon =
- (ImageView) peekView
- .findViewById(R.id.ws_action_drawer_peek_action_icon);
- mPeekExpandIcon =
- (ImageView) peekView
- .findViewById(R.id.ws_action_drawer_expand_icon);
+ mPeekActionIcon = peekView.findViewById(R.id.ws_action_drawer_peek_action_icon);
+ mPeekExpandIcon = peekView.findViewById(R.id.ws_action_drawer_expand_icon);
} else {
mPeekActionIcon = null;
mPeekExpandIcon = null;
@@ -193,6 +187,16 @@ public class WearableActionDrawerView extends WearableDrawerView {
}
@Override
+ public void onDrawerOpened() {
+ if (mActionListAdapter.getItemCount() > 0) {
+ RecyclerView.ViewHolder holder = mActionList.findViewHolderForAdapterPosition(0);
+ if (holder != null && holder.itemView != null) {
+ holder.itemView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+ }
+ }
+
+ @Override
public boolean canScrollHorizontally(int direction) {
// Prevent the window from being swiped closed while it is open by saying that it can scroll
// horizontally.
@@ -412,7 +416,6 @@ public class WearableActionDrawerView extends WearableDrawerView {
CharSequence title = mActionMenu.getItem(titleAwarePosition).getTitle();
holder.textView.setText(title);
holder.textView.setContentDescription(title);
- holder.iconView.setContentDescription(title);
holder.iconView.setImageDrawable(icon);
} else if (viewHolder instanceof TitleViewHolder) {
TitleViewHolder holder = (TitleViewHolder) viewHolder;
diff --git a/android/support/wear/widget/drawer/WearableDrawerLayout.java b/android/support/wear/widget/drawer/WearableDrawerLayout.java
index 6d270647..e100a467 100644
--- a/android/support/wear/widget/drawer/WearableDrawerLayout.java
+++ b/android/support/wear/widget/drawer/WearableDrawerLayout.java
@@ -19,11 +19,10 @@ package android.support.wear.widget.drawer;
import static android.support.wear.widget.drawer.WearableDrawerView.STATE_IDLE;
import static android.support.wear.widget.drawer.WearableDrawerView.STATE_SETTLING;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v4.view.NestedScrollingParent;
@@ -98,7 +97,6 @@ import android.widget.FrameLayout;
* &lt;/android.support.wear.widget.drawer.WearableDrawerView&gt;
* &lt;/android.support.wear.widget.drawer.WearableDrawerLayout&gt;</pre>
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableDrawerLayout extends FrameLayout
implements View.OnLayoutChangeListener, NestedScrollingParent, FlingListener {
@@ -654,12 +652,13 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override // NestedScrollingParent
- public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY,
+ boolean consumed) {
return false;
}
@Override // NestedScrollingParent
- public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+ public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
maybeUpdateScrollingContentView(target);
mLastScrollWasFling = true;
@@ -674,13 +673,13 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override // NestedScrollingParent
- public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
+ public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
maybeUpdateScrollingContentView(target);
}
@Override // NestedScrollingParent
- public void onNestedScroll(
- View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+ public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
boolean scrolledUp = dyConsumed < 0;
boolean scrolledDown = dyConsumed > 0;
@@ -873,18 +872,20 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override // NestedScrollingParent
- public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
+ public void onNestedScrollAccepted(@NonNull View child, @NonNull View target,
+ int nestedScrollAxes) {
mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
}
@Override // NestedScrollingParent
- public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
+ public boolean onStartNestedScroll(@NonNull View child, @NonNull View target,
+ int nestedScrollAxes) {
mCurrentNestedScrollSlopTracker = 0;
return true;
}
@Override // NestedScrollingParent
- public void onStopNestedScroll(View target) {
+ public void onStopNestedScroll(@NonNull View target) {
mNestedScrollingParentHelper.onStopNestedScroll(target);
}
@@ -961,7 +962,7 @@ public class WearableDrawerLayout extends FrameLayout
public abstract WearableDrawerView getDrawerView();
@Override
- public boolean tryCaptureView(View child, int pointerId) {
+ public boolean tryCaptureView(@NonNull View child, int pointerId) {
WearableDrawerView drawerView = getDrawerView();
// Returns true if the dragger is dragging the drawer.
return child == drawerView && !drawerView.isLocked()
@@ -969,13 +970,13 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public int getViewVerticalDragRange(View child) {
+ public int getViewVerticalDragRange(@NonNull View child) {
// Defines the vertical drag range of the drawer.
return child == getDrawerView() ? child.getHeight() : 0;
}
@Override
- public void onViewCaptured(View capturedChild, int activePointerId) {
+ public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
showDrawerContentMaybeAnimate((WearableDrawerView) capturedChild);
}
@@ -1036,7 +1037,7 @@ public class WearableDrawerLayout extends FrameLayout
private class TopDrawerDraggerCallback extends DrawerDraggerCallback {
@Override
- public int clampViewPositionVertical(View child, int top, int dy) {
+ public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
if (mTopDrawerView == child) {
int peekHeight = mTopDrawerView.getPeekContainer().getHeight();
// The top drawer can be dragged vertically from peekHeight - height to 0.
@@ -1063,7 +1064,7 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
+ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
if (releasedChild == mTopDrawerView) {
// Settle to final position. Either swipe open or close.
final float openedPercent = mTopDrawerView.getOpenedPercent();
@@ -1085,7 +1086,8 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,
+ int dy) {
if (changedView == mTopDrawerView) {
// Compute the offset and invalidate will move the drawer during layout.
final int height = changedView.getHeight();
@@ -1106,7 +1108,7 @@ public class WearableDrawerLayout extends FrameLayout
private class BottomDrawerDraggerCallback extends DrawerDraggerCallback {
@Override
- public int clampViewPositionVertical(View child, int top, int dy) {
+ public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
if (mBottomDrawerView == child) {
// The bottom drawer can be dragged vertically from (parentHeight - height) to
// (parentHeight - peekHeight).
@@ -1131,7 +1133,7 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public void onViewReleased(View releasedChild, float xvel, float yvel) {
+ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
if (releasedChild == mBottomDrawerView) {
// Settle to final position. Either swipe open or close.
final int parentHeight = getHeight();
@@ -1151,7 +1153,8 @@ public class WearableDrawerLayout extends FrameLayout
}
@Override
- public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx,
+ int dy) {
if (changedView == mBottomDrawerView) {
// Compute the offset and invalidate will move the drawer during layout.
final int height = changedView.getHeight();
diff --git a/android/support/wear/widget/drawer/WearableDrawerView.java b/android/support/wear/widget/drawer/WearableDrawerView.java
index dafac39b..2462cba8 100644
--- a/android/support/wear/widget/drawer/WearableDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableDrawerView.java
@@ -16,11 +16,9 @@
package android.support.wear.widget.drawer;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
@@ -87,7 +85,6 @@ import java.lang.annotation.RetentionPolicy;
* &lt;/LinearLayout&gt;
* &lt;/android.support.wear.widget.drawer.WearableDrawerView&gt;</pre>
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableDrawerView extends FrameLayout {
/**
* Indicates that the drawer is in an idle, settled state. No animation is in progress.
@@ -109,7 +106,7 @@ public class WearableDrawerView extends FrameLayout {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @RestrictTo(Scope.LIBRARY)
@IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
public @interface DrawerState {}
@@ -155,8 +152,8 @@ public class WearableDrawerView extends FrameLayout {
setElevation(context.getResources()
.getDimension(R.dimen.ws_wearable_drawer_view_elevation));
- mPeekContainer = (ViewGroup) findViewById(R.id.ws_drawer_view_peek_container);
- mPeekIcon = (ImageView) findViewById(R.id.ws_drawer_view_peek_icon);
+ mPeekContainer = findViewById(R.id.ws_drawer_view_peek_container);
+ mPeekIcon = findViewById(R.id.ws_drawer_view_peek_icon);
mPeekContainer.setOnClickListener(
new OnClickListener() {
diff --git a/android/support/wear/widget/drawer/WearableNavigationDrawerView.java b/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
index 480812b8..c5c49fe3 100644
--- a/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
+++ b/android/support/wear/widget/drawer/WearableNavigationDrawerView.java
@@ -16,11 +16,9 @@
package android.support.wear.widget.drawer;
-import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
@@ -58,7 +56,6 @@ import java.util.concurrent.TimeUnit;
* <p>The developer may specify which style to use with the {@code app:navigationStyle} custom
* attribute. If not specified, {@link #SINGLE_PAGE singlePage} will be used as the default.
*/
-@TargetApi(Build.VERSION_CODES.M)
public class WearableNavigationDrawerView extends WearableDrawerView {
private static final String TAG = "WearableNavDrawer";
@@ -79,7 +76,7 @@ public class WearableNavigationDrawerView extends WearableDrawerView {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @RestrictTo(Scope.LIBRARY)
@IntDef({SINGLE_PAGE, MULTI_PAGE})
public @interface NavigationStyle {}
@@ -282,7 +279,7 @@ public class WearableNavigationDrawerView extends WearableDrawerView {
/**
* @hide
*/
- @RestrictTo(Scope.LIBRARY_GROUP)
+ @RestrictTo(Scope.LIBRARY)
public void setPresenter(WearableNavigationDrawerPresenter presenter) {
mPresenter = presenter;
}
diff --git a/android/support/wearable/watchface/decomposition/package-info.java b/android/support/wearable/watchface/decomposition/package-info.java
new file mode 100644
index 00000000..dbd815e7
--- /dev/null
+++ b/android/support/wearable/watchface/decomposition/package-info.java
@@ -0,0 +1,2 @@
+/** @hide */
+package android.support.wearable.watchface.decomposition;
diff --git a/android/system/Int32Ref.java b/android/system/Int32Ref.java
new file mode 100644
index 00000000..25a818da
--- /dev/null
+++ b/android/system/Int32Ref.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+/**
+ * A signed 32bit integer reference suitable for passing to lower-level system calls.
+ */
+public class Int32Ref {
+ public int value;
+
+ public Int32Ref(int value) {
+ this.value = value;
+ }
+}
diff --git a/android/system/Int64Ref.java b/android/system/Int64Ref.java
new file mode 100644
index 00000000..f42450d8
--- /dev/null
+++ b/android/system/Int64Ref.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+/**
+ * A signed 64bit integer reference suitable for passing to lower-level system calls.
+ */
+public class Int64Ref {
+ public long value;
+
+ public Int64Ref(long value) {
+ this.value = value;
+ }
+}
diff --git a/android/system/Os.java b/android/system/Os.java
index 2dabae2f..fe8f7d45 100644
--- a/android/system/Os.java
+++ b/android/system/Os.java
@@ -188,6 +188,17 @@ public final class Os {
public static int getgid() { return Libcore.os.getgid(); }
/**
+ * See <a href="http://man7.org/linux/man-pages/man2/getgroups.2.html">getgroups(2)</a>.
+ *
+ * <p>Should the number of groups change during the execution of this call, the call may
+ * return an arbitrary subset. This may be worth reconsidering should this be exposed
+ * as public API.
+ *
+ * @hide
+ */
+ public static int[] getgroups() throws ErrnoException { return Libcore.os.getgroups(); }
+
+ /**
* See <a href="http://man7.org/linux/man-pages/man3/getenv.3.html">getenv(3)</a>.
*/
public static String getenv(String name) { return Libcore.os.getenv(name); }
@@ -268,7 +279,16 @@ public final class Os {
public static InetAddress inet_pton(int family, String address) { return Libcore.os.inet_pton(family, address); }
/** @hide */ public static InetAddress ioctlInetAddress(FileDescriptor fd, int cmd, String interfaceName) throws ErrnoException { return Libcore.os.ioctlInetAddress(fd, cmd, interfaceName); }
- /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, MutableInt arg) throws ErrnoException { return Libcore.os.ioctlInt(fd, cmd, arg); }
+
+
+ /** @hide */ public static int ioctlInt(FileDescriptor fd, int cmd, Int32Ref arg) throws ErrnoException {
+ libcore.util.MutableInt internalArg = new libcore.util.MutableInt(arg.value);
+ try {
+ return Libcore.os.ioctlInt(fd, cmd, internalArg);
+ } finally {
+ arg.value = internalArg.value;
+ }
+ }
/**
* See <a href="http://man7.org/linux/man-pages/man3/isatty.3.html">isatty(3)</a>.
@@ -453,8 +473,41 @@ public final class Os {
/**
* See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
+ *
+ * @deprecated This method will be removed in a future version of Android. Use
+ * {@link #sendfile(FileDescriptor, FileDescriptor, Int64Ref, long)} instead.
+ */
+ @Deprecated
+ public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException {
+ if (inOffset == null) {
+ return Libcore.os.sendfile(outFd, inFd, null, byteCount);
+ } else {
+ libcore.util.MutableLong internalInOffset = new libcore.util.MutableLong(
+ inOffset.value);
+ try {
+ return Libcore.os.sendfile(outFd, inFd, internalInOffset, byteCount);
+ } finally {
+ inOffset.value = internalInOffset.value;
+ }
+ }
+ }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/sendfile.2.html">sendfile(2)</a>.
*/
- public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, MutableLong inOffset, long byteCount) throws ErrnoException { return Libcore.os.sendfile(outFd, inFd, inOffset, byteCount); }
+ public static long sendfile(FileDescriptor outFd, FileDescriptor inFd, Int64Ref inOffset, long byteCount) throws ErrnoException {
+ if (inOffset == null) {
+ return Libcore.os.sendfile(outFd, inFd, null, byteCount);
+ } else {
+ libcore.util.MutableLong internalInOffset = new libcore.util.MutableLong(
+ inOffset.value);
+ try {
+ return Libcore.os.sendfile(outFd, inFd, internalInOffset, byteCount);
+ } finally {
+ inOffset.value = internalInOffset.value;
+ }
+ }
+ }
/**
* See <a href="http://man7.org/linux/man-pages/man2/sendto.2.html">sendto(2)</a>.
@@ -492,6 +545,13 @@ public final class Os {
public static void setgid(int gid) throws ErrnoException { Libcore.os.setgid(gid); }
/**
+ * See <a href="http://man7.org/linux/man-pages/man2/setgroups.2.html">setgroups(2)</a>.
+ *
+ * @hide
+ */
+ public static void setgroups(int[] gids) throws ErrnoException { Libcore.os.setgroups(gids); }
+
+ /**
* See <a href="http://man7.org/linux/man-pages/man2/setpgid.2.html">setpgid(2)</a>.
*/
/** @hide */ public static void setpgid(int pid, int pgid) throws ErrnoException { Libcore.os.setpgid(pid, pgid); }
@@ -611,8 +671,41 @@ public final class Os {
/**
* See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
- */
- public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException { return Libcore.os.waitpid(pid, status, options); }
+ *
+ * @deprecated This method will be removed in a future version of Android. Use
+ * {@link #waitpid(int, Int32Ref, int)} instead.
+ */
+ @Deprecated
+ public static int waitpid(int pid, MutableInt status, int options) throws ErrnoException {
+ if (status == null) {
+ return Libcore.os.waitpid(pid, null, options);
+ } else {
+ libcore.util.MutableInt internalStatus = new libcore.util.MutableInt(status.value);
+ try {
+ return Libcore.os.waitpid(pid, internalStatus, options);
+ } finally {
+ status.value = internalStatus.value;
+ }
+ }
+ }
+
+ /**
+ * See <a href="http://man7.org/linux/man-pages/man2/waitpid.2.html">waitpid(2)</a>.
+ *
+ * @throws IllegalArgumentException if {@code status != null && status.length != 1}
+ */
+ public static int waitpid(int pid, Int32Ref status, int options) throws ErrnoException {
+ if (status == null) {
+ return Libcore.os.waitpid(pid, null, options);
+ } else {
+ libcore.util.MutableInt internalStatus = new libcore.util.MutableInt(status.value);
+ try {
+ return Libcore.os.waitpid(pid, internalStatus, options);
+ } finally {
+ status.value = internalStatus.value;
+ }
+ }
+ }
/**
* See <a href="http://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
diff --git a/android/system/UnixSocketAddressTest.java b/android/system/UnixSocketAddressTest.java
deleted file mode 100644
index f1b7fc23..00000000
--- a/android/system/UnixSocketAddressTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.system;
-
-import junit.framework.TestCase;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-public class UnixSocketAddressTest extends TestCase {
-
- public void testFilesystemSunPath() throws Exception {
- String path = "/foo/bar";
- UnixSocketAddress sa = UnixSocketAddress.createFileSystem(path);
-
- byte[] abstractNameBytes = path.getBytes(StandardCharsets.UTF_8);
- byte[] expected = new byte[abstractNameBytes.length + 1];
- // See unix(7)
- System.arraycopy(abstractNameBytes, 0, expected, 0, abstractNameBytes.length);
- assertTrue(Arrays.equals(expected, sa.getSunPath()));
- }
-
- public void testUnnamedSunPath() throws Exception {
- UnixSocketAddress sa = UnixSocketAddress.createUnnamed();
- assertEquals(0, sa.getSunPath().length);
- }
-
- public void testAbstractSunPath() throws Exception {
- String abstractName = "abstract";
- UnixSocketAddress sa = UnixSocketAddress.createAbstract(abstractName);
- byte[] abstractNameBytes = abstractName.getBytes(StandardCharsets.UTF_8);
- byte[] expected = new byte[abstractNameBytes.length + 1];
- // See unix(7)
- System.arraycopy(abstractNameBytes, 0, expected, 1, abstractNameBytes.length);
- assertTrue(Arrays.equals(expected, sa.getSunPath()));
- }
-}
diff --git a/android/telecom/Call.java b/android/telecom/Call.java
index e13bd619..a07f2bbf 100644
--- a/android/telecom/Call.java
+++ b/android/telecom/Call.java
@@ -855,6 +855,39 @@ public final class Call {
*/
public static abstract class Callback {
/**
+ * @hide
+ */
+ @IntDef({HANDOVER_FAILURE_DEST_APP_REJECTED, HANDOVER_FAILURE_DEST_NOT_SUPPORTED,
+ HANDOVER_FAILURE_DEST_INVALID_PERM, HANDOVER_FAILURE_DEST_USER_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HandoverFailureErrors {}
+
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when the app
+ * to handover the call rejects handover.
+ */
+ public static final int HANDOVER_FAILURE_DEST_APP_REJECTED = 1;
+
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when there is
+ * an error associated with unsupported handover.
+ */
+ public static final int HANDOVER_FAILURE_DEST_NOT_SUPPORTED = 2;
+
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when there
+ * are some permission errors associated with APIs doing handover.
+ */
+ public static final int HANDOVER_FAILURE_DEST_INVALID_PERM = 3;
+
+ /**
+ * Handover failure reason returned via {@link #onHandoverFailed(Call, int)} when user
+ * rejects handover.
+ */
+ public static final int HANDOVER_FAILURE_DEST_USER_REJECTED = 4;
+
+
+ /**
* Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
*
* @param call The {@code Call} invoking this method.
@@ -989,6 +1022,21 @@ public final class Call {
* {@link android.telecom.Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
*/
public void onRttInitiationFailure(Call call, int reason) {}
+
+ /**
+ * Invoked when Call handover from one {@link PhoneAccount} to other {@link PhoneAccount}
+ * has completed successfully.
+ * @param call The call which had initiated handover.
+ */
+ public void onHandoverComplete(Call call) {}
+
+ /**
+ * Invoked when Call handover from one {@link PhoneAccount} to other {@link PhoneAccount}
+ * has failed.
+ * @param call The call which had initiated handover.
+ * @param failureReason Error reason for failure
+ */
+ public void onHandoverFailed(Call call, @HandoverFailureErrors int failureReason) {}
}
/**
@@ -1367,6 +1415,24 @@ public final class Call {
}
/**
+ * Initiates a handover of this {@link Call} to the {@link ConnectionService} identified
+ * by {@code toHandle}. The videoState specified indicates the desired video state after the
+ * handover.
+ * <p>
+ * A handover request is initiated by the user from one app to indicate a desire
+ * to handover a call to another.
+ *
+ * @param toHandle {@link PhoneAccountHandle} of the {@link ConnectionService} to handover
+ * this call to.
+ * @param videoState Indicates the video state desired after the handover.
+ * @param extras Bundle containing extra information to be passed to the
+ * {@link ConnectionService}
+ */
+ public void handoverTo(PhoneAccountHandle toHandle, int videoState, Bundle extras) {
+ mInCallAdapter.handoverTo(mTelecomCallId, toHandle, videoState, extras);
+ }
+
+ /**
* Terminate the RTT session on this call. The resulting state change will be notified via
* the {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback.
*/
diff --git a/android/telecom/CallAudioState.java b/android/telecom/CallAudioState.java
index f601d8b5..4b827d2e 100644
--- a/android/telecom/CallAudioState.java
+++ b/android/telecom/CallAudioState.java
@@ -16,16 +16,35 @@
package android.telecom;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Collectors;
/**
* Encapsulates the telecom audio state, including the current audio routing, supported audio
* routing and mute.
*/
public final class CallAudioState implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER},
+ flag=true)
+ public @interface CallAudioRoute {}
+
/** Direct the audio stream through the device's earpiece. */
public static final int ROUTE_EARPIECE = 0x00000001;
@@ -55,6 +74,8 @@ public final class CallAudioState implements Parcelable {
private final boolean isMuted;
private final int route;
private final int supportedRouteMask;
+ private final BluetoothDevice activeBluetoothDevice;
+ private final Collection<BluetoothDevice> supportedBluetoothDevices;
/**
* Constructor for a {@link CallAudioState} object.
@@ -73,10 +94,21 @@ public final class CallAudioState implements Parcelable {
* {@link #ROUTE_WIRED_HEADSET}
* {@link #ROUTE_SPEAKER}
*/
- public CallAudioState(boolean muted, int route, int supportedRouteMask) {
- this.isMuted = muted;
+ public CallAudioState(boolean muted, @CallAudioRoute int route,
+ @CallAudioRoute int supportedRouteMask) {
+ this(muted, route, supportedRouteMask, null, Collections.emptyList());
+ }
+
+ /** @hide */
+ public CallAudioState(boolean isMuted, @CallAudioRoute int route,
+ @CallAudioRoute int supportedRouteMask,
+ @Nullable BluetoothDevice activeBluetoothDevice,
+ @NonNull Collection<BluetoothDevice> supportedBluetoothDevices) {
+ this.isMuted = isMuted;
this.route = route;
this.supportedRouteMask = supportedRouteMask;
+ this.activeBluetoothDevice = activeBluetoothDevice;
+ this.supportedBluetoothDevices = supportedBluetoothDevices;
}
/** @hide */
@@ -84,6 +116,8 @@ public final class CallAudioState implements Parcelable {
isMuted = state.isMuted();
route = state.getRoute();
supportedRouteMask = state.getSupportedRouteMask();
+ activeBluetoothDevice = state.activeBluetoothDevice;
+ supportedBluetoothDevices = state.getSupportedBluetoothDevices();
}
/** @hide */
@@ -92,6 +126,8 @@ public final class CallAudioState implements Parcelable {
isMuted = state.isMuted();
route = state.getRoute();
supportedRouteMask = state.getSupportedRouteMask();
+ activeBluetoothDevice = null;
+ supportedBluetoothDevices = Collections.emptyList();
}
@Override
@@ -103,17 +139,32 @@ public final class CallAudioState implements Parcelable {
return false;
}
CallAudioState state = (CallAudioState) obj;
- return isMuted() == state.isMuted() && getRoute() == state.getRoute() &&
- getSupportedRouteMask() == state.getSupportedRouteMask();
+ if (supportedBluetoothDevices.size() != state.supportedBluetoothDevices.size()) {
+ return false;
+ }
+ for (BluetoothDevice device : supportedBluetoothDevices) {
+ if (!state.supportedBluetoothDevices.contains(device)) {
+ return false;
+ }
+ }
+ return Objects.equals(activeBluetoothDevice, state.activeBluetoothDevice) && isMuted() ==
+ state.isMuted() && getRoute() == state.getRoute() && getSupportedRouteMask() ==
+ state.getSupportedRouteMask();
}
@Override
public String toString() {
+ String bluetoothDeviceList = supportedBluetoothDevices.stream()
+ .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
+
return String.format(Locale.US,
- "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s]",
+ "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
+ "activeBluetoothDevice: [%s], supportedBluetoothDevices: [%s]]",
isMuted,
audioRouteToString(route),
- audioRouteToString(supportedRouteMask));
+ audioRouteToString(supportedRouteMask),
+ activeBluetoothDevice,
+ bluetoothDeviceList);
}
/**
@@ -126,6 +177,7 @@ public final class CallAudioState implements Parcelable {
/**
* @return The current audio route being used.
*/
+ @CallAudioRoute
public int getRoute() {
return route;
}
@@ -133,11 +185,27 @@ public final class CallAudioState implements Parcelable {
/**
* @return Bit mask of all routes supported by this call.
*/
+ @CallAudioRoute
public int getSupportedRouteMask() {
return supportedRouteMask;
}
/**
+ * @return The {@link BluetoothDevice} through which audio is being routed.
+ * Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}.
+ */
+ public BluetoothDevice getActiveBluetoothDevice() {
+ return activeBluetoothDevice;
+ }
+
+ /**
+ * @return {@link List} of {@link BluetoothDevice}s that can be used for this call.
+ */
+ public Collection<BluetoothDevice> getSupportedBluetoothDevices() {
+ return supportedBluetoothDevices;
+ }
+
+ /**
* Converts the provided audio route into a human readable string representation.
*
* @param route to convert into a string.
@@ -177,7 +245,13 @@ public final class CallAudioState implements Parcelable {
boolean isMuted = source.readByte() == 0 ? false : true;
int route = source.readInt();
int supportedRouteMask = source.readInt();
- return new CallAudioState(isMuted, route, supportedRouteMask);
+ BluetoothDevice activeBluetoothDevice = source.readParcelable(
+ ClassLoader.getSystemClassLoader());
+ List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
+ source.readParcelableList(supportedBluetoothDevices,
+ ClassLoader.getSystemClassLoader());
+ return new CallAudioState(isMuted, route,
+ supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
}
@Override
@@ -202,6 +276,8 @@ public final class CallAudioState implements Parcelable {
destination.writeByte((byte) (isMuted ? 1 : 0));
destination.writeInt(route);
destination.writeInt(supportedRouteMask);
+ destination.writeParcelable(activeBluetoothDevice, 0);
+ destination.writeParcelableList(new ArrayList<>(supportedBluetoothDevices), 0);
}
private static void listAppend(StringBuffer buffer, String str) {
diff --git a/android/telecom/Connection.java b/android/telecom/Connection.java
index 8ba934cc..2bb1c4ed 100644
--- a/android/telecom/Connection.java
+++ b/android/telecom/Connection.java
@@ -25,6 +25,7 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Notification;
+import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -819,7 +820,7 @@ public abstract class Connection extends Conferenceable {
public void onConnectionEvent(Connection c, String event, Bundle extras) {}
/** @hide */
public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
- public void onAudioRouteChanged(Connection c, int audioRoute) {}
+ public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {}
public void onRttInitiationSuccess(Connection c) {}
public void onRttInitiationFailure(Connection c, int reason) {}
public void onRttSessionRemotelyTerminated(Connection c) {}
@@ -1683,6 +1684,8 @@ public abstract class Connection extends Conferenceable {
// The internal telecom call ID associated with this connection.
private String mTelecomCallId;
+ // The PhoneAccountHandle associated with this connection.
+ private PhoneAccountHandle mPhoneAccountHandle;
private int mState = STATE_NEW;
private CallAudioState mCallAudioState;
private Uri mAddress;
@@ -2576,7 +2579,29 @@ public abstract class Connection extends Conferenceable {
*/
public final void setAudioRoute(int route) {
for (Listener l : mListeners) {
- l.onAudioRouteChanged(this, route);
+ l.onAudioRouteChanged(this, route, null);
+ }
+ }
+
+ /**
+ *
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified if the
+ * bluetooth stack is unable to route audio to the requested device.
+ * A list of available devices can be obtained via
+ * {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * <p>
+ * Used by self-managed {@link ConnectionService}s which wish to use bluetooth audio for a
+ * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+ * <p>
+ * See also {@link InCallService#requestBluetoothAudio(String)}
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}.
+ */
+ public void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+ for (Listener l : mListeners) {
+ l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
}
}
@@ -3076,6 +3101,27 @@ public abstract class Connection extends Conferenceable {
}
/**
+ * Sets the {@link PhoneAccountHandle} associated with this connection.
+ *
+ * @hide
+ */
+ public void setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
+ if (mPhoneAccountHandle != phoneAccountHandle) {
+ mPhoneAccountHandle = phoneAccountHandle;
+ notifyPhoneAccountChanged(phoneAccountHandle);
+ }
+ }
+
+ /**
+ * Returns the {@link PhoneAccountHandle} associated with this connection.
+ *
+ * @hide
+ */
+ public PhoneAccountHandle getPhoneAccountHandle() {
+ return mPhoneAccountHandle;
+ }
+
+ /**
* Sends an event associated with this {@code Connection} with associated event extras to the
* {@link InCallService}.
* <p>
diff --git a/android/telecom/ConnectionService.java b/android/telecom/ConnectionService.java
index a81fba95..7e833066 100644
--- a/android/telecom/ConnectionService.java
+++ b/android/telecom/ConnectionService.java
@@ -1294,10 +1294,10 @@ public abstract class ConnectionService extends Service {
}
@Override
- public void onAudioRouteChanged(Connection c, int audioRoute) {
+ public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {
String id = mIdByConnection.get(c);
if (id != null) {
- mAdapter.setAudioRoute(id, audioRoute);
+ mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress);
}
}
@@ -1382,7 +1382,7 @@ public abstract class ConnectionService extends Service {
connection.setTelecomCallId(callId);
if (connection.getState() != Connection.STATE_DISCONNECTED) {
- addConnection(callId, connection);
+ addConnection(request.getAccountHandle(), callId, connection);
}
Uri address = connection.getAddress();
@@ -1846,6 +1846,7 @@ public abstract class ConnectionService extends Service {
mAdapter.setIsConferenced(connectionId, id);
}
}
+ onConferenceAdded(conference);
}
}
@@ -2033,6 +2034,43 @@ public abstract class ConnectionService extends Service {
}
/**
+ * Called by Telecom on the initiating side of the handover to create an instance of a
+ * handover connection.
+ * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
+ * ConnectionService which needs to handover the call.
+ * @param request Details about the call which needs to be handover.
+ * @return Connection object corresponding to the handover call.
+ */
+ public Connection onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+ ConnectionRequest request) {
+ return null;
+ }
+
+ /**
+ * Called by Telecom on the receiving side of the handover to request the
+ * {@link ConnectionService} to create an instance of a handover connection.
+ * @param fromPhoneAccountHandle {@link PhoneAccountHandle} associated with the
+ * ConnectionService which needs to handover the call.
+ * @param request Details about the call which needs to be handover.
+ * @return {@link Connection} object corresponding to the handover call.
+ */
+ public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+ ConnectionRequest request) {
+ return null;
+ }
+
+ /**
+ * Called by Telecom in response to a {@code TelecomManager#acceptHandover()}
+ * invocation which failed.
+ * @param request Details about the call which needs to be handover.
+ * @param error Reason for handover failure as defined in
+ * {@link android.telecom.Call.Callback#HANDOVER_FAILURE_DEST_INVALID_PERM}
+ */
+ public void onHandoverFailed(ConnectionRequest request, int error) {
+ return;
+ }
+
+ /**
* Create a {@code Connection} for a new unknown call. An unknown call is a call originating
* from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming
* call created using
@@ -2056,6 +2094,30 @@ public abstract class ConnectionService extends Service {
public void onConference(Connection connection1, Connection connection2) {}
/**
+ * Called when a connection is added.
+ * @hide
+ */
+ public void onConnectionAdded(Connection connection) {}
+
+ /**
+ * Called when a connection is removed.
+ * @hide
+ */
+ public void onConnectionRemoved(Connection connection) {}
+
+ /**
+ * Called when a conference is added.
+ * @hide
+ */
+ public void onConferenceAdded(Conference conference) {}
+
+ /**
+ * Called when a conference is removed.
+ * @hide
+ */
+ public void onConferenceRemoved(Conference conference) {}
+
+ /**
* Indicates that a remote conference has been created for existing {@link RemoteConnection}s.
* When this method is invoked, this {@link ConnectionService} should create its own
* representation of the conference call and send it to telecom using {@link #addConference}.
@@ -2122,16 +2184,18 @@ public abstract class ConnectionService extends Service {
// prefix for a unique incremental call ID.
id = handle.getComponentName().getClassName() + "@" + getNextCallId();
}
- addConnection(id, connection);
+ addConnection(handle, id, connection);
return id;
}
- private void addConnection(String callId, Connection connection) {
+ private void addConnection(PhoneAccountHandle handle, String callId, Connection connection) {
connection.setTelecomCallId(callId);
mConnectionById.put(callId, connection);
mIdByConnection.put(connection, callId);
connection.addConnectionListener(mConnectionListener);
connection.setConnectionService(this);
+ connection.setPhoneAccountHandle(handle);
+ onConnectionAdded(connection);
}
/** {@hide} */
@@ -2143,6 +2207,7 @@ public abstract class ConnectionService extends Service {
mConnectionById.remove(id);
mIdByConnection.remove(connection);
mAdapter.removeCall(id);
+ onConnectionRemoved(connection);
}
}
@@ -2179,6 +2244,8 @@ public abstract class ConnectionService extends Service {
mConferenceById.remove(id);
mIdByConference.remove(conference);
mAdapter.removeCall(id);
+
+ onConferenceRemoved(conference);
}
}
diff --git a/android/telecom/ConnectionServiceAdapter.java b/android/telecom/ConnectionServiceAdapter.java
index 111fcc78..92a9dc23 100644
--- a/android/telecom/ConnectionServiceAdapter.java
+++ b/android/telecom/ConnectionServiceAdapter.java
@@ -520,11 +520,14 @@ final class ConnectionServiceAdapter implements DeathRecipient {
* @param callId The unique ID of the call.
* @param audioRoute The new audio route (see {@code CallAudioState#ROUTE_*}).
*/
- void setAudioRoute(String callId, int audioRoute) {
- Log.v(this, "setAudioRoute: %s %s", callId, CallAudioState.audioRouteToString(audioRoute));
+ void setAudioRoute(String callId, int audioRoute, String bluetoothAddress) {
+ Log.v(this, "setAudioRoute: %s %s %s", callId,
+ CallAudioState.audioRouteToString(audioRoute),
+ bluetoothAddress);
for (IConnectionServiceAdapter adapter : mAdapters) {
try {
- adapter.setAudioRoute(callId, audioRoute, Log.getExternalSession());
+ adapter.setAudioRoute(callId, audioRoute,
+ bluetoothAddress, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
diff --git a/android/telecom/ConnectionServiceAdapterServant.java b/android/telecom/ConnectionServiceAdapterServant.java
index b1617f4d..3fbdeb1e 100644
--- a/android/telecom/ConnectionServiceAdapterServant.java
+++ b/android/telecom/ConnectionServiceAdapterServant.java
@@ -298,8 +298,8 @@ final class ConnectionServiceAdapterServant {
case MSG_SET_AUDIO_ROUTE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
- mDelegate.setAudioRoute((String) args.arg1, args.argi1,
- (Session.Info) args.arg2);
+ mDelegate.setAudioRoute((String) args.arg1, args.argi1, (String) args.arg2,
+ (Session.Info) args.arg3);
} finally {
args.recycle();
}
@@ -548,12 +548,12 @@ final class ConnectionServiceAdapterServant {
@Override
public final void setAudioRoute(String connectionId, int audioRoute,
- Session.Info sessionInfo) {
-
+ String bluetoothAddress, Session.Info sessionInfo) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = connectionId;
args.argi1 = audioRoute;
- args.arg2 = sessionInfo;
+ args.arg2 = bluetoothAddress;
+ args.arg3 = sessionInfo;
mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, args).sendToTarget();
}
diff --git a/android/telecom/InCallAdapter.java b/android/telecom/InCallAdapter.java
index 9559a28c..4bc2a9b1 100644
--- a/android/telecom/InCallAdapter.java
+++ b/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.os.RemoteException;
@@ -128,7 +129,22 @@ public final class InCallAdapter {
*/
public void setAudioRoute(int route) {
try {
- mAdapter.setAudioRoute(route);
+ mAdapter.setAudioRoute(route, null);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified. A list of
+ * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+ */
+ public void requestBluetoothAudio(String bluetoothAddress) {
+ try {
+ mAdapter.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
} catch (RemoteException e) {
}
}
@@ -419,4 +435,21 @@ public final class InCallAdapter {
} catch (RemoteException ignored) {
}
}
+
+
+ /**
+ * Initiates a handover of this {@link Call} to the {@link ConnectionService} identified
+ * by destAcct.
+ * @param callId The callId of the Call which calls this function.
+ * @param destAcct ConnectionService to which the call should be handed over.
+ * @param videoState The video state desired after the handover.
+ * @param extras Extra information to be passed to ConnectionService
+ */
+ public void handoverTo(String callId, PhoneAccountHandle destAcct, int videoState,
+ Bundle extras) {
+ try {
+ mAdapter.handoverTo(callId, destAcct, videoState, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
}
diff --git a/android/telecom/InCallService.java b/android/telecom/InCallService.java
index e384d469..d558bbae 100644
--- a/android/telecom/InCallService.java
+++ b/android/telecom/InCallService.java
@@ -16,9 +16,11 @@
package android.telecom;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
+import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -377,6 +379,22 @@ public abstract class InCallService extends Service {
}
/**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified if the
+ * bluetooth stack is unable to route audio to the requested device.
+ * A list of available devices can be obtained via
+ * {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}.
+ */
+ public final void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+ if (mPhone != null) {
+ mPhone.requestBluetoothAudio(bluetoothAddress);
+ }
+ }
+
+ /**
* Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
* to start displaying in-call information to the user. Each instance of {@code InCallService}
* will have only one {@code Phone}, and this method will be called exactly once in the lifetime
diff --git a/android/telecom/Phone.java b/android/telecom/Phone.java
index 066f6c26..421b1a4b 100644
--- a/android/telecom/Phone.java
+++ b/android/telecom/Phone.java
@@ -17,7 +17,9 @@
package android.telecom;
import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
+import android.os.RemoteException;
import android.util.ArrayMap;
import java.util.Collections;
@@ -295,6 +297,18 @@ public final class Phone {
}
/**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified. A list of
+ * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+ */
+ public void requestBluetoothAudio(String bluetoothAddress) {
+ mInCallAdapter.requestBluetoothAudio(bluetoothAddress);
+ }
+
+ /**
* Turns the proximity sensor on. When this request is made, the proximity sensor will
* become active, and the touch screen and display will be turned off when the user's face
* is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/android/telecom/PhoneAccount.java b/android/telecom/PhoneAccount.java
index 691e7cf1..74b94650 100644
--- a/android/telecom/PhoneAccount.java
+++ b/android/telecom/PhoneAccount.java
@@ -86,13 +86,11 @@ public final class PhoneAccount implements Parcelable {
/**
* Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
* indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
- * connection (see {@link android.telecom.Call#EVENT_REQUEST_HANDOVER}) to this
- * {@link PhoneAccount} from a {@link PhoneAccount} specifying
- * {@link #EXTRA_SUPPORTS_HANDOVER_FROM}.
+ * connection (see {@code android.telecom.Call#handoverTo()}) to this {@link PhoneAccount} from
+ * a {@link PhoneAccount} specifying {@link #EXTRA_SUPPORTS_HANDOVER_FROM}.
* <p>
* A handover request is initiated by the user from the default dialer app to indicate a desire
* to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
- * @hide
*/
public static final String EXTRA_SUPPORTS_HANDOVER_TO =
"android.telecom.extra.SUPPORTS_HANDOVER_TO";
@@ -113,12 +111,11 @@ public final class PhoneAccount implements Parcelable {
* Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which
* indicates whether this {@link PhoneAccount} is capable of supporting a request to handover a
* connection from this {@link PhoneAccount} to another {@link PhoneAccount}.
- * (see {@link android.telecom.Call#EVENT_REQUEST_HANDOVER}) which specifies
+ * (see {@code android.telecom.Call#handoverTo()}) which specifies
* {@link #EXTRA_SUPPORTS_HANDOVER_TO}.
* <p>
* A handover request is initiated by the user from the default dialer app to indicate a desire
* to handover a call from one {@link PhoneAccount}/{@link ConnectionService} to another.
- * @hide
*/
public static final String EXTRA_SUPPORTS_HANDOVER_FROM =
"android.telecom.extra.SUPPORTS_HANDOVER_FROM";
@@ -132,7 +129,6 @@ public final class PhoneAccount implements Parcelable {
* <p>
* By default, Self-Managed {@link PhoneAccount}s do not log their calls to the call log.
* Setting this extra to {@code true} provides a means for them to log their calls.
- * @hide
*/
public static final String EXTRA_LOG_SELF_MANAGED_CALLS =
"android.telecom.extra.LOG_SELF_MANAGED_CALLS";
diff --git a/android/telecom/RemoteConnectionService.java b/android/telecom/RemoteConnectionService.java
index 2cc43143..85906ad1 100644
--- a/android/telecom/RemoteConnectionService.java
+++ b/android/telecom/RemoteConnectionService.java
@@ -398,7 +398,8 @@ final class RemoteConnectionService {
}
@Override
- public void setAudioRoute(String callId, int audioRoute, Session.Info sessionInfo) {
+ public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+ Session.Info sessionInfo) {
if (hasConnection(callId)) {
// TODO(3pcalls): handle this for remote connections.
// Likely we don't want to do anything since it doesn't make sense for self-managed
diff --git a/android/telecom/TelecomManager.java b/android/telecom/TelecomManager.java
index 9e52c71b..da32e0be 100644
--- a/android/telecom/TelecomManager.java
+++ b/android/telecom/TelecomManager.java
@@ -1750,6 +1750,41 @@ public class TelecomManager {
return false;
}
+ /**
+ * Called from the recipient side of a handover to indicate a desire to accept the handover
+ * of an ongoing call to another {@link ConnectionService} identified by
+ * {@link PhoneAccountHandle} destAcct. For managed {@link ConnectionService}s, the specified
+ * {@link PhoneAccountHandle} must have been registered with {@link #registerPhoneAccount} and
+ * the user must have enabled the corresponding {@link PhoneAccount}. This can be checked using
+ * {@link #getPhoneAccount}. Self-managed {@link ConnectionService}s must have
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS} to handover a call to it.
+ * <p>
+ * Once invoked, this method will cause the system to bind to the {@link ConnectionService}
+ * associated with the {@link PhoneAccountHandle} destAcct and call
+ * (See {@link ConnectionService#onCreateIncomingHandoverConnection}).
+ * <p>
+ * For a managed {@link ConnectionService}, a {@link SecurityException} will be thrown if either
+ * the {@link PhoneAccountHandle} destAcct does not correspond to a registered
+ * {@link PhoneAccount} or the associated {@link PhoneAccount} is not currently enabled by the
+ * user.
+ * <p>
+ * For a self-managed {@link ConnectionService}, a {@link SecurityException} will be thrown if
+ * the calling app does not have {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+ *
+ * @param srcAddr The {@link android.net.Uri} of the ongoing call to handover to the caller’s
+ * {@link ConnectionService}.
+ * @param videoState Video state after the handover.
+ * @param destAcct The {@link PhoneAccountHandle} registered to the calling package.
+ */
+ public void acceptHandover(Uri srcAddr, int videoState, PhoneAccountHandle destAcct) {
+ try {
+ if (isServiceConnected()) {
+ getTelecomService().acceptHandover(srcAddr, videoState, destAcct);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException acceptHandover: " + e);
+ }
+ }
private ITelecomService getTelecomService() {
if (mTelecomServiceOverride != null) {
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 6fc7d23a..1db6ef7b 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -352,6 +352,17 @@ public class CarrierConfigManager {
public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
/**
+ * Flag that specifies to use the user's own phone number as the voicemail number when there is
+ * no pre-loaded voicemail number on the SIM card.
+ * <p>
+ * {@link #KEY_DEFAULT_VM_NUMBER_STRING} takes precedence over this flag.
+ * <p>
+ * If false, the system default (*86) will be used instead.
+ */
+ public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL =
+ "config_telephony_use_own_number_for_voicemail_bool";
+
+ /**
* When {@code true}, changes to the mobile data enabled switch will not cause the VT
* registration state to change. That is, turning on or off mobile data will not cause VT to be
* enabled or disabled.
@@ -844,6 +855,14 @@ public class CarrierConfigManager {
public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
/**
+ * Default Enhanced 4G LTE mode enabled. When this is {@code true}, Enhanced 4G LTE mode by
+ * default is on, otherwise if {@code false}, Enhanced 4G LTE mode by default is off.
+ * @hide
+ */
+ public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL =
+ "enhanced_4g_lte_on_by_default_bool";
+
+ /**
* Determine whether IMS apn can be shown.
*/
public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
@@ -1187,24 +1206,21 @@ public class CarrierConfigManager {
*/
public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
-
/**
- * @hide
- * The default value for preferred CDMA roaming mode (aka CDMA system select.)
- * CDMA_ROAMING_MODE_RADIO_DEFAULT = the default roaming mode from the radio
- * CDMA_ROAMING_MODE_HOME = Home Networks
- * CDMA_ROAMING_MODE_AFFILIATED = Roaming on Affiliated networks
- * CDMA_ROAMING_MODE_ANY = Roaming on any networks
+ * The CDMA roaming mode (aka CDMA system select).
+ *
+ * <p>The value should be one of the CDMA_ROAMING_MODE_ constants in {@link TelephonyManager}.
+ * Values other than {@link TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT} (which is the
+ * default) will take precedence over user selection.
+ *
+ * @see TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT
+ * @see TelephonyManager#CDMA_ROAMING_MODE_HOME
+ * @see TelephonyManager#CDMA_ROAMING_MODE_AFFILIATED
+ * @see TelephonyManager#CDMA_ROAMING_MODE_ANY
*/
public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
- /** @hide */
- public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_HOME = 0;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
- /** @hide */
- public static final int CDMA_ROAMING_MODE_ANY = 2;
+
+
/**
* Boolean indicating if support is provided for directly dialing FDN number from FDN list.
* If false, this feature is not supported.
@@ -1535,6 +1551,13 @@ public class CarrierConfigManager {
"boosted_lte_earfcns_string_array";
/**
+ * Determine whether to use only RSRP for the number of LTE signal bars.
+ * @hide
+ */
+ public static final String KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL =
+ "use_only_rsrp_for_lte_signal_bar_bool";
+
+ /**
* Key identifying if voice call barring notification is required to be shown to the user.
* @hide
*/
@@ -1628,6 +1651,33 @@ public class CarrierConfigManager {
public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
"feature_access_codes_string_array";
+ /**
+ * Determines if the carrier wants to identify high definition calls in the call log.
+ * @hide
+ */
+ public static final String KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL =
+ "identify_high_definition_calls_in_call_log_bool";
+
+ /**
+ * Flag specifying whether to use the {@link ServiceState} roaming status, which can be
+ * affected by other carrier configs (e.g.
+ * {@link #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY}), when setting the SPN display.
+ * <p>
+ * If {@code true}, the SPN display uses {@link ServiceState#getRoaming}.
+ * If {@code false} the SPN display checks if the current MCC/MNC is different from the
+ * SIM card's MCC/MNC.
+ *
+ * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+ * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+ * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
+ * @see KEY_ROAMING_OPERATOR_STRING_ARRAY
+ * @see KEY_FORCE_HOME_NETWORK_BOOL
+ *
+ * @hide
+ */
+ public static final String KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL =
+ "spn_display_rule_use_roaming_from_service_state_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1645,6 +1695,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
+ sDefaults.putBoolean(KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
@@ -1769,6 +1820,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_ENHANCED_4G_LTE_BOOL, false);
+ sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL, false);
@@ -1821,7 +1873,8 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true);
sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false);
- sDefaults.putInt(KEY_CDMA_ROAMING_MODE_INT, CDMA_ROAMING_MODE_RADIO_DEFAULT);
+ sDefaults.putInt(
+ KEY_CDMA_ROAMING_MODE_INT, TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
sDefaults.putString(KEY_RCS_CONFIG_SERVER_URL_STRING, "");
// Carrier Signalling Receivers
@@ -1892,6 +1945,7 @@ public class CarrierConfigManager {
null);
sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
sDefaults.putInt(IMSI_KEY_AVAILABILITY_INT, 0);
sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null);
@@ -1902,6 +1956,8 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
+ sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
}
/**
diff --git a/android/telephony/PhoneStateListener.java b/android/telephony/PhoneStateListener.java
index afff6d54..9ccfa942 100644
--- a/android/telephony/PhoneStateListener.java
+++ b/android/telephony/PhoneStateListener.java
@@ -204,16 +204,6 @@ public class PhoneStateListener {
public static final int LISTEN_VOLTE_STATE = 0x00004000;
/**
- * Listen for OEM hook raw event
- *
- * @see #onOemHookRawEvent
- * @hide
- * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
- */
- @Deprecated
- public static final int LISTEN_OEM_HOOK_RAW_EVENT = 0x00008000;
-
- /**
* Listen for carrier network changes indicated by a carrier app.
*
* @see #onCarrierNetworkRequest
@@ -359,9 +349,6 @@ public class PhoneStateListener {
case LISTEN_DATA_ACTIVATION_STATE:
PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
break;
- case LISTEN_OEM_HOOK_RAW_EVENT:
- PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
- break;
case LISTEN_CARRIER_NETWORK_CHANGE:
PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
break;
@@ -556,16 +543,6 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when OEM hook raw event is received. Requires
- * the READ_PRIVILEGED_PHONE_STATE permission.
- * @param rawData is the byte array of the OEM hook raw data.
- * @hide
- */
- public void onOemHookRawEvent(byte[] rawData) {
- // default implementation empty
- }
-
- /**
* Callback invoked when telephony has received notice from a carrier
* app that a network action that could result in connectivity loss
* has been requested by an app using
@@ -677,10 +654,6 @@ public class PhoneStateListener {
send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
}
- public void onOemHookRawEvent(byte[] rawData) {
- send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
- }
-
public void onCarrierNetworkChange(boolean active) {
send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
}
diff --git a/android/telephony/SignalStrength.java b/android/telephony/SignalStrength.java
index c8b47765..de02de7b 100644
--- a/android/telephony/SignalStrength.java
+++ b/android/telephony/SignalStrength.java
@@ -68,6 +68,7 @@ public class SignalStrength implements Parcelable {
private int mTdScdmaRscp;
private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
+ private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
/**
* Create a new SignalStrength from a intent notifier Bundle
@@ -108,6 +109,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = true;
+ mUseOnlyRsrpForLteLevel = false;
}
/**
@@ -134,6 +136,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = gsmFlag;
+ mUseOnlyRsrpForLteLevel = false;
}
/**
@@ -145,10 +148,10 @@ public class SignalStrength implements Parcelable {
int cdmaDbm, int cdmaEcio,
int evdoDbm, int evdoEcio, int evdoSnr,
int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) {
+ int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag, boolean lteLevelBaseOnRsrp) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag, lteLevelBaseOnRsrp);
mTdScdmaRscp = tdScdmaRscp;
}
@@ -164,7 +167,7 @@ public class SignalStrength implements Parcelable {
int tdScdmaRscp, boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
mTdScdmaRscp = tdScdmaRscp;
}
@@ -180,7 +183,7 @@ public class SignalStrength implements Parcelable {
boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
- lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
+ lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false);
}
/**
@@ -194,7 +197,7 @@ public class SignalStrength implements Parcelable {
boolean gsmFlag) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
- INVALID, INVALID, INVALID, 0, gsmFlag);
+ INVALID, INVALID, INVALID, 0, gsmFlag, false);
}
/**
@@ -228,7 +231,7 @@ public class SignalStrength implements Parcelable {
boolean gsm) {
initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
- INVALID, INVALID, INVALID, 0, gsm);
+ INVALID, INVALID, INVALID, 0, gsm, false);
}
/**
@@ -248,6 +251,7 @@ public class SignalStrength implements Parcelable {
* @param lteCqi
* @param lteRsrpBoost
* @param gsm
+ * @param useOnlyRsrpForLteLevel
*
* @hide
*/
@@ -255,7 +259,7 @@ public class SignalStrength implements Parcelable {
int cdmaDbm, int cdmaEcio,
int evdoDbm, int evdoEcio, int evdoSnr,
int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
- int lteRsrpBoost, boolean gsm) {
+ int lteRsrpBoost, boolean gsm, boolean useOnlyRsrpForLteLevel) {
mGsmSignalStrength = gsmSignalStrength;
mGsmBitErrorRate = gsmBitErrorRate;
mCdmaDbm = cdmaDbm;
@@ -271,6 +275,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = lteRsrpBoost;
mTdScdmaRscp = INVALID;
isGsm = gsm;
+ mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
if (DBG) log("initialize: " + toString());
}
@@ -293,6 +298,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = s.mLteRsrpBoost;
mTdScdmaRscp = s.mTdScdmaRscp;
isGsm = s.isGsm;
+ mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
}
/**
@@ -318,6 +324,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = in.readInt();
mTdScdmaRscp = in.readInt();
isGsm = (in.readInt() != 0);
+ mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
}
/**
@@ -366,6 +373,7 @@ public class SignalStrength implements Parcelable {
out.writeInt(mLteRsrpBoost);
out.writeInt(mTdScdmaRscp);
out.writeInt(isGsm ? 1 : 0);
+ out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
}
/**
@@ -449,6 +457,17 @@ public class SignalStrength implements Parcelable {
}
/**
+ * @param useOnlyRsrpForLteLevel true if it uses only RSRP for the number of LTE signal bar,
+ * otherwise false.
+ *
+ * Used by phone to use only RSRP or not for the number of LTE signal bar.
+ * @hide
+ */
+ public void setUseOnlyRsrpForLteLevel(boolean useOnlyRsrpForLteLevel) {
+ mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
+ }
+
+ /**
* @param lteRsrpBoost - signal strength offset
*
* Used by phone to set the lte signal strength offset which will be
@@ -835,6 +854,13 @@ public class SignalStrength implements Parcelable {
}
}
+ if (useOnlyRsrpForLteLevel()) {
+ log("getLTELevel - rsrp = " + rsrpIconLevel);
+ if (rsrpIconLevel != -1) {
+ return rsrpIconLevel;
+ }
+ }
+
/*
* Values are -200 dB to +300 (SNR*10dB) RS_SNR >= 13.0 dB =>4 bars 4.5
* dB <= RS_SNR < 13.0 dB => 3 bars 1.0 dB <= RS_SNR < 4.5 dB => 2 bars
@@ -915,6 +941,15 @@ public class SignalStrength implements Parcelable {
}
/**
+ * @return true if it uses only RSRP for the number of LTE signal bar, otherwise false.
+ *
+ * @hide
+ */
+ public boolean useOnlyRsrpForLteLevel() {
+ return this.mUseOnlyRsrpForLteLevel;
+ }
+
+ /**
* @return get TD_SCDMA dbm
*
* @hide
@@ -974,7 +1009,8 @@ public class SignalStrength implements Parcelable {
+ (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)
+ (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
+ (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
- + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0));
+ + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)
+ + (mUseOnlyRsrpForLteLevel ? 1 : 0));
}
/**
@@ -1008,7 +1044,8 @@ public class SignalStrength implements Parcelable {
&& mLteCqi == s.mLteCqi
&& mLteRsrpBoost == s.mLteRsrpBoost
&& mTdScdmaRscp == s.mTdScdmaRscp
- && isGsm == s.isGsm);
+ && isGsm == s.isGsm
+ && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel);
}
/**
@@ -1031,7 +1068,9 @@ public class SignalStrength implements Parcelable {
+ " " + mLteCqi
+ " " + mLteRsrpBoost
+ " " + mTdScdmaRscp
- + " " + (isGsm ? "gsm|lte" : "cdma"));
+ + " " + (isGsm ? "gsm|lte" : "cdma")
+ + " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" :
+ "use_rsrp_and_rssnr_for_lte_level"));
}
/** Returns the signal strength related to GSM. */
@@ -1086,6 +1125,7 @@ public class SignalStrength implements Parcelable {
mLteRsrpBoost = m.getInt("lteRsrpBoost");
mTdScdmaRscp = m.getInt("TdScdma");
isGsm = m.getBoolean("isGsm");
+ mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
}
/**
@@ -1110,6 +1150,7 @@ public class SignalStrength implements Parcelable {
m.putInt("lteRsrpBoost", mLteRsrpBoost);
m.putInt("TdScdma", mTdScdmaRscp);
m.putBoolean("isGsm", isGsm);
+ m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
}
/**
diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java
index 98195ada..5d039268 100644
--- a/android/telephony/SmsManager.java
+++ b/android/telephony/SmsManager.java
@@ -338,16 +338,18 @@ public final class SmsManager {
/**
* Send a text based SMS without writing it into the SMS Provider.
*
+ * <p>
+ * The message will be sent directly over the network and will not be visible in SMS
+ * applications. Intended for internal carrier use only.
+ * </p>
+ *
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
* privileges.
* </p>
*
* @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
- * @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void sendTextMessageWithoutPersisting(
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
@@ -387,6 +389,112 @@ public final class SmsManager {
}
/**
+ * Send a text based SMS with messaging options.
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
+ *
+ * @throws IllegalArgumentException if destinationAddress or text are empty
+ * {@hide}
+ */
+ public void sendTextMessage(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ true /* persistMessage*/, priority, expectMore, validityPeriod);
+ }
+
+ private void sendTextMessageInternal(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+ int priority, boolean expectMore, int validityPeriod) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (priority < 0x00 || priority > 0x03) {
+ throw new IllegalArgumentException("Invalid priority");
+ }
+
+ if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+ throw new IllegalArgumentException("Invalid validity period");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ if (iccISms != null) {
+ iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
+ sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+ validityPeriod);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendTextMessage(String, String, String, PendingIntent,
+ * PendingIntent, int, boolean, int)
+ * @hide
+ */
+ public void sendTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
+ boolean expectMore, int validityPeriod) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ false /* persistMessage */, priority, expectMore, validityPeriod);
+ }
+
+ /**
+ *
* Inject an SMS PDU into the android application framework.
*
* <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
@@ -544,6 +652,140 @@ public final class SmsManager {
}
/**
+ * Send a multi-part text based SMS with messaging options. The callee should have already
+ * divided the message into correctly sized parts by calling
+ * <code>divideMessage</code>.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+ * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ * @param priority Priority level of the message
+ * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+ * ---------------------------------
+ * PRIORITY | Level of Priority
+ * ---------------------------------
+ * '00' | Normal
+ * '01' | Interactive
+ * '10' | Urgent
+ * '11' | Emergency
+ * ----------------------------------
+ * Any Other values included Negative considered as Invalid Priority Indicator of the message.
+ * @param expectMore is a boolean to indicate the sending messages through same link or not.
+ * @param validityPeriod Validity Period of the message in mins.
+ * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+ * Validity Period(Minimum) -> 5 mins
+ * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+ * Any Other values included Negative considered as Invalid Validity Period of the message.
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ * {@hide}
+ */
+ public void sendMultipartTextMessage(
+ String destinationAddress, String scAddress, ArrayList<String> parts,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, true /* persistMessage*/);
+ }
+
+ private void sendMultipartTextMessageInternal(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+ boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+ if (parts == null || parts.size() < 1) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (priority < 0x00 || priority > 0x03) {
+ throw new IllegalArgumentException("Invalid priority");
+ }
+
+ if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+ throw new IllegalArgumentException("Invalid validity period");
+ }
+
+ if (parts.size() > 1) {
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ if (iccISms != null) {
+ iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress,
+ parts, sentIntents, deliveryIntents, persistMessage, priority,
+ expectMore, validityPeriod);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ } else {
+ PendingIntent sentIntent = null;
+ PendingIntent deliveryIntent = null;
+ if (sentIntents != null && sentIntents.size() > 0) {
+ sentIntent = sentIntents.get(0);
+ }
+ if (deliveryIntents != null && deliveryIntents.size() > 0) {
+ deliveryIntent = deliveryIntents.get(0);
+ }
+ sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
+ sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+ validityPeriod);
+ }
+ }
+
+ /**
+ * Send a multi-part text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList,
+ * ArrayList, int, boolean, int)
+ * @hide
+ **/
+ public void sendMultipartTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+ int priority, boolean expectMore, int validityPeriod) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, false /* persistMessage*/, priority, expectMore,
+ validityPeriod);
+ }
+
+ /**
* Send a data based SMS to a specific application port.
*
* <p class="note"><strong>Note:</strong> Using this method requires that your app has the
@@ -1006,7 +1248,7 @@ public final class SmsManager {
* <code>getAllMessagesFromIcc</code>
* @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
*/
- private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+ private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
if (records != null) {
int count = records.size();
@@ -1014,7 +1256,8 @@ public final class SmsManager {
SmsRawData data = records.get(i);
// List contains all records, including "free" records (null)
if (data != null) {
- SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+ SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(),
+ getSubscriptionId());
if (sms != null) {
messages.add(sms);
}
diff --git a/android/telephony/SmsMessage.java b/android/telephony/SmsMessage.java
index df412335..a5d67c60 100644
--- a/android/telephony/SmsMessage.java
+++ b/android/telephony/SmsMessage.java
@@ -271,6 +271,31 @@ public class SmsMessage {
}
/**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by SmsManager.getAllMessagesFromSim + 1.
+ * @param data Record data.
+ * @param subId Subscription Id of the SMS
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data, int subId) {
+ SmsMessageBase wrappedMessage;
+
+ if (isCdmaVoice(subId)) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+ index, data);
+ } else {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+ index, data);
+ }
+
+ return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+ }
+
+ /**
* Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
* length in bytes (not hex chars) less the SMSC header
*
@@ -822,6 +847,7 @@ public class SmsMessage {
int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId);
return (PHONE_TYPE_CDMA == activePhone);
}
+
/**
* Decide if the carrier supports long SMS.
* {@hide}
diff --git a/android/telephony/SubscriptionManager.java b/android/telephony/SubscriptionManager.java
index 88f4880a..2f39ddb1 100644
--- a/android/telephony/SubscriptionManager.java
+++ b/android/telephony/SubscriptionManager.java
@@ -360,6 +360,42 @@ public class SubscriptionManager {
public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
/**
+ * TelephonyProvider column name for enable Volte.
+ *@hide
+ */
+ public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+
+ /**
+ * TelephonyProvider column name for enable VT (Video Telephony over IMS)
+ *@hide
+ */
+ public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+
+ /**
+ * TelephonyProvider column name for enable Wifi calling
+ *@hide
+ */
+ public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+
+ /**
+ * TelephonyProvider column name for Wifi calling mode
+ *@hide
+ */
+ public static final String WFC_IMS_MODE = "wfc_ims_mode";
+
+ /**
+ * TelephonyProvider column name for Wifi calling mode in roaming
+ *@hide
+ */
+ public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+
+ /**
+ * TelephonyProvider column name for enable Wifi calling in roaming
+ *@hide
+ */
+ public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+
+ /**
* Broadcast Action: The user has changed one of the default subs related to
* data, phone calls, or sms</p>
*
diff --git a/android/telephony/TelephonyManager.java b/android/telephony/TelephonyManager.java
index c0564c55..4ffb3c32 100644
--- a/android/telephony/TelephonyManager.java
+++ b/android/telephony/TelephonyManager.java
@@ -953,6 +953,27 @@ public class TelephonyManager {
*/
public static final int USSD_ERROR_SERVICE_UNAVAIL = -2;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which leaves the roaming
+ * mode set to the radio default or to the user's preference if they've indicated one.
+ */
+ public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which only permits
+ * connections on home networks.
+ */
+ public static final int CDMA_ROAMING_MODE_HOME = 0;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+ * affiliated networks.
+ */
+ public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
+ /**
+ * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+ * any network.
+ */
+ public static final int CDMA_ROAMING_MODE_ANY = 2;
+
//
//
// Device Info
@@ -2145,13 +2166,16 @@ public class TelephonyManager {
* @hide
*/
public String getSimOperatorNumeric() {
- int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ int subId = mSubId;
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultSmsSubscriptionId();
+ subId = SubscriptionManager.getDefaultDataSubscriptionId();
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ subId = SubscriptionManager.getDefaultSmsSubscriptionId();
if (!SubscriptionManager.isUsableSubIdValue(subId)) {
- subId = SubscriptionManager.getDefaultSubscriptionId();
+ subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+ if (!SubscriptionManager.isUsableSubIdValue(subId)) {
+ subId = SubscriptionManager.getDefaultSubscriptionId();
+ }
}
}
}
@@ -5685,29 +5709,6 @@ public class TelephonyManager {
return retVal;
}
- /**
- * Returns the result and response from RIL for oem request
- *
- * @param oemReq the data is sent to ril.
- * @param oemResp the respose data from RIL.
- * @return negative value request was not handled or get error
- * 0 request was handled succesfully, but no response data
- * positive value success, data length of response
- * @hide
- * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
- */
- @Deprecated
- public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.invokeOemRilRequestRaw(oemReq, oemResp);
- } catch (RemoteException ex) {
- } catch (NullPointerException ex) {
- }
- return -1;
- }
-
/** @hide */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
diff --git a/android/telephony/TelephonyScanManager.java b/android/telephony/TelephonyScanManager.java
index 92a21b6f..7bcdcdcc 100644
--- a/android/telephony/TelephonyScanManager.java
+++ b/android/telephony/TelephonyScanManager.java
@@ -73,8 +73,8 @@ public final class TelephonyScanManager {
/**
* Informs the user that there is some error about the scan.
*
- * This callback will be called whenever there is any error about the scan, but the scan
- * won't stop unless the onComplete() callback is called.
+ * This callback will be called whenever there is any error about the scan, and the scan
+ * will be terminated. onComplete() will NOT be called.
*/
public void onError(int error) {}
}
diff --git a/android/telephony/euicc/DownloadableSubscription.java b/android/telephony/euicc/DownloadableSubscription.java
index b5484e34..01041c8b 100644
--- a/android/telephony/euicc/DownloadableSubscription.java
+++ b/android/telephony/euicc/DownloadableSubscription.java
@@ -53,6 +53,8 @@ public final class DownloadableSubscription implements Parcelable {
@Nullable
public final String encodedActivationCode;
+ @Nullable private String confirmationCode;
+
// see getCarrierName and setCarrierName
@Nullable
private String carrierName;
@@ -66,6 +68,7 @@ public final class DownloadableSubscription implements Parcelable {
private DownloadableSubscription(Parcel in) {
encodedActivationCode = in.readString();
+ confirmationCode = in.readString();
carrierName = in.readString();
accessRules = in.createTypedArray(UiccAccessRule.CREATOR);
}
@@ -83,6 +86,21 @@ public final class DownloadableSubscription implements Parcelable {
}
/**
+ * Sets the confirmation code.
+ */
+ public void setConfirmationCode(String confirmationCode) {
+ this.confirmationCode = confirmationCode;
+ }
+
+ /**
+ * Returns the confirmation code.
+ */
+ @Nullable
+ public String getConfirmationCode() {
+ return confirmationCode;
+ }
+
+ /**
* Set the user-visible carrier name.
* @hide
*
@@ -134,6 +152,7 @@ public final class DownloadableSubscription implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(encodedActivationCode);
+ dest.writeString(confirmationCode);
dest.writeString(carrierName);
dest.writeTypedArray(accessRules, flags);
}
diff --git a/android/telephony/ims/ImsServiceProxy.java b/android/telephony/ims/ImsServiceProxy.java
deleted file mode 100644
index 038e295d..00000000
--- a/android/telephony/ims/ImsServiceProxy.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony.ims;
-
-import android.app.PendingIntent;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.telephony.ims.feature.IRcsFeature;
-import android.telephony.ims.feature.ImsFeature;
-import android.util.Log;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
-import com.android.ims.internal.IImsUt;
-
-/**
- * A container of the IImsServiceController binder, which implements all of the ImsFeatures that
- * the platform currently supports: MMTel and RCS.
- * @hide
- */
-
-public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeature {
-
- protected String LOG_TAG = "ImsServiceProxy";
- private final int mSupportedFeature;
-
- // Start by assuming the proxy is available for usage.
- private boolean mIsAvailable = true;
- // ImsFeature Status from the ImsService. Cached.
- private Integer mFeatureStatusCached = null;
- private ImsServiceProxy.INotifyStatusChanged mStatusCallback;
- private final Object mLock = new Object();
-
- public interface INotifyStatusChanged {
- void notifyStatusChanged();
- }
-
- private final IImsServiceFeatureListener mListenerBinder =
- new IImsServiceFeatureListener.Stub() {
-
- @Override
- public void imsFeatureCreated(int slotId, int feature) throws RemoteException {
- // The feature has been re-enabled. This may happen when the service crashes.
- synchronized (mLock) {
- if (!mIsAvailable && mSlotId == slotId && feature == mSupportedFeature) {
- Log.i(LOG_TAG, "Feature enabled on slotId: " + slotId + " for feature: " +
- feature);
- mIsAvailable = true;
- }
- }
- }
-
- @Override
- public void imsFeatureRemoved(int slotId, int feature) throws RemoteException {
- synchronized (mLock) {
- if (mIsAvailable && mSlotId == slotId && feature == mSupportedFeature) {
- Log.i(LOG_TAG, "Feature disabled on slotId: " + slotId + " for feature: " +
- feature);
- mIsAvailable = false;
- }
- }
- }
-
- @Override
- public void imsStatusChanged(int slotId, int feature, int status) throws RemoteException {
- synchronized (mLock) {
- Log.i(LOG_TAG, "imsStatusChanged: slot: " + slotId + " feature: " + feature +
- " status: " + status);
- if (mSlotId == slotId && feature == mSupportedFeature) {
- mFeatureStatusCached = status;
- if (mStatusCallback != null) {
- mStatusCallback.notifyStatusChanged();
- }
- }
- }
- }
- };
-
- public ImsServiceProxy(int slotId, IBinder binder, int featureType) {
- super(slotId, binder);
- mSupportedFeature = featureType;
- }
-
- public ImsServiceProxy(int slotId, int featureType) {
- super(slotId, null /*IBinder*/);
- mSupportedFeature = featureType;
- }
-
- public IImsServiceFeatureListener getListener() {
- return mListenerBinder;
- }
-
- public void setBinder(IBinder binder) {
- mBinder = binder;
- }
-
- @Override
- public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
- throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature,
- incomingCallIntent, listener);
- }
- }
-
- @Override
- public void endSession(int sessionId) throws RemoteException {
- synchronized (mLock) {
- // Only check to make sure the binder connection still exists. This method should
- // still be able to be called when the state is STATE_NOT_AVAILABLE.
- checkBinderConnection();
- getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
- }
- }
-
- @Override
- public boolean isConnected(int callServiceType, int callType)
- throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
- callServiceType, callType);
- }
- }
-
- @Override
- public boolean isOpened() throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
- }
- }
-
- @Override
- public void addRegistrationListener(IImsRegistrationListener listener)
- throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
- listener);
- }
- }
-
- @Override
- public void removeRegistrationListener(IImsRegistrationListener listener)
- throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
- listener);
- }
- }
-
- @Override
- public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
- throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature,
- sessionId, callServiceType, callType);
- }
- }
-
- @Override
- public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
- IImsCallSessionListener listener) throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature,
- sessionId, profile, listener);
- }
- }
-
- @Override
- public IImsCallSession getPendingCallSession(int sessionId, String callId)
- throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature,
- sessionId, callId);
- }
- }
-
- @Override
- public IImsUt getUtInterface() throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
- }
- }
-
- @Override
- public IImsConfig getConfigInterface() throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
- }
- }
-
- @Override
- public void turnOnIms() throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
- }
- }
-
- @Override
- public void turnOffIms() throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
- }
- }
-
- @Override
- public IImsEcbm getEcbmInterface() throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
- }
- }
-
- @Override
- public void setUiTTYMode(int uiTtyMode, Message onComplete)
- throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
- onComplete);
- }
- }
-
- @Override
- public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
- synchronized (mLock) {
- checkServiceIsReady();
- return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
- mSupportedFeature);
- }
- }
-
- @Override
- public int getFeatureStatus() {
- synchronized (mLock) {
- if (isBinderAlive() && mFeatureStatusCached != null) {
- Log.i(LOG_TAG, "getFeatureStatus - returning cached: " + mFeatureStatusCached);
- return mFeatureStatusCached;
- }
- }
- // Don't synchronize on Binder call.
- Integer status = retrieveFeatureStatus();
- synchronized (mLock) {
- if (status == null) {
- return ImsFeature.STATE_NOT_AVAILABLE;
- }
- // Cache only non-null value for feature status.
- mFeatureStatusCached = status;
- }
- Log.i(LOG_TAG, "getFeatureStatus - returning " + status);
- return status;
- }
-
- /**
- * Internal method used to retrieve the feature status from the corresponding ImsService.
- */
- private Integer retrieveFeatureStatus() {
- if (mBinder != null) {
- try {
- return getServiceInterface(mBinder).getFeatureStatus(mSlotId, mSupportedFeature);
- } catch (RemoteException e) {
- // Status check failed, don't update cache
- }
- }
- return null;
- }
-
- /**
- * @param c Callback that will fire when the feature status has changed.
- */
- public void setStatusCallback(INotifyStatusChanged c) {
- mStatusCallback = c;
- }
-
- /**
- * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
- * method returns false, it doesn't mean that the Binder connection is not available (use
- * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
- * at this time.
- *
- * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
- * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}.
- */
- public boolean isBinderReady() {
- return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
- }
-
- @Override
- public boolean isBinderAlive() {
- return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
- }
-
- protected void checkServiceIsReady() throws RemoteException {
- if (!isBinderReady()) {
- throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
- }
- }
-
- private IImsServiceController getServiceInterface(IBinder b) {
- return IImsServiceController.Stub.asInterface(b);
- }
-}
diff --git a/android/telephony/ims/ImsServiceProxyCompat.java b/android/telephony/ims/ImsServiceProxyCompat.java
deleted file mode 100644
index bbd5f027..00000000
--- a/android/telephony/ims/ImsServiceProxyCompat.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony.ims;
-
-import android.app.PendingIntent;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.telephony.ims.feature.IMMTelFeature;
-import android.telephony.ims.feature.ImsFeature;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsService;
-import com.android.ims.internal.IImsUt;
-
-/**
- * Compatibility class that implements the new ImsService IMMTelFeature interface, but
- * uses the old IImsService interface to support older devices that implement the deprecated
- * opt/net/ims interface.
- * @hide
- */
-
-public class ImsServiceProxyCompat implements IMMTelFeature {
-
- private static final int SERVICE_ID = ImsFeature.MMTEL;
-
- protected final int mSlotId;
- protected IBinder mBinder;
-
- public ImsServiceProxyCompat(int slotId, IBinder binder) {
- mSlotId = slotId;
- mBinder = binder;
- }
-
- @Override
- public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
- throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).open(mSlotId, ImsFeature.MMTEL, incomingCallIntent,
- listener);
- }
-
- @Override
- public void endSession(int sessionId) throws RemoteException {
- checkBinderConnection();
- getServiceInterface(mBinder).close(sessionId);
- }
-
- @Override
- public boolean isConnected(int callServiceType, int callType)
- throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).isConnected(SERVICE_ID, callServiceType, callType);
- }
-
- @Override
- public boolean isOpened() throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).isOpened(SERVICE_ID);
- }
-
- @Override
- public void addRegistrationListener(IImsRegistrationListener listener)
- throws RemoteException {
- checkBinderConnection();
- getServiceInterface(mBinder).addRegistrationListener(mSlotId, ImsFeature.MMTEL, listener);
- }
-
- @Override
- public void removeRegistrationListener(IImsRegistrationListener listener)
- throws RemoteException {
- // Not Implemented in old ImsService. If the registration listener becomes invalid, the
- // ImsService will remove.
- }
-
- @Override
- public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
- throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).createCallProfile(sessionId, callServiceType, callType);
- }
-
- @Override
- public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
- IImsCallSessionListener listener) throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).createCallSession(sessionId, profile, listener);
- }
-
- @Override
- public IImsCallSession getPendingCallSession(int sessionId, String callId)
- throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).getPendingCallSession(sessionId, callId);
- }
-
- @Override
- public IImsUt getUtInterface() throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).getUtInterface(SERVICE_ID);
- }
-
- @Override
- public IImsConfig getConfigInterface() throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).getConfigInterface(mSlotId);
- }
-
- @Override
- public void turnOnIms() throws RemoteException {
- checkBinderConnection();
- getServiceInterface(mBinder).turnOnIms(mSlotId);
- }
-
- @Override
- public void turnOffIms() throws RemoteException {
- checkBinderConnection();
- getServiceInterface(mBinder).turnOffIms(mSlotId);
- }
-
- @Override
- public IImsEcbm getEcbmInterface() throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).getEcbmInterface(SERVICE_ID);
- }
-
- @Override
- public void setUiTTYMode(int uiTtyMode, Message onComplete)
- throws RemoteException {
- checkBinderConnection();
- getServiceInterface(mBinder).setUiTTYMode(SERVICE_ID, uiTtyMode, onComplete);
- }
-
- @Override
- public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
- checkBinderConnection();
- return getServiceInterface(mBinder).getMultiEndpointInterface(SERVICE_ID);
- }
-
- /**
- * Base implementation, always returns READY for compatibility with old ImsService.
- */
- public int getFeatureStatus() {
- return ImsFeature.STATE_READY;
- }
-
- /**
- * @return false if the binder connection is no longer alive.
- */
- public boolean isBinderAlive() {
- return mBinder != null && mBinder.isBinderAlive();
- }
-
- private IImsService getServiceInterface(IBinder b) {
- return IImsService.Stub.asInterface(b);
- }
-
- protected void checkBinderConnection() throws RemoteException {
- if (!isBinderAlive()) {
- throw new RemoteException("ImsServiceProxy is not available for that feature.");
- }
- }
-}
diff --git a/android/telephony/ims/feature/IMMTelFeature.java b/android/telephony/ims/feature/IMMTelFeature.java
deleted file mode 100644
index d65e27eb..00000000
--- a/android/telephony/ims/feature/IMMTelFeature.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telephony.ims.feature;
-
-import android.app.PendingIntent;
-import android.os.Message;
-import android.os.RemoteException;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsUt;
-
-/**
- * MMTel interface for an ImsService. When updating this interface, ensure that base implementations
- * of your changes are also present in MMTelFeature for compatibility with older versions of the
- * MMTel feature.
- * @hide
- */
-
-public interface IMMTelFeature {
-
- /**
- * Notifies the MMTel feature that you would like to start a session. This should always be
- * done before making/receiving IMS calls. The IMS service will register the device to the
- * operator's network with the credentials (from ISIM) periodically in order to receive calls
- * from the operator's network. When the IMS service receives a new call, it will send out an
- * intent with the provided action string. The intent contains a call ID extra
- * {@link IImsCallSession#getCallId} and it can be used to take a call.
- *
- * @param incomingCallIntent When an incoming call is received, the IMS service will call
- * {@link PendingIntent#send} to send back the intent to the caller with
- * {@link #INCOMING_CALL_RESULT_CODE} as the result code and the intent to fill in the call ID;
- * It cannot be null.
- * @param listener To listen to IMS registration events; It cannot be null
- * @return an integer (greater than 0) representing the session id associated with the session
- * that has been started.
- */
- int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
- throws RemoteException;
-
- /**
- * End a previously started session using the associated sessionId.
- * @param sessionId an integer (greater than 0) representing the ongoing session. See
- * {@link #startSession}.
- */
- void endSession(int sessionId) throws RemoteException;
-
- /**
- * Checks if the IMS service has successfully registered to the IMS network with the specified
- * service & call type.
- *
- * @param callServiceType a service type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
- * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
- * @param callType a call type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
- * {@link ImsCallProfile#CALL_TYPE_VOICE}
- * {@link ImsCallProfile#CALL_TYPE_VT}
- * {@link ImsCallProfile#CALL_TYPE_VS}
- * @return true if the specified service id is connected to the IMS network; false otherwise
- * @throws RemoteException
- */
- boolean isConnected(int callServiceType, int callType) throws RemoteException;
-
- /**
- * Checks if the specified IMS service is opened.
- *
- * @return true if the specified service id is opened; false otherwise
- */
- boolean isOpened() throws RemoteException;
-
- /**
- * Add a new registration listener for the client associated with the session Id.
- * @param listener An implementation of IImsRegistrationListener.
- */
- void addRegistrationListener(IImsRegistrationListener listener)
- throws RemoteException;
-
- /**
- * Remove a previously registered listener using {@link #addRegistrationListener} for the client
- * associated with the session Id.
- * @param listener A previously registered IImsRegistrationListener
- */
- void removeRegistrationListener(IImsRegistrationListener listener)
- throws RemoteException;
-
- /**
- * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
- *
- * @param sessionId a session id which is obtained from {@link #startSession}
- * @param callServiceType a service type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#SERVICE_TYPE_NONE}
- * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
- * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
- * @param callType a call type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#CALL_TYPE_VOICE}
- * {@link ImsCallProfile#CALL_TYPE_VT}
- * {@link ImsCallProfile#CALL_TYPE_VT_TX}
- * {@link ImsCallProfile#CALL_TYPE_VT_RX}
- * {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
- * {@link ImsCallProfile#CALL_TYPE_VS}
- * {@link ImsCallProfile#CALL_TYPE_VS_TX}
- * {@link ImsCallProfile#CALL_TYPE_VS_RX}
- * @return a {@link ImsCallProfile} object
- */
- ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
- throws RemoteException;
-
- /**
- * Creates a {@link ImsCallSession} with the specified call profile.
- * Use other methods, if applicable, instead of interacting with
- * {@link ImsCallSession} directly.
- *
- * @param sessionId a session id which is obtained from {@link #startSession}
- * @param profile a call profile to make the call
- * @param listener An implementation of IImsCallSessionListener.
- */
- IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
- IImsCallSessionListener listener) throws RemoteException;
-
- /**
- * Retrieves the call session associated with a pending call.
- *
- * @param sessionId a session id which is obtained from {@link #startSession}
- * @param callId a call id to make the call
- */
- IImsCallSession getPendingCallSession(int sessionId, String callId) throws RemoteException;
-
- /**
- * @return The Ut interface for the supplementary service configuration.
- */
- IImsUt getUtInterface() throws RemoteException;
-
- /**
- * @return The config interface for IMS Configuration
- */
- IImsConfig getConfigInterface() throws RemoteException;
-
- /**
- * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
- * @param sessionId a session id which is obtained from {@link #startSession}
- */
- void turnOnIms() throws RemoteException;
-
- /**
- * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
- * @param sessionId a session id which is obtained from {@link #startSession}
- */
- void turnOffIms() throws RemoteException;
-
- /**
- * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
- */
- IImsEcbm getEcbmInterface() throws RemoteException;
-
- /**
- * Sets the current UI TTY mode for the MMTelFeature.
- * @param uiTtyMode An integer containing the new UI TTY Mode.
- * @param onComplete A {@link Message} to be used when the mode has been set.
- * @throws RemoteException
- */
- void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException;
-
- /**
- * @return MultiEndpoint interface for DEP notifications
- */
- IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException;
-}
diff --git a/android/telephony/ims/feature/MMTelFeature.java b/android/telephony/ims/feature/MMTelFeature.java
index a71f0bf0..758c379f 100644
--- a/android/telephony/ims/feature/MMTelFeature.java
+++ b/android/telephony/ims/feature/MMTelFeature.java
@@ -32,90 +32,183 @@ import java.util.ArrayList;
import java.util.List;
/**
- * Base implementation, which implements all methods in IMMTelFeature. Any class wishing to use
- * MMTelFeature should extend this class and implement all methods that the service supports.
+ * Base implementation for MMTel.
+ * Any class wishing to use MMTelFeature should extend this class and implement all methods that the
+ * service supports.
*
* @hide
*/
-public class MMTelFeature extends ImsFeature implements IMMTelFeature {
-
- @Override
+public class MMTelFeature extends ImsFeature {
+
+ /**
+ * Notifies the MMTel feature that you would like to start a session. This should always be
+ * done before making/receiving IMS calls. The IMS service will register the device to the
+ * operator's network with the credentials (from ISIM) periodically in order to receive calls
+ * from the operator's network. When the IMS service receives a new call, it will send out an
+ * intent with the provided action string. The intent contains a call ID extra
+ * {@link IImsCallSession#getCallId} and it can be used to take a call.
+ *
+ * @param incomingCallIntent When an incoming call is received, the IMS service will call
+ * {@link PendingIntent#send} to send back the intent to the caller with
+ * ImsManager#INCOMING_CALL_RESULT_CODE as the result code and the intent to fill in the call
+ * ID; It cannot be null.
+ * @param listener To listen to IMS registration events; It cannot be null
+ * @return an integer (greater than 0) representing the session id associated with the session
+ * that has been started.
+ */
public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) {
return 0;
}
- @Override
+ /**
+ * End a previously started session using the associated sessionId.
+ * @param sessionId an integer (greater than 0) representing the ongoing session. See
+ * {@link #startSession}.
+ */
public void endSession(int sessionId) {
}
- @Override
+ /**
+ * Checks if the IMS service has successfully registered to the IMS network with the specified
+ * service & call type.
+ *
+ * @param callSessionType a service type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+ * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+ * @param callType a call type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
+ * {@link ImsCallProfile#CALL_TYPE_VOICE}
+ * {@link ImsCallProfile#CALL_TYPE_VT}
+ * {@link ImsCallProfile#CALL_TYPE_VS}
+ * @return true if the specified service id is connected to the IMS network; false otherwise
+ */
public boolean isConnected(int callSessionType, int callType) {
return false;
}
- @Override
+ /**
+ * Checks if the specified IMS service is opened.
+ *
+ * @return true if the specified service id is opened; false otherwise
+ */
public boolean isOpened() {
return false;
}
- @Override
+ /**
+ * Add a new registration listener for the client associated with the session Id.
+ * @param listener An implementation of IImsRegistrationListener.
+ */
public void addRegistrationListener(IImsRegistrationListener listener) {
}
- @Override
+ /**
+ * Remove a previously registered listener using {@link #addRegistrationListener} for the client
+ * associated with the session Id.
+ * @param listener A previously registered IImsRegistrationListener
+ */
public void removeRegistrationListener(IImsRegistrationListener listener) {
}
- @Override
+ /**
+ * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
+ *
+ * @param sessionId a session id which is obtained from {@link #startSession}
+ * @param callSessionType a service type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#SERVICE_TYPE_NONE}
+ * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+ * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+ * @param callType a call type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#CALL_TYPE_VOICE}
+ * {@link ImsCallProfile#CALL_TYPE_VT}
+ * {@link ImsCallProfile#CALL_TYPE_VT_TX}
+ * {@link ImsCallProfile#CALL_TYPE_VT_RX}
+ * {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
+ * {@link ImsCallProfile#CALL_TYPE_VS}
+ * {@link ImsCallProfile#CALL_TYPE_VS_TX}
+ * {@link ImsCallProfile#CALL_TYPE_VS_RX}
+ * @return a {@link ImsCallProfile} object
+ */
public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) {
return null;
}
- @Override
+ /**
+ * Creates a {@link ImsCallSession} with the specified call profile.
+ * Use other methods, if applicable, instead of interacting with
+ * {@link ImsCallSession} directly.
+ *
+ * @param sessionId a session id which is obtained from {@link #startSession}
+ * @param profile a call profile to make the call
+ * @param listener An implementation of IImsCallSessionListener.
+ */
public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
IImsCallSessionListener listener) {
return null;
}
- @Override
+ /**
+ * Retrieves the call session associated with a pending call.
+ *
+ * @param sessionId a session id which is obtained from {@link #startSession}
+ * @param callId a call id to make the call
+ */
public IImsCallSession getPendingCallSession(int sessionId, String callId) {
return null;
}
- @Override
+ /**
+ * @return The Ut interface for the supplementary service configuration.
+ */
public IImsUt getUtInterface() {
return null;
}
- @Override
+ /**
+ * @return The config interface for IMS Configuration
+ */
public IImsConfig getConfigInterface() {
return null;
}
- @Override
+ /**
+ * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
+ */
public void turnOnIms() {
}
- @Override
+ /**
+ * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
+ */
public void turnOffIms() {
}
- @Override
+ /**
+ * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
+ */
public IImsEcbm getEcbmInterface() {
return null;
}
- @Override
+ /**
+ * Sets the current UI TTY mode for the MMTelFeature.
+ * @param uiTtyMode An integer containing the new UI TTY Mode.
+ * @param onComplete A {@link Message} to be used when the mode has been set.
+ */
public void setUiTTYMode(int uiTtyMode, Message onComplete) {
}
- @Override
+ /**
+ * @return MultiEndpoint interface for DEP notifications
+ */
public IImsMultiEndpoint getMultiEndpointInterface() {
return null;
}
- @Override
+ /**
+ * {@inheritDoc}
+ */
public void onFeatureRemoved() {
}
diff --git a/android/telephony/ims/feature/RcsFeature.java b/android/telephony/ims/feature/RcsFeature.java
index 9cddc1b9..332cca3e 100644
--- a/android/telephony/ims/feature/RcsFeature.java
+++ b/android/telephony/ims/feature/RcsFeature.java
@@ -18,11 +18,11 @@ package android.telephony.ims.feature;
/**
* Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
- * this class and provide implementations of the IRcsFeature methods that they support.
+ * this class and provide implementations of the RcsFeature methods that they support.
* @hide
*/
-public class RcsFeature extends ImsFeature implements IRcsFeature {
+public class RcsFeature extends ImsFeature {
public RcsFeature() {
super();
diff --git a/android/telephony/mbms/DownloadStateCallback.java b/android/telephony/mbms/DownloadStateCallback.java
index 892fbf07..9f60cc36 100644
--- a/android/telephony/mbms/DownloadStateCallback.java
+++ b/android/telephony/mbms/DownloadStateCallback.java
@@ -38,7 +38,7 @@ public class DownloadStateCallback {
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef({ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
+ @IntDef(flag = true, value = {ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
public @interface FilterFlag {}
/**
diff --git a/android/telephony/mbms/FileInfo.java b/android/telephony/mbms/FileInfo.java
index 0d737b58..e064adb5 100644
--- a/android/telephony/mbms/FileInfo.java
+++ b/android/telephony/mbms/FileInfo.java
@@ -17,10 +17,13 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* Describes a single file that is available over MBMS.
*/
@@ -47,6 +50,7 @@ public final class FileInfo implements Parcelable {
* @hide
*/
@SystemApi
+ @TestApi
public FileInfo(Uri uri, String mimeType) {
this.uri = uri;
this.mimeType = mimeType;
@@ -82,4 +86,23 @@ public final class FileInfo implements Parcelable {
public String getMimeType() {
return mimeType;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ FileInfo fileInfo = (FileInfo) o;
+ return Objects.equals(uri, fileInfo.uri) &&
+ Objects.equals(mimeType, fileInfo.mimeType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(uri, mimeType);
+ }
}
diff --git a/android/telephony/mbms/FileServiceInfo.java b/android/telephony/mbms/FileServiceInfo.java
index d8d7f48a..b30a3af7 100644
--- a/android/telephony/mbms/FileServiceInfo.java
+++ b/android/telephony/mbms/FileServiceInfo.java
@@ -17,6 +17,7 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,6 +36,7 @@ public final class FileServiceInfo extends ServiceInfo implements Parcelable {
/** @hide */
@SystemApi
+ @TestApi
public FileServiceInfo(Map<Locale, String> newNames, String newClassName,
List<Locale> newLocales, String newServiceId, Date start, Date end,
List<FileInfo> newFiles) {
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index 9af1eb9e..9ef188cf 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -165,16 +165,16 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
return false;
}
+ if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
+ Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
+ return false;
+ }
// We do not need to verify below extras if the result is not success.
if (MbmsDownloadSession.RESULT_SUCCESSFUL !=
intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
MbmsDownloadSession.RESULT_CANCELLED)) {
return true;
}
- if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
- Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
- return false;
- }
if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) {
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
@@ -242,10 +242,12 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
MbmsDownloadSession.RESULT_CANCELLED);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {
Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
context.sendBroadcast(intentForApp);
+ setResultCode(RESULT_OK);
return;
}
@@ -273,7 +275,6 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI,
stagedFileLocation);
intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
- intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
context.sendBroadcast(intentForApp);
setResultCode(RESULT_OK);
diff --git a/android/telephony/mbms/UriPathPair.java b/android/telephony/mbms/UriPathPair.java
index 187e9eed..dd20a692 100644
--- a/android/telephony/mbms/UriPathPair.java
+++ b/android/telephony/mbms/UriPathPair.java
@@ -17,6 +17,7 @@
package android.telephony.mbms;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Parcel;
@@ -29,6 +30,7 @@ import android.telephony.mbms.vendor.VendorUtils;
* @hide
*/
@SystemApi
+@TestApi
public final class UriPathPair implements Parcelable {
private final Uri mFilePathUri;
private final Uri mContentUri;
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index 9ccdd56f..4fee3df8 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -18,6 +18,7 @@ package android.telephony.mbms.vendor;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
@@ -42,6 +43,7 @@ import java.util.Map;
* @hide
*/
@SystemApi
+@TestApi
public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
diff --git a/android/telephony/mbms/vendor/VendorUtils.java b/android/telephony/mbms/vendor/VendorUtils.java
index a43f1224..f1cac8cf 100644
--- a/android/telephony/mbms/vendor/VendorUtils.java
+++ b/android/telephony/mbms/vendor/VendorUtils.java
@@ -17,6 +17,7 @@
package android.telephony.mbms.vendor;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -34,6 +35,7 @@ import java.util.List;
* @hide
*/
@SystemApi
+@TestApi
public class VendorUtils {
/**
diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java
index fc1d4873..2a272208 100644
--- a/android/util/FeatureFlagUtils.java
+++ b/android/util/FeatureFlagUtils.java
@@ -16,6 +16,7 @@
package android.util;
+import android.content.Context;
import android.os.SystemProperties;
import android.text.TextUtils;
@@ -37,7 +38,7 @@ public class FeatureFlagUtils {
* @param feature the flag name
* @return true if the flag is enabled (either by default in system, or override by user)
*/
- public static boolean isEnabled(String feature) {
+ public static boolean isEnabled(Context context, String feature) {
// Tries to get feature flag from system property.
// Step 1: check if feature flag has any override. Flag name: sys.fflag.override.<feature>
String value = SystemProperties.get(FFLAG_OVERRIDE_PREFIX + feature);
diff --git a/android/util/KeyValueListParser.java b/android/util/KeyValueListParser.java
index be531ff3..d50395e2 100644
--- a/android/util/KeyValueListParser.java
+++ b/android/util/KeyValueListParser.java
@@ -147,4 +147,18 @@ public class KeyValueListParser {
}
return def;
}
+
+ /**
+ * @return the number of keys.
+ */
+ public int size() {
+ return mValues.size();
+ }
+
+ /**
+ * @return the key at {@code index}. Use with {@link #size()} to enumerate all key-value pairs.
+ */
+ public String keyAt(int index) {
+ return mValues.keyAt(index);
+ }
}
diff --git a/android/util/Log.java b/android/util/Log.java
index 02998653..b94e48b3 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,45 +16,12 @@
package android.util;
-import android.os.DeadSystemException;
-
-import com.android.internal.os.RuntimeInit;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.LineBreakBufferedWriter;
-
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.io.Writer;
import java.net.UnknownHostException;
/**
- * API for sending log output.
- *
- * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()},
- * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs.
- * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>.
- *
- * <p>The order in terms of verbosity, from least to most is
- * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled
- * into an application except during development. Debug logs are compiled
- * in but stripped at runtime. Error, warning and info logs are always kept.
- *
- * <p><b>Tip:</b> A good convention is to declare a <code>TAG</code> constant
- * in your class:
- *
- * <pre>private static final String TAG = "MyActivity";</pre>
- *
- * and use that in subsequent calls to the log methods.
- * </p>
- *
- * <p><b>Tip:</b> Don't forget that when you make a call like
- * <pre>Log.v(TAG, "index=" + i);</pre>
- * that when you're building the string to pass into Log.d, the compiler uses a
- * StringBuilder and at least three allocations occur: the StringBuilder
- * itself, the buffer, and the String object. Realistically, there is also
- * another buffer allocation and copy, and even more pressure on the gc.
- * That means that if your log message is filtered out, you might be doing
- * significant work and incurring significant overhead.
+ * Mock Log implementation for testing on non android host.
*/
public final class Log {
@@ -88,29 +55,6 @@ public final class Log {
*/
public static final int ASSERT = 7;
- /**
- * Exception class used to capture a stack trace in {@link #wtf}.
- * @hide
- */
- public static class TerribleFailure extends Exception {
- TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
- }
-
- /**
- * Interface to handle terrible failures from {@link #wtf}.
- *
- * @hide
- */
- public interface TerribleFailureHandler {
- void onTerribleFailure(String tag, TerribleFailure what, boolean system);
- }
-
- private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
- public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
- RuntimeInit.wtf(tag, what, system);
- }
- };
-
private Log() {
}
@@ -121,7 +65,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
- return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
+ return println(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
@@ -132,7 +76,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
+ return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -142,7 +86,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
- return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
+ return println(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
@@ -153,7 +97,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
+ return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -163,7 +107,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
- return println_native(LOG_ID_MAIN, INFO, tag, msg);
+ return println(LOG_ID_MAIN, INFO, tag, msg);
}
/**
@@ -174,7 +118,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
+ return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -184,7 +128,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
- return println_native(LOG_ID_MAIN, WARN, tag, msg);
+ return println(LOG_ID_MAIN, WARN, tag, msg);
}
/**
@@ -195,31 +139,9 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, WARN, tag, msg, tr);
+ return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
}
- /**
- * Checks to see whether or not a log for the specified tag is loggable at the specified level.
- *
- * The default level of any tag is set to INFO. This means that any level above and including
- * INFO will be logged. Before you make any calls to a logging method you should check to see
- * if your tag should be logged. You can change the default level by setting a system property:
- * 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
- * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
- * turn off all logging for your tag. You can also create a local.prop file that with the
- * following in it:
- * 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
- * and place that in /data/local.prop.
- *
- * @param tag The tag to check.
- * @param level The level to check.
- * @return Whether or not that this is allowed to be logged.
- * @throws IllegalArgumentException is thrown if the tag.length() > 23
- * for Nougat (7.0) releases (API <= 23) and prior, there is no
- * tag limit of concern after this API level.
- */
- public static native boolean isLoggable(String tag, int level);
-
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
@@ -227,7 +149,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
- return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
+ return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
}
/**
@@ -237,7 +159,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
- return println_native(LOG_ID_MAIN, ERROR, tag, msg);
+ return println(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
@@ -248,82 +170,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- return printlns(LOG_ID_MAIN, ERROR, tag, msg, tr);
- }
-
- /**
- * What a Terrible Failure: Report a condition that should never happen.
- * The error will always be logged at level ASSERT with the call stack.
- * Depending on system configuration, a report may be added to the
- * {@link android.os.DropBoxManager} and/or the process may be terminated
- * immediately with an error dialog.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- */
- public static int wtf(String tag, String msg) {
- return wtf(LOG_ID_MAIN, tag, msg, null, false, false);
- }
-
- /**
- * Like {@link #wtf(String, String)}, but also writes to the log the full
- * call stack.
- * @hide
- */
- public static int wtfStack(String tag, String msg) {
- return wtf(LOG_ID_MAIN, tag, msg, null, true, false);
- }
-
- /**
- * What a Terrible Failure: Report an exception that should never happen.
- * Similar to {@link #wtf(String, String)}, with an exception to log.
- * @param tag Used to identify the source of a log message.
- * @param tr An exception to log.
- */
- public static int wtf(String tag, Throwable tr) {
- return wtf(LOG_ID_MAIN, tag, tr.getMessage(), tr, false, false);
- }
-
- /**
- * What a Terrible Failure: Report an exception that should never happen.
- * Similar to {@link #wtf(String, Throwable)}, with a message as well.
- * @param tag Used to identify the source of a log message.
- * @param msg The message you would like logged.
- * @param tr An exception to log. May be null.
- */
- public static int wtf(String tag, String msg, Throwable tr) {
- return wtf(LOG_ID_MAIN, tag, msg, tr, false, false);
- }
-
- static int wtf(int logId, String tag, String msg, Throwable tr, boolean localStack,
- boolean system) {
- TerribleFailure what = new TerribleFailure(msg, tr);
- // Only mark this as ERROR, do not use ASSERT since that should be
- // reserved for cases where the system is guaranteed to abort.
- // The onTerribleFailure call does not always cause a crash.
- int bytes = printlns(logId, ERROR, tag, msg, localStack ? what : tr);
- sWtfHandler.onTerribleFailure(tag, what, system);
- return bytes;
- }
-
- static void wtfQuiet(int logId, String tag, String msg, boolean system) {
- TerribleFailure what = new TerribleFailure(msg, null);
- sWtfHandler.onTerribleFailure(tag, what, system);
- }
-
- /**
- * Sets the terrible failure handler, for testing.
- *
- * @return the old handler
- *
- * @hide
- */
- public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
- if (handler == null) {
- throw new NullPointerException("handler == null");
- }
- TerribleFailureHandler oldHandler = sWtfHandler;
- sWtfHandler = handler;
- return oldHandler;
+ return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -346,7 +193,7 @@ public final class Log {
}
StringWriter sw = new StringWriter();
- PrintWriter pw = new FastPrintWriter(sw, false, 256);
+ PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
@@ -361,7 +208,7 @@ public final class Log {
* @return The number of bytes written.
*/
public static int println(int priority, String tag, String msg) {
- return println_native(LOG_ID_MAIN, priority, tag, msg);
+ return println(LOG_ID_MAIN, priority, tag, msg);
}
/** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -370,115 +217,9 @@ public final class Log {
/** @hide */ public static final int LOG_ID_SYSTEM = 3;
/** @hide */ public static final int LOG_ID_CRASH = 4;
- /** @hide */ public static native int println_native(int bufID,
- int priority, String tag, String msg);
-
- /**
- * Return the maximum payload the log daemon accepts without truncation.
- * @return LOGGER_ENTRY_MAX_PAYLOAD.
- */
- private static native int logger_entry_max_payload_native();
-
- /**
- * Helper function for long messages. Uses the LineBreakBufferedWriter to break
- * up long messages and stacktraces along newlines, but tries to write in large
- * chunks. This is to avoid truncation.
- * @hide
- */
- public static int printlns(int bufID, int priority, String tag, String msg,
- Throwable tr) {
- ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
- // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
- // and the length of the tag.
- // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
- // is too expensive to compute that ahead of time.
- int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
- - 2 // Two terminators.
- - (tag != null ? tag.length() : 0) // Tag length.
- - 32; // Some slack.
- // At least assume you can print *some* characters (tag is not too large).
- bufferSize = Math.max(bufferSize, 100);
-
- LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);
-
- lbbw.println(msg);
-
- if (tr != null) {
- // This is to reduce the amount of log spew that apps do in the non-error
- // condition of the network being unavailable.
- Throwable t = tr;
- while (t != null) {
- if (t instanceof UnknownHostException) {
- break;
- }
- if (t instanceof DeadSystemException) {
- lbbw.println("DeadSystemException: The system died; "
- + "earlier logs will point to the root cause");
- break;
- }
- t = t.getCause();
- }
- if (t == null) {
- tr.printStackTrace(lbbw);
- }
- }
-
- lbbw.flush();
-
- return logWriter.getWritten();
- }
-
- /**
- * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
- * a JNI call during logging.
- */
- static class PreloadHolder {
- public final static int LOGGER_ENTRY_MAX_PAYLOAD =
- logger_entry_max_payload_native();
- }
-
- /**
- * Helper class to write to the logcat. Different from LogWriter, this writes
- * the whole given buffer and does not break along newlines.
- */
- private static class ImmediateLogWriter extends Writer {
-
- private int bufID;
- private int priority;
- private String tag;
-
- private int written = 0;
-
- /**
- * Create a writer that immediately writes to the log, using the given
- * parameters.
- */
- public ImmediateLogWriter(int bufID, int priority, String tag) {
- this.bufID = bufID;
- this.priority = priority;
- this.tag = tag;
- }
-
- public int getWritten() {
- return written;
- }
-
- @Override
- public void write(char[] cbuf, int off, int len) {
- // Note: using String here has a bit of overhead as a Java object is created,
- // but using the char[] directly is not easier, as it needs to be translated
- // to a C char[] for logging.
- written += println_native(bufID, priority, tag, new String(cbuf, off, len));
- }
-
- @Override
- public void flush() {
- // Ignored.
- }
-
- @Override
- public void close() {
- // Ignored.
- }
+ /** @hide */ @SuppressWarnings("unused")
+ public static int println(int bufID,
+ int priority, String tag, String msg) {
+ return 0;
}
}
diff --git a/android/util/StatsManager.java b/android/util/StatsManager.java
new file mode 100644
index 00000000..55b33a61
--- /dev/null
+++ b/android/util/StatsManager.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.IBinder;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * API for StatsD clients to send configurations and retrieve data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsManager {
+ IStatsManager mService;
+ private static final String TAG = "StatsManager";
+
+ /**
+ * Constructor for StatsManagerClient.
+ *
+ * @hide
+ */
+ public StatsManager() {
+ }
+
+ /**
+ * Clients can send a configuration and simultaneously registers the name of a broadcast
+ * receiver that listens for when it should request data.
+ *
+ * @param configKey An arbitrary string that allows clients to track the configuration.
+ * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
+ * dependencies eg, conditions and matchers).
+ * @param pkg The package name to receive the broadcast.
+ * @param cls The name of the class that receives the broadcast.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ throw new RuntimeException("StatsD service connection lost");
+ }
+ return service.addConfiguration(configKey, config, pkg, cls);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connect to statsd when getting data");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Remove a configuration from logging.
+ *
+ * @param configKey Configuration key to remove.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean removeConfiguration(String configKey) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ throw new RuntimeException("StatsD service connection lost");
+ }
+ return service.removeConfiguration(configKey);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connect to statsd when getting data");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Clients can request data with a binder call.
+ *
+ * @param configKey Configuration key to retrieve data from.
+ * @return Serialized ConfigMetricsReport proto. Returns null on failure.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getData(String configKey) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ throw new RuntimeException("StatsD service connection lost");
+ }
+ return service.getData(configKey);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connecto statsd when getting data");
+ return null;
+ }
+ }
+ }
+
+ private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+ @Override
+ public void binderDied() {
+ synchronized (this) {
+ mService = null;
+ }
+ }
+ }
+
+ private IStatsManager getIStatsManagerLocked() throws RemoteException {
+ if (mService != null) {
+ return mService;
+ }
+ mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+ if (mService != null) {
+ mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+ }
+ return mService;
+ }
+}
diff --git a/android/util/TimeUtils.java b/android/util/TimeUtils.java
index 2b03ed6c..cc4a0b60 100644
--- a/android/util/TimeUtils.java
+++ b/android/util/TimeUtils.java
@@ -340,6 +340,14 @@ public class TimeUtils {
}
/** @hide Just for debugging; not internationalized. */
+ public static String formatDuration(long duration) {
+ synchronized (sFormatSync) {
+ int len = formatDurationLocked(duration, 0);
+ return new String(sFormatStr, 0, len);
+ }
+ }
+
+ /** @hide Just for debugging; not internationalized. */
public static void formatDuration(long duration, PrintWriter pw) {
formatDuration(duration, pw, 0);
}
diff --git a/android/util/apk/ApkSignatureSchemeV2Verifier.java b/android/util/apk/ApkSignatureSchemeV2Verifier.java
index a9ccae11..18081234 100644
--- a/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -16,9 +16,6 @@
package android.util.apk;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
import android.util.ArrayMap;
import android.util.Pair;
@@ -30,7 +27,6 @@ import java.math.BigInteger;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.nio.DirectByteBuffer;
import java.security.DigestException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -122,40 +118,6 @@ public class ApkSignatureSchemeV2Verifier {
}
/**
- * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
- * contained in the block against the file.
- */
- private static class SignatureInfo {
- /** Contents of APK Signature Scheme v2 block. */
- private final ByteBuffer signatureBlock;
-
- /** Position of the APK Signing Block in the file. */
- private final long apkSigningBlockOffset;
-
- /** Position of the ZIP Central Directory in the file. */
- private final long centralDirOffset;
-
- /** Position of the ZIP End of Central Directory (EoCD) in the file. */
- private final long eocdOffset;
-
- /** Contents of ZIP End of Central Directory (EoCD) of the file. */
- private final ByteBuffer eocd;
-
- private SignatureInfo(
- ByteBuffer signatureBlock,
- long apkSigningBlockOffset,
- long centralDirOffset,
- long eocdOffset,
- ByteBuffer eocd) {
- this.signatureBlock = signatureBlock;
- this.apkSigningBlockOffset = apkSigningBlockOffset;
- this.centralDirOffset = centralDirOffset;
- this.eocdOffset = eocdOffset;
- this.eocd = eocd;
- }
- }
-
- /**
* Returns the APK Signature Scheme v2 block contained in the provided APK file and the
* additional information relevant for verifying the block against the file.
*
@@ -497,6 +459,7 @@ public class ApkSignatureSchemeV2Verifier {
// TODO: Compute digests of chunks in parallel when beneficial. This requires some research
// into how to parallelize (if at all) based on the capabilities of the hardware on which
// this code is running and based on the size of input.
+ DataDigester digester = new MultipleDigestDataDigester(mds);
int dataSourceIndex = 0;
for (DataSource input : contents) {
long inputOffset = 0;
@@ -508,7 +471,7 @@ public class ApkSignatureSchemeV2Verifier {
mds[i].update(chunkContentPrefix);
}
try {
- input.feedIntoMessageDigests(mds, inputOffset, chunkSize);
+ input.feedIntoDataDigester(digester, inputOffset, chunkSize);
} catch (IOException e) {
throw new DigestException(
"Failed to digest chunk #" + chunkIndex + " of section #"
@@ -967,155 +930,26 @@ public class ApkSignatureSchemeV2Verifier {
}
/**
- * Source of data to be digested.
+ * {@link DataDigester} that updates multiple {@link MessageDigest}s whenever data is feeded.
*/
- private static interface DataSource {
-
- /**
- * Returns the size (in bytes) of the data offered by this source.
- */
- long size();
-
- /**
- * Feeds the specified region of this source's data into the provided digests. Each digest
- * instance gets the same data.
- *
- * @param offset offset of the region inside this data source.
- * @param size size (in bytes) of the region.
- */
- void feedIntoMessageDigests(MessageDigest[] mds, long offset, int size) throws IOException;
- }
+ private static class MultipleDigestDataDigester implements DataDigester {
+ private final MessageDigest[] mMds;
- /**
- * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
- * of the file requested by
- * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
- */
- private static final class MemoryMappedFileDataSource implements DataSource {
- private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
-
- private final FileDescriptor mFd;
- private final long mFilePosition;
- private final long mSize;
-
- /**
- * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
- *
- * @param position start position of the region in the file.
- * @param size size (in bytes) of the region.
- */
- public MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
- mFd = fd;
- mFilePosition = position;
- mSize = size;
+ MultipleDigestDataDigester(MessageDigest[] mds) {
+ mMds = mds;
}
@Override
- public long size() {
- return mSize;
- }
-
- @Override
- public void feedIntoMessageDigests(
- MessageDigest[] mds, long offset, int size) throws IOException {
- // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
- // method was settled on a straightforward mmap with prefaulting.
- //
- // This method is not using FileChannel.map API because that API does not offset a way
- // to "prefault" the resulting memory pages. Without prefaulting, performance is about
- // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
- // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
- // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
- // time, which is not compensated for by faster reads.
-
- // We mmap the smallest region of the file containing the requested data. mmap requires
- // that the start offset in the file must be a multiple of memory page size. We thus may
- // need to mmap from an offset less than the requested offset.
- long filePosition = mFilePosition + offset;
- long mmapFilePosition =
- (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
- int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
- long mmapRegionSize = size + dataStartOffsetInMmapRegion;
- long mmapPtr = 0;
- try {
- mmapPtr = Os.mmap(
- 0, // let the OS choose the start address of the region in memory
- mmapRegionSize,
- OsConstants.PROT_READ,
- OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
- mFd,
- mmapFilePosition);
- // Feeding a memory region into MessageDigest requires the region to be represented
- // as a direct ByteBuffer.
- ByteBuffer buf = new DirectByteBuffer(
- size,
- mmapPtr + dataStartOffsetInMmapRegion,
- mFd, // not really needed, but just in case
- null, // no need to clean up -- it's taken care of by the finally block
- true // read only buffer
- );
- for (MessageDigest md : mds) {
- buf.position(0);
- md.update(buf);
- }
- } catch (ErrnoException e) {
- throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
- } finally {
- if (mmapPtr != 0) {
- try {
- Os.munmap(mmapPtr, mmapRegionSize);
- } catch (ErrnoException ignored) {}
- }
+ public void consume(ByteBuffer buffer) {
+ buffer = buffer.slice();
+ for (MessageDigest md : mMds) {
+ buffer.position(0);
+ md.update(buffer);
}
}
- }
-
- /**
- * {@link DataSource} which provides data from a {@link ByteBuffer}.
- */
- private static final class ByteBufferDataSource implements DataSource {
- /**
- * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
- * The buffer's position is 0 and limit is equal to capacity.
- */
- private final ByteBuffer mBuf;
-
- public ByteBufferDataSource(ByteBuffer buf) {
- // Defensive copy, to avoid changes to mBuf being visible in buf.
- mBuf = buf.slice();
- }
@Override
- public long size() {
- return mBuf.capacity();
- }
-
- @Override
- public void feedIntoMessageDigests(
- MessageDigest[] mds, long offset, int size) throws IOException {
- // There's no way to tell MessageDigest to read data from ByteBuffer from a position
- // other than the buffer's current position. We thus need to change the buffer's
- // position to match the requested offset.
- //
- // In the future, it may be necessary to compute digests of multiple regions in
- // parallel. Given that digest computation is a slow operation, we enable multiple
- // such requests to be fulfilled by this instance. This is achieved by serially
- // creating a new ByteBuffer corresponding to the requested data range and then,
- // potentially concurrently, feeding these buffers into MessageDigest instances.
- ByteBuffer region;
- synchronized (mBuf) {
- mBuf.position((int) offset);
- mBuf.limit((int) offset + size);
- region = mBuf.slice();
- }
-
- for (MessageDigest md : mds) {
- // Need to reset position to 0 at the start of each iteration because
- // MessageDigest.update below sets it to the buffer's limit.
- region.position(0);
- md.update(region);
- }
- }
+ public void finish() {}
}
/**
diff --git a/android/util/apk/ApkVerityBuilder.java b/android/util/apk/ApkVerityBuilder.java
new file mode 100644
index 00000000..7412ef41
--- /dev/null
+++ b/android/util/apk/ApkVerityBuilder.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+
+/**
+ * ApkVerityBuilder builds the APK verity tree and the verity header, which will be used by the
+ * kernel to verity the APK content on access.
+ *
+ * <p>Unlike a regular Merkle tree, APK verity tree does not cover the content fully. Due to
+ * the existing APK format, it has to skip APK Signing Block and also has some special treatment for
+ * the "Central Directory offset" field of ZIP End of Central Directory.
+ *
+ * @hide
+ */
+abstract class ApkVerityBuilder {
+ private ApkVerityBuilder() {}
+
+ private static final int CHUNK_SIZE_BYTES = 4096; // Typical Linux block size
+ private static final int DIGEST_SIZE_BYTES = 32; // SHA-256 size
+ private static final int FSVERITY_HEADER_SIZE_BYTES = 64;
+ private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE = 4;
+ private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
+ private static final String JCA_DIGEST_ALGORITHM = "SHA-256";
+ private static final byte[] DEFAULT_SALT = new byte[8];
+
+ static class ApkVerityResult {
+ public final ByteBuffer fsverityData;
+ public final byte[] rootHash;
+
+ ApkVerityResult(ByteBuffer fsverityData, byte[] rootHash) {
+ this.fsverityData = fsverityData;
+ this.rootHash = rootHash;
+ }
+ }
+
+ /**
+ * Generates fsverity metadata and the Merkle tree into the {@link ByteBuffer} created by the
+ * {@link ByteBufferFactory}. The bytes layout in the buffer will be used by the kernel and is
+ * ready to be appended to the target file to set up fsverity. For fsverity to work, this data
+ * must be placed at the next page boundary, and the caller must add additional padding in that
+ * case.
+ *
+ * @return ApkVerityResult containing the fsverity data and the root hash of the Merkle tree.
+ */
+ static ApkVerityResult generateApkVerity(RandomAccessFile apk,
+ SignatureInfo signatureInfo, ByteBufferFactory bufferFactory)
+ throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
+ assertSigningBlockAlignedAndHasFullPages(signatureInfo);
+
+ long signingBlockSize =
+ signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
+ long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ int[] levelOffset = calculateVerityLevelOffset(dataSize);
+ ByteBuffer output = bufferFactory.create(
+ CHUNK_SIZE_BYTES + // fsverity header + extensions + padding
+ levelOffset[levelOffset.length - 1] + // Merkle tree size
+ FSVERITY_HEADER_SIZE_BYTES); // second fsverity header (verbatim copy)
+
+ // Start generating the tree from the block boundary as the kernel will expect.
+ ByteBuffer treeOutput = slice(output, CHUNK_SIZE_BYTES,
+ output.limit() - FSVERITY_HEADER_SIZE_BYTES);
+ byte[] rootHash = generateApkVerityTree(apk, signatureInfo, DEFAULT_SALT, levelOffset,
+ treeOutput);
+
+ ByteBuffer integrityHeader = generateFsverityHeader(apk.length(), DEFAULT_SALT);
+ output.put(integrityHeader);
+ output.put(generateFsverityExtensions());
+
+ integrityHeader.rewind();
+ output.put(integrityHeader);
+ output.rewind();
+ return new ApkVerityResult(output, rootHash);
+ }
+
+ /**
+ * A helper class to consume and digest data by block continuously, and write into a buffer.
+ */
+ private static class BufferedDigester implements DataDigester {
+ /** Amount of the data to digest in each cycle before writting out the digest. */
+ private static final int BUFFER_SIZE = CHUNK_SIZE_BYTES;
+
+ /**
+ * Amount of data the {@link MessageDigest} has consumed since the last reset. This must be
+ * always less than BUFFER_SIZE since {@link MessageDigest} is reset whenever it has
+ * consumed BUFFER_SIZE of data.
+ */
+ private int mBytesDigestedSinceReset;
+
+ /** The final output {@link ByteBuffer} to write the digest to sequentially. */
+ private final ByteBuffer mOutput;
+
+ private final MessageDigest mMd;
+ private final byte[] mDigestBuffer = new byte[DIGEST_SIZE_BYTES];
+ private final byte[] mSalt;
+
+ private BufferedDigester(byte[] salt, ByteBuffer output) throws NoSuchAlgorithmException {
+ mSalt = salt;
+ mOutput = output.slice();
+ mMd = MessageDigest.getInstance(JCA_DIGEST_ALGORITHM);
+ mMd.update(mSalt);
+ mBytesDigestedSinceReset = 0;
+ }
+
+ /**
+ * Consumes and digests data up to BUFFER_SIZE (may continue from the previous remaining),
+ * then writes the final digest to the output buffer. Repeat until all data are consumed.
+ * If the last consumption is not enough for BUFFER_SIZE, the state will stay and future
+ * consumption will continuous from there.
+ */
+ @Override
+ public void consume(ByteBuffer buffer) throws DigestException {
+ int offset = buffer.position();
+ int remaining = buffer.remaining();
+ while (remaining > 0) {
+ int allowance = (int) Math.min(remaining, BUFFER_SIZE - mBytesDigestedSinceReset);
+ // Optimization: set the buffer limit to avoid allocating a new ByteBuffer object.
+ buffer.limit(buffer.position() + allowance);
+ mMd.update(buffer);
+ offset += allowance;
+ remaining -= allowance;
+ mBytesDigestedSinceReset += allowance;
+
+ if (mBytesDigestedSinceReset == BUFFER_SIZE) {
+ mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+ mOutput.put(mDigestBuffer);
+ // After digest, MessageDigest resets automatically, so no need to reset again.
+ mMd.update(mSalt);
+ mBytesDigestedSinceReset = 0;
+ }
+ }
+ }
+
+ /** Finish the current digestion if any. */
+ @Override
+ public void finish() throws DigestException {
+ if (mBytesDigestedSinceReset == 0) {
+ return;
+ }
+ mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
+ mOutput.put(mDigestBuffer);
+ }
+
+ private void fillUpLastOutputChunk() {
+ int extra = (int) (BUFFER_SIZE - mOutput.position() % BUFFER_SIZE);
+ if (extra == 0) {
+ return;
+ }
+ mOutput.put(ByteBuffer.allocate(extra));
+ }
+ }
+
+ /**
+ * Digest the source by chunk in the given range. If the last chunk is not a full chunk,
+ * digest the remaining.
+ */
+ private static void consumeByChunk(DataDigester digester, DataSource source, int chunkSize)
+ throws IOException, DigestException {
+ long inputRemaining = source.size();
+ long inputOffset = 0;
+ while (inputRemaining > 0) {
+ int size = (int) Math.min(inputRemaining, chunkSize);
+ source.feedIntoDataDigester(digester, inputOffset, size);
+ inputOffset += size;
+ inputRemaining -= size;
+ }
+ }
+
+ // Rationale: 1) 1 MB should fit in memory space on all devices. 2) It is not too granular
+ // thus the syscall overhead is not too big.
+ private static final int MMAP_REGION_SIZE_BYTES = 1024 * 1024;
+
+ private static void generateApkVerityDigestAtLeafLevel(RandomAccessFile apk,
+ SignatureInfo signatureInfo, byte[] salt, ByteBuffer output)
+ throws IOException, NoSuchAlgorithmException, DigestException {
+ BufferedDigester digester = new BufferedDigester(salt, output);
+
+ // 1. Digest from the beginning of the file, until APK Signing Block is reached.
+ consumeByChunk(digester,
+ new MemoryMappedFileDataSource(apk.getFD(), 0, signatureInfo.apkSigningBlockOffset),
+ MMAP_REGION_SIZE_BYTES);
+
+ // 2. Skip APK Signing Block and continue digesting, until the Central Directory offset
+ // field in EoCD is reached.
+ long eocdCdOffsetFieldPosition =
+ signatureInfo.eocdOffset + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET;
+ consumeByChunk(digester,
+ new MemoryMappedFileDataSource(apk.getFD(), signatureInfo.centralDirOffset,
+ eocdCdOffsetFieldPosition - signatureInfo.centralDirOffset),
+ MMAP_REGION_SIZE_BYTES);
+
+ // 3. Fill up the rest of buffer with 0s.
+ ByteBuffer alternativeCentralDirOffset = ByteBuffer.allocate(
+ ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ alternativeCentralDirOffset.putInt(Math.toIntExact(signatureInfo.apkSigningBlockOffset));
+ alternativeCentralDirOffset.flip();
+ digester.consume(alternativeCentralDirOffset);
+
+ // 4. Read from end of the Central Directory offset field in EoCD to the end of the file.
+ long offsetAfterEocdCdOffsetField =
+ eocdCdOffsetFieldPosition + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ consumeByChunk(digester,
+ new MemoryMappedFileDataSource(apk.getFD(), offsetAfterEocdCdOffsetField,
+ apk.length() - offsetAfterEocdCdOffsetField),
+ MMAP_REGION_SIZE_BYTES);
+ digester.finish();
+
+ // 5. Fill up the rest of buffer with 0s.
+ digester.fillUpLastOutputChunk();
+ }
+
+ private static byte[] generateApkVerityTree(RandomAccessFile apk, SignatureInfo signatureInfo,
+ byte[] salt, int[] levelOffset, ByteBuffer output)
+ throws IOException, NoSuchAlgorithmException, DigestException {
+ // 1. Digest the apk to generate the leaf level hashes.
+ generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output,
+ levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
+
+ // 2. Digest the lower level hashes bottom up.
+ for (int level = levelOffset.length - 3; level >= 0; level--) {
+ ByteBuffer inputBuffer = slice(output, levelOffset[level + 1], levelOffset[level + 2]);
+ ByteBuffer outputBuffer = slice(output, levelOffset[level], levelOffset[level + 1]);
+
+ DataSource source = new ByteBufferDataSource(inputBuffer);
+ BufferedDigester digester = new BufferedDigester(salt, outputBuffer);
+ consumeByChunk(digester, source, CHUNK_SIZE_BYTES);
+ digester.finish();
+
+ digester.fillUpLastOutputChunk();
+ }
+
+ // 3. Digest the first block (i.e. first level) to generate the root hash.
+ byte[] rootHash = new byte[DIGEST_SIZE_BYTES];
+ BufferedDigester digester = new BufferedDigester(salt, ByteBuffer.wrap(rootHash));
+ digester.consume(slice(output, 0, CHUNK_SIZE_BYTES));
+ digester.finish();
+ return rootHash;
+ }
+
+ private static ByteBuffer generateFsverityHeader(long fileSize, byte[] salt) {
+ if (salt.length != 8) {
+ throw new IllegalArgumentException("salt is not 8 bytes long");
+ }
+
+ ByteBuffer buffer = ByteBuffer.allocate(FSVERITY_HEADER_SIZE_BYTES);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ // TODO(b/30972906): insert a reference when there is a public one.
+ buffer.put("TrueBrew".getBytes()); // magic
+ buffer.put((byte) 1); // major version
+ buffer.put((byte) 0); // minor version
+ buffer.put((byte) 12); // log2(block-size) == log2(4096)
+ buffer.put((byte) 7); // log2(leaves-per-node) == log2(block-size / digest-size)
+ // == log2(4096 / 32)
+ buffer.putShort((short) 1); // meta algorithm, 1: SHA-256 FIXME finalize constant
+ buffer.putShort((short) 1); // data algorithm, 1: SHA-256 FIXME finalize constant
+ buffer.putInt(0x1); // flags, 0x1: has extension, FIXME also hide it
+ buffer.putInt(0); // reserved
+ buffer.putLong(fileSize); // original i_size
+ buffer.put(salt); // salt (8 bytes)
+
+ // TODO(b/30972906): Add extension.
+
+ buffer.rewind();
+ return buffer;
+ }
+
+ private static ByteBuffer generateFsverityExtensions() {
+ return ByteBuffer.allocate(64); // TODO(b/30972906): implement this.
+ }
+
+ /**
+ * Returns an array of summed area table of level size in the verity tree. In other words, the
+ * returned array is offset of each level in the verity tree file format, plus an additional
+ * offset of the next non-existing level (i.e. end of the last level + 1). Thus the array size
+ * is level + 1. Thus, the returned array is guarantee to have at least 2 elements.
+ */
+ private static int[] calculateVerityLevelOffset(long fileSize) {
+ ArrayList<Long> levelSize = new ArrayList<>();
+ while (true) {
+ long levelDigestSize = divideRoundup(fileSize, CHUNK_SIZE_BYTES) * DIGEST_SIZE_BYTES;
+ long chunksSize = CHUNK_SIZE_BYTES * divideRoundup(levelDigestSize, CHUNK_SIZE_BYTES);
+ levelSize.add(chunksSize);
+ if (levelDigestSize <= CHUNK_SIZE_BYTES) {
+ break;
+ }
+ fileSize = levelDigestSize;
+ }
+
+ // Reverse and convert to summed area table.
+ int[] levelOffset = new int[levelSize.size() + 1];
+ levelOffset[0] = 0;
+ for (int i = 0; i < levelSize.size(); i++) {
+ // We don't support verity tree if it is larger then Integer.MAX_VALUE.
+ levelOffset[i + 1] = levelOffset[i]
+ + Math.toIntExact(levelSize.get(levelSize.size() - i - 1));
+ }
+ return levelOffset;
+ }
+
+ private static void assertSigningBlockAlignedAndHasFullPages(SignatureInfo signatureInfo) {
+ if (signatureInfo.apkSigningBlockOffset % CHUNK_SIZE_BYTES != 0) {
+ throw new IllegalArgumentException(
+ "APK Signing Block does not start at the page boundary: "
+ + signatureInfo.apkSigningBlockOffset);
+ }
+
+ if ((signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset)
+ % CHUNK_SIZE_BYTES != 0) {
+ throw new IllegalArgumentException(
+ "Size of APK Signing Block is not a multiple of 4096: "
+ + (signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset));
+ }
+ }
+
+ /** Returns a slice of the buffer which shares content with the provided buffer. */
+ private static ByteBuffer slice(ByteBuffer buffer, int begin, int end) {
+ ByteBuffer b = buffer.duplicate();
+ b.position(0); // to ensure position <= limit invariant.
+ b.limit(end);
+ b.position(begin);
+ return b.slice();
+ }
+
+ /** Divides a number and round up to the closest integer. */
+ private static long divideRoundup(long dividend, long divisor) {
+ return (dividend + divisor - 1) / divisor;
+ }
+}
diff --git a/android/util/apk/ByteBufferDataSource.java b/android/util/apk/ByteBufferDataSource.java
new file mode 100644
index 00000000..3976568a
--- /dev/null
+++ b/android/util/apk/ByteBufferDataSource.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+
+/**
+ * {@link DataSource} which provides data from a {@link ByteBuffer}.
+ */
+class ByteBufferDataSource implements DataSource {
+ /**
+ * Underlying buffer. The data is stored between position 0 and the buffer's capacity.
+ * The buffer's position is 0 and limit is equal to capacity.
+ */
+ private final ByteBuffer mBuf;
+
+ ByteBufferDataSource(ByteBuffer buf) {
+ // Defensive copy, to avoid changes to mBuf being visible in buf, and to ensure position is
+ // 0 and limit == capacity.
+ mBuf = buf.slice();
+ }
+
+ @Override
+ public long size() {
+ return mBuf.capacity();
+ }
+
+ @Override
+ public void feedIntoDataDigester(DataDigester md, long offset, int size)
+ throws IOException, DigestException {
+ // There's no way to tell MessageDigest to read data from ByteBuffer from a position
+ // other than the buffer's current position. We thus need to change the buffer's
+ // position to match the requested offset.
+ //
+ // In the future, it may be necessary to compute digests of multiple regions in
+ // parallel. Given that digest computation is a slow operation, we enable multiple
+ // such requests to be fulfilled by this instance. This is achieved by serially
+ // creating a new ByteBuffer corresponding to the requested data range and then,
+ // potentially concurrently, feeding these buffers into MessageDigest instances.
+ ByteBuffer region;
+ synchronized (mBuf) {
+ mBuf.position(0);
+ mBuf.limit((int) offset + size);
+ mBuf.position((int) offset);
+ region = mBuf.slice();
+ }
+
+ md.consume(region);
+ }
+}
diff --git a/android/util/apk/ByteBufferFactory.java b/android/util/apk/ByteBufferFactory.java
new file mode 100644
index 00000000..7a998822
--- /dev/null
+++ b/android/util/apk/ByteBufferFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Provider of {@link ByteBuffer} instances.
+ * @hide
+ */
+public interface ByteBufferFactory {
+ /** Initiates a {@link ByteBuffer} with the given size. */
+ ByteBuffer create(int capacity);
+}
diff --git a/android/util/apk/DataDigester.java b/android/util/apk/DataDigester.java
new file mode 100644
index 00000000..278be803
--- /dev/null
+++ b/android/util/apk/DataDigester.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+
+interface DataDigester {
+ /** Consumes the {@link ByteBuffer}. */
+ void consume(ByteBuffer buffer) throws DigestException;
+
+ /** Finishes the digestion. Must be called after the last {@link #consume(ByteBuffer)}. */
+ void finish() throws DigestException;
+}
diff --git a/android/util/apk/DataSource.java b/android/util/apk/DataSource.java
new file mode 100644
index 00000000..82f3800a
--- /dev/null
+++ b/android/util/apk/DataSource.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.io.IOException;
+import java.security.DigestException;
+
+/** Source of data to be digested. */
+interface DataSource {
+
+ /**
+ * Returns the size (in bytes) of the data offered by this source.
+ */
+ long size();
+
+ /**
+ * Feeds the specified region of this source's data into the provided digester.
+ *
+ * @param offset offset of the region inside this data source.
+ * @param size size (in bytes) of the region.
+ */
+ void feedIntoDataDigester(DataDigester md, long offset, int size)
+ throws IOException, DigestException;
+}
diff --git a/android/util/apk/MemoryMappedFileDataSource.java b/android/util/apk/MemoryMappedFileDataSource.java
new file mode 100644
index 00000000..8d2b1e32
--- /dev/null
+++ b/android/util/apk/MemoryMappedFileDataSource.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.DirectByteBuffer;
+import java.security.DigestException;
+
+/**
+ * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
+ * of the file.
+ */
+class MemoryMappedFileDataSource implements DataSource {
+ private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
+
+ private final FileDescriptor mFd;
+ private final long mFilePosition;
+ private final long mSize;
+
+ /**
+ * Constructs a new {@code MemoryMappedFileDataSource} for the specified region of the file.
+ *
+ * @param position start position of the region in the file.
+ * @param size size (in bytes) of the region.
+ */
+ MemoryMappedFileDataSource(FileDescriptor fd, long position, long size) {
+ mFd = fd;
+ mFilePosition = position;
+ mSize = size;
+ }
+
+ @Override
+ public long size() {
+ return mSize;
+ }
+
+ @Override
+ public void feedIntoDataDigester(DataDigester md, long offset, int size)
+ throws IOException, DigestException {
+ // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
+ // method was settled on a straightforward mmap with prefaulting.
+ //
+ // This method is not using FileChannel.map API because that API does not offset a way
+ // to "prefault" the resulting memory pages. Without prefaulting, performance is about
+ // 10% slower on small to medium APKs, but is significantly worse for APKs in 500+ MB
+ // range. FileChannel.load (which currently uses madvise) doesn't help. Finally,
+ // invoking madvise (MADV_SEQUENTIAL) after mmap with prefaulting wastes quite a bit of
+ // time, which is not compensated for by faster reads.
+
+ // We mmap the smallest region of the file containing the requested data. mmap requires
+ // that the start offset in the file must be a multiple of memory page size. We thus may
+ // need to mmap from an offset less than the requested offset.
+ long filePosition = mFilePosition + offset;
+ long mmapFilePosition =
+ (filePosition / MEMORY_PAGE_SIZE_BYTES) * MEMORY_PAGE_SIZE_BYTES;
+ int dataStartOffsetInMmapRegion = (int) (filePosition - mmapFilePosition);
+ long mmapRegionSize = size + dataStartOffsetInMmapRegion;
+ long mmapPtr = 0;
+ try {
+ mmapPtr = Os.mmap(
+ 0, // let the OS choose the start address of the region in memory
+ mmapRegionSize,
+ OsConstants.PROT_READ,
+ OsConstants.MAP_SHARED | OsConstants.MAP_POPULATE, // "prefault" all pages
+ mFd,
+ mmapFilePosition);
+ ByteBuffer buf = new DirectByteBuffer(
+ size,
+ mmapPtr + dataStartOffsetInMmapRegion,
+ mFd, // not really needed, but just in case
+ null, // no need to clean up -- it's taken care of by the finally block
+ true // read only buffer
+ );
+ md.consume(buf);
+ } catch (ErrnoException e) {
+ throw new IOException("Failed to mmap " + mmapRegionSize + " bytes", e);
+ } finally {
+ if (mmapPtr != 0) {
+ try {
+ Os.munmap(mmapPtr, mmapRegionSize);
+ } catch (ErrnoException ignored) { }
+ }
+ }
+ }
+}
diff --git a/android/util/apk/SignatureInfo.java b/android/util/apk/SignatureInfo.java
new file mode 100644
index 00000000..8e1233af
--- /dev/null
+++ b/android/util/apk/SignatureInfo.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util.apk;
+
+import java.nio.ByteBuffer;
+
+/**
+ * APK Signature Scheme v2 block and additional information relevant to verifying the signatures
+ * contained in the block against the file.
+ */
+class SignatureInfo {
+ /** Contents of APK Signature Scheme v2 block. */
+ public final ByteBuffer signatureBlock;
+
+ /** Position of the APK Signing Block in the file. */
+ public final long apkSigningBlockOffset;
+
+ /** Position of the ZIP Central Directory in the file. */
+ public final long centralDirOffset;
+
+ /** Position of the ZIP End of Central Directory (EoCD) in the file. */
+ public final long eocdOffset;
+
+ /** Contents of ZIP End of Central Directory (EoCD) of the file. */
+ public final ByteBuffer eocd;
+
+ SignatureInfo(ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset,
+ long eocdOffset, ByteBuffer eocd) {
+ this.signatureBlock = signatureBlock;
+ this.apkSigningBlockOffset = apkSigningBlockOffset;
+ this.centralDirOffset = centralDirOffset;
+ this.eocdOffset = eocdOffset;
+ this.eocd = eocd;
+ }
+}
diff --git a/android/util/proto/ProtoOutputStream.java b/android/util/proto/ProtoOutputStream.java
index 43a97897..a94806a0 100644
--- a/android/util/proto/ProtoOutputStream.java
+++ b/android/util/proto/ProtoOutputStream.java
@@ -127,42 +127,48 @@ public final class ProtoOutputStream {
public static final long FIELD_TYPE_UNKNOWN = 0;
+ /**
+ * The types are copied from external/protobuf/src/google/protobuf/descriptor.h directly,
+ * so no extra mapping needs to be maintained in this case.
+ */
public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
- public static final long FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT64 = 3L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT64 = 4L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT32 = 5L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED64 = 6L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED32 = 7L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BOOL = 8L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_STRING = 9L << FIELD_TYPE_SHIFT;
+// public static final long FIELD_TYPE_GROUP = 10L << FIELD_TYPE_SHIFT; // Deprecated.
+ public static final long FIELD_TYPE_MESSAGE = 11L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BYTES = 12L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT32 = 13L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_ENUM = 14L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED32 = 15L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED64 = 16L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT32 = 17L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT64 = 18L << FIELD_TYPE_SHIFT;
private static final String[] FIELD_TYPE_NAMES = new String[] {
"Double",
"Float",
- "Int32",
"Int64",
- "UInt32",
"UInt64",
- "SInt32",
- "SInt64",
- "Fixed32",
+ "Int32",
"Fixed64",
- "SFixed32",
- "SFixed64",
+ "Fixed32",
"Bool",
"String",
+ "Group", // This field is deprecated but reserved here for indexing.
+ "Message",
"Bytes",
+ "UInt32",
"Enum",
- "Object",
+ "SFixed32",
+ "SFixed64",
+ "SInt32",
+ "SInt64",
};
//
@@ -867,21 +873,21 @@ public final class ProtoOutputStream {
assertNotCompacted();
final int id = (int)fieldId;
- switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ switch ((int) ((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
// bytes
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
writeBytesImpl(id, val);
break;
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
- case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
writeRepeatedBytesImpl(id, val);
break;
// Object
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
writeObjectImpl(id, val);
break;
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
- case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
writeRepeatedObjectImpl(id, val);
break;
// nothing else allowed
@@ -899,7 +905,7 @@ public final class ProtoOutputStream {
assertNotCompacted();
final int id = (int)fieldId;
- if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_OBJECT) {
+ if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_MESSAGE) {
final long count = fieldId & FIELD_COUNT_MASK;
if (count == FIELD_COUNT_SINGLE) {
return startObjectImpl(id, false);
@@ -2091,7 +2097,7 @@ public final class ProtoOutputStream {
@Deprecated
public long startObject(long fieldId) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
return startObjectImpl(id, false);
}
@@ -2119,7 +2125,7 @@ public final class ProtoOutputStream {
@Deprecated
public long startRepeatedObject(long fieldId) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
return startObjectImpl(id, true);
}
@@ -2217,7 +2223,7 @@ public final class ProtoOutputStream {
@Deprecated
public void writeObject(long fieldId, byte[] value) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);
writeObjectImpl(id, value);
}
@@ -2237,7 +2243,7 @@ public final class ProtoOutputStream {
@Deprecated
public void writeRepeatedObject(long fieldId, byte[] value) {
assertNotCompacted();
- final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);
writeRepeatedObjectImpl(id, value);
}
@@ -2296,7 +2302,7 @@ public final class ProtoOutputStream {
final String typeString = getFieldTypeString(fieldType);
if (typeString != null && countString != null) {
final StringBuilder sb = new StringBuilder();
- if (expectedType == FIELD_TYPE_OBJECT) {
+ if (expectedType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
@@ -2306,7 +2312,7 @@ public final class ProtoOutputStream {
sb.append(" called for field ");
sb.append((int)fieldId);
sb.append(" which should be used with ");
- if (fieldType == FIELD_TYPE_OBJECT) {
+ if (fieldType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
@@ -2321,7 +2327,7 @@ public final class ProtoOutputStream {
throw new IllegalArgumentException(sb.toString());
} else {
final StringBuilder sb = new StringBuilder();
- if (expectedType == FIELD_TYPE_OBJECT) {
+ if (expectedType == FIELD_TYPE_MESSAGE) {
sb.append("start");
} else {
sb.append("write");
diff --git a/android/view/Display.java b/android/view/Display.java
index e7c3f92d..6a44cdb9 100644
--- a/android/view/Display.java
+++ b/android/view/Display.java
@@ -294,11 +294,10 @@ public final class Display {
/**
* Display state: The display is dozing in a suspended low power state; it is still
- * on but is optimized for showing static system-provided content while the device
- * is non-interactive. This mode may be used to conserve even more power by allowing
- * the hardware to stop applying frame buffer updates from the graphics subsystem or
- * to take over the display and manage it autonomously to implement low power always-on
- * display functionality.
+ * on but the CPU is not updating it. This may be used in one of two ways: to show
+ * static system-provided content while the device is non-interactive, or to allow
+ * a "Sidekick" compute resource to update the display. For this reason, the
+ * CPU must not control the display in this mode.
*
* @see #getState
* @see android.os.PowerManager#isInteractive
@@ -313,6 +312,18 @@ public final class Display {
*/
public static final int STATE_VR = 5;
+ /**
+ * Display state: The display is in a suspended full power state; it is still
+ * on but the CPU is not updating it. This may be used in one of two ways: to show
+ * static system-provided content while the device is non-interactive, or to allow
+ * a "Sidekick" compute resource to update the display. For this reason, the
+ * CPU must not control the display in this mode.
+ *
+ * @see #getState
+ * @see android.os.PowerManager#isInteractive
+ */
+ public static final int STATE_ON_SUSPEND = 6;
+
/* The color mode constants defined below must be kept in sync with the ones in
* system/core/include/system/graphics-base.h */
@@ -994,7 +1005,7 @@ public final class Display {
* Gets the state of the display, such as whether it is on or off.
*
* @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON},
- * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, or
+ * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, {@link #STATE_ON_SUSPEND}, or
* {@link #STATE_UNKNOWN}.
*/
public int getState() {
@@ -1113,6 +1124,8 @@ public final class Display {
return "DOZE_SUSPEND";
case STATE_VR:
return "VR";
+ case STATE_ON_SUSPEND:
+ return "ON_SUSPEND";
default:
return Integer.toString(state);
}
@@ -1120,11 +1133,11 @@ public final class Display {
/**
* Returns true if display updates may be suspended while in the specified
- * display power state.
+ * display power state. In SUSPEND states, updates are absolutely forbidden.
* @hide
*/
public static boolean isSuspendedState(int state) {
- return state == STATE_OFF || state == STATE_DOZE_SUSPEND;
+ return state == STATE_OFF || state == STATE_DOZE_SUSPEND || state == STATE_ON_SUSPEND;
}
/**
diff --git a/android/view/DisplayFrames.java b/android/view/DisplayFrames.java
new file mode 100644
index 00000000..e6861d83
--- /dev/null
+++ b/android/view/DisplayFrames.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;
+
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.PrintWriter;
+
+/**
+ * Container class for all the display frames that affect how we do window layout on a display.
+ * @hide
+ */
+public class DisplayFrames {
+ public final int mDisplayId;
+
+ /**
+ * The current size of the screen; really; extends into the overscan area of the screen and
+ * doesn't account for any system elements like the status bar.
+ */
+ public final Rect mOverscan = new Rect();
+
+ /**
+ * The current visible size of the screen; really; (ir)regardless of whether the status bar can
+ * be hidden but not extending into the overscan area.
+ */
+ public final Rect mUnrestricted = new Rect();
+
+ /** Like mOverscan*, but allowed to move into the overscan region where appropriate. */
+ public final Rect mRestrictedOverscan = new Rect();
+
+ /**
+ * The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar
+ * can't be hidden; in that case it effectively carves out that area of the display from all
+ * other windows.
+ */
+ public final Rect mRestricted = new Rect();
+
+ /**
+ * During layout, the current screen borders accounting for any currently visible system UI
+ * elements.
+ */
+ public final Rect mSystem = new Rect();
+
+ /** For applications requesting stable content insets, these are them. */
+ public final Rect mStable = new Rect();
+
+ /**
+ * For applications requesting stable content insets but have also set the fullscreen window
+ * flag, these are the stable dimensions without the status bar.
+ */
+ public final Rect mStableFullscreen = new Rect();
+
+ /**
+ * During layout, the current screen borders with all outer decoration (status bar, input method
+ * dock) accounted for.
+ */
+ public final Rect mCurrent = new Rect();
+
+ /**
+ * During layout, the frame in which content should be displayed to the user, accounting for all
+ * screen decoration except for any space they deem as available for other content. This is
+ * usually the same as mCurrent*, but may be larger if the screen decor has supplied content
+ * insets.
+ */
+ public final Rect mContent = new Rect();
+
+ /**
+ * During layout, the frame in which voice content should be displayed to the user, accounting
+ * for all screen decoration except for any space they deem as available for other content.
+ */
+ public final Rect mVoiceContent = new Rect();
+
+ /** During layout, the current screen borders along which input method windows are placed. */
+ public final Rect mDock = new Rect();
+
+ private final Rect mDisplayInfoOverscan = new Rect();
+ private final Rect mRotatedDisplayInfoOverscan = new Rect();
+ public int mDisplayWidth;
+ public int mDisplayHeight;
+
+ public int mRotation;
+
+ public DisplayFrames(int displayId, DisplayInfo info) {
+ mDisplayId = displayId;
+ onDisplayInfoUpdated(info);
+ }
+
+ public void onDisplayInfoUpdated(DisplayInfo info) {
+ mDisplayWidth = info.logicalWidth;
+ mDisplayHeight = info.logicalHeight;
+ mRotation = info.rotation;
+ mDisplayInfoOverscan.set(
+ info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
+ }
+
+ public void onBeginLayout() {
+ switch (mRotation) {
+ case ROTATION_90:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.right;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.left;
+ break;
+ case ROTATION_180:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.right;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.left;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.top;
+ break;
+ case ROTATION_270:
+ mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.bottom;
+ mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.left;
+ mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.top;
+ mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.right;
+ break;
+ default:
+ mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan);
+ break;
+ }
+
+ mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight);
+ mOverscan.set(mRestrictedOverscan);
+ mSystem.set(mRestrictedOverscan);
+ mUnrestricted.set(mRotatedDisplayInfoOverscan);
+ mUnrestricted.right = mDisplayWidth - mUnrestricted.right;
+ mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom;
+ mRestricted.set(mUnrestricted);
+ mDock.set(mUnrestricted);
+ mContent.set(mUnrestricted);
+ mVoiceContent.set(mUnrestricted);
+ mStable.set(mUnrestricted);
+ mStableFullscreen.set(mUnrestricted);
+ mCurrent.set(mUnrestricted);
+
+ }
+
+ public int getInputMethodWindowVisibleHeight() {
+ return mDock.bottom - mCurrent.bottom;
+ }
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ mStable.writeToProto(proto, STABLE_BOUNDS);
+ proto.end(token);
+ }
+
+ public void dump(String prefix, PrintWriter pw) {
+ pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
+ + " r=" + mRotation);
+ final String myPrefix = prefix + " ";
+ dumpFrame(mStable, "mStable", myPrefix, pw);
+ dumpFrame(mStableFullscreen, "mStableFullscreen", myPrefix, pw);
+ dumpFrame(mDock, "mDock", myPrefix, pw);
+ dumpFrame(mCurrent, "mCurrent", myPrefix, pw);
+ dumpFrame(mSystem, "mSystem", myPrefix, pw);
+ dumpFrame(mContent, "mContent", myPrefix, pw);
+ dumpFrame(mVoiceContent, "mVoiceContent", myPrefix, pw);
+ dumpFrame(mOverscan, "mOverscan", myPrefix, pw);
+ dumpFrame(mRestrictedOverscan, "mRestrictedOverscan", myPrefix, pw);
+ dumpFrame(mRestricted, "mRestricted", myPrefix, pw);
+ dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw);
+ dumpFrame(mDisplayInfoOverscan, "mDisplayInfoOverscan", myPrefix, pw);
+ dumpFrame(mRotatedDisplayInfoOverscan, "mRotatedDisplayInfoOverscan", myPrefix, pw);
+ }
+
+ private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) {
+ pw.print(prefix + name + "="); frame.printShortString(pw); pw.println();
+ }
+}
diff --git a/android/view/FocusFinder.java b/android/view/FocusFinder.java
index 74555de5..713cfb48 100644
--- a/android/view/FocusFinder.java
+++ b/android/view/FocusFinder.java
@@ -530,7 +530,7 @@ public class FocusFinder {
* axis distances. Warning: this fudge factor is finely tuned, be sure to
* run all focus tests if you dare tweak it.
*/
- int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
+ long getWeightedDistanceFor(long majorAxisDistance, long minorAxisDistance) {
return 13 * majorAxisDistance * majorAxisDistance
+ minorAxisDistance * minorAxisDistance;
}
diff --git a/android/view/IWindowManagerImpl.java b/android/view/IWindowManagerImpl.java
index b34dfbfe..6c006cae 100644
--- a/android/view/IWindowManagerImpl.java
+++ b/android/view/IWindowManagerImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import android.app.IAssistDataReceiver;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
@@ -29,7 +30,6 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.DisplayMetrics;
-import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
@@ -261,7 +261,7 @@ public class IWindowManagerImpl implements IWindowManager {
}
@Override
- public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver)
+ public boolean requestAssistScreenshot(IAssistDataReceiver receiver)
throws RemoteException {
// TODO Auto-generated method stub
return false;
@@ -507,7 +507,7 @@ public class IWindowManagerImpl implements IWindowManager {
throws RemoteException {}
@Override
- public void createInputConsumer(String name, InputChannel inputChannel)
+ public void createInputConsumer(IBinder token, String name, InputChannel inputChannel)
throws RemoteException {}
@Override
diff --git a/android/view/NotificationHeaderView.java b/android/view/NotificationHeaderView.java
index 58045602..ab0b3eec 100644
--- a/android/view/NotificationHeaderView.java
+++ b/android/view/NotificationHeaderView.java
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -43,6 +44,7 @@ public class NotificationHeaderView extends ViewGroup {
public static final int NO_COLOR = Notification.COLOR_INVALID;
private final int mChildMinWidth;
private final int mContentEndMargin;
+ private final int mGravity;
private View mAppName;
private View mHeaderText;
private OnClickListener mExpandClickListener;
@@ -50,7 +52,6 @@ public class NotificationHeaderView extends ViewGroup {
private ImageView mExpandButton;
private CachingIconView mIcon;
private View mProfileBadge;
- private View mInfo;
private int mIconColor;
private int mOriginalNotificationColor;
private boolean mExpanded;
@@ -61,6 +62,7 @@ public class NotificationHeaderView extends ViewGroup {
private boolean mEntireHeaderClickable;
private boolean mExpandOnlyOnButton;
private boolean mAcceptAllTouches;
+ private int mTotalWidth;
ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
@@ -92,6 +94,11 @@ public class NotificationHeaderView extends ViewGroup {
mHeaderBackgroundHeight = res.getDimensionPixelSize(
R.dimen.notification_header_background_height);
mEntireHeaderClickable = res.getBoolean(R.bool.config_notificationHeaderClickableForExpand);
+
+ int[] attrIds = { android.R.attr.gravity };
+ TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
+ mGravity = ta.getInt(0, 0);
+ ta.recycle();
}
@Override
@@ -146,6 +153,7 @@ public class NotificationHeaderView extends ViewGroup {
mHeaderText.measure(childWidthSpec, wrapContentHeightSpec);
}
}
+ mTotalWidth = Math.min(totalWidth, givenWidth);
setMeasuredDimension(givenWidth, givenHeight);
}
@@ -153,6 +161,10 @@ public class NotificationHeaderView extends ViewGroup {
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = getPaddingStart();
int end = getMeasuredWidth();
+ final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
+ if (centerAligned) {
+ left += getMeasuredWidth() / 2 - mTotalWidth / 2;
+ }
int childCount = getChildCount();
int ownHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
for (int i = 0; i < childCount; i++) {
diff --git a/android/view/RenderNode.java b/android/view/RenderNode.java
index ea6e63c3..50701518 100644
--- a/android/view/RenderNode.java
+++ b/android/view/RenderNode.java
@@ -353,6 +353,11 @@ public class RenderNode {
return nHasShadow(mNativeRenderNode);
}
+ /** setShadowColor */
+ public boolean setShadowColor(int color) {
+ return nSetShadowColor(mNativeRenderNode, color);
+ }
+
/**
* Enables or disables clipping to the outline.
*
@@ -910,6 +915,8 @@ public class RenderNode {
@CriticalNative
private static native boolean nHasShadow(long renderNode);
@CriticalNative
+ private static native boolean nSetShadowColor(long renderNode, int color);
+ @CriticalNative
private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
@CriticalNative
private static native boolean nSetRevealClip(long renderNode,
diff --git a/android/view/RenderNodeAnimator.java b/android/view/RenderNodeAnimator.java
index 95150409..c4a71601 100644
--- a/android/view/RenderNodeAnimator.java
+++ b/android/view/RenderNodeAnimator.java
@@ -19,7 +19,6 @@ package android.view;
import android.animation.Animator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.util.SparseIntArray;
@@ -281,12 +280,9 @@ public class RenderNodeAnimator extends Animator {
setTarget(mViewTarget.mRenderNode);
}
- public void setTarget(Canvas canvas) {
- if (!(canvas instanceof DisplayListCanvas)) {
- throw new IllegalArgumentException("Not a GLES20RecordingCanvas");
- }
- final DisplayListCanvas recordingCanvas = (DisplayListCanvas) canvas;
- setTarget(recordingCanvas.mNode);
+ /** Sets the animation target to the owning view of the DisplayListCanvas */
+ public void setTarget(DisplayListCanvas canvas) {
+ setTarget(canvas.mNode);
}
private void setTarget(RenderNode node) {
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 6f8315ae..5641009c 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -295,6 +295,12 @@ public class SurfaceControl {
public static final int POWER_MODE_DOZE_SUSPEND = 3;
/**
+ * Display power mode on: used while putting the screen into a suspended
+ * full power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ */
+ public static final int POWER_MODE_ON_SUSPEND = 4;
+
+ /**
* A value for windowType used to indicate that the window should be omitted from screenshots
* and display mirroring. A temporary workaround until we express such things with
* the hierarchy.
@@ -1206,56 +1212,65 @@ public class SurfaceControl {
}
public Transaction show(SurfaceControl sc) {
+ sc.checkNotReleased();
nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
return this;
}
public Transaction hide(SurfaceControl sc) {
+ sc.checkNotReleased();
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
return this;
}
public Transaction setPosition(SurfaceControl sc, float x, float y) {
+ sc.checkNotReleased();
nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
return this;
}
public Transaction setSize(SurfaceControl sc, int w, int h) {
- nativeSetSize(mNativeObject, sc.mNativeObject,
- w, h);
+ sc.checkNotReleased();
+ nativeSetSize(mNativeObject, sc.mNativeObject, w, h);
return this;
}
public Transaction setLayer(SurfaceControl sc, int z) {
+ sc.checkNotReleased();
nativeSetLayer(mNativeObject, sc.mNativeObject, z);
return this;
}
public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
+ sc.checkNotReleased();
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
relativeTo.getHandle(), z);
return this;
}
public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
+ sc.checkNotReleased();
nativeSetTransparentRegionHint(mNativeObject,
sc.mNativeObject, transparentRegion);
return this;
}
public Transaction setAlpha(SurfaceControl sc, float alpha) {
+ sc.checkNotReleased();
nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
return this;
}
public Transaction setMatrix(SurfaceControl sc,
float dsdx, float dtdx, float dtdy, float dsdy) {
+ sc.checkNotReleased();
nativeSetMatrix(mNativeObject, sc.mNativeObject,
dsdx, dtdx, dtdy, dsdy);
return this;
}
public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+ sc.checkNotReleased();
if (crop != null) {
nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
crop.left, crop.top, crop.right, crop.bottom);
@@ -1267,6 +1282,7 @@ public class SurfaceControl {
}
public Transaction setFinalCrop(SurfaceControl sc, Rect crop) {
+ sc.checkNotReleased();
if (crop != null) {
nativeSetFinalCrop(mNativeObject, sc.mNativeObject,
crop.left, crop.top, crop.right, crop.bottom);
@@ -1278,40 +1294,48 @@ public class SurfaceControl {
}
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+ sc.checkNotReleased();
nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
return this;
}
- public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) {
+ public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle,
+ long frameNumber) {
+ sc.checkNotReleased();
nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber);
return this;
}
public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
long frameNumber) {
+ sc.checkNotReleased();
nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
barrierSurface.mNativeObject, frameNumber);
return this;
}
public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
+ sc.checkNotReleased();
nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
return this;
}
/** Re-parents a specific child layer to a new parent */
public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+ sc.checkNotReleased();
nativeReparent(mNativeObject, sc.mNativeObject,
newParentHandle);
return this;
}
public Transaction detachChildren(SurfaceControl sc) {
+ sc.checkNotReleased();
nativeSeverChildren(mNativeObject, sc.mNativeObject);
return this;
}
public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
+ sc.checkNotReleased();
nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
overrideScalingMode);
return this;
@@ -1322,6 +1346,7 @@ public class SurfaceControl {
* @param color A float array with three values to represent r, g, b in range [0..1]
*/
public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
+ sc.checkNotReleased();
nativeSetColor(mNativeObject, sc.mNativeObject, color);
return this;
}
@@ -1334,6 +1359,7 @@ public class SurfaceControl {
* (at which point the geometry influencing aspects of this transaction will then occur)
*/
public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
+ sc.checkNotReleased();
nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
return this;
}
@@ -1343,6 +1369,7 @@ public class SurfaceControl {
* Surface with the {@link #SECURE} flag.
*/
Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+ sc.checkNotReleased();
if (isSecure) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
} else {
@@ -1356,6 +1383,7 @@ public class SurfaceControl {
* Surface with the {@link #OPAQUE} flag.
*/
public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+ sc.checkNotReleased();
if (isOpaque) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
} else {
diff --git a/android/view/ThreadedRenderer.java b/android/view/ThreadedRenderer.java
index 2166f6e4..7c76bab2 100644
--- a/android/view/ThreadedRenderer.java
+++ b/android/view/ThreadedRenderer.java
@@ -70,6 +70,7 @@ public final class ThreadedRenderer {
* Name of the file that holds the shaders cache.
*/
private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
+ private static final String CACHE_PATH_SKIASHADERS = "com.android.skia.shaders_cache";
/**
* System property used to enable or disable threaded rendering profiling.
@@ -272,7 +273,9 @@ public final class ThreadedRenderer {
* @hide
*/
public static void setupDiskCache(File cacheDir) {
- ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+ ThreadedRenderer.setupShadersDiskCache(
+ new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath(),
+ new File(cacheDir, CACHE_PATH_SKIASHADERS).getAbsolutePath());
}
/**
@@ -1007,7 +1010,7 @@ public final class ThreadedRenderer {
/** Not actually public - internal use only. This doc to make lint happy */
public static native void disableVsync();
- static native void setupShadersDiskCache(String cacheFile);
+ static native void setupShadersDiskCache(String cacheFile, String skiaCacheFile);
private static native void nRotateProcessStatsBuffer();
private static native void nSetProcessStatsBuffer(int fd);
diff --git a/android/view/View.java b/android/view/View.java
index c043dcac..be09fe86 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -14218,6 +14218,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void setScaleX(float scaleX) {
if (scaleX != getScaleX()) {
+ requireIsFinite(scaleX, "scaleX");
invalidateViewProperty(true, false);
mRenderNode.setScaleX(scaleX);
invalidateViewProperty(false, true);
@@ -14254,6 +14255,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void setScaleY(float scaleY) {
if (scaleY != getScaleY()) {
+ requireIsFinite(scaleY, "scaleY");
invalidateViewProperty(true, false);
mRenderNode.setScaleY(scaleY);
invalidateViewProperty(false, true);
@@ -14803,6 +14805,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ private static void requireIsFinite(float transform, String propertyName) {
+ if (Float.isNaN(transform)) {
+ throw new IllegalArgumentException("Cannot set '" + propertyName + "' to Float.NaN");
+ }
+ if (Float.isInfinite(transform)) {
+ throw new IllegalArgumentException("Cannot set '" + propertyName + "' to infinity");
+ }
+ }
+
/**
* The visual x position of this view, in pixels. This is equivalent to the
* {@link #setTranslationX(float) translationX} property plus the current
@@ -14889,6 +14900,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void setElevation(float elevation) {
if (elevation != getElevation()) {
+ requireIsFinite(elevation, "elevation");
invalidateViewProperty(true, false);
mRenderNode.setElevation(elevation);
invalidateViewProperty(false, true);
@@ -14981,6 +14993,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void setTranslationZ(float translationZ) {
if (translationZ != getTranslationZ()) {
+ requireIsFinite(translationZ, "translationZ");
invalidateViewProperty(true, false);
mRenderNode.setTranslationZ(translationZ);
invalidateViewProperty(false, true);
@@ -15169,6 +15182,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return mRenderNode.hasShadow();
}
+ /**
+ * @hide
+ */
+ public void setShadowColor(@ColorInt int color) {
+ if (mRenderNode.setShadowColor(color)) {
+ invalidateViewProperty(true, true);
+ }
+ }
+
/** @hide */
public void setRevealClip(boolean shouldClip, float x, float y, float radius) {
diff --git a/android/view/ViewConfiguration.java b/android/view/ViewConfiguration.java
index 45008627..c44c8dda 100644
--- a/android/view/ViewConfiguration.java
+++ b/android/view/ViewConfiguration.java
@@ -92,7 +92,7 @@ public class ViewConfiguration {
* Defines the duration in milliseconds a user needs to hold down the
* appropriate button to enable the accessibility shortcut once it's configured.
*/
- private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+ private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1000;
/**
* Defines the duration in milliseconds we will wait to see if a touch event
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 37829f0b..e30496fb 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -513,7 +513,7 @@ public final class ViewRootImpl implements ViewParent,
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
- sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
+ sAlwaysAssignFocus = true;
sCompatibilityDone = true;
}
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
index c29a1daf..eb5fc92e 100644
--- a/android/view/WindowManager.java
+++ b/android/view/WindowManager.java
@@ -1422,7 +1422,7 @@ public interface WindowManager extends ViewManager {
* this window is visible.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ @RequiresPermission(permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
/**
@@ -1443,6 +1443,15 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN = 0x00200000;
/**
+ * Flag to indicate that this window should be considered a screen decoration similar to the
+ * nav bar and status bar. This will cause this window to affect the window insets reported
+ * to other windows when it is visible.
+ * @hide
+ */
+ @RequiresPermission(permission.STATUS_BAR_SERVICE)
+ public static final int PRIVATE_FLAG_IS_SCREEN_DECOR = 0x00400000;
+
+ /**
* Control flags that are private to the platform.
* @hide
*/
@@ -1526,7 +1535,11 @@ public interface WindowManager extends ViewManager {
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
- name = "ACQUIRES_SLEEP_TOKEN")
+ name = "ACQUIRES_SLEEP_TOKEN"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_IS_SCREEN_DECOR,
+ equals = PRIVATE_FLAG_IS_SCREEN_DECOR,
+ name = "IS_SCREEN_DECOR")
})
@TestApi
public int privateFlags;
diff --git a/android/view/WindowManagerGlobal.java b/android/view/WindowManagerGlobal.java
index c7e8dee3..cca66d6b 100644
--- a/android/view/WindowManagerGlobal.java
+++ b/android/view/WindowManagerGlobal.java
@@ -605,9 +605,10 @@ public final class WindowManagerGlobal {
public void setStoppedState(IBinder token, boolean stopped) {
synchronized (mLock) {
int count = mViews.size();
- for (int i = 0; i < count; i++) {
+ for (int i = count - 1; i >= 0; i--) {
if (token == null || mParams.get(i).token == token) {
ViewRootImpl root = mRoots.get(i);
+ // Client might remove the view by "stopped" event.
root.setWindowStopped(stopped);
}
}
diff --git a/android/view/WindowManagerInternal.java b/android/view/WindowManagerInternal.java
index 69cc1002..cd1b1908 100644
--- a/android/view/WindowManagerInternal.java
+++ b/android/view/WindowManagerInternal.java
@@ -18,6 +18,7 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ClipData;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
@@ -140,6 +141,30 @@ public abstract class WindowManagerInternal {
}
/**
+ * An interface to customize drag and drop behaviors.
+ */
+ public interface IDragDropCallback {
+ /**
+ * Called when drag operation is started.
+ */
+ default boolean performDrag(IWindow window, IBinder dragToken,
+ int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data) {
+ return true;
+ }
+
+ /**
+ * Called when drop result is reported.
+ */
+ default void reportDropResult(IWindow window, boolean consumed) {}
+
+ /**
+ * Called when drag operation is cancelled.
+ */
+ default void cancelDragAndDrop(IBinder dragToken) {}
+ }
+
+ /**
* Request that the window manager call
* {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
* within a surface transaction at a later time.
@@ -225,9 +250,6 @@ public abstract class WindowManagerInternal {
*/
public abstract boolean isKeyguardLocked();
- /** @return {@code true} if the keyguard is going away. */
- public abstract boolean isKeyguardGoingAway();
-
/**
* @return Whether the keyguard is showing and not occluded.
*/
@@ -354,4 +376,9 @@ public abstract class WindowManagerInternal {
* {@param vr2dDisplayId}.
*/
public abstract void setVr2dDisplayId(int vr2dDisplayId);
+
+ /**
+ * Sets callback to DragDropController.
+ */
+ public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
}
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index 137e551d..534335bf 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -66,7 +66,6 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.app.ActivityManager.StackId;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
@@ -722,12 +721,6 @@ public interface WindowManagerPolicy {
public void setInitialDisplaySize(Display display, int width, int height, int density);
/**
- * Called by window manager to set the overscan region that should be used for the
- * given display.
- */
- public void setDisplayOverscan(Display display, int left, int top, int right, int bottom);
-
- /**
* Check permissions when adding a window.
*
* @param attrs The window's LayoutParams.
@@ -758,7 +751,8 @@ public interface WindowManagerPolicy {
* @param attrs The window layout parameters to be modified. These values
* are modified in-place.
*/
- public void adjustWindowParamsLw(WindowManager.LayoutParams attrs);
+ public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+ boolean hasStatusBarServicePermission);
/**
* After the window manager has computed the current configuration based
@@ -1172,14 +1166,10 @@ public interface WindowManagerPolicy {
/**
* Called when layout of the windows is about to start.
*
- * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}.
- * @param displayWidth The current full width of the screen.
- * @param displayHeight The current full height of the screen.
- * @param displayRotation The current rotation being applied to the base window.
+ * @param displayFrames frames of the display we are doing layout on.
* @param uiMode The current uiMode in configuration.
*/
- public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
- int displayRotation, int uiMode);
+ default void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {}
/**
* Returns the bottom-most layer of the system decor, above which no policy decor should
@@ -1188,37 +1178,28 @@ public interface WindowManagerPolicy {
public int getSystemDecorLayerLw();
/**
- * Return the rectangle of the screen that is available for applications to run in.
- * This will be called immediately after {@link #beginLayoutLw}.
- *
- * @param r The rectangle to be filled with the boundaries available to applications.
- */
- public void getContentRectLw(Rect r);
-
- /**
- * Called for each window attached to the window manager as layout is
- * proceeding. The implementation of this function must take care of
- * setting the window's frame, either here or in finishLayout().
+ * Called for each window attached to the window manager as layout is proceeding. The
+ * implementation of this function must take care of setting the window's frame, either here or
+ * in finishLayout().
*
* @param win The window being positioned.
* @param attached For sub-windows, the window it is attached to; this
* window will already have had layoutWindow() called on it
* so you can use its Rect. Otherwise null.
+ * @param displayFrames The display frames.
*/
- public void layoutWindowLw(WindowState win, WindowState attached);
+ default void layoutWindowLw(
+ WindowState win, WindowState attached, DisplayFrames displayFrames) {}
/**
- * Return the insets for the areas covered by system windows. These values
- * are computed on the most recent layout, so they are not guaranteed to
- * be correct.
+ * Return the insets for the areas covered by system windows. These values are computed on the
+ * most recent layout, so they are not guaranteed to be correct.
*
* @param attrs The LayoutParams of the window.
* @param taskBounds The bounds of the task this window is on or {@code null} if no task is
* associated with the window.
- * @param displayRotation Rotation of the display.
- * @param displayWidth The width of the display.
- * @param displayHeight The height of the display.
+ * @param displayFrames display frames.
* @param outContentInsets The areas covered by system windows, expressed as positive insets.
* @param outStableInsets The areas covered by stable system windows irrespective of their
* current visibility. Expressed as positive insets.
@@ -1226,16 +1207,11 @@ public interface WindowManagerPolicy {
* @return Whether to always consume the navigation bar.
* See {@link #isNavBarForcedShownLw(WindowState)}.
*/
- public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
- int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
- Rect outStableInsets, Rect outOutsets);
-
- /**
- * Called when layout of the windows is finished. After this function has
- * returned, all windows given to layoutWindow() <em>must</em> have had a
- * frame assigned.
- */
- public void finishLayoutLw();
+ default boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
+ DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+ Rect outOutsets) {
+ return false;
+ }
/** Layout state may have changed (so another layout will be performed) */
static final int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
@@ -1652,11 +1628,6 @@ public interface WindowManagerPolicy {
public void showGlobalActions();
/**
- * @return The current height of the input method window.
- */
- public int getInputMethodWindowVisibleHeightLw();
-
- /**
* Called when the current user changes. Guaranteed to be called before the broadcast
* of the new user id is made to all listeners.
*
diff --git a/android/view/accessibility/AccessibilityInteractionClient.java b/android/view/accessibility/AccessibilityInteractionClient.java
index 19213ca0..c3d6c695 100644
--- a/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/android/view/accessibility/AccessibilityInteractionClient.java
@@ -187,8 +187,11 @@ public final class AccessibilityInteractionClient
Log.i(LOG_TAG, "Window cache miss");
}
final long identityToken = Binder.clearCallingIdentity();
- window = connection.getWindow(accessibilityWindowId);
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ window = connection.getWindow(accessibilityWindowId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (window != null) {
sAccessibilityCache.addWindow(window);
return window;
@@ -225,8 +228,11 @@ public final class AccessibilityInteractionClient
Log.i(LOG_TAG, "Windows cache miss");
}
final long identityToken = Binder.clearCallingIdentity();
- windows = connection.getWindows();
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ windows = connection.getWindows();
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (windows != null) {
sAccessibilityCache.setWindows(windows);
return windows;
@@ -283,10 +289,14 @@ public final class AccessibilityInteractionClient
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
- accessibilityWindowId, accessibilityNodeId, interactionId, this,
- prefetchFlags, Thread.currentThread().getId(), arguments);
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfoByAccessibilityId(
+ accessibilityWindowId, accessibilityNodeId, interactionId, this,
+ prefetchFlags, Thread.currentThread().getId(), arguments);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -333,10 +343,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfosByViewId(
- accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfosByViewId(
+ accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -381,10 +396,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findAccessibilityNodeInfosByText(
- accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findAccessibilityNodeInfosByText(
+ accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
@@ -428,10 +448,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.findFocus(accessibilityWindowId,
- accessibilityNodeId, focusType, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.findFocus(accessibilityWindowId,
+ accessibilityNodeId, focusType, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
@@ -472,10 +497,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.focusSearch(accessibilityWindowId,
- accessibilityNodeId, direction, interactionId, this,
- Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.focusSearch(accessibilityWindowId,
+ accessibilityNodeId, direction, interactionId, this,
+ Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
@@ -515,10 +545,15 @@ public final class AccessibilityInteractionClient
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
- final boolean success = connection.performAccessibilityAction(
- accessibilityWindowId, accessibilityNodeId, action, arguments,
- interactionId, this, Thread.currentThread().getId());
- Binder.restoreCallingIdentity(identityToken);
+ final boolean success;
+ try {
+ success = connection.performAccessibilityAction(
+ accessibilityWindowId, accessibilityNodeId, action, arguments,
+ interactionId, this, Thread.currentThread().getId());
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (success) {
return getPerformAccessibilityActionResultAndClear(interactionId);
}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 0b9bc576..35f6acba 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -436,8 +436,11 @@ public final class AccessibilityManager {
// client using it is called through Binder from another process. Example: MMS
// app adds a SMS notification and the NotificationManagerService calls this method
long identityToken = Binder.clearCallingIdentity();
- service.sendAccessibilityEvent(event, userId);
- Binder.restoreCallingIdentity(identityToken);
+ try {
+ service.sendAccessibilityEvent(event, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
if (DEBUG) {
Log.i(LOG_TAG, event + " sent");
}
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index e79d201b..9241ec00 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -54,6 +54,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+// TODO: use java.lang.ref.Cleaner once Android supports Java 9
+import sun.misc.Cleaner;
+
/**
* The {@link AutofillManager} provides ways for apps and custom views to integrate with the
* Autofill Framework lifecycle.
@@ -303,6 +306,9 @@ public final class AutofillManager {
private IAutoFillManagerClient mServiceClient;
@GuardedBy("mLock")
+ private Cleaner mServiceClientCleaner;
+
+ @GuardedBy("mLock")
private AutofillCallback mCallback;
private final Context mContext;
@@ -1172,10 +1178,19 @@ public final class AutofillManager {
if (mServiceClient == null) {
mServiceClient = new AutofillManagerClient(this);
try {
- final int flags = mService.addClient(mServiceClient, mContext.getUserId());
+ final int userId = mContext.getUserId();
+ final int flags = mService.addClient(mServiceClient, userId);
mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
+ final IAutoFillManager service = mService;
+ final IAutoFillManagerClient serviceClient = mServiceClient;
+ mServiceClientCleaner = Cleaner.create(this, () -> {
+ try {
+ service.removeClient(serviceClient, userId);
+ } catch (RemoteException e) {
+ }
+ });
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1272,18 +1287,36 @@ public final class AutofillManager {
}
}
- private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+ /** @hide */
+ public static final int SET_STATE_FLAG_ENABLED = 0x01;
+ /** @hide */
+ public static final int SET_STATE_FLAG_RESET_SESSION = 0x02;
+ /** @hide */
+ public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04;
+ /** @hide */
+ public static final int SET_STATE_FLAG_DEBUG = 0x08;
+ /** @hide */
+ public static final int SET_STATE_FLAG_VERBOSE = 0x10;
+
+ private void setState(int flags) {
+ if (sVerbose) Log.v(TAG, "setState(" + flags + ")");
synchronized (mLock) {
- mEnabled = enabled;
- if (!mEnabled || resetSession) {
+ mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0;
+ if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) {
// Reset the session state
resetSessionLocked();
}
- if (resetClient) {
+ if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) {
// Reset connection to system
mServiceClient = null;
+ if (mServiceClientCleaner != null) {
+ mServiceClientCleaner.clean();
+ mServiceClientCleaner = null;
+ }
}
}
+ sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0;
+ sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0;
}
/**
@@ -1609,6 +1642,7 @@ public final class AutofillManager {
pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
pw.print(pfx); pw.print("context: "); pw.println(mContext);
+ pw.print(pfx); pw.print("client: "); pw.println(getClientLocked());
pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
@@ -1625,6 +1659,8 @@ public final class AutofillManager {
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
+ pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
+ pw.print(" verbose: "); pw.println(sVerbose);
}
private String getStateAsStringLocked() {
@@ -1880,7 +1916,7 @@ public final class AutofillManager {
public abstract static class AutofillCallback {
/** @hide */
- @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN})
+ @IntDef({EVENT_INPUT_SHOWN, EVENT_INPUT_HIDDEN, EVENT_INPUT_UNAVAILABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface AutofillEventType {}
@@ -1940,10 +1976,10 @@ public final class AutofillManager {
}
@Override
- public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+ public void setState(int flags) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.setState(enabled, resetSession, resetClient));
+ afm.post(() -> afm.setState(flags));
}
}
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 26d2141e..2779aa2d 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.os.LocaleList;
import android.view.View.OnClickListener;
import android.view.textclassifier.TextClassifier.EntityType;
@@ -438,4 +439,31 @@ public final class TextClassification {
mLogType, mVersionInfo);
}
}
+
+ /**
+ * TextClassification optional input parameters.
+ */
+ public static final class Options {
+
+ private LocaleList mDefaultLocales;
+
+ /**
+ * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+ * the provided text. If no locale preferences exist, set this to null or an empty
+ * locale list.
+ */
+ public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+ mDefaultLocales = defaultLocales;
+ return this;
+ }
+
+ /**
+ * @return ordered list of locale preferences that can be used to disambiguate
+ * the provided text.
+ */
+ @Nullable
+ public LocaleList getDefaultLocales() {
+ return mDefaultLocales;
+ }
+ }
}
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index 46dbd0e3..aeb84897 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -56,23 +56,7 @@ public interface TextClassifier {
* No-op TextClassifier.
* This may be used to turn off TextClassifier features.
*/
- TextClassifier NO_OP = new TextClassifier() {
-
- @Override
- public TextSelection suggestSelection(
- CharSequence text,
- int selectionStartIndex,
- int selectionEndIndex,
- LocaleList defaultLocales) {
- return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
- }
-
- @Override
- public TextClassification classifyText(
- CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales) {
- return TextClassification.EMPTY;
- }
- };
+ TextClassifier NO_OP = new TextClassifier() {};
/**
* Returns suggested text selection start and end indices, recognized entity types, and their
@@ -82,21 +66,34 @@ public interface TextClassifier {
* by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
* @param selectionStartIndex start index of the selected part of text
* @param selectionEndIndex end index of the selected part of text
- * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
- * the provided text. If no locale preferences exist, set this to null or an empty locale
- * list in which case the classifier will decide whether to use no locale information, use
- * a default locale, or use the system default.
+ * @param options optional input parameters
*
* @throws IllegalArgumentException if text is null; selectionStartIndex is negative;
* selectionEndIndex is greater than text.length() or not greater than selectionStartIndex
*/
@WorkerThread
@NonNull
- TextSelection suggestSelection(
+ default TextSelection suggestSelection(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int selectionStartIndex,
+ @IntRange(from = 0) int selectionEndIndex,
+ @Nullable TextSelection.Options options) {
+ return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ }
+
+ /**
+ * @see #suggestSelection(CharSequence, int, int, TextSelection.Options)
+ */
+ // TODO: Consider deprecating (b/68846316)
+ @WorkerThread
+ @NonNull
+ default TextSelection suggestSelection(
@NonNull CharSequence text,
@IntRange(from = 0) int selectionStartIndex,
@IntRange(from = 0) int selectionEndIndex,
- @Nullable LocaleList defaultLocales);
+ @Nullable LocaleList defaultLocales) {
+ return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build();
+ }
/**
* Classifies the specified text and returns a {@link TextClassification} object that can be
@@ -106,41 +103,48 @@ public interface TextClassifier {
* by the sub sequence starting at startIndex and ending at endIndex)
* @param startIndex start index of the text to classify
* @param endIndex end index of the text to classify
- * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
- * the provided text. If no locale preferences exist, set this to null or an empty locale
- * list in which case the classifier will decide whether to use no locale information, use
- * a default locale, or use the system default.
+ * @param options optional input parameters
*
* @throws IllegalArgumentException if text is null; startIndex is negative;
* endIndex is greater than text.length() or not greater than startIndex
*/
@WorkerThread
@NonNull
- TextClassification classifyText(
+ default TextClassification classifyText(
@NonNull CharSequence text,
@IntRange(from = 0) int startIndex,
@IntRange(from = 0) int endIndex,
- @Nullable LocaleList defaultLocales);
+ @Nullable TextClassification.Options options) {
+ return TextClassification.EMPTY;
+ }
/**
- * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
+ * @see #classifyText(CharSequence, int, int, TextClassification.Options)
+ */
+ // TODO: Consider deprecating (b/68846316)
+ @WorkerThread
+ @NonNull
+ default TextClassification classifyText(
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int startIndex,
+ @IntRange(from = 0) int endIndex,
+ @Nullable LocaleList defaultLocales) {
+ return TextClassification.EMPTY;
+ }
+
+ /**
+ * Returns a {@link TextLinks} that may be applied to the text to annotate it with links
* information.
*
* @param text the text to generate annotations for
- * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
- * specified. Subclasses of this interface may specify additional linkMasks
- * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
- * the provided text. If no locale preferences exist, set this to null or an empty locale
- * list in which case the classifier will decide whether to use no locale information, use
- * a default locale, or use the system default.
+ * @param options configuration for link generation. If null, defaults will be used.
*
* @throws IllegalArgumentException if text is null
- * @hide
*/
@WorkerThread
- default LinksInfo getLinks(
- @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
- return LinksInfo.NO_OP;
+ default TextLinks generateLinks(
+ @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+ return new TextLinks.Builder(text.toString()).build();
}
/**
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 1c07be4b..2ad6e02c 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -30,13 +30,8 @@ import android.os.ParcelFileDescriptor;
import android.provider.Browser;
import android.provider.ContactsContract;
import android.provider.Settings;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.method.WordIterator;
-import android.text.style.ClickableSpan;
import android.text.util.Linkify;
import android.util.Patterns;
-import android.view.View;
import android.widget.TextViewMetrics;
import com.android.internal.annotations.GuardedBy;
@@ -46,13 +41,8 @@ import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.text.BreakIterator;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -100,16 +90,24 @@ final class TextClassifierImpl implements TextClassifier {
@Override
public TextSelection suggestSelection(
@NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
- @Nullable LocaleList defaultLocales) {
+ @Nullable TextSelection.Options options) {
validateInput(text, selectionStartIndex, selectionEndIndex);
try {
if (text.length() > 0) {
- final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+ final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+ final SmartSelection smartSelection = getSmartSelection(locales);
final String string = text.toString();
- final int[] startEnd = smartSelection.suggest(
- string, selectionStartIndex, selectionEndIndex);
- final int start = startEnd[0];
- final int end = startEnd[1];
+ final int start;
+ final int end;
+ if (getSettings().isDarkLaunch() && !options.isDarkLaunchAllowed()) {
+ start = selectionStartIndex;
+ end = selectionEndIndex;
+ } else {
+ final int[] startEnd = smartSelection.suggest(
+ string, selectionStartIndex, selectionEndIndex);
+ start = startEnd[0];
+ end = startEnd[1];
+ }
if (start <= end
&& start >= 0 && end <= string.length()
&& start <= selectionStartIndex && end >= selectionEndIndex) {
@@ -139,18 +137,27 @@ final class TextClassifierImpl implements TextClassifier {
}
// Getting here means something went wrong, return a NO_OP result.
return TextClassifier.NO_OP.suggestSelection(
- text, selectionStartIndex, selectionEndIndex, defaultLocales);
+ text, selectionStartIndex, selectionEndIndex, options);
+ }
+
+ @Override
+ public TextSelection suggestSelection(
+ @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
+ @Nullable LocaleList defaultLocales) {
+ return suggestSelection(text, selectionStartIndex, selectionEndIndex,
+ new TextSelection.Options().setDefaultLocales(defaultLocales));
}
@Override
public TextClassification classifyText(
@NonNull CharSequence text, int startIndex, int endIndex,
- @Nullable LocaleList defaultLocales) {
+ @Nullable TextClassification.Options options) {
validateInput(text, startIndex, endIndex);
try {
if (text.length() > 0) {
final String string = text.toString();
- SmartSelection.ClassificationResult[] results = getSmartSelection(defaultLocales)
+ final LocaleList locales = (options == null) ? null : options.getDefaultLocales();
+ final SmartSelection.ClassificationResult[] results = getSmartSelection(locales)
.classifyText(string, startIndex, endIndex,
getHintFlags(string, startIndex, endIndex));
if (results.length > 0) {
@@ -165,23 +172,41 @@ final class TextClassifierImpl implements TextClassifier {
Log.e(LOG_TAG, "Error getting text classification info.", t);
}
// Getting here means something went wrong, return a NO_OP result.
- return TextClassifier.NO_OP.classifyText(
- text, startIndex, endIndex, defaultLocales);
+ return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options);
}
@Override
- public LinksInfo getLinks(
- @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
- Preconditions.checkArgument(text != null);
+ public TextClassification classifyText(
+ @NonNull CharSequence text, int startIndex, int endIndex,
+ @Nullable LocaleList defaultLocales) {
+ return classifyText(text, startIndex, endIndex,
+ new TextClassification.Options().setDefaultLocales(defaultLocales));
+ }
+
+ @Override
+ public TextLinks generateLinks(
+ @NonNull CharSequence text, @Nullable TextLinks.Options options) {
+ Preconditions.checkNotNull(text);
+ final String textString = text.toString();
+ final TextLinks.Builder builder = new TextLinks.Builder(textString);
try {
- return LinksInfoFactory.create(
- mContext, getSmartSelection(defaultLocales), text.toString(), linkMask);
+ LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
+ final SmartSelection smartSelection = getSmartSelection(defaultLocales);
+ final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString);
+ for (SmartSelection.AnnotatedSpan span : annotations) {
+ final Map<String, Float> entityScores = new HashMap<>();
+ final SmartSelection.ClassificationResult[] results = span.getClassification();
+ for (int i = 0; i < results.length; i++) {
+ entityScores.put(results[i].mCollection, results[i].mScore);
+ }
+ builder.addLink(new TextLinks.TextLink(
+ textString, span.getStartIndex(), span.getEndIndex(), entityScores));
+ }
} catch (Throwable t) {
// Avoid throwing from this method. Log the error.
Log.e(LOG_TAG, "Error getting links info.", t);
}
- // Getting here means something went wrong, return a NO_OP result.
- return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
+ return builder.build();
}
@Override
@@ -210,7 +235,9 @@ final class TextClassifierImpl implements TextClassifier {
if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
destroySmartSelectionIfExistsLocked();
final ParcelFileDescriptor fd = getFdLocked(locale);
- mSmartSelection = new SmartSelection(fd.getFd());
+ final int modelFd = fd.getFd();
+ mVersion = SmartSelection.getVersion(modelFd);
+ mSmartSelection = new SmartSelection(modelFd);
closeAndLogError(fd);
mLocale = locale;
}
@@ -231,18 +258,26 @@ final class TextClassifierImpl implements TextClassifier {
@GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException {
ParcelFileDescriptor updateFd;
+ int updateVersion = -1;
try {
updateFd = ParcelFileDescriptor.open(
new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
+ if (updateFd != null) {
+ updateVersion = SmartSelection.getVersion(updateFd.getFd());
+ }
} catch (FileNotFoundException e) {
updateFd = null;
}
ParcelFileDescriptor factoryFd;
+ int factoryVersion = -1;
try {
final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale);
if (factoryModelFilePath != null) {
factoryFd = ParcelFileDescriptor.open(
new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
+ if (factoryFd != null) {
+ factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
+ }
} else {
factoryFd = null;
}
@@ -278,15 +313,11 @@ final class TextClassifierImpl implements TextClassifier {
return factoryFd;
}
- final int updateVersion = SmartSelection.getVersion(updateFdInt);
- final int factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
if (updateVersion > factoryVersion) {
closeAndLogError(factoryFd);
- mVersion = updateVersion;
return updateFd;
} else {
closeAndLogError(updateFd);
- mVersion = factoryVersion;
return factoryFd;
}
}
@@ -466,180 +497,6 @@ final class TextClassifierImpl implements TextClassifier {
}
/**
- * Detects and creates links for specified text.
- */
- private static final class LinksInfoFactory {
-
- private LinksInfoFactory() {}
-
- public static LinksInfo create(
- Context context, SmartSelection smartSelection, String text, int linkMask) {
- final WordIterator wordIterator = new WordIterator();
- wordIterator.setCharSequence(text, 0, text.length());
- final List<SpanSpec> spans = new ArrayList<>();
- int start = 0;
- int end;
- while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
- final String token = text.substring(start, end);
- if (TextUtils.isEmpty(token)) {
- continue;
- }
-
- final int[] selection = smartSelection.suggest(text, start, end);
- final int selectionStart = selection[0];
- final int selectionEnd = selection[1];
- if (selectionStart >= 0 && selectionEnd <= text.length()
- && selectionStart <= selectionEnd) {
- final SmartSelection.ClassificationResult[] results =
- smartSelection.classifyText(
- text, selectionStart, selectionEnd,
- getHintFlags(text, selectionStart, selectionEnd));
- if (results.length > 0) {
- final String type = getHighestScoringType(results);
- if (matches(type, linkMask)) {
- // For links without disambiguation, we simply use the default intent.
- final List<Intent> intents = IntentFactory.create(
- context, type, text.substring(selectionStart, selectionEnd));
- if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
- final ClickableSpan span = createSpan(context, intents.get(0));
- spans.add(new SpanSpec(selectionStart, selectionEnd, span));
- }
- }
- }
- }
- start = end;
- }
- return new LinksInfoImpl(text, avoidOverlaps(spans, text));
- }
-
- /**
- * Returns true if the classification type matches the specified linkMask.
- */
- private static boolean matches(String type, int linkMask) {
- type = type.trim().toLowerCase(Locale.ENGLISH);
- if ((linkMask & Linkify.PHONE_NUMBERS) != 0
- && TextClassifier.TYPE_PHONE.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
- && TextClassifier.TYPE_EMAIL.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.MAP_ADDRESSES) != 0
- && TextClassifier.TYPE_ADDRESS.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.WEB_URLS) != 0
- && TextClassifier.TYPE_URL.equals(type)) {
- return true;
- }
- return false;
- }
-
- /**
- * Trim the number of spans so that no two spans overlap.
- *
- * This algorithm first ensures that there is only one span per start index, then it
- * makes sure that no two spans overlap.
- */
- private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
- Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
- // Group spans by start index. Take the longest span.
- final Map<Integer, SpanSpec> reps = new LinkedHashMap<>(); // order matters.
- final int size = spans.size();
- for (int i = 0; i < size; i++) {
- final SpanSpec span = spans.get(i);
- final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
- if (rep == null || rep.mEnd < span.mEnd) {
- reps.put(span.mStart, span);
- }
- }
- // Avoid span intersections. Take the longer span.
- final LinkedList<SpanSpec> result = new LinkedList<>();
- for (SpanSpec rep : reps.values()) {
- if (result.isEmpty()) {
- result.add(rep);
- continue;
- }
-
- final SpanSpec last = result.getLast();
- if (rep.mStart < last.mEnd) {
- // Spans intersect. Use the one with characters.
- if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
- result.set(result.size() - 1, rep);
- }
- } else {
- result.add(rep);
- }
- }
- return result;
- }
-
- private static ClickableSpan createSpan(final Context context, final Intent intent) {
- return new ClickableSpan() {
- // TODO: Style this span.
- @Override
- public void onClick(View widget) {
- context.startActivity(intent);
- }
- };
- }
-
- private static boolean hasActivityHandler(Context context, Intent intent) {
- if (intent == null) {
- return false;
- }
- final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
- return resolveInfo != null && resolveInfo.activityInfo != null;
- }
-
- /**
- * Implementation of LinksInfo that adds ClickableSpans to the specified text.
- */
- private static final class LinksInfoImpl implements LinksInfo {
-
- private final CharSequence mOriginalText;
- private final List<SpanSpec> mSpans;
-
- LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
- mOriginalText = originalText;
- mSpans = spans;
- }
-
- @Override
- public boolean apply(@NonNull CharSequence text) {
- Preconditions.checkArgument(text != null);
- if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
- Spannable spannable = (Spannable) text;
- final int size = mSpans.size();
- for (int i = 0; i < size; i++) {
- final SpanSpec span = mSpans.get(i);
- spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
- }
- return true;
- }
- return false;
- }
- }
-
- /**
- * Span plus its start and end index.
- */
- private static final class SpanSpec {
-
- private final int mStart;
- private final int mEnd;
- private final ClickableSpan mSpan;
-
- SpanSpec(int start, int end, ClickableSpan span) {
- mStart = start;
- mEnd = end;
- mSpan = span;
- }
- }
- }
-
- /**
* Creates intents based on the classification type.
*/
private static final class IntentFactory {
@@ -656,8 +513,8 @@ final class TextClassifierImpl implements TextClassifier {
intents.add(new Intent(Intent.ACTION_SENDTO)
.setData(Uri.parse(String.format("mailto:%s", text))));
intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
- .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
break;
case TextClassifier.TYPE_PHONE:
intents.add(new Intent(Intent.ACTION_DIAL)
diff --git a/android/view/textclassifier/TextLinks.java b/android/view/textclassifier/TextLinks.java
new file mode 100644
index 00000000..f3cc827f
--- /dev/null
+++ b/android/view/textclassifier/TextLinks.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.textclassifier;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
+import android.text.SpannableString;
+import android.text.style.ClickableSpan;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * A collection of links, representing subsequences of text and the entity types (phone number,
+ * address, url, etc) they may be.
+ */
+public final class TextLinks {
+ private final String mFullText;
+ private final List<TextLink> mLinks;
+
+ private TextLinks(String fullText, Collection<TextLink> links) {
+ mFullText = fullText;
+ mLinks = Collections.unmodifiableList(new ArrayList<>(links));
+ }
+
+ /**
+ * Returns an unmodifiable Collection of the links.
+ */
+ public Collection<TextLink> getLinks() {
+ return mLinks;
+ }
+
+ /**
+ * Annotates the given text with the generated links. It will fail if the provided text doesn't
+ * match the original text used to crete the TextLinks.
+ *
+ * @param text the text to apply the links to. Must match the original text.
+ * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null.
+ *
+ * @return Success or failure.
+ */
+ public boolean apply(
+ @NonNull SpannableString text,
+ @Nullable Function<TextLink, ClickableSpan> spanFactory) {
+ Preconditions.checkNotNull(text);
+ if (!mFullText.equals(text.toString())) {
+ return false;
+ }
+
+ if (spanFactory == null) {
+ spanFactory = DEFAULT_SPAN_FACTORY;
+ }
+ for (TextLink link : mLinks) {
+ final ClickableSpan span = spanFactory.apply(link);
+ if (span != null) {
+ text.setSpan(span, link.getStart(), link.getEnd(), 0);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * A link, identifying a substring of text and possible entity types for it.
+ */
+ public static final class TextLink {
+ private final EntityConfidence<String> mEntityScores;
+ private final String mOriginalText;
+ private final int mStart;
+ private final int mEnd;
+
+ /**
+ * Create a new TextLink.
+ *
+ * @throws IllegalArgumentException if entityScores is null or empty.
+ */
+ public TextLink(String originalText, int start, int end, Map<String, Float> entityScores) {
+ Preconditions.checkNotNull(originalText);
+ Preconditions.checkNotNull(entityScores);
+ Preconditions.checkArgument(!entityScores.isEmpty());
+ Preconditions.checkArgument(start <= end);
+ mOriginalText = originalText;
+ mStart = start;
+ mEnd = end;
+ mEntityScores = new EntityConfidence<>();
+
+ for (Map.Entry<String, Float> entry : entityScores.entrySet()) {
+ mEntityScores.setEntityType(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Returns the start index of this link in the original text.
+ *
+ * @return the start index.
+ */
+ public int getStart() {
+ return mStart;
+ }
+
+ /**
+ * Returns the end index of this link in the original text.
+ *
+ * @return the end index.
+ */
+ public int getEnd() {
+ return mEnd;
+ }
+
+ /**
+ * Returns the number of entity types that have confidence scores.
+ *
+ * @return the entity count.
+ */
+ public int getEntityCount() {
+ return mEntityScores.getEntities().size();
+ }
+
+ /**
+ * Returns the entity type at a given index. Entity types are sorted by confidence.
+ *
+ * @return the entity type at the provided index.
+ */
+ @NonNull public @TextClassifier.EntityType String getEntity(int index) {
+ return mEntityScores.getEntities().get(index);
+ }
+
+ /**
+ * Returns the confidence score for a particular entity type.
+ *
+ * @param entityType the entity type.
+ */
+ public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore(
+ @TextClassifier.EntityType String entityType) {
+ return mEntityScores.getConfidenceScore(entityType);
+ }
+ }
+
+ /**
+ * Optional input parameters for generating TextLinks.
+ */
+ public static final class Options {
+ private final LocaleList mLocaleList;
+
+ private Options(LocaleList localeList) {
+ this.mLocaleList = localeList;
+ }
+
+ /**
+ * Builder to construct Options.
+ */
+ public static final class Builder {
+ private LocaleList mLocaleList;
+
+ /**
+ * Sets the LocaleList to use.
+ *
+ * @return this Builder.
+ */
+ public Builder setLocaleList(@Nullable LocaleList localeList) {
+ this.mLocaleList = localeList;
+ return this;
+ }
+
+ /**
+ * Builds the Options object.
+ */
+ public Options build() {
+ return new Options(mLocaleList);
+ }
+ }
+ public @Nullable LocaleList getDefaultLocales() {
+ return mLocaleList;
+ }
+ };
+
+ /**
+ * A function to create spans from TextLinks.
+ *
+ * Applies only to TextViews.
+ * We can hide this until we are convinced we want it to be part of the public API.
+ *
+ * @hide
+ */
+ public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY =
+ new Function<TextLink, ClickableSpan>() {
+ @Override
+ public ClickableSpan apply(TextLink textLink) {
+ // TODO: Implement.
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+ };
+
+ /**
+ * A builder to construct a TextLinks instance.
+ */
+ public static final class Builder {
+ private final String mFullText;
+ private final Collection<TextLink> mLinks;
+
+ /**
+ * Create a new TextLinks.Builder.
+ *
+ * @param fullText The full text that links will be added to.
+ */
+ public Builder(@NonNull String fullText) {
+ mFullText = Preconditions.checkNotNull(fullText);
+ mLinks = new ArrayList<>();
+ }
+
+ /**
+ * Adds a TextLink.
+ *
+ * @return this instance.
+ */
+ public Builder addLink(TextLink link) {
+ Preconditions.checkNotNull(link);
+ mLinks.add(link);
+ return this;
+ }
+
+ /**
+ * Constructs a TextLinks instance.
+ *
+ * @return the constructed TextLinks.
+ */
+ public TextLinks build() {
+ return new TextLinks(mFullText, mLinks);
+ }
+ }
+}
diff --git a/android/view/textclassifier/TextSelection.java b/android/view/textclassifier/TextSelection.java
index 11ebe835..0a67954a 100644
--- a/android/view/textclassifier/TextSelection.java
+++ b/android/view/textclassifier/TextSelection.java
@@ -19,6 +19,8 @@ package android.view.textclassifier;
import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.LocaleList;
import android.view.textclassifier.TextClassifier.EntityType;
import com.android.internal.util.Preconditions;
@@ -181,4 +183,55 @@ public final class TextSelection {
mStartIndex, mEndIndex, mEntityConfidence, mLogSource, mVersionInfo);
}
}
+
+ /**
+ * TextSelection optional input parameters.
+ */
+ public static final class Options {
+
+ private LocaleList mDefaultLocales;
+ private boolean mDarkLaunchAllowed;
+
+ /**
+ * @param defaultLocales ordered list of locale preferences that may be used to disambiguate
+ * the provided text. If no locale preferences exist, set this to null or an empty
+ * locale list.
+ */
+ public Options setDefaultLocales(@Nullable LocaleList defaultLocales) {
+ mDefaultLocales = defaultLocales;
+ return this;
+ }
+
+ /**
+ * @return ordered list of locale preferences that can be used to disambiguate
+ * the provided text.
+ */
+ @Nullable
+ public LocaleList getDefaultLocales() {
+ return mDefaultLocales;
+ }
+
+ /**
+ * @param allowed whether or not the TextClassifier should return selection suggestions
+ * when "dark launched". When a TextClassifier is dark launched, it can suggest
+ * selection changes that should not be used to actually change the user's selection.
+ * Instead, the suggested selection is logged, compared with the user's selection
+ * interaction, and used to generate quality metrics for the TextClassifier.
+ *
+ * @hide
+ */
+ public void setDarkLaunchAllowed(boolean allowed) {
+ mDarkLaunchAllowed = allowed;
+ }
+
+ /**
+ * Returns true if the TextClassifier should return selection suggestions when
+ * "dark launched". Otherwise, returns false.
+ *
+ * @hide
+ */
+ public boolean isDarkLaunchAllowed() {
+ return mDarkLaunchAllowed;
+ }
+ }
}
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 83af19bb..2833564f 100644
--- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -48,31 +48,45 @@ public final class SmartSelectionEventTracker {
private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
- private static final int VERSION_TAG = MetricsEvent.FIELD_SELECTION_VERSION_TAG;
- private static final int SMART_INDICES = MetricsEvent.FIELD_SELECTION_SMART_RANGE;
- private static final int EVENT_INDICES = MetricsEvent.FIELD_SELECTION_RANGE;
+ private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
+ private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION;
+ private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
+ private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
+ private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START;
+ private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END;
+ private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START;
+ private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END;
private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
private static final String ZERO = "0";
private static final String TEXTVIEW = "textview";
private static final String EDITTEXT = "edittext";
+ private static final String UNSELECTABLE_TEXTVIEW = "nosel-textview";
private static final String WEBVIEW = "webview";
private static final String EDIT_WEBVIEW = "edit-webview";
+ private static final String CUSTOM_TEXTVIEW = "customview";
+ private static final String CUSTOM_EDITTEXT = "customedit";
+ private static final String CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
private static final String UNKNOWN = "unknown";
@Retention(RetentionPolicy.SOURCE)
@IntDef({WidgetType.UNSPECIFIED, WidgetType.TEXTVIEW, WidgetType.WEBVIEW,
WidgetType.EDITTEXT, WidgetType.EDIT_WEBVIEW})
public @interface WidgetType {
- int UNSPECIFIED = 0;
- int TEXTVIEW = 1;
- int WEBVIEW = 2;
- int EDITTEXT = 3;
- int EDIT_WEBVIEW = 4;
+ int UNSPECIFIED = 0;
+ int TEXTVIEW = 1;
+ int WEBVIEW = 2;
+ int EDITTEXT = 3;
+ int EDIT_WEBVIEW = 4;
+ int UNSELECTABLE_TEXTVIEW = 5;
+ int CUSTOM_TEXTVIEW = 6;
+ int CUSTOM_EDITTEXT = 7;
+ int CUSTOM_UNSELECTABLE_TEXTVIEW = 8;
}
private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final int mWidgetType;
+ @Nullable private final String mWidgetVersion;
private final Context mContext;
@Nullable private String mSessionId;
@@ -83,10 +97,18 @@ public final class SmartSelectionEventTracker {
private long mSessionStartTime;
private long mLastEventTime;
private boolean mSmartSelectionTriggered;
- private String mVersionTag;
+ private String mModelName;
public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
mWidgetType = widgetType;
+ mWidgetVersion = null;
+ mContext = Preconditions.checkNotNull(context);
+ }
+
+ public SmartSelectionEventTracker(
+ @NonNull Context context, @WidgetType int widgetType, @Nullable String widgetVersion) {
+ mWidgetType = widgetType;
+ mWidgetVersion = widgetVersion;
mContext = Preconditions.checkNotNull(context);
}
@@ -115,7 +137,7 @@ public final class SmartSelectionEventTracker {
case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through
case SelectionEvent.EventType.SMART_SELECTION_MULTI:
mSmartSelectionTriggered = true;
- mVersionTag = getVersionTag(event);
+ mModelName = getModelName(event);
mSmartIndices[0] = event.mStart;
mSmartIndices[1] = event.mEnd;
break;
@@ -137,14 +159,19 @@ public final class SmartSelectionEventTracker {
final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime;
final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
.setType(getLogType(event))
- .setSubtype(getLogSubType(event))
+ .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL)
.setPackageName(mContext.getPackageName())
.addTaggedData(START_EVENT_DELTA, now - mSessionStartTime)
.addTaggedData(PREV_EVENT_DELTA, prevEventDelta)
.addTaggedData(INDEX, mIndex)
- .addTaggedData(VERSION_TAG, mVersionTag)
- .addTaggedData(SMART_INDICES, getSmartDelta())
- .addTaggedData(EVENT_INDICES, getEventDelta(event))
+ .addTaggedData(WIDGET_TYPE, getWidgetTypeName())
+ .addTaggedData(WIDGET_VERSION, mWidgetVersion)
+ .addTaggedData(MODEL_NAME, mModelName)
+ .addTaggedData(ENTITY_TYPE, event.mEntityType)
+ .addTaggedData(SMART_START, getSmartRangeDelta(mSmartIndices[0]))
+ .addTaggedData(SMART_END, getSmartRangeDelta(mSmartIndices[1]))
+ .addTaggedData(EVENT_START, getRangeDelta(event.mStart))
+ .addTaggedData(EVENT_END, getRangeDelta(event.mEnd))
.addTaggedData(SESSION_ID, mSessionId);
mMetricsLogger.write(log);
debugLog(log);
@@ -169,7 +196,7 @@ public final class SmartSelectionEventTracker {
mSessionStartTime = 0;
mLastEventTime = 0;
mSmartSelectionTriggered = false;
- mVersionTag = getVersionTag(null);
+ mModelName = getModelName(null);
mSessionId = null;
}
@@ -251,113 +278,75 @@ public final class SmartSelectionEventTracker {
}
}
- private static int getLogSubType(SelectionEvent event) {
- switch (event.mEntityType) {
- case TextClassifier.TYPE_OTHER:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER;
- case TextClassifier.TYPE_EMAIL:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL;
- case TextClassifier.TYPE_PHONE:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE;
- case TextClassifier.TYPE_ADDRESS:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS;
- case TextClassifier.TYPE_URL:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_URL;
- default:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_UNKNOWN;
- }
- }
-
- private static String getLogSubTypeString(int logSubType) {
- switch (logSubType) {
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER:
- return TextClassifier.TYPE_OTHER;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL:
- return TextClassifier.TYPE_EMAIL;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE:
- return TextClassifier.TYPE_PHONE;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS:
- return TextClassifier.TYPE_ADDRESS;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_URL:
- return TextClassifier.TYPE_URL;
- default:
- return TextClassifier.TYPE_UNKNOWN;
- }
- }
-
- private int getSmartDelta() {
- if (mSmartSelectionTriggered) {
- return (clamp(mSmartIndices[0] - mOrigStart) << 16)
- | (clamp(mSmartIndices[1] - mOrigStart) & 0xffff);
- }
- // If the smart selection model was not run, return invalid selection indices [0,0]. This
- // allows us to tell from the terminal event alone whether the model was run.
- return 0;
+ private int getRangeDelta(int offset) {
+ return offset - mOrigStart;
}
- private int getEventDelta(SelectionEvent event) {
- return (clamp(event.mStart - mOrigStart) << 16)
- | (clamp(event.mEnd - mOrigStart) & 0xffff);
+ private int getSmartRangeDelta(int offset) {
+ return mSmartSelectionTriggered ? getRangeDelta(offset) : 0;
}
- private String getVersionTag(@Nullable SelectionEvent event) {
- final String widgetType;
+ private String getWidgetTypeName() {
switch (mWidgetType) {
case WidgetType.TEXTVIEW:
- widgetType = TEXTVIEW;
- break;
+ return TEXTVIEW;
case WidgetType.WEBVIEW:
- widgetType = WEBVIEW;
- break;
+ return WEBVIEW;
case WidgetType.EDITTEXT:
- widgetType = EDITTEXT;
- break;
+ return EDITTEXT;
case WidgetType.EDIT_WEBVIEW:
- widgetType = EDIT_WEBVIEW;
- break;
+ return EDIT_WEBVIEW;
+ case WidgetType.UNSELECTABLE_TEXTVIEW:
+ return UNSELECTABLE_TEXTVIEW;
+ case WidgetType.CUSTOM_TEXTVIEW:
+ return CUSTOM_TEXTVIEW;
+ case WidgetType.CUSTOM_EDITTEXT:
+ return CUSTOM_EDITTEXT;
+ case WidgetType.CUSTOM_UNSELECTABLE_TEXTVIEW:
+ return CUSTOM_UNSELECTABLE_TEXTVIEW;
default:
- widgetType = UNKNOWN;
+ return UNKNOWN;
}
- final String version = event == null
+ }
+
+ private String getModelName(@Nullable SelectionEvent event) {
+ return event == null
? SelectionEvent.NO_VERSION_TAG
: Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
- return String.format("%s/%s", widgetType, version);
}
private static String createSessionId() {
return UUID.randomUUID().toString();
}
- private static int clamp(int val) {
- return Math.max(Math.min(val, Short.MAX_VALUE), Short.MIN_VALUE);
- }
-
private static void debugLog(LogMaker log) {
if (!DEBUG_LOG_ENABLED) return;
- final String tag = Objects.toString(log.getTaggedData(VERSION_TAG), "tag");
+ final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
+ final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), "");
+ final String widget = widgetVersion.isEmpty()
+ ? widgetType : widgetType + "-" + widgetVersion;
final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
- Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
+ Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId));
}
+ final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN);
+ final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN);
final String type = getLogTypeString(log.getType());
- final String subType = getLogSubTypeString(log.getSubtype());
-
- final int smartIndices = Integer.parseInt(
- Objects.toString(log.getTaggedData(SMART_INDICES), ZERO));
- final int smartStart = (short) ((smartIndices & 0xffff0000) >> 16);
- final int smartEnd = (short) (smartIndices & 0xffff);
-
- final int eventIndices = Integer.parseInt(
- Objects.toString(log.getTaggedData(EVENT_INDICES), ZERO));
- final int eventStart = (short) ((eventIndices & 0xffff0000) >> 16);
- final int eventEnd = (short) (eventIndices & 0xffff);
-
- Log.d(LOG_TAG, String.format("%2d: %s/%s, context=%d,%d - old=%d,%d (%s)",
- index, type, subType, eventStart, eventEnd, smartStart, smartEnd, tag));
+ final int smartStart = Integer.parseInt(
+ Objects.toString(log.getTaggedData(SMART_START), ZERO));
+ final int smartEnd = Integer.parseInt(
+ Objects.toString(log.getTaggedData(SMART_END), ZERO));
+ final int eventStart = Integer.parseInt(
+ Objects.toString(log.getTaggedData(EVENT_START), ZERO));
+ final int eventEnd = Integer.parseInt(
+ Objects.toString(log.getTaggedData(EVENT_END), ZERO));
+
+ Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
+ index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model));
}
/**
@@ -369,12 +358,12 @@ public final class SmartSelectionEventTracker {
/**
* Use this to specify an indeterminate positive index.
*/
- public static final int OUT_OF_BOUNDS = Short.MAX_VALUE;
+ public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE;
/**
* Use this to specify an indeterminate negative index.
*/
- public static final int OUT_OF_BOUNDS_NEGATIVE = Short.MIN_VALUE;
+ public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE;
private static final String NO_VERSION_TAG = "";
diff --git a/android/webkit/ServiceWorkerClient.java b/android/webkit/ServiceWorkerClient.java
index d6e8f36c..9124c855 100644
--- a/android/webkit/ServiceWorkerClient.java
+++ b/android/webkit/ServiceWorkerClient.java
@@ -29,9 +29,9 @@ public class ServiceWorkerClient {
* application to return the data. If the return value is {@code null}, the
* Service Worker will continue to load the resource as usual.
* Otherwise, the return response and data will be used.
- * NOTE: This method is called on a thread other than the UI thread
- * so clients should exercise caution when accessing private data
- * or the view system.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread other than the UI thread so
+ * clients should exercise caution when accessing private data or the view system.
*
* @param request Object containing the details of the request.
* @return A {@link android.webkit.WebResourceResponse} containing the
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 259bf60a..665d694e 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -191,7 +191,7 @@ import java.util.Map;
* {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
* (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
*
- * <p>NOTE: Using zoom if either the height or width is set to
+ * <p class="note"><b>Note:</b> Using zoom if either the height or width is set to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
* and should be avoided.
*
@@ -308,10 +308,15 @@ import java.util.Map;
* WebView may upload anonymous diagnostic data to Google when the user has consented. This data
* helps Google improve WebView. Data is collected on a per-app basis for each app which has
* instantiated a WebView. An individual app can opt out of this feature by putting the following
- * tag in its manifest:
+ * tag in its manifest's {@code <application>} element:
* <pre>
- * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
- * android:value="true" /&gt;
+ * &lt;manifest&gt;
+ * &lt;application&gt;
+ * ...
+ * &lt;meta-data android:name=&quot;android.webkit.WebView.MetricsOptOut&quot;
+ * android:value=&quot;true&quot; /&gt;
+ * &lt;/application&gt;
+ * &lt;/manifest&gt;
* </pre>
* <p>
* Data will only be uploaded for a given app if the user has consented AND the app has not opted
@@ -323,11 +328,17 @@ import java.util.Map;
* If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
* user to allow them to navigate back safely or proceed to the malicious page.
* <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest:
+ * The recommended way for apps to enable the feature is putting the following tag in the manifest's
+ * {@code <application>} element:
* <p>
* <pre>
- * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- * android:value="true" /&gt;
+ * &lt;manifest&gt;
+ * &lt;application&gt;
+ * ...
+ * &lt;meta-data android:name=&quot;android.webkit.WebView.EnableSafeBrowsing&quot;
+ * android:value=&quot;true&quot; /&gt;
+ * &lt;/application&gt;
+ * &lt;/manifest&gt;
* </pre>
*
*/
@@ -536,9 +547,13 @@ public class WebView extends AbsoluteLayout
}
/**
- * Constructs a new WebView with a Context object.
+ * Constructs a new WebView with an Activity Context object.
*
- * @param context a Context object used to access application assets
+ * <p class="note"><b>Note:</b> WebView should always be instantiated with an Activity Context.
+ * If instantiated with an Application Context, WebView will be unable to provide several
+ * features, such as JavaScript dialogs and autofill.
+ *
+ * @param context an Activity Context to access application assets
*/
public WebView(Context context) {
this(context, null);
@@ -547,7 +562,7 @@ public class WebView extends AbsoluteLayout
/**
* Constructs a new WebView with layout parameters.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
*/
public WebView(Context context, AttributeSet attrs) {
@@ -557,7 +572,7 @@ public class WebView extends AbsoluteLayout
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -570,7 +585,7 @@ public class WebView extends AbsoluteLayout
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -587,7 +602,7 @@ public class WebView extends AbsoluteLayout
/**
* Constructs a new WebView with layout parameters and a default style.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
@@ -612,7 +627,7 @@ public class WebView extends AbsoluteLayout
* time. This guarantees that these interfaces will be available when the JS
* context is initialized.
*
- * @param context a Context object used to access application assets
+ * @param context an Activity Context to access application assets
* @param attrs an AttributeSet passed to our parent
* @param defStyleAttr an attribute in the current theme that contains a
* reference to a style resource that supplies default values for
diff --git a/android/webkit/WebViewClient.java b/android/webkit/WebViewClient.java
index c5b64eb8..517ad07c 100644
--- a/android/webkit/WebViewClient.java
+++ b/android/webkit/WebViewClient.java
@@ -130,7 +130,7 @@ public class WebViewClient {
* <p>This method is called when the body of the HTTP response has started loading, is reflected
* in the DOM, and will be visible in subsequent draws. This callback occurs early in the
* document loading process, and as such you should expect that linked resources (for example,
- * css and images) may not be available.
+ * CSS and images) may not be available.
*
* <p>For more fine-grained notification of visual state updates, see {@link
* WebView#postVisualStateCallback}.
@@ -150,13 +150,15 @@ public class WebViewClient {
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is {@code null}, the WebView
* will continue to load the resource as usual. Otherwise, the return
- * response and data will be used. NOTE: This method is called on a thread
+ * response and data will be used.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread
* other than the UI thread so clients should exercise caution
* when accessing private data or the view system.
*
- * <p>Note: when Safe Browsing is enabled, these URLs still undergo Safe Browsing checks. If
- * this is undesired, whitelist the URL with {@link WebView#setSafeBrowsingWhitelist} or ignore
- * the warning with {@link #onSafeBrowsingHit}.
+ * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
+ * Browsing checks. If this is undesired, whitelist the URL with {@link
+ * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}.
*
* @param view The {@link android.webkit.WebView} that is requesting the
* resource.
@@ -178,13 +180,15 @@ public class WebViewClient {
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is {@code null}, the WebView
* will continue to load the resource as usual. Otherwise, the return
- * response and data will be used. NOTE: This method is called on a thread
+ * response and data will be used.
+ *
+ * <p class="note"><b>Note:</b> This method is called on a thread
* other than the UI thread so clients should exercise caution
* when accessing private data or the view system.
*
- * <p>Note: when Safe Browsing is enabled, these URLs still undergo Safe Browsing checks. If
- * this is undesired, whitelist the URL with {@link WebView#setSafeBrowsingWhitelist} or ignore
- * the warning with {@link #onSafeBrowsingHit}.
+ * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
+ * Browsing checks. If this is undesired, whitelist the URL with {@link
+ * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}.
*
* @param view The {@link android.webkit.WebView} that is requesting the
* resource.
@@ -248,7 +252,7 @@ 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;
- /** Resource load was cancelled by Safe Browsing */
+ /** Resource load was canceled by Safe Browsing */
public static final int ERROR_UNSAFE_RESOURCE = -16;
/** @hide */
@@ -272,8 +276,8 @@ public class WebViewClient {
/**
* Report an error to the host application. These errors are unrecoverable
- * (i.e. the main resource is unavailable). The errorCode parameter
- * corresponds to one of the ERROR_* constants.
+ * (i.e. the main resource is unavailable). The {@code errorCode} parameter
+ * corresponds to one of the {@code ERROR_*} constants.
* @param view The WebView that is initiating the callback.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
@@ -289,11 +293,11 @@ public class WebViewClient {
/**
* 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
+ * 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.
+ * @param error Information about the error occurred.
*/
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
if (request.isForMainFrame()) {
@@ -306,12 +310,12 @@ public class WebViewClient {
/**
* 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.
+ * 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 {@code errorResponse} parameter.
* @param view The WebView that is initiating the callback.
* @param request The originating request.
- * @param errorResponse Information about the error occured.
+ * @param errorResponse Information about the error occurred.
*/
public void onReceivedHttpError(
WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
@@ -365,7 +369,7 @@ public class WebViewClient {
* if desired and providing the keys. There are three ways to
* respond: proceed(), cancel() or ignore(). Webview stores the response
* in memory (for the life of the application) if proceed() or cancel() is
- * called and does not call onReceivedClientCertRequest() again for the
+ * called and does not call {@code onReceivedClientCertRequest()} again for the
* same host and port pair. Webview does not store the response if ignore()
* is called. Note that, multiple layers in chromium network stack might be
* caching the responses, so the behavior for ignore is only a best case
@@ -432,7 +436,7 @@ public class WebViewClient {
/**
* Notify the host application that a key was not handled by the WebView.
* Except system keys, WebView always consumes the keys in the normal flow
- * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+ * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
* from where the key is dispatched. It gives the host application a chance
* to handle the unhandled key events.
*
@@ -446,7 +450,7 @@ public class WebViewClient {
/**
* Notify the host application that a input event was not handled by the WebView.
* Except system keys, WebView always consumes input events in the normal flow
- * or if shouldOverrideKeyEvent returns {@code true}. This is called asynchronously
+ * or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
* from where the event is dispatched. It gives the host application a chance
* to handle the unhandled input events.
*
@@ -503,7 +507,7 @@ public class WebViewClient {
}
/**
- * Notify host application that the given webview's render process has exited.
+ * Notify host application that the given WebView's render process has exited.
*
* Multiple WebView instances may be associated with a single render process;
* onRenderProcessGone will be called for each WebView that was affected.
@@ -513,10 +517,10 @@ public class WebViewClient {
*
* The given WebView can't be used, and should be removed from the view hierarchy,
* all references to it should be cleaned up, e.g any references in the Activity
- * or other classes saved using findViewById and similar calls, etc
+ * or other classes saved using {@link android.view.View#findViewById} and similar calls, etc.
*
* To cause an render process crash for test purpose, the application can
- * call loadUrl("chrome://crash") on the WebView. Note that multiple WebView
+ * call {@code loadUrl("chrome://crash")} on the WebView. Note that multiple WebView
* instances may be affected if they share a render process, not just the
* specific WebView which loaded chrome://crash.
*
@@ -537,12 +541,13 @@ public class WebViewClient {
* behavior is to show an interstitial to the user, with the reporting checkbox visible.
*
* If the application needs to show its own custom interstitial UI, the callback can be invoked
- * asynchronously with backToSafety() or proceed(), depending on user response.
+ * asynchronously with {@link SafeBrowsingResponse#backToSafety} or {@link
+ * SafeBrowsingResponse#proceed}, depending on user response.
*
* @param view The WebView that hit the malicious resource.
* @param request Object containing the details of the request.
* @param threatType The reason the resource was caught by Safe Browsing, corresponding to a
- * SAFE_BROWSING_THREAT_* value.
+ * {@code SAFE_BROWSING_THREAT_*} value.
* @param callback Applications must invoke one of the callback methods.
*/
public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
diff --git a/android/webkit/WebViewFragment.java b/android/webkit/WebViewFragment.java
index d803f62d..e5b7c8d2 100644
--- a/android/webkit/WebViewFragment.java
+++ b/android/webkit/WebViewFragment.java
@@ -27,7 +27,10 @@ import android.webkit.WebView;
* A fragment that displays a WebView.
* <p>
* The WebView is automically paused or resumed when the Fragment is paused or resumed.
+ *
+ * @deprecated Manually call {@link WebView#onPause()} and {@link WebView#onResume()}
*/
+@Deprecated
public class WebViewFragment extends Fragment {
private WebView mWebView;
private boolean mIsWebViewAvailable;
diff --git a/android/webkit/WebViewLibraryLoader.java b/android/webkit/WebViewLibraryLoader.java
index 341c69fd..de0b97d1 100644
--- a/android/webkit/WebViewLibraryLoader.java
+++ b/android/webkit/WebViewLibraryLoader.java
@@ -229,7 +229,9 @@ public class WebViewLibraryLoader {
/**
* Load WebView's native library into the current process.
- * Note: assumes that we have waited for relro creation.
+ *
+ * <p class="note"><b>Note:</b> Assumes that we have waited for relro creation.
+ *
* @param clazzLoader class loader used to find the linker namespace to load the library into.
* @param packageInfo the package from which WebView is loaded.
*/
diff --git a/android/webkit/WebViewProvider.java b/android/webkit/WebViewProvider.java
index c46c681c..a8969252 100644
--- a/android/webkit/WebViewProvider.java
+++ b/android/webkit/WebViewProvider.java
@@ -316,7 +316,7 @@ public interface WebViewProvider {
/**
* Provides mechanism for the name-sake methods declared in View and ViewGroup to be delegated
* into the WebViewProvider instance.
- * NOTE For many of these methods, the WebView will provide a super.Foo() call before or after
+ * NOTE: For many of these methods, the WebView will provide a super.Foo() call before or after
* making the call into the provider instance. This is done for convenience in the common case
* of maintaining backward compatibility. For remaining super class calls (e.g. where the
* provider may need to only conditionally make the call based on some internal state) see the
diff --git a/android/widget/AbsListView.java b/android/widget/AbsListView.java
index 170582b3..e0c897d3 100644
--- a/android/widget/AbsListView.java
+++ b/android/widget/AbsListView.java
@@ -3866,6 +3866,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private void onTouchDown(MotionEvent ev) {
mHasPerformedLongPress = false;
mActivePointerId = ev.getPointerId(0);
+ hideSelector();
if (mTouchMode == TOUCH_MODE_OVERFLING) {
// Stopped the fling. It is a scroll.
@@ -5226,17 +5227,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
mRecycler.fullyDetachScrapViews();
+ boolean selectorOnScreen = false;
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(mSelectedPosition, getChildAt(childIndex));
+ selectorOnScreen = true;
}
} else if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
- positionSelector(INVALID_POSITION, getChildAt(childIndex));
+ positionSelector(mSelectorPosition, getChildAt(childIndex));
+ selectorOnScreen = true;
}
- } else {
+ }
+ if (!selectorOnScreen) {
mSelectorRect.setEmpty();
}
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index e6da69dc..d477ffdf 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -3842,14 +3842,10 @@ public class Editor {
mProcessTextIntentActionsHandler.onInitializeMenu(menu);
}
- if (menu.hasVisibleItems() || mode.getCustomView() != null) {
- if (mHasSelection && !mTextView.hasTransientState()) {
- mTextView.setHasTransientState(true);
- }
- return true;
- } else {
- return false;
+ if (mHasSelection && !mTextView.hasTransientState()) {
+ mTextView.setHasTransientState(true);
}
+ return true;
}
private Callback getCustomCallback() {
@@ -6557,12 +6553,12 @@ public class Editor {
* Adds "PROCESS_TEXT" menu items to the specified menu.
*/
public void onInitializeMenu(Menu menu) {
- final int size = mSupportedActivities.size();
loadSupportedActivities();
+ final int size = mSupportedActivities.size();
for (int i = 0; i < size; i++) {
final ResolveInfo resolveInfo = mSupportedActivities.get(i);
menu.add(Menu.NONE, Menu.NONE,
- Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i++,
+ Editor.MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START + i,
getLabel(resolveInfo))
.setIntent(createProcessTextIntentForResolveInfo(resolveInfo))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java
index 8dc8cab1..e91db139 100644
--- a/android/widget/PopupWindow.java
+++ b/android/widget/PopupWindow.java
@@ -1354,6 +1354,7 @@ public class PopupWindow {
}
mDecorView = createDecorView(mBackgroundView);
+ mDecorView.setIsRootNamespace(true);
// The background owner should be elevated so that it casts a shadow.
mBackgroundView.setElevation(mElevation);
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 5e22650a..d0ad27af 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -20,10 +20,12 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
+import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.LocaleList;
import android.text.Layout;
import android.text.Selection;
@@ -81,6 +83,7 @@ public final class SelectionActionModeHelper {
mEditor = Preconditions.checkNotNull(editor);
mTextView = mEditor.getTextView();
mTextClassificationHelper = new TextClassificationHelper(
+ mTextView.getContext(),
mTextView.getTextClassifier(),
getText(mTextView),
0, 1, mTextView.getTextLocales());
@@ -385,6 +388,7 @@ public final class SelectionActionModeHelper {
private void resetTextClassificationHelper() {
mTextClassificationHelper.init(
+ mTextView.getContext(),
mTextView.getTextClassifier(),
getText(mTextView),
mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
@@ -587,7 +591,9 @@ public final class SelectionActionModeHelper {
Preconditions.checkNotNull(textView);
final @SmartSelectionEventTracker.WidgetType int widgetType = textView.isTextEditable()
? SmartSelectionEventTracker.WidgetType.EDITTEXT
- : SmartSelectionEventTracker.WidgetType.TEXTVIEW;
+ : (textView.isTextSelectable()
+ ? SmartSelectionEventTracker.WidgetType.TEXTVIEW
+ : SmartSelectionEventTracker.WidgetType.UNSELECTABLE_TEXTVIEW);
mDelegate = new SmartSelectionEventTracker(textView.getContext(), widgetType);
mEditTextLogger = textView.isTextEditable();
mWordIterator = BreakIterator.getWordInstance(textView.getTextLocale());
@@ -787,6 +793,7 @@ public final class SelectionActionModeHelper {
private static final int TRIM_DELTA = 120; // characters
+ private Context mContext;
private TextClassifier mTextClassifier;
/** The original TextView text. **/
@@ -795,7 +802,10 @@ public final class SelectionActionModeHelper {
private int mSelectionStart;
/** End index relative to mText. */
private int mSelectionEnd;
- private LocaleList mLocales;
+
+ private final TextSelection.Options mSelectionOptions = new TextSelection.Options();
+ private final TextClassification.Options mClassificationOptions =
+ new TextClassification.Options();
/** Trimmed text starting from mTrimStart in mText. */
private CharSequence mTrimmedText;
@@ -816,21 +826,24 @@ public final class SelectionActionModeHelper {
/** Whether the TextClassifier has been initialized. */
private boolean mHot;
- TextClassificationHelper(TextClassifier textClassifier,
+ TextClassificationHelper(Context context, TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
- init(textClassifier, text, selectionStart, selectionEnd, locales);
+ init(context, textClassifier, text, selectionStart, selectionEnd, locales);
}
@UiThread
- public void init(TextClassifier textClassifier,
+ public void init(Context context, TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
+ mContext = Preconditions.checkNotNull(context);
mTextClassifier = Preconditions.checkNotNull(textClassifier);
mText = Preconditions.checkNotNull(text).toString();
mLastClassificationText = null; // invalidate.
Preconditions.checkArgument(selectionEnd > selectionStart);
mSelectionStart = selectionStart;
mSelectionEnd = selectionEnd;
- mLocales = locales;
+ mClassificationOptions.setDefaultLocales(locales);
+ mSelectionOptions.setDefaultLocales(locales)
+ .setDarkLaunchAllowed(true);
}
@WorkerThread
@@ -843,8 +856,16 @@ public final class SelectionActionModeHelper {
public SelectionResult suggestSelection() {
mHot = true;
trimText();
- final TextSelection selection = mTextClassifier.suggestSelection(
- mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
+ final TextSelection selection;
+ if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+ selection = mTextClassifier.suggestSelection(
+ mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions);
+ } else {
+ // Use old APIs.
+ selection = mTextClassifier.suggestSelection(
+ mTrimmedText, mRelativeStart, mRelativeEnd,
+ mSelectionOptions.getDefaultLocales());
+ }
// Do not classify new selection boundaries if TextClassifier should be dark launched.
if (!mTextClassifier.getSettings().isDarkLaunch()) {
mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
@@ -874,20 +895,28 @@ public final class SelectionActionModeHelper {
if (!Objects.equals(mText, mLastClassificationText)
|| mSelectionStart != mLastClassificationSelectionStart
|| mSelectionEnd != mLastClassificationSelectionEnd
- || !Objects.equals(mLocales, mLastClassificationLocales)) {
+ || !Objects.equals(
+ mClassificationOptions.getDefaultLocales(),
+ mLastClassificationLocales)) {
mLastClassificationText = mText;
mLastClassificationSelectionStart = mSelectionStart;
mLastClassificationSelectionEnd = mSelectionEnd;
- mLastClassificationLocales = mLocales;
+ mLastClassificationLocales = mClassificationOptions.getDefaultLocales();
trimText();
+ final TextClassification classification;
+ if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) {
+ classification = mTextClassifier.classifyText(
+ mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions);
+ } else {
+ // Use old APIs.
+ classification = mTextClassifier.classifyText(
+ mTrimmedText, mRelativeStart, mRelativeEnd,
+ mClassificationOptions.getDefaultLocales());
+ }
mLastClassificationResult = new SelectionResult(
- mSelectionStart,
- mSelectionEnd,
- mTextClassifier.classifyText(
- mTrimmedText, mRelativeStart, mRelativeEnd, mLocales),
- selection);
+ mSelectionStart, mSelectionEnd, classification, selection);
}
return mLastClassificationResult;