summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-10-10 15:20:13 -0400
committerJustin Klaassen <justinklaassen@google.com>2017-10-10 15:20:13 -0400
commit93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6 (patch)
tree49f76f879a89c256a4f65b674086be50760bdffb /android
parentbc81c7ada5aab3806dd0b17498f5c9672c9b33c4 (diff)
downloadandroid-28-93b7ee4fce01df52a6607f0b1965cbafdfeaf1a6.tar.gz
Import Android SDK Platform P [4386628]
/google/data/ro/projects/android/fetch_artifact \ --bid 4386628 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4386628.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: I9b8400ac92116cae4f033d173f7a5682b26ccba9
Diffstat (limited to 'android')
-rw-r--r--android/accounts/AbstractAccountAuthenticator.java3
-rw-r--r--android/accounts/AccountManager.java4
-rw-r--r--android/animation/AnimatorSet.java5
-rw-r--r--android/annotation/NavigationRes.java37
-rw-r--r--android/app/Activity.java13
-rw-r--r--android/app/ActivityManager.java243
-rw-r--r--android/app/ActivityOptions.java51
-rw-r--r--android/app/ActivityThread.java4
-rw-r--r--android/app/ContextImpl.java15
-rw-r--r--android/app/KeyguardManager.java2
-rw-r--r--android/app/Notification.java64
-rw-r--r--android/app/NotificationChannel.java119
-rw-r--r--android/app/NotificationChannelGroup.java12
-rw-r--r--android/app/NotificationManager.java22
-rw-r--r--android/app/SharedElementCallback.java6
-rw-r--r--android/app/StatusBarManager.java13
-rw-r--r--android/app/SystemServiceRegistry.java16
-rw-r--r--android/app/TaskStackListener.java3
-rw-r--r--android/app/VrManager.java37
-rw-r--r--android/app/WindowConfiguration.java82
-rw-r--r--android/app/admin/DevicePolicyManager.java56
-rw-r--r--android/app/assist/AssistStructure.java31
-rw-r--r--android/app/job/JobInfo.java48
-rw-r--r--android/app/timezone/RulesUpdaterContract.java7
-rw-r--r--android/appwidget/AppWidgetHostView.java131
-rw-r--r--android/arch/core/executor/ArchTaskExecutor.java (renamed from android/arch/core/executor/AppToolkitTaskExecutor.java)14
-rw-r--r--android/arch/core/executor/JunitTaskExecutorRule.java4
-rw-r--r--android/arch/core/executor/testing/CountingTaskExecutorRule.java6
-rw-r--r--android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java6
-rw-r--r--android/arch/core/executor/testing/InstantTaskExecutorRule.java6
-rw-r--r--android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java6
-rw-r--r--android/arch/lifecycle/ClassesInfoCache.java237
-rw-r--r--android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java44
-rw-r--r--android/arch/lifecycle/ComputableLiveData.java139
-rw-r--r--android/arch/lifecycle/ComputableLiveDataTest.java10
-rw-r--r--android/arch/lifecycle/DefaultLifecycleObserver.java100
-rw-r--r--android/arch/lifecycle/FragmentInBackStackLifecycleTest.java10
-rw-r--r--android/arch/lifecycle/FragmentOperationsLifecycleTest.java9
-rw-r--r--android/arch/lifecycle/FullLifecycleObserver.java32
-rw-r--r--android/arch/lifecycle/FullLifecycleObserverAdapter.java52
-rw-r--r--android/arch/lifecycle/FullLifecycleObserverTest.java89
-rw-r--r--android/arch/lifecycle/GeneratedAdapter.java38
-rw-r--r--android/arch/lifecycle/GeneratedAdaptersTest.java212
-rw-r--r--android/arch/lifecycle/Lifecycle.java22
-rw-r--r--android/arch/lifecycle/Lifecycling.java165
-rw-r--r--android/arch/lifecycle/LifecyclingTest.java83
-rw-r--r--android/arch/lifecycle/LiveData.java411
-rw-r--r--android/arch/lifecycle/LiveDataReactiveStreams.java122
-rw-r--r--android/arch/lifecycle/LiveDataReactiveStreamsTest.java90
-rw-r--r--android/arch/lifecycle/LiveDataTest.java44
-rw-r--r--android/arch/lifecycle/MediatorLiveDataTest.java4
-rw-r--r--android/arch/lifecycle/MethodCallsLogger.java42
-rw-r--r--android/arch/lifecycle/ProcessOwnerTest.java20
-rw-r--r--android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java187
-rw-r--r--android/arch/lifecycle/SingleGeneratedAdapterObserver.java38
-rw-r--r--android/arch/lifecycle/TestUtils.java7
-rw-r--r--android/arch/lifecycle/Transformations.java48
-rw-r--r--android/arch/lifecycle/TransformationsTest.java4
-rw-r--r--android/arch/lifecycle/ViewModelProviderTest.java14
-rw-r--r--android/arch/lifecycle/ViewModelProviders.java29
-rw-r--r--android/arch/lifecycle/ViewModelTest.java3
-rw-r--r--android/arch/lifecycle/activity/EmptyActivity.java7
-rw-r--r--android/arch/lifecycle/activity/FragmentLifecycleActivity.java11
-rw-r--r--android/arch/lifecycle/observers/Base.java28
-rw-r--r--android/arch/lifecycle/observers/Base_LifecycleAdapter.java34
-rw-r--r--android/arch/lifecycle/observers/DerivedSequence1.java23
-rw-r--r--android/arch/lifecycle/observers/DerivedSequence2.java28
-rw-r--r--android/arch/lifecycle/observers/DerivedWithNewMethods.java28
-rw-r--r--android/arch/lifecycle/observers/DerivedWithNoNewMethods.java20
-rw-r--r--android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java29
-rw-r--r--android/arch/lifecycle/observers/Interface1.java27
-rw-r--r--android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java34
-rw-r--r--android/arch/lifecycle/observers/Interface2.java30
-rw-r--r--android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java34
-rw-r--r--android/arch/lifecycle/observers/InterfaceImpl1.java23
-rw-r--r--android/arch/lifecycle/observers/InterfaceImpl2.java28
-rw-r--r--android/arch/lifecycle/observers/InterfaceImpl3.java23
-rw-r--r--android/arch/lifecycle/testapp/FullLifecycleTestActivity.java4
-rw-r--r--android/arch/lifecycle/testapp/LifecycleTestActivity.java4
-rw-r--r--android/arch/lifecycle/testapp/NavigationDialogActivity.java4
-rw-r--r--android/arch/lifecycle/testapp/NavigationTestActivityFirst.java4
-rw-r--r--android/arch/lifecycle/testapp/NavigationTestActivitySecond.java4
-rw-r--r--android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java4
-rw-r--r--android/arch/lifecycle/viewmodeltest/ViewModelActivity.java11
-rw-r--r--android/arch/paging/BoundedDataSource.java4
-rw-r--r--android/arch/paging/ContiguousDataSource.java8
-rw-r--r--android/arch/paging/DataSource.java3
-rw-r--r--android/arch/paging/KeyedDataSource.java67
-rw-r--r--android/arch/paging/PagedList.java119
-rw-r--r--android/arch/paging/PagedListAdapter.java39
-rw-r--r--android/arch/paging/PagedListAdapterHelper.java11
-rw-r--r--android/arch/paging/TestExecutor.java2
-rw-r--r--android/arch/paging/TiledDataSource.java19
-rw-r--r--android/arch/persistence/db/SupportSQLiteOpenHelper.java158
-rw-r--r--android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java3
-rw-r--r--android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java106
-rw-r--r--android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java4
-rw-r--r--android/arch/persistence/db/framework/FrameworkSQLiteStatement.java3
-rw-r--r--android/arch/persistence/room/Entity.java3
-rw-r--r--android/arch/persistence/room/ForeignKey.java2
-rw-r--r--android/arch/persistence/room/InvalidationTracker.java29
-rw-r--r--android/arch/persistence/room/InvalidationTrackerTest.java2
-rw-r--r--android/arch/persistence/room/RoomDatabase.java14
-rw-r--r--android/arch/persistence/room/RoomOpenHelper.java7
-rw-r--r--android/arch/persistence/room/RoomWarnings.java8
-rw-r--r--android/arch/persistence/room/RxRoom.java4
-rw-r--r--android/arch/persistence/room/Transaction.java51
-rw-r--r--android/arch/persistence/room/integration/testapp/CustomerViewModel.java6
-rw-r--r--android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java27
-rw-r--r--android/arch/persistence/room/integration/testapp/TestDatabase.java5
-rw-r--r--android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java50
-rw-r--r--android/arch/persistence/room/integration/testapp/dao/SchoolDao.java6
-rw-r--r--android/arch/persistence/room/integration/testapp/dao/UserDao.java7
-rw-r--r--android/arch/persistence/room/integration/testapp/dao/UserPetDao.java5
-rw-r--r--android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java3
-rw-r--r--android/arch/persistence/room/integration/testapp/database/SampleDatabase.java4
-rw-r--r--android/arch/persistence/room/integration/testapp/migration/MigrationTest.java2
-rw-r--r--android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java8
-rw-r--r--android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java1
-rw-r--r--android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java96
-rw-r--r--android/arch/persistence/room/integration/testapp/test/InvalidationTest.java8
-rw-r--r--android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java7
-rw-r--r--android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java57
-rw-r--r--android/arch/persistence/room/integration/testapp/test/RxJava2Test.java65
-rw-r--r--android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java70
-rw-r--r--android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java22
-rw-r--r--android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java3
-rw-r--r--android/arch/persistence/room/integration/testapp/test/WithClauseTest.java3
-rw-r--r--android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java75
-rw-r--r--android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java27
-rw-r--r--android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java34
-rw-r--r--android/arch/persistence/room/integration/testapp/vo/PetCouple.java2
-rw-r--r--android/arch/persistence/room/migration/TableInfoTest.java41
-rw-r--r--android/arch/persistence/room/package-info.java4
-rw-r--r--android/arch/persistence/room/testing/MigrationTestHelper.java24
-rw-r--r--android/arch/persistence/room/util/TableInfo.java217
-rw-r--r--android/bluetooth/BluetoothAdapter.java23
-rw-r--r--android/bluetooth/BluetoothClass.java46
-rw-r--r--android/bluetooth/BluetoothInputHost.java2
-rw-r--r--android/bluetooth/BluetoothPbap.java46
-rw-r--r--android/bluetooth/BluetoothPbapClient.java2
-rw-r--r--android/bluetooth/BluetoothUuid.java2
-rw-r--r--android/content/ComponentName.java54
-rw-r--r--android/content/ContentProvider.java3
-rw-r--r--android/content/ContentResolver.java36
-rw-r--r--android/content/Context.java38
-rw-r--r--android/content/ContextWrapper.java18
-rw-r--r--android/content/Intent.java23
-rw-r--r--android/content/pm/ActivityInfo.java66
-rw-r--r--android/content/pm/PackageManager.java10
-rw-r--r--android/content/pm/PackageManagerInternal.java90
-rw-r--r--android/content/pm/PermissionInfo.java17
-rw-r--r--android/content/pm/ShortcutInfo.java87
-rw-r--r--android/content/res/Configuration.java64
-rw-r--r--android/content/res/Resources_Theme_Delegate.java9
-rw-r--r--android/database/sqlite/SQLiteConnection.java26
-rw-r--r--android/database/sqlite/SQLiteConnectionPool.java8
-rw-r--r--android/ext/services/notification/Assistant.java165
-rw-r--r--android/ext/services/notification/ChannelImpressions.java137
-rw-r--r--android/graphics/BidiRenderer.java13
-rw-r--r--android/graphics/ImageFormat.java8
-rw-r--r--android/graphics/NinePatch_Delegate.java8
-rw-r--r--android/graphics/PixelFormat.java48
-rw-r--r--android/graphics/RadialGradient_Delegate.java9
-rw-r--r--android/graphics/Shader.java6
-rw-r--r--android/graphics/drawable/RippleBackground.java4
-rw-r--r--android/graphics/drawable/RippleDrawable.java8
-rw-r--r--android/graphics/drawable/RippleForeground.java67
-rw-r--r--android/graphics/drawable/VectorDrawable_Delegate.java4
-rw-r--r--android/hardware/Camera.java66
-rw-r--r--android/hardware/LegacySensorManager.java66
-rw-r--r--android/hardware/Sensor.java16
-rw-r--r--android/hardware/SensorAdditionalInfo.java8
-rw-r--r--android/hardware/SensorEvent.java27
-rw-r--r--android/hardware/SensorListener.java30
-rw-r--r--android/hardware/SensorManager.java189
-rw-r--r--android/hardware/SystemSensorManager.java120
-rw-r--r--android/hardware/camera2/DngCreator.java7
-rw-r--r--android/hardware/display/DisplayManager.java13
-rw-r--r--android/hardware/location/NanoAppInstanceInfo.java4
-rw-r--r--android/media/AmrInputStream.java39
-rw-r--r--android/media/AudioAttributes.java19
-rw-r--r--android/media/AudioManager.java10
-rw-r--r--android/media/AudioPortEventHandler.java26
-rw-r--r--android/media/ExifInterface.java7
-rw-r--r--android/media/MediaCodecInfo.java4
-rw-r--r--android/media/MediaRouter.java70
-rw-r--r--android/media/PlayerBase.java21
-rw-r--r--android/mtp/MtpDatabase.java27
-rw-r--r--android/multiuser/BenchmarkRunner.java13
-rw-r--r--android/net/ConnectivityManager.java18
-rw-r--r--android/net/IpSecAlgorithm.java21
-rw-r--r--android/net/IpSecConfig.java274
-rw-r--r--android/net/IpSecManager.java14
-rw-r--r--android/net/IpSecTransform.java110
-rw-r--r--android/net/LocalServerSocket.java7
-rw-r--r--android/net/ip/ConnectivityPacketTracker.java38
-rw-r--r--android/net/ip/IpManager.java3
-rw-r--r--android/net/metrics/WakeupStats.java6
-rw-r--r--android/net/util/BlockingSocketReader.java216
-rw-r--r--android/net/util/NetworkConstants.java14
-rw-r--r--android/net/wifi/WifiConfiguration.java22
-rw-r--r--android/net/wifi/WifiManager.java3
-rw-r--r--android/net/wifi/WifiScanner.java31
-rw-r--r--android/net/wifi/aware/DiscoverySession.java32
-rw-r--r--android/net/wifi/aware/PeerHandle.java20
-rw-r--r--android/net/wifi/aware/WifiAwareManager.java114
-rw-r--r--android/net/wifi/rtt/RangingRequest.java237
-rw-r--r--android/net/wifi/rtt/RangingResult.java194
-rw-r--r--android/net/wifi/rtt/RangingResultCallback.java56
-rw-r--r--android/net/wifi/rtt/WifiRttManager.java100
-rw-r--r--android/os/BatteryStats.java267
-rw-r--r--android/os/Binder.java45
-rw-r--r--android/os/IServiceManager.java27
-rw-r--r--android/os/Parcel.java191
-rw-r--r--android/os/ParcelableException.java6
-rw-r--r--android/os/PowerManager.java36
-rw-r--r--android/os/ServiceManager.java105
-rw-r--r--android/os/ServiceManagerNative.java102
-rw-r--r--android/os/StrictMode.java106
-rw-r--r--android/os/UserManagerInternal.java12
-rw-r--r--android/provider/Settings.java49
-rw-r--r--android/service/autofill/AutofillService.java16
-rw-r--r--android/service/autofill/AutofillServiceInfo.java5
-rw-r--r--android/service/autofill/Dataset.java196
-rw-r--r--android/service/autofill/ImageTransformation.java118
-rw-r--r--android/service/autofill/InternalSanitizer.java38
-rw-r--r--android/service/autofill/Sanitizer.java26
-rw-r--r--android/service/autofill/SaveCallback.java15
-rw-r--r--android/service/autofill/SaveInfo.java106
-rw-r--r--android/service/autofill/SaveRequest.java5
-rw-r--r--android/service/autofill/TextValueSanitizer.java122
-rw-r--r--android/service/carrier/CarrierService.java26
-rw-r--r--android/service/notification/Adjustment.java9
-rw-r--r--android/service/notification/NotificationAssistantService.java35
-rw-r--r--android/service/notification/NotificationListenerService.java83
-rw-r--r--android/service/notification/NotificationRankingUpdate.java10
-rw-r--r--android/service/notification/NotificationStats.java256
-rw-r--r--android/service/settings/suggestions/Suggestion.java65
-rw-r--r--android/slice/Slice.java347
-rw-r--r--android/slice/SliceItem.java344
-rw-r--r--android/slice/SliceProvider.java156
-rw-r--r--android/slice/SliceQuery.java151
-rw-r--r--android/slice/views/ActionRow.java201
-rw-r--r--android/slice/views/GridView.java186
-rw-r--r--android/slice/views/LargeSliceAdapter.java224
-rw-r--r--android/slice/views/LargeTemplateView.java116
-rw-r--r--android/slice/views/MessageView.java77
-rw-r--r--android/slice/views/RemoteInputView.java445
-rw-r--r--android/slice/views/ShortcutView.java110
-rw-r--r--android/slice/views/SliceView.java249
-rw-r--r--android/slice/views/SliceViewUtil.java182
-rw-r--r--android/slice/views/SmallTemplateView.java211
-rw-r--r--android/support/LibraryVersions.java6
-rw-r--r--android/support/Version.java46
-rw-r--r--android/support/VersionFileWriterTask.java109
-rw-r--r--android/support/annotation/NavigationRes.java36
-rw-r--r--android/support/car/utils/ColumnCalculator.java141
-rw-r--r--android/support/car/widget/CarItemAnimator.java70
-rw-r--r--android/support/car/widget/CarLayoutManager.java1636
-rw-r--r--android/support/car/widget/CarRecyclerView.java204
-rw-r--r--android/support/car/widget/ColumnCardView.java115
-rw-r--r--android/support/car/widget/DayNightStyle.java66
-rw-r--r--android/support/car/widget/PagedListView.java854
-rw-r--r--android/support/car/widget/PagedScrollBarView.java253
-rw-r--r--android/support/customtabs/CustomTabsCallback.java16
-rw-r--r--android/support/customtabs/CustomTabsClient.java15
-rw-r--r--android/support/customtabs/CustomTabsService.java43
-rw-r--r--android/support/customtabs/CustomTabsSession.java50
-rw-r--r--android/support/customtabs/CustomTabsSessionToken.java49
-rw-r--r--android/support/customtabs/TrustedWebUtils.java82
-rw-r--r--android/support/media/ExifInterface.java22
-rw-r--r--android/support/percent/PercentFrameLayout.java1
-rw-r--r--android/support/testutils/AppCompatActivityUtils.java95
-rw-r--r--android/support/testutils/FragmentActivityUtils.java91
-rw-r--r--android/support/testutils/RecreatedActivity.java64
-rw-r--r--android/support/testutils/RecreatedAppCompatActivity.java65
-rw-r--r--android/support/text/emoji/widget/EmojiEditTextHelper.java10
-rw-r--r--android/support/v13/app/FragmentCompat.java58
-rw-r--r--android/support/v13/app/FragmentPagerAdapter.java6
-rw-r--r--android/support/v13/app/FragmentStatePagerAdapter.java6
-rw-r--r--android/support/v14/preference/PreferenceFragment.java4
-rw-r--r--android/support/v17/leanback/app/BaseFragment.java11
-rw-r--r--android/support/v17/leanback/app/BaseRowFragment.java9
-rw-r--r--android/support/v17/leanback/app/BaseRowSupportFragment.java9
-rw-r--r--android/support/v17/leanback/app/BaseSupportFragment.java11
-rw-r--r--android/support/v17/leanback/app/BrandedFragment.java9
-rw-r--r--android/support/v17/leanback/app/BrandedSupportFragment.java9
-rw-r--r--android/support/v17/leanback/app/BrowseFragment.java17
-rw-r--r--android/support/v17/leanback/app/BrowseSupportFragment.java11
-rw-r--r--android/support/v17/leanback/app/DetailsFragment.java8
-rw-r--r--android/support/v17/leanback/app/DetailsFragmentBackgroundController.java5
-rw-r--r--android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java5
-rw-r--r--android/support/v17/leanback/app/ErrorFragment.java3
-rw-r--r--android/support/v17/leanback/app/ErrorSupportFragment.java3
-rw-r--r--android/support/v17/leanback/app/GuidedStepFragment.java17
-rw-r--r--android/support/v17/leanback/app/GuidedStepSupportFragment.java13
-rw-r--r--android/support/v17/leanback/app/HeadersFragment.java7
-rw-r--r--android/support/v17/leanback/app/HeadersSupportFragment.java7
-rw-r--r--android/support/v17/leanback/app/MediaControllerGlue.java236
-rw-r--r--android/support/v17/leanback/app/OnboardingFragment.java24
-rw-r--r--android/support/v17/leanback/app/OnboardingSupportFragment.java8
-rw-r--r--android/support/v17/leanback/app/PlaybackControlGlue.java337
-rw-r--r--android/support/v17/leanback/app/PlaybackControlSupportGlue.java202
-rw-r--r--android/support/v17/leanback/app/PlaybackFragment.java15
-rw-r--r--android/support/v17/leanback/app/PlaybackFragmentGlueHost.java3
-rw-r--r--android/support/v17/leanback/app/PlaybackOverlayFragment.java863
-rw-r--r--android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java866
-rw-r--r--android/support/v17/leanback/app/PlaybackSupportFragment.java9
-rw-r--r--android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java3
-rw-r--r--android/support/v17/leanback/app/RowsFragment.java12
-rw-r--r--android/support/v17/leanback/app/RowsSupportFragment.java12
-rw-r--r--android/support/v17/leanback/app/SearchFragment.java17
-rw-r--r--android/support/v17/leanback/app/SearchSupportFragment.java15
-rw-r--r--android/support/v17/leanback/app/VerticalGridFragment.java5
-rw-r--r--android/support/v17/leanback/app/VerticalGridSupportFragment.java3
-rw-r--r--android/support/v17/leanback/app/VideoFragment.java5
-rw-r--r--android/support/v17/leanback/app/VideoFragmentGlueHost.java3
-rw-r--r--android/support/v17/leanback/app/VideoSupportFragment.java3
-rw-r--r--android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java3
-rw-r--r--android/support/v17/leanback/app/package-info.java51
-rw-r--r--android/support/v17/leanback/media/MediaControllerGlue.java3
-rw-r--r--android/support/v17/leanback/media/MediaPlayerGlue.java10
-rw-r--r--android/support/v17/leanback/media/PlaybackBannerControlGlue.java18
-rw-r--r--android/support/v17/leanback/media/PlaybackGlue.java64
-rw-r--r--android/support/v17/leanback/media/PlaybackTransportControlGlue.java12
-rw-r--r--android/support/v17/leanback/package-info.java15
-rw-r--r--android/support/v17/leanback/widget/ActionPresenterSelector.java75
-rw-r--r--android/support/v17/leanback/widget/ArrayObjectAdapter.java11
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java22
-rw-r--r--android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java2
-rw-r--r--android/support/v17/leanback/widget/SearchBar.java25
-rw-r--r--android/support/v17/leanback/widget/SpeechRecognitionCallback.java5
-rw-r--r--android/support/v17/leanback/widget/WindowAlignment.java20
-rw-r--r--android/support/v17/preference/LeanbackPreferenceFragment.java4
-rw-r--r--android/support/v17/preference/LeanbackSettingsFragment.java4
-rw-r--r--android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java10
-rw-r--r--android/support/v4/app/ActivityCompat.java113
-rw-r--r--android/support/v4/app/ActivityOptionsCompat.java33
-rw-r--r--android/support/v4/app/AlarmManagerCompat.java17
-rw-r--r--android/support/v4/app/AppLaunchChecker.java9
-rw-r--r--android/support/v4/app/AppOpsManagerCompat.java2
-rw-r--r--android/support/v4/app/BundleCompat.java8
-rw-r--r--android/support/v4/app/Fragment.java65
-rw-r--r--android/support/v4/app/FragmentActivity.java18
-rw-r--r--android/support/v4/app/FragmentHostCallback.java3
-rw-r--r--android/support/v4/app/FragmentManager.java21
-rw-r--r--android/support/v4/app/FragmentPagerAdapter.java6
-rw-r--r--android/support/v4/app/FragmentStatePagerAdapter.java6
-rw-r--r--android/support/v4/app/FragmentTabHost.java4
-rw-r--r--android/support/v4/app/JobIntentService.java2
-rw-r--r--android/support/v4/app/ListFragment.java4
-rw-r--r--android/support/v4/app/NavUtils.java24
-rw-r--r--android/support/v4/app/NotificationManagerCompat.java12
-rw-r--r--android/support/v4/app/ServiceCompat.java3
-rw-r--r--android/support/v4/app/TaskStackBuilder.java34
-rw-r--r--android/support/v4/content/AsyncTaskLoader.java10
-rw-r--r--android/support/v4/content/ContextCompat.java41
-rw-r--r--android/support/v4/content/CursorLoader.java24
-rw-r--r--android/support/v4/content/FileProvider.java25
-rw-r--r--android/support/v4/content/IntentCompat.java6
-rw-r--r--android/support/v4/content/Loader.java22
-rw-r--r--android/support/v4/content/LocalBroadcastManager.java21
-rw-r--r--android/support/v4/content/MimeTypeFilter.java3
-rw-r--r--android/support/v4/content/PermissionChecker.java5
-rw-r--r--android/support/v4/content/SharedPreferencesCompat.java22
-rw-r--r--android/support/v4/content/WakefulBroadcastReceiver.java4
-rw-r--r--android/support/v4/content/pm/ShortcutInfoCompat.java42
-rw-r--r--android/support/v4/content/res/FontResourcesParserCompat.java17
-rw-r--r--android/support/v4/content/res/ResourcesCompat.java199
-rw-r--r--android/support/v4/content/res/TypedArrayUtils.java42
-rw-r--r--android/support/v4/graphics/BitmapCompat.java7
-rw-r--r--android/support/v4/graphics/TypefaceCompat.java43
-rw-r--r--android/support/v4/graphics/TypefaceCompatApi24Impl.java3
-rw-r--r--android/support/v4/graphics/TypefaceCompatApi26Impl.java5
-rw-r--r--android/support/v4/graphics/drawable/IconCompat.java55
-rw-r--r--android/support/v4/graphics/drawable/RoundedBitmapDrawable.java10
-rw-r--r--android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java15
-rw-r--r--android/support/v4/hardware/display/DisplayManagerCompat.java8
-rw-r--r--android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java15
-rw-r--r--android/support/v4/media/session/MediaControllerCompat.java84
-rw-r--r--android/support/v4/media/session/MediaSessionCompat.java104
-rw-r--r--android/support/v4/media/session/PlaybackStateCompat.java1
-rw-r--r--android/support/v4/net/ConnectivityManagerCompat.java10
-rw-r--r--android/support/v4/net/TrafficStatsCompat.java5
-rw-r--r--android/support/v4/provider/FontRequest.java3
-rw-r--r--android/support/v4/provider/FontsContractCompat.java138
-rw-r--r--android/support/v4/provider/SelfDestructiveThread.java2
-rw-r--r--android/support/v4/util/AtomicFile.java20
-rw-r--r--android/support/v4/util/ObjectsCompat.java3
-rw-r--r--android/support/v4/util/Pair.java12
-rw-r--r--android/support/v4/util/Pools.java12
-rw-r--r--android/support/v4/view/AbsSavedState.java9
-rw-r--r--android/support/v4/view/AsyncLayoutInflater.java3
-rw-r--r--android/support/v4/view/PagerAdapter.java36
-rw-r--r--android/support/v4/view/PagerTabStrip.java6
-rw-r--r--android/support/v4/view/PagerTitleStrip.java6
-rw-r--r--android/support/v4/view/ViewCompat.java3
-rw-r--r--android/support/v4/view/ViewPager.java26
-rw-r--r--android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java2
-rw-r--r--android/support/v4/widget/AutoScrollHelper.java12
-rw-r--r--android/support/v4/widget/CircularProgressDrawable.java8
-rw-r--r--android/support/v4/widget/ContentLoadingProgressBar.java6
-rw-r--r--android/support/v4/widget/DrawerLayout.java45
-rw-r--r--android/support/v4/widget/EdgeEffectCompat.java4
-rw-r--r--android/support/v4/widget/ExploreByTouchHelper.java13
-rw-r--r--android/support/v4/widget/ImageViewCompat.java13
-rw-r--r--android/support/v4/widget/ListPopupWindowCompat.java5
-rw-r--r--android/support/v4/widget/ListViewAutoScrollHelper.java3
-rw-r--r--android/support/v4/widget/NestedScrollView.java13
-rw-r--r--android/support/v4/widget/PopupMenuCompat.java5
-rw-r--r--android/support/v4/widget/PopupWindowCompat.java13
-rw-r--r--android/support/v4/widget/SlidingPaneLayout.java28
-rw-r--r--android/support/v4/widget/Space.java8
-rw-r--r--android/support/v4/widget/SwipeRefreshLayout.java11
-rw-r--r--android/support/v4/widget/TextViewCompat.java19
-rw-r--r--android/support/v4/widget/ViewDragHelper.java42
-rw-r--r--android/support/v7/app/NotificationCompat.java52
-rw-r--r--android/support/v7/graphics/Palette.java18
-rw-r--r--android/support/v7/graphics/Target.java16
-rw-r--r--android/support/v7/preference/Preference.java3
-rw-r--r--android/support/v7/preference/PreferenceFragmentCompat.java6
-rw-r--r--android/support/v7/preference/PreferenceManager.java10
-rw-r--r--android/support/v7/recyclerview/extensions/ListAdapter.java36
-rw-r--r--android/support/v7/recyclerview/extensions/ListAdapterConfig.java6
-rw-r--r--android/support/v7/recyclerview/extensions/ListAdapterHelper.java36
-rw-r--r--android/support/v7/widget/AppCompatTextHelper.java2
-rw-r--r--android/support/v7/widget/CardView.java8
-rw-r--r--android/support/v7/widget/TintTypedArray.java12
-rw-r--r--android/support/v7/widget/helper/ItemTouchHelper.java51
-rw-r--r--android/support/wear/ambient/AmbientDelegate.java207
-rw-r--r--android/support/wear/ambient/AmbientMode.java286
-rw-r--r--android/support/wear/ambient/SharedLibraryVersion.java97
-rw-r--r--android/support/wear/ambient/WearableControllerProvider.java96
-rw-r--r--android/system/Os.java1
-rw-r--r--android/system/StructGroupSourceReq.java41
-rw-r--r--android/telephony/MbmsDownloadSession.java6
-rw-r--r--android/telephony/PhoneNumberUtils.java185
-rw-r--r--android/telephony/TelephonyManager.java23
-rw-r--r--android/telephony/mbms/DownloadStateCallback.java69
-rw-r--r--android/telephony/mbms/MbmsDownloadReceiver.java6
-rw-r--r--android/telephony/mbms/ServiceInfo.java20
-rw-r--r--android/telephony/mbms/vendor/MbmsDownloadServiceBase.java70
-rw-r--r--android/telephony/mbms/vendor/VendorUtils.java3
-rw-r--r--android/text/BoringLayoutCreateDrawPerfTest.java150
-rw-r--r--android/text/BoringLayoutIsBoringPerfTest.java109
-rw-r--r--android/text/DynamicLayout.java2
-rw-r--r--android/text/DynamicLayoutPerfTest.java21
-rw-r--r--android/text/Hyphenator.java320
-rw-r--r--android/text/Hyphenator_Delegate.java46
-rw-r--r--android/text/Layout.java21
-rw-r--r--android/text/MeasuredText.java50
-rw-r--r--android/text/NonEditableTextGenerator.java138
-rw-r--r--android/text/PaintMeasureDrawPerfTest.java131
-rw-r--r--android/text/StaticLayout.java101
-rw-r--r--android/text/StaticLayoutCreateDrawPerfTest.java151
-rw-r--r--android/text/StaticLayout_Delegate.java46
-rw-r--r--android/text/TextViewSetTextMeasurePerfTest.java151
-rw-r--r--android/text/format/Formatter.java29
-rw-r--r--android/transition/TransitionUtils.java25
-rw-r--r--android/util/FeatureFlagUtils.java7
-rw-r--r--android/util/Log.java6
-rw-r--r--android/util/LruCache.java25
-rw-r--r--android/util/StatsLogKey.java48
-rw-r--r--android/util/StatsLogTag.java32
-rw-r--r--android/util/StatsLogValue.java54
-rw-r--r--android/util/proto/ProtoOutputStream.java29
-rw-r--r--android/util/proto/ProtoUtils.java39
-rw-r--r--android/view/DragEvent.java5
-rw-r--r--android/view/Gravity.java53
-rw-r--r--android/view/MenuInflater_Delegate.java14
-rw-r--r--android/view/Surface.java2
-rw-r--r--android/view/SurfaceControl.java21
-rw-r--r--android/view/SurfaceView.java1135
-rw-r--r--android/view/View.java145
-rw-r--r--android/view/ViewDebug.java75
-rw-r--r--android/view/ViewRootImpl.java6
-rw-r--r--android/view/ViewStructure.java2
-rw-r--r--android/view/WindowManager.java395
-rw-r--r--android/view/WindowManagerPolicy.java67
-rw-r--r--android/view/accessibility/AccessibilityManager.java916
-rw-r--r--android/view/autofill/AutofillManager.java129
-rw-r--r--android/view/autofill/AutofillPopupWindow.java29
-rw-r--r--android/view/textclassifier/TextClassification.java188
-rw-r--r--android/view/textclassifier/TextClassifierImpl.java118
-rw-r--r--android/view/textclassifier/logging/SmartSelectionEventTracker.java1
-rw-r--r--android/view/textservice/TextServicesManager.java200
-rw-r--r--android/webkit/CacheManager.java5
-rw-r--r--android/webkit/ClientCertRequest.java8
-rw-r--r--android/webkit/CookieManager.java8
-rw-r--r--android/webkit/FindActionModeCallback.java17
-rw-r--r--android/webkit/MimeTypeMap.java5
-rw-r--r--android/webkit/Plugin.java8
-rw-r--r--android/webkit/ServiceWorkerClient.java3
-rw-r--r--android/webkit/TokenBindingService.java19
-rw-r--r--android/webkit/URLUtil.java7
-rw-r--r--android/webkit/UrlInterceptHandler.java11
-rw-r--r--android/webkit/UrlInterceptRegistry.java3
-rw-r--r--android/webkit/WebBackForwardList.java3
-rw-r--r--android/webkit/WebChromeClient.java10
-rw-r--r--android/webkit/WebHistoryItem.java2
-rw-r--r--android/webkit/WebMessage.java3
-rw-r--r--android/webkit/WebResourceResponse.java17
-rw-r--r--android/webkit/WebSettings.java3
-rw-r--r--android/webkit/WebSyncManager.java2
-rw-r--r--android/webkit/WebView.java2878
-rw-r--r--android/webkit/WebViewClient.java5
-rw-r--r--android/webkit/WebViewDatabase.java2
-rw-r--r--android/webkit/WebViewFactory.java49
-rw-r--r--android/webkit/WebViewLibraryLoader.java32
-rw-r--r--android/webkit/WebViewUpdateService.java18
-rw-r--r--android/webkit/WebViewZygote.java19
-rw-r--r--android/widget/EditTextBackspacePerfTest.java19
-rw-r--r--android/widget/EditTextCursorMovementPerfTest.java19
-rw-r--r--android/widget/Editor.java153
-rw-r--r--android/widget/PopupWindow.java11
-rw-r--r--android/widget/RemoteViews.java551
-rw-r--r--android/widget/SelectionActionModeHelper.java164
-rw-r--r--android/widget/SmartSelectSprite.java192
-rw-r--r--android/widget/Switch.java5
-rw-r--r--android/widget/TabWidget.java32
-rw-r--r--android/widget/TextView.java14
-rw-r--r--android/widget/TextViewSetTextLocalePerfTest.java20
523 files changed, 25881 insertions, 8050 deletions
diff --git a/android/accounts/AbstractAccountAuthenticator.java b/android/accounts/AbstractAccountAuthenticator.java
index bf9bd79e..a3b3a9f2 100644
--- a/android/accounts/AbstractAccountAuthenticator.java
+++ b/android/accounts/AbstractAccountAuthenticator.java
@@ -175,6 +175,9 @@ public abstract class AbstractAccountAuthenticator {
}
if (result != null) {
response.onResult(result);
+ } else {
+ response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "null bundle returned");
}
} catch (Exception e) {
handleException(response, "addAccount", accountType, e);
diff --git a/android/accounts/AccountManager.java b/android/accounts/AccountManager.java
index dd6ad55f..bd9c9fa3 100644
--- a/android/accounts/AccountManager.java
+++ b/android/accounts/AccountManager.java
@@ -2321,6 +2321,10 @@ public class AccountManager {
private class Response extends IAccountManagerResponse.Stub {
@Override
public void onResult(Bundle bundle) {
+ if (bundle == null) {
+ onError(ERROR_CODE_INVALID_RESPONSE, "null bundle returned");
+ return;
+ }
Intent intent = bundle.getParcelable(KEY_INTENT);
if (intent != null && mActivity != null) {
// since the user provided an Activity we will silently start intents
diff --git a/android/animation/AnimatorSet.java b/android/animation/AnimatorSet.java
index 00d6657e..1a2dc5cd 100644
--- a/android/animation/AnimatorSet.java
+++ b/android/animation/AnimatorSet.java
@@ -843,7 +843,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
// Assumes forward playing from here on.
for (int i = 0; i < mEvents.size(); i++) {
AnimationEvent event = mEvents.get(i);
- if (event.getTime() > currentPlayTime) {
+ if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
break;
}
@@ -1264,7 +1264,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
} else {
for (int i = mLastEventId + 1; i < size; i++) {
AnimationEvent event = mEvents.get(i);
- if (event.getTime() <= currentPlayTime) {
+ // TODO: need a function that accounts for infinite duration to compare time
+ if (event.getTime() != DURATION_INFINITE && event.getTime() <= currentPlayTime) {
latestId = i;
}
}
diff --git a/android/annotation/NavigationRes.java b/android/annotation/NavigationRes.java
new file mode 100644
index 00000000..3af5ecff
--- /dev/null
+++ b/android/annotation/NavigationRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a navigation resource reference (e.g. {@code R.navigation.flow}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NavigationRes {
+}
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 4e258a3a..e0ac9113 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -114,6 +114,7 @@ import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.AutofillClient;
import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.IAutofillWindowPresenter;
import android.widget.AdapterView;
@@ -947,6 +948,18 @@ public class Activity extends ContextThemeWrapper
return mAutofillManager;
}
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(newBase);
+ newBase.setAutofillClient(this);
+ }
+
+ /** @hide */
+ @Override
+ public final AutofillClient getAutofillClient() {
+ return this;
+ }
+
/**
* Called when the activity is starting. This is where most initialization
* should go: calling {@link #setContentView(int)} to inflate the
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index a8665037..5e61727f 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -16,14 +16,10 @@
package android.app;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -46,6 +42,7 @@ import android.content.pm.IPackageDataObserver;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -675,32 +672,27 @@ public class ActivityManager {
/** First static stack ID.
* @hide */
- public static final int FIRST_STATIC_STACK_ID = 0;
+ private static final int FIRST_STATIC_STACK_ID = 0;
- /** Home activity stack ID. */
- public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
-
- /** ID of stack where fullscreen activities are normally launched into. */
+ /** ID of stack where fullscreen activities are normally launched into.
+ * @hide */
public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
- /** ID of stack where freeform/resized activities are normally launched into. */
+ /** ID of stack where freeform/resized activities are normally launched into.
+ * @hide */
public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
- /** ID of stack that occupies a dedicated region of the screen. */
+ /** ID of stack that occupies a dedicated region of the screen.
+ * @hide */
public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
- /** ID of stack that always on top (always visible) when it exist. */
+ /** ID of stack that always on top (always visible) when it exist.
+ * @hide */
public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
- /** ID of stack that contains the Recents activity. */
- public static final int RECENTS_STACK_ID = PINNED_STACK_ID + 1;
-
- /** ID of stack that contains activities launched by the assistant. */
- public static final int ASSISTANT_STACK_ID = RECENTS_STACK_ID + 1;
-
/** Last static stack stack ID.
* @hide */
- public static final int LAST_STATIC_STACK_ID = ASSISTANT_STACK_ID;
+ private static final int LAST_STATIC_STACK_ID = PINNED_STACK_ID;
/** Start of ID range used by stacks that are created dynamically.
* @hide */
@@ -720,15 +712,6 @@ public class ActivityManager {
}
/**
- * Returns true if dynamic stacks are allowed to be visible behind the input stack.
- * @hide
- */
- // TODO: Figure-out a way to remove.
- public static boolean isDynamicStacksVisibleBehindAllowed(int stackId) {
- return stackId == PINNED_STACK_ID || stackId == ASSISTANT_STACK_ID;
- }
-
- /**
* Returns true if we try to maintain focus in the current stack when the top activity
* finishes.
* @hide
@@ -740,15 +723,6 @@ public class ActivityManager {
}
/**
- * Returns true if the input stack is affected by drag resizing.
- * @hide
- */
- public static boolean isStackAffectedByDragResizing(int stackId) {
- return isStaticStack(stackId) && stackId != PINNED_STACK_ID
- && stackId != ASSISTANT_STACK_ID;
- }
-
- /**
* Returns true if the windows of tasks being moved to the target stack from the source
* stack should be replaced, meaning that window manager will keep the old window around
* until the new is ready.
@@ -760,41 +734,6 @@ public class ActivityManager {
}
/**
- * Return whether a stackId is a stack that be a backdrop to a translucent activity. These
- * are generally fullscreen stacks.
- * @hide
- */
- public static boolean isBackdropToTranslucentActivity(int stackId) {
- return stackId == FULLSCREEN_WORKSPACE_STACK_ID
- || stackId == ASSISTANT_STACK_ID;
- }
-
- /**
- * Returns true if animation specs should be constructed for app transition that moves
- * the task to the specified stack.
- * @hide
- */
- public static boolean useAnimationSpecForAppTransition(int stackId) {
- // TODO: INVALID_STACK_ID is also animated because we don't persist stack id's across
- // reboots.
- return stackId == FREEFORM_WORKSPACE_STACK_ID
- || stackId == FULLSCREEN_WORKSPACE_STACK_ID
- || stackId == ASSISTANT_STACK_ID
- || stackId == DOCKED_STACK_ID
- || stackId == INVALID_STACK_ID;
- }
-
- /**
- * Returns true if activities from stasks in the given {@param stackId} are allowed to
- * enter picture-in-picture.
- * @hide
- */
- public static boolean isAllowedToEnterPictureInPicture(int stackId) {
- return stackId != HOME_STACK_ID && stackId != ASSISTANT_STACK_ID &&
- stackId != RECENTS_STACK_ID;
- }
-
- /**
* Returns true if the top task in the task is allowed to return home when finished and
* there are other tasks in the stack.
* @hide
@@ -825,34 +764,18 @@ public class ActivityManager {
&& stackId != DOCKED_STACK_ID;
}
- /**
- * Returns true if the input stack id should only be present on a device that supports
- * multi-window mode.
- * @see android.app.ActivityManager#supportsMultiWindow
- * @hide
- */
- // TODO: What about the other side of docked stack if we move this to WindowConfiguration?
- public static boolean isMultiWindowStack(int stackId) {
- return stackId == PINNED_STACK_ID || stackId == FREEFORM_WORKSPACE_STACK_ID
- || stackId == DOCKED_STACK_ID;
- }
-
- /**
- * Returns true if the input {@param stackId} is HOME_STACK_ID or RECENTS_STACK_ID
- * @hide
- */
- public static boolean isHomeOrRecentsStack(int stackId) {
- return stackId == HOME_STACK_ID || stackId == RECENTS_STACK_ID;
- }
-
- /** Returns true if the input stack and its content can affect the device orientation.
+ /** Returns the stack id for the input windowing mode.
* @hide */
- public static boolean canSpecifyOrientation(int stackId) {
- return stackId == HOME_STACK_ID
- || stackId == RECENTS_STACK_ID
- || stackId == FULLSCREEN_WORKSPACE_STACK_ID
- || stackId == ASSISTANT_STACK_ID
- || isDynamicStack(stackId);
+ // TODO: To be removed once we are not using stack id for stuff...
+ public static int getStackIdForWindowingMode(int windowingMode) {
+ switch (windowingMode) {
+ case WINDOWING_MODE_PINNED: return PINNED_STACK_ID;
+ case WINDOWING_MODE_FREEFORM: return FREEFORM_WORKSPACE_STACK_ID;
+ case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return DOCKED_STACK_ID;
+ case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return FULLSCREEN_WORKSPACE_STACK_ID;
+ case WINDOWING_MODE_FULLSCREEN: return FULLSCREEN_WORKSPACE_STACK_ID;
+ default: return INVALID_STACK_ID;
+ }
}
/** Returns the windowing mode that should be used for this input stack id.
@@ -862,14 +785,9 @@ public class ActivityManager {
final int windowingMode;
switch (stackId) {
case FULLSCREEN_WORKSPACE_STACK_ID:
- case HOME_STACK_ID:
- case RECENTS_STACK_ID:
windowingMode = inSplitScreenMode
? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN;
break;
- case ASSISTANT_STACK_ID:
- windowingMode = WINDOWING_MODE_FULLSCREEN;
- break;
case PINNED_STACK_ID:
windowingMode = WINDOWING_MODE_PINNED;
break;
@@ -884,27 +802,6 @@ public class ActivityManager {
}
return windowingMode;
}
-
- /** Returns the activity type that should be used for this input stack id.
- * @hide */
- // TODO: To be removed once we are not using stack id for stuff...
- public static int getActivityTypeForStackId(int stackId) {
- final int activityType;
- switch (stackId) {
- case HOME_STACK_ID:
- activityType = ACTIVITY_TYPE_HOME;
- break;
- case RECENTS_STACK_ID:
- activityType = ACTIVITY_TYPE_RECENTS;
- break;
- case ASSISTANT_STACK_ID:
- activityType = ACTIVITY_TYPE_ASSISTANT;
- break;
- default :
- activityType = ACTIVITY_TYPE_STANDARD;
- }
- return activityType;
- }
}
/**
@@ -1143,6 +1040,7 @@ public class ActivityManager {
* E.g. freeform, split-screen, picture-in-picture.
* @hide
*/
+ @TestApi
static public boolean supportsMultiWindow(Context context) {
// On watches, multi-window is used to present essential system UI, and thus it must be
// supported regardless of device memory characteristics.
@@ -1157,6 +1055,7 @@ public class ActivityManager {
* Returns true if the system supports split screen multi-window.
* @hide
*/
+ @TestApi
static public boolean supportsSplitScreenMultiWindow(Context context) {
return supportsMultiWindow(context)
&& Resources.getSystem().getBoolean(
@@ -1636,6 +1535,12 @@ public class ActivityManager {
*/
public int resizeMode;
+ /**
+ * The current configuration this task is in.
+ * @hide
+ */
+ final public Configuration configuration = new Configuration();
+
public RecentTaskInfo() {
}
@@ -1681,6 +1586,7 @@ public class ActivityManager {
}
dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
dest.writeInt(resizeMode);
+ configuration.writeToParcel(dest, flags);
}
public void readFromParcel(Parcel source) {
@@ -1705,6 +1611,7 @@ public class ActivityManager {
Rect.CREATOR.createFromParcel(source) : null;
supportsSplitScreenMultiWindow = source.readInt() == 1;
resizeMode = source.readInt();
+ configuration.readFromParcel(source);
}
public static final Creator<RecentTaskInfo> CREATOR
@@ -1899,6 +1806,12 @@ public class ActivityManager {
*/
public int resizeMode;
+ /**
+ * The full configuration the task is currently running in.
+ * @hide
+ */
+ final public Configuration configuration = new Configuration();
+
public RunningTaskInfo() {
}
@@ -1923,6 +1836,7 @@ public class ActivityManager {
dest.writeInt(numRunning);
dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
dest.writeInt(resizeMode);
+ configuration.writeToParcel(dest, flags);
}
public void readFromParcel(Parcel source) {
@@ -1940,6 +1854,7 @@ public class ActivityManager {
numRunning = source.readInt();
supportsSplitScreenMultiWindow = source.readInt() != 0;
resizeMode = source.readInt();
+ configuration.readFromParcel(source);
}
public static final Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() {
@@ -2122,6 +2037,73 @@ public class ActivityManager {
}
/**
+ * Sets the windowing mode for a specific task. Only works on tasks of type
+ * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}
+ * @param taskId The id of the task to set the windowing mode for.
+ * @param windowingMode The windowing mode to set for the task.
+ * @param toTop If the task should be moved to the top once the windowing mode changes.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop)
+ throws SecurityException {
+ try {
+ getService().setTaskWindowingMode(taskId, windowingMode, toTop);
+ } 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.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void resizeStack(int stackId, Rect bounds) throws SecurityException {
+ try {
+ getService().resizeStack(stackId, bounds, false /* allowResizeInDockedMode */,
+ false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes stacks in the windowing modes from the system if they are of activity type
+ * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void removeStacksInWindowingModes(int[] windowingModes) throws SecurityException {
+ try {
+ getService().removeStacksInWindowingModes(windowingModes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes stack of the activity types from the system.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void removeStacksWithActivityTypes(int[] activityTypes) throws SecurityException {
+ try {
+ getService().removeStacksWithActivityTypes(activityTypes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Represents a task snapshot.
* @hide
*/
@@ -2599,6 +2581,11 @@ public class ActivityManager {
public boolean visible;
// Index of the stack in the display's stack list, can be used for comparison of stack order
public int position;
+ /**
+ * The full configuration the stack is currently running in.
+ * @hide
+ */
+ final public Configuration configuration = new Configuration();
@Override
public int describeContents() {
@@ -2633,6 +2620,7 @@ public class ActivityManager {
} else {
dest.writeInt(0);
}
+ configuration.writeToParcel(dest, flags);
}
public void readFromParcel(Parcel source) {
@@ -2660,6 +2648,7 @@ public class ActivityManager {
if (source.readInt() > 0) {
topActivity = ComponentName.readFromParcel(source);
}
+ configuration.readFromParcel(source);
}
public static final Creator<StackInfo> CREATOR = new Creator<StackInfo>() {
@@ -2687,6 +2676,8 @@ public class ActivityManager {
sb.append(" displayId="); sb.append(displayId);
sb.append(" userId="); sb.append(userId);
sb.append("\n");
+ sb.append(" configuration="); sb.append(configuration);
+ sb.append("\n");
prefix = prefix + " ";
for (int i = 0; i < taskIds.length; ++i) {
sb.append(prefix); sb.append("taskId="); sb.append(taskIds[i]);
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
index 0bffc9e6..a68c3a5c 100644
--- a/android/app/ActivityOptions.java
+++ b/android/app/ActivityOptions.java
@@ -18,6 +18,8 @@ package android.app;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.Nullable;
@@ -164,10 +166,16 @@ public class ActivityOptions {
private static final String KEY_LAUNCH_DISPLAY_ID = "android.activity.launchDisplayId";
/**
- * The stack id the activity should be launched into.
+ * The windowing mode the activity should be launched into.
* @hide
*/
- private static final String KEY_LAUNCH_STACK_ID = "android.activity.launchStackId";
+ private static final String KEY_LAUNCH_WINDOWING_MODE = "android.activity.windowingMode";
+
+ /**
+ * The activity type the activity should be launched as.
+ * @hide
+ */
+ private static final String KEY_LAUNCH_ACTIVITY_TYPE = "android.activity.activityType";
/**
* The task id the activity should be launched into.
@@ -272,7 +280,10 @@ public class ActivityOptions {
private int mExitCoordinatorIndex;
private PendingIntent mUsageTimeReport;
private int mLaunchDisplayId = INVALID_DISPLAY;
- private int mLaunchStackId = INVALID_STACK_ID;
+ @WindowConfiguration.WindowingMode
+ private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
+ @WindowConfiguration.ActivityType
+ private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
private int mLaunchTaskId = -1;
private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
private boolean mDisallowEnterPictureInPictureWhileLaunching;
@@ -860,7 +871,8 @@ public class ActivityOptions {
break;
}
mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
- mLaunchStackId = opts.getInt(KEY_LAUNCH_STACK_ID, INVALID_STACK_ID);
+ mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
+ mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
@@ -1070,14 +1082,34 @@ public class ActivityOptions {
}
/** @hide */
- public int getLaunchStackId() {
- return mLaunchStackId;
+ public int getLaunchWindowingMode() {
+ return mLaunchWindowingMode;
+ }
+
+ /**
+ * Sets the windowing mode the activity should launch into. If the input windowing mode is
+ * {@link android.app.WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} and the device
+ * isn't currently in split-screen windowing mode, then the activity will be launched in
+ * {@link android.app.WindowConfiguration#WINDOWING_MODE_FULLSCREEN} windowing mode. For clarity
+ * on this you can use
+ * {@link android.app.WindowConfiguration#WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY}
+ *
+ * @hide
+ */
+ @TestApi
+ public void setLaunchWindowingMode(int windowingMode) {
+ mLaunchWindowingMode = windowingMode;
+ }
+
+ /** @hide */
+ public int getLaunchActivityType() {
+ return mLaunchActivityType;
}
/** @hide */
@TestApi
- public void setLaunchStackId(int launchStackId) {
- mLaunchStackId = launchStackId;
+ public void setLaunchActivityType(int activityType) {
+ mLaunchActivityType = activityType;
}
/**
@@ -1291,7 +1323,8 @@ public class ActivityOptions {
break;
}
b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
- b.putInt(KEY_LAUNCH_STACK_ID, mLaunchStackId);
+ b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
+ b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType);
b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
index 4e8d2400..2516a3e9 100644
--- a/android/app/ActivityThread.java
+++ b/android/app/ActivityThread.java
@@ -5281,7 +5281,7 @@ public final class ActivityThread {
final ApplicationInfo aInfo =
sPackageManager.getApplicationInfo(
packageName,
- 0 /*flags*/,
+ PackageManager.GET_SHARED_LIBRARY_FILES,
UserHandle.myUserId());
if (mActivities.size() > 0) {
@@ -5780,7 +5780,7 @@ public final class ActivityThread {
final int preloadedFontsResource = info.metaData.getInt(
ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
if (preloadedFontsResource != 0) {
- data.info.mResources.preloadFonts(preloadedFontsResource);
+ data.info.getResources().preloadFonts(preloadedFontsResource);
}
}
} catch (RemoteException e) {
diff --git a/android/app/ContextImpl.java b/android/app/ContextImpl.java
index c48be770..5f343226 100644
--- a/android/app/ContextImpl.java
+++ b/android/app/ContextImpl.java
@@ -74,6 +74,7 @@ import android.util.Log;
import android.util.Slog;
import android.view.Display;
import android.view.DisplayAdjustments;
+import android.view.autofill.AutofillManager.AutofillClient;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -185,6 +186,8 @@ class ContextImpl extends Context {
// The name of the split this Context is representing. May be null.
private @Nullable String mSplitName = null;
+ private AutofillClient mAutofillClient = null;
+
private final Object mSync = new Object();
@GuardedBy("mSync")
@@ -2225,6 +2228,18 @@ class ContextImpl extends Context {
return mUser.getIdentifier();
}
+ /** @hide */
+ @Override
+ public AutofillClient getAutofillClient() {
+ return mAutofillClient;
+ }
+
+ /** @hide */
+ @Override
+ public void setAutofillClient(AutofillClient client) {
+ mAutofillClient = client;
+ }
+
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
diff --git a/android/app/KeyguardManager.java b/android/app/KeyguardManager.java
index 76643d60..54f74b15 100644
--- a/android/app/KeyguardManager.java
+++ b/android/app/KeyguardManager.java
@@ -174,7 +174,7 @@ public class KeyguardManager {
*/
public Intent createConfirmFactoryResetCredentialIntent(
CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
- if (!LockPatternUtils.frpCredentialEnabled()) {
+ if (!LockPatternUtils.frpCredentialEnabled(mContext)) {
Log.w(TAG, "Factory reset credentials not supported.");
return null;
}
diff --git a/android/app/Notification.java b/android/app/Notification.java
index ee6c1cba..fee7d6c8 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -67,7 +67,6 @@ import android.text.style.TextAppearanceSpan;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
-import android.util.TypedValue;
import android.view.Gravity;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -3840,8 +3839,8 @@ public class Notification implements Parcelable
contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
if (isColorized()) {
- contentView.setDrawableParameters(R.id.profile_badge, false, -1,
- getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP, -1);
+ contentView.setDrawableTint(R.id.profile_badge, false,
+ getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP);
}
}
}
@@ -3906,7 +3905,6 @@ public class Notification implements Parcelable
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
contentView.setTextViewText(R.id.title, processTextSpans(p.title));
- updateTextSizePrimary(contentView, R.id.title);
if (!p.ambient) {
setTextViewColorPrimary(contentView, R.id.title);
}
@@ -3918,7 +3916,6 @@ public class Notification implements Parcelable
int textId = showProgress ? com.android.internal.R.id.text_line_1
: com.android.internal.R.id.text;
contentView.setTextViewText(textId, processTextSpans(p.text));
- updateTextSizeSecondary(contentView, textId);
if (!p.ambient) {
setTextViewColorSecondary(contentView, textId);
}
@@ -3930,25 +3927,6 @@ public class Notification implements Parcelable
return contentView;
}
- private void updateTextSizeSecondary(RemoteViews contentView, int textId) {
- updateTextSizeColorized(contentView, textId,
- com.android.internal.R.dimen.notification_text_size_colorized,
- com.android.internal.R.dimen.notification_text_size);
- }
-
- private void updateTextSizePrimary(RemoteViews contentView, int textId) {
- updateTextSizeColorized(contentView, textId,
- com.android.internal.R.dimen.notification_title_text_size_colorized,
- com.android.internal.R.dimen.notification_title_text_size);
- }
-
- private void updateTextSizeColorized(RemoteViews contentView, int textId,
- int colorizedDimen, int normalDimen) {
- int size = mContext.getResources().getDimensionPixelSize(isColorized()
- ? colorizedDimen : normalDimen);
- contentView.setTextViewTextSize(textId, TypedValue.COMPLEX_UNIT_PX, size);
- }
-
private CharSequence processTextSpans(CharSequence text) {
if (hasForegroundColor()) {
return NotificationColorUtil.clearColorSpans(text);
@@ -4152,18 +4130,14 @@ public class Notification implements Parcelable
if (action != null) {
int contrastColor = resolveContrastColor();
- contentView.setDrawableParameters(R.id.reply_icon_action,
+ contentView.setDrawableTint(R.id.reply_icon_action,
true /* targetBackground */,
- -1,
- contrastColor,
- PorterDuff.Mode.SRC_ATOP, -1);
+ contrastColor, PorterDuff.Mode.SRC_ATOP);
int iconColor = NotificationColorUtil.isColorLight(contrastColor)
? Color.BLACK : Color.WHITE;
- contentView.setDrawableParameters(R.id.reply_icon_action,
+ contentView.setDrawableTint(R.id.reply_icon_action,
false /* targetBackground */,
- -1,
- iconColor,
- PorterDuff.Mode.SRC_ATOP, -1);
+ iconColor, PorterDuff.Mode.SRC_ATOP);
contentView.setOnClickPendingIntent(R.id.right_icon,
action.actionIntent);
contentView.setOnClickPendingIntent(R.id.reply_icon_action,
@@ -4207,8 +4181,8 @@ public class Notification implements Parcelable
private void bindExpandButton(RemoteViews contentView) {
int color = getPrimaryHighlightColor();
- contentView.setDrawableParameters(R.id.expand_button, false, -1, color,
- PorterDuff.Mode.SRC_ATOP, -1);
+ contentView.setDrawableTint(R.id.expand_button, false, color,
+ PorterDuff.Mode.SRC_ATOP);
contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
color);
}
@@ -4315,8 +4289,7 @@ public class Notification implements Parcelable
mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
}
contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
- contentView.setDrawableParameters(R.id.icon, false /* targetBackground */,
- -1 /* alpha */, -1 /* colorFilter */, null /* mode */, mN.iconLevel);
+ contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
processSmallIconColor(mN.mSmallIcon, contentView, ambient);
}
@@ -4706,8 +4679,8 @@ public class Notification implements Parcelable
bgColor = mContext.getColor(oddAction ? R.color.notification_action_list
: R.color.notification_action_list_dark);
}
- button.setDrawableParameters(R.id.button_holder, true, -1, bgColor,
- PorterDuff.Mode.SRC_ATOP, -1);
+ button.setDrawableTint(R.id.button_holder, true,
+ bgColor, PorterDuff.Mode.SRC_ATOP);
CharSequence title = action.title;
ColorStateList[] outResultColor = null;
if (isLegacy()) {
@@ -4840,8 +4813,8 @@ public class Notification implements Parcelable
boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
int color = ambient ? resolveAmbientColor() : getPrimaryHighlightColor();
if (colorable) {
- contentView.setDrawableParameters(R.id.icon, false, -1, color,
- PorterDuff.Mode.SRC_ATOP, -1);
+ contentView.setDrawableTint(R.id.icon, false, color,
+ PorterDuff.Mode.SRC_ATOP);
}
contentView.setInt(R.id.notification_header, "setOriginalIconColor",
@@ -4857,8 +4830,8 @@ public class Notification implements Parcelable
if (largeIcon != null && isLegacy()
&& getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
// resolve color will fall back to the default when legacy
- contentView.setDrawableParameters(R.id.icon, false, -1, resolveContrastColor(),
- PorterDuff.Mode.SRC_ATOP, -1);
+ contentView.setDrawableTint(R.id.icon, false, resolveContrastColor(),
+ PorterDuff.Mode.SRC_ATOP);
}
}
@@ -5874,7 +5847,6 @@ public class Notification implements Parcelable
builder.setTextViewColorSecondary(contentView, R.id.big_text);
contentView.setViewVisibility(R.id.big_text,
TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE);
- builder.updateTextSizeSecondary(contentView, R.id.big_text);
contentView.setBoolean(R.id.big_text, "setHasImage", builder.mN.hasLargeIcon());
}
}
@@ -6208,7 +6180,6 @@ public class Notification implements Parcelable
contentView.setViewVisibility(rowId, View.VISIBLE);
contentView.setTextViewText(rowId, mBuilder.processTextSpans(
makeMessageLine(m, mBuilder)));
- mBuilder.updateTextSizeSecondary(contentView, rowId);
mBuilder.setTextViewColorSecondary(contentView, rowId);
if (contractedMessage == m) {
@@ -6576,7 +6547,6 @@ public class Notification implements Parcelable
contentView.setViewVisibility(rowIds[i], View.VISIBLE);
contentView.setTextViewText(rowIds[i],
mBuilder.processTextSpans(mBuilder.processLegacyText(str)));
- mBuilder.updateTextSizeSecondary(contentView, rowIds[i]);
mBuilder.setTextViewColorSecondary(contentView, rowIds[i]);
contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
handleInboxImageMargin(contentView, rowIds[i], first);
@@ -6775,8 +6745,8 @@ public class Notification implements Parcelable
: NotificationColorUtil.resolveColor(mBuilder.mContext,
Notification.COLOR_DEFAULT);
- button.setDrawableParameters(R.id.action0, false, -1, tintColor,
- PorterDuff.Mode.SRC_ATOP, -1);
+ button.setDrawableTint(R.id.action0, false, tintColor,
+ PorterDuff.Mode.SRC_ATOP);
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
diff --git a/android/app/NotificationChannel.java b/android/app/NotificationChannel.java
index 163a8dca..47063f08 100644
--- a/android/app/NotificationChannel.java
+++ b/android/app/NotificationChannel.java
@@ -15,8 +15,11 @@
*/
package android.app;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.NotificationManager.Importance;
+import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.net.Uri;
@@ -25,6 +28,9 @@ import android.os.Parcelable;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.Preconditions;
import org.json.JSONException;
import org.json.JSONObject;
@@ -135,12 +141,15 @@ public final class NotificationChannel implements Parcelable {
private boolean mLights;
private int mLightColor = DEFAULT_LIGHT_COLOR;
private long[] mVibration;
+ // Bitwise representation of fields that have been changed by the user, preventing the app from
+ // making changes to these fields.
private int mUserLockedFields;
private boolean mVibrationEnabled;
private boolean mShowBadge = DEFAULT_SHOW_BADGE;
private boolean mDeleted = DEFAULT_DELETED;
private String mGroup;
private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
+ // If this is a blockable system notification channel.
private boolean mBlockableSystem = false;
/**
@@ -565,14 +574,35 @@ public final class NotificationChannel implements Parcelable {
/**
* @hide
*/
+ public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
+ populateFromXml(parser, true, context);
+ }
+
+ /**
+ * @hide
+ */
@SystemApi
public void populateFromXml(XmlPullParser parser) {
+ populateFromXml(parser, false, null);
+ }
+
+ /**
+ * If {@param forRestore} is true, {@param Context} MUST be non-null.
+ */
+ private void populateFromXml(XmlPullParser parser, boolean forRestore,
+ @Nullable Context context) {
+ Preconditions.checkArgument(!forRestore || context != null,
+ "forRestore is true but got null context");
+
// Name, id, and importance are set in the constructor.
setDescription(parser.getAttributeValue(null, ATT_DESC));
setBypassDnd(Notification.PRIORITY_DEFAULT
!= safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
- setSound(safeUri(parser, ATT_SOUND), safeAudioAttributes(parser));
+
+ Uri sound = safeUri(parser, ATT_SOUND);
+ setSound(forRestore ? restoreSoundUri(context, sound) : sound, safeAudioAttributes(parser));
+
enableLights(safeBool(parser, ATT_LIGHTS, false));
setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
@@ -584,11 +614,62 @@ public final class NotificationChannel implements Parcelable {
setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
}
+ @Nullable
+ private Uri restoreSoundUri(Context context, @Nullable Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ ContentResolver contentResolver = context.getContentResolver();
+ // There are backups out there with uncanonical uris (because we fixed this after
+ // shipping). If uncanonical uris are given to MediaProvider.uncanonicalize it won't
+ // verify the uri against device storage and we'll possibly end up with a broken uri.
+ // We then canonicalize the uri to uncanonicalize it back, which means we properly check
+ // the uri and in the case of not having the resource we end up with the default - better
+ // than broken. As a side effect we'll canonicalize already canonicalized uris, this is fine
+ // according to the docs because canonicalize method has to handle canonical uris as well.
+ Uri canonicalizedUri = contentResolver.canonicalize(uri);
+ if (canonicalizedUri == null) {
+ // We got a null because the uri in the backup does not exist here, so we return default
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ }
+ return contentResolver.uncanonicalize(canonicalizedUri);
+ }
+
/**
* @hide
*/
@SystemApi
public void writeXml(XmlSerializer out) throws IOException {
+ writeXml(out, false, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void writeXmlForBackup(XmlSerializer out, Context context) throws IOException {
+ writeXml(out, true, context);
+ }
+
+ private Uri getSoundForBackup(Context context) {
+ Uri sound = getSound();
+ if (sound == null) {
+ return null;
+ }
+ Uri canonicalSound = context.getContentResolver().canonicalize(sound);
+ if (canonicalSound == null) {
+ // The content provider does not support canonical uris so we backup the default
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ }
+ return canonicalSound;
+ }
+
+ /**
+ * If {@param forBackup} is true, {@param Context} MUST be non-null.
+ */
+ private void writeXml(XmlSerializer out, boolean forBackup, @Nullable Context context)
+ throws IOException {
+ Preconditions.checkArgument(!forBackup || context != null,
+ "forBackup is true but got null context");
out.startTag(null, TAG_CHANNEL);
out.attribute(null, ATT_ID, getId());
if (getName() != null) {
@@ -609,8 +690,9 @@ public final class NotificationChannel implements Parcelable {
out.attribute(null, ATT_VISIBILITY,
Integer.toString(getLockscreenVisibility()));
}
- if (getSound() != null) {
- out.attribute(null, ATT_SOUND, getSound().toString());
+ Uri sound = forBackup ? getSoundForBackup(context) : getSound();
+ if (sound != null) {
+ out.attribute(null, ATT_SOUND, sound.toString());
}
if (getAudioAttributes() != null) {
out.attribute(null, ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
@@ -850,4 +932,35 @@ public final class NotificationChannel implements Parcelable {
+ ", mBlockableSystem=" + mBlockableSystem
+ '}';
}
+
+ /** @hide */
+ public void toProto(ProtoOutputStream proto) {
+ proto.write(NotificationChannelProto.ID, mId);
+ proto.write(NotificationChannelProto.NAME, mName);
+ proto.write(NotificationChannelProto.DESCRIPTION, mDesc);
+ proto.write(NotificationChannelProto.IMPORTANCE, mImportance);
+ proto.write(NotificationChannelProto.CAN_BYPASS_DND, mBypassDnd);
+ proto.write(NotificationChannelProto.LOCKSCREEN_VISIBILITY, mLockscreenVisibility);
+ if (mSound != null) {
+ proto.write(NotificationChannelProto.SOUND, mSound.toString());
+ }
+ proto.write(NotificationChannelProto.USE_LIGHTS, mLights);
+ proto.write(NotificationChannelProto.LIGHT_COLOR, mLightColor);
+ if (mVibration != null) {
+ for (long v : mVibration) {
+ proto.write(NotificationChannelProto.VIBRATION, v);
+ }
+ }
+ proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields);
+ proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled);
+ proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge);
+ proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
+ proto.write(NotificationChannelProto.GROUP, mGroup);
+ if (mAudioAttributes != null) {
+ long aToken = proto.start(NotificationChannelProto.AUDIO_ATTRIBUTES);
+ mAudioAttributes.toProto(proto);
+ proto.end(aToken);
+ }
+ proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
+ }
}
diff --git a/android/app/NotificationChannelGroup.java b/android/app/NotificationChannelGroup.java
index 51733114..5cb7fb7a 100644
--- a/android/app/NotificationChannelGroup.java
+++ b/android/app/NotificationChannelGroup.java
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
import org.json.JSONException;
import org.json.JSONObject;
@@ -295,4 +296,15 @@ public final class NotificationChannelGroup implements Parcelable {
+ ", mChannels=" + mChannels
+ '}';
}
+
+ /** @hide */
+ public void toProto(ProtoOutputStream proto) {
+ proto.write(NotificationChannelGroupProto.ID, mId);
+ proto.write(NotificationChannelGroupProto.NAME, mName.toString());
+ proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
+ proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
+ for (NotificationChannel channel : mChannels) {
+ channel.toProto(proto);
+ }
+ }
}
diff --git a/android/app/NotificationManager.java b/android/app/NotificationManager.java
index 8fa7d6c3..eb52cb7f 100644
--- a/android/app/NotificationManager.java
+++ b/android/app/NotificationManager.java
@@ -41,6 +41,7 @@ import android.provider.Settings.Global;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1061,6 +1062,27 @@ public class NotificationManager {
+ "]";
}
+ /** @hide */
+ public void toProto(ProtoOutputStream proto, long fieldId) {
+ final long pToken = proto.start(fieldId);
+
+ bitwiseToProtoEnum(proto, PolicyProto.PRIORITY_CATEGORIES, priorityCategories);
+ proto.write(PolicyProto.PRIORITY_CALL_SENDER, priorityCallSenders);
+ proto.write(PolicyProto.PRIORITY_MESSAGE_SENDER, priorityMessageSenders);
+ bitwiseToProtoEnum(
+ proto, PolicyProto.SUPPRESSED_VISUAL_EFFECTS, suppressedVisualEffects);
+
+ proto.end(pToken);
+ }
+
+ private static void bitwiseToProtoEnum(ProtoOutputStream proto, long fieldId, int data) {
+ for (int i = 1; data > 0; ++i, data >>>= 1) {
+ if ((data & 1) == 1) {
+ proto.write(fieldId, i);
+ }
+ }
+ }
+
public static String suppressedEffectsToString(int effects) {
if (effects <= 0) return "";
final StringBuilder sb = new StringBuilder();
diff --git a/android/app/SharedElementCallback.java b/android/app/SharedElementCallback.java
index af13e695..80fb8058 100644
--- a/android/app/SharedElementCallback.java
+++ b/android/app/SharedElementCallback.java
@@ -27,6 +27,7 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.transition.TransitionUtils;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
@@ -176,7 +177,7 @@ public abstract class SharedElementCallback {
Drawable d = imageView.getDrawable();
Drawable bg = imageView.getBackground();
if (d != null && (bg == null || bg.getAlpha() == 0)) {
- Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
+ Bitmap bitmap = TransitionUtils.createDrawableBitmap(d, imageView);
if (bitmap != null) {
Bundle bundle = new Bundle();
if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
@@ -202,7 +203,8 @@ public abstract class SharedElementCallback {
} else {
mTempMatrix.set(viewToGlobalMatrix);
}
- return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds);
+ ViewGroup parent = (ViewGroup) sharedElement.getParent();
+ return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds, parent);
}
/**
diff --git a/android/app/StatusBarManager.java b/android/app/StatusBarManager.java
index 4a092140..8987bc02 100644
--- a/android/app/StatusBarManager.java
+++ b/android/app/StatusBarManager.java
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-
package android.app;
import android.annotation.IntDef;
import android.annotation.SystemService;
import android.content.Context;
import android.os.Binder;
-import android.os.RemoteException;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
import android.view.View;
@@ -71,14 +70,18 @@ public class StatusBarManager {
* Setting this flag disables quick settings completely, but does not disable expanding the
* notification shade.
*/
- public static final int DISABLE2_QUICK_SETTINGS = 0x00000001;
+ public static final int DISABLE2_QUICK_SETTINGS = 1;
+ public static final int DISABLE2_SYSTEM_ICONS = 1 << 1;
+ public static final int DISABLE2_NOTIFICATION_SHADE = 1 << 2;
public static final int DISABLE2_NONE = 0x00000000;
- public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS;
+ public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS
+ | DISABLE2_NOTIFICATION_SHADE;
@IntDef(flag = true,
- value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS})
+ value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS,
+ DISABLE2_NOTIFICATION_SHADE})
@Retention(RetentionPolicy.SOURCE)
public @interface Disable2Flags {}
diff --git a/android/app/SystemServiceRegistry.java b/android/app/SystemServiceRegistry.java
index ab70f0e7..50f1f364 100644
--- a/android/app/SystemServiceRegistry.java
+++ b/android/app/SystemServiceRegistry.java
@@ -81,10 +81,10 @@ import android.net.INetworkPolicyManager;
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
-import android.net.nsd.INsdManager;
-import android.net.nsd.NsdManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
import android.net.wifi.IRttManager;
import android.net.wifi.IWifiManager;
import android.net.wifi.IWifiScanner;
@@ -95,6 +95,8 @@ import android.net.wifi.aware.IWifiAwareManager;
import android.net.wifi.aware.WifiAwareManager;
import android.net.wifi.p2p.IWifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.WifiRttManager;
import android.nfc.NfcManager;
import android.os.BatteryManager;
import android.os.BatteryStats;
@@ -603,6 +605,16 @@ final class SystemServiceRegistry {
ConnectivityThread.getInstanceLooper());
}});
+ registerService(Context.WIFI_RTT2_SERVICE, WifiRttManager.class,
+ new CachedServiceFetcher<WifiRttManager>() {
+ @Override
+ public WifiRttManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT2_SERVICE);
+ IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
+ return new WifiRttManager(ctx.getOuterContext(), service);
+ }});
+
registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
new CachedServiceFetcher<EthernetManager>() {
@Override
diff --git a/android/app/TaskStackListener.java b/android/app/TaskStackListener.java
index a52ca0a6..402e2095 100644
--- a/android/app/TaskStackListener.java
+++ b/android/app/TaskStackListener.java
@@ -31,7 +31,8 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub {
}
@Override
- public void onActivityPinned(String packageName, int taskId) throws RemoteException {
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
+ throws RemoteException {
}
@Override
diff --git a/android/app/VrManager.java b/android/app/VrManager.java
index 363e20a7..5c6ffa39 100644
--- a/android/app/VrManager.java
+++ b/android/app/VrManager.java
@@ -4,6 +4,7 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
@@ -62,7 +63,10 @@ public class VrManager {
* @param callback The callback to register.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.RESTRICTED_VR_ACCESS,
+ android.Manifest.permission.ACCESS_VR_STATE
+ })
public void registerVrStateCallback(VrStateCallback callback, @NonNull Handler handler) {
if (callback == null || mCallbackMap.containsKey(callback)) {
return;
@@ -88,7 +92,10 @@ public class VrManager {
* @param callback The callback to deregister.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.RESTRICTED_VR_ACCESS,
+ android.Manifest.permission.ACCESS_VR_STATE
+ })
public void unregisterVrStateCallback(VrStateCallback callback) {
CallbackEntry entry = mCallbackMap.remove(callback);
if (entry != null) {
@@ -110,7 +117,10 @@ public class VrManager {
* Returns the current VrMode state.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_VR_STATE)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.RESTRICTED_VR_ACCESS,
+ android.Manifest.permission.ACCESS_VR_STATE
+ })
public boolean getVrModeEnabled() {
try {
return mService.getVrModeState();
@@ -124,7 +134,10 @@ public class VrManager {
* Returns the current VrMode state.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_VR_STATE)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.RESTRICTED_VR_ACCESS,
+ android.Manifest.permission.ACCESS_VR_STATE
+ })
public boolean getPersistentVrModeEnabled() {
try {
return mService.getPersistentVrModeEnabled();
@@ -169,4 +182,20 @@ public class VrManager {
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Set the component name of the compositor service to bind.
+ *
+ * @param componentName ComponentName of a Service in the application's compositor process to
+ * bind to, or null to clear the current binding.
+ */
+ @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ public void setAndBindVrCompositor(ComponentName componentName) {
+ try {
+ mService.setAndBindCompositor(
+ (componentName == null) ? null : componentName.flattenToString());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 5d87e1c2..6b405384 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -17,6 +17,9 @@
package android.app;
import static android.app.ActivityThread.isSystem;
+import static android.app.WindowConfigurationProto.ACTIVITY_TYPE;
+import static android.app.WindowConfigurationProto.APP_BOUNDS;
+import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -25,6 +28,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
/**
@@ -48,26 +52,33 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
/** The current windowing mode of the configuration. */
private @WindowingMode int mWindowingMode;
- /** Windowing mode is currently not defined.
- * @hide */
+ /** Windowing mode is currently not defined. */
public static final int WINDOWING_MODE_UNDEFINED = 0;
- /** Occupies the full area of the screen or the parent container.
- * @hide */
+ /** Occupies the full area of the screen or the parent container. */
public static final int WINDOWING_MODE_FULLSCREEN = 1;
- /** Always on-top (always visible). of other siblings in its parent container.
- * @hide */
+ /** Always on-top (always visible). of other siblings in its parent container. */
public static final int WINDOWING_MODE_PINNED = 2;
- /** The primary container driving the screen to be in split-screen mode.
- * @hide */
+ /** The primary container driving the screen to be in split-screen mode. */
public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;
/**
* The containers adjacent to the {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} container in
* split-screen mode.
- * @hide
+ * NOTE: Containers launched with the windowing mode with APIs like
+ * {@link ActivityOptions#setLaunchWindowingMode(int)} will be launched in
+ * {@link #WINDOWING_MODE_FULLSCREEN} if the display isn't currently in split-screen windowing
+ * mode
+ * @see #WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY
*/
public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;
- /** Can be freely resized within its parent container.
- * @hide */
+ /**
+ * Alias for {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} that makes it clear that the usage
+ * points for APIs like {@link ActivityOptions#setLaunchWindowingMode(int)} that the container
+ * will launch into fullscreen or split-screen secondary depending on if the device is currently
+ * in fullscreen mode or split-screen mode.
+ */
+ public static final int WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY =
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ /** Can be freely resized within its parent container. */
public static final int WINDOWING_MODE_FREEFORM = 5;
/** @hide */
@@ -77,6 +88,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
WINDOWING_MODE_PINNED,
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY,
WINDOWING_MODE_FREEFORM,
})
public @interface WindowingMode {}
@@ -84,18 +96,15 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
/** The current activity type of the configuration. */
private @ActivityType int mActivityType;
- /** Activity type is currently not defined.
- * @hide */
+ /** Activity type is currently not defined. */
public static final int ACTIVITY_TYPE_UNDEFINED = 0;
- /** Standard activity type. Nothing special about the activity...
- * @hide */
+ /** Standard activity type. Nothing special about the activity... */
public static final int ACTIVITY_TYPE_STANDARD = 1;
/** Home/Launcher activity type. */
public static final int ACTIVITY_TYPE_HOME = 2;
/** Recents/Overview activity type. */
public static final int ACTIVITY_TYPE_RECENTS = 3;
- /** Assistant activity type.
- * @hide */
+ /** Assistant activity type. */
public static final int ACTIVITY_TYPE_ASSISTANT = 4;
/** @hide */
@@ -127,7 +136,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
})
public @interface WindowConfig {}
- /** @hide */
public WindowConfiguration() {
unset();
}
@@ -176,7 +184,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
* Set {@link #mAppBounds} to the input Rect.
* @param rect The rect value to set {@link #mAppBounds} to.
* @see #getAppBounds()
- * @hide
*/
public void setAppBounds(Rect rect) {
if (rect == null) {
@@ -200,26 +207,20 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
mAppBounds.set(left, top, right, bottom);
}
- /**
- * @see #setAppBounds(Rect)
- * @hide
- */
+ /** @see #setAppBounds(Rect) */
public Rect getAppBounds() {
return mAppBounds;
}
- /** @hide */
public void setWindowingMode(@WindowingMode int windowingMode) {
mWindowingMode = windowingMode;
}
- /** @hide */
@WindowingMode
public int getWindowingMode() {
return mWindowingMode;
}
- /** @hide */
public void setActivityType(@ActivityType int activityType) {
if (mActivityType == activityType) {
return;
@@ -237,13 +238,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
mActivityType = activityType;
}
- /** @hide */
@ActivityType
public int getActivityType() {
return mActivityType;
}
- /** @hide */
public void setTo(WindowConfiguration other) {
setAppBounds(other.mAppBounds);
setWindowingMode(other.mWindowingMode);
@@ -382,6 +381,24 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.app.WindowConfigurationProto}
+ *
+ * @param protoOutputStream Stream to write the WindowConfiguration object to.
+ * @param fieldId Field Id of the WindowConfiguration as defined in the parent message
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+ final long token = protoOutputStream.start(fieldId);
+ if (mAppBounds != null) {
+ mAppBounds.writeToProto(protoOutputStream, APP_BOUNDS);
+ }
+ protoOutputStream.write(WINDOWING_MODE, mWindowingMode);
+ protoOutputStream.write(ACTIVITY_TYPE, mActivityType);
+ protoOutputStream.end(token);
+ }
+
+ /**
* Returns true if the activities associated with this window configuration display a shadow
* around their border.
* @hide
@@ -483,10 +500,15 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
* @hide
*/
public boolean supportSplitScreenWindowingMode() {
- if (mActivityType == ACTIVITY_TYPE_ASSISTANT) {
+ return supportSplitScreenWindowingMode(mWindowingMode, mActivityType);
+ }
+
+ /** @hide */
+ public static boolean supportSplitScreenWindowingMode(int windowingMode, int activityType) {
+ if (activityType == ACTIVITY_TYPE_ASSISTANT) {
return false;
}
- return mWindowingMode != WINDOWING_MODE_FREEFORM && mWindowingMode != WINDOWING_MODE_PINNED;
+ return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
}
private static String windowingModeToString(@WindowingMode int windowingMode) {
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index 6bccad9f..3c530633 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -63,7 +63,9 @@ import android.telephony.TelephonyManager;
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import com.android.org.conscrypt.TrustedCertificateStore;
import java.io.ByteArrayInputStream;
@@ -3142,6 +3144,7 @@ public class DevicePolicyManager {
*/
public static final int WIPE_EUICC = 0x0004;
+
/**
* Ask that all user data be wiped. If called as a secondary user, the user will be removed and
* other users will remain unaffected. Calling from the primary user will cause the device to
@@ -3157,10 +3160,47 @@ 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);
+ }
+
+ /**
+ * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
+ * other users will remain unaffected, the provided reason for wiping data can be shown to
+ * user. Calling from the primary user will cause the device to reboot, erasing all device data
+ * - including all the secondary users and their data - while booting up. In this case, we don't
+ * show the reason to the user since the device would be factory reset.
+ * <p>
+ * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
+ * be able to call this method; if it has not, a security exception will be thrown.
+ *
+ * @param flags Bit mask of additional options: currently supported flags are
+ * {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+ * @param reason a string that contains the reason for wiping data, which can be
+ * presented to the user.
+ * @throws SecurityException if the calling application does not own an active administrator
+ * that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+ * @throws IllegalArgumentException if the input reason string is null or empty.
+ */
+ public void wipeDataWithReason(int flags, @NonNull CharSequence reason) {
+ Preconditions.checkNotNull(reason, "CharSequence is null");
+ wipeDataInternal(flags, reason.toString());
+ }
+
+ /**
+ * Internal function for both {@link #wipeData(int)} and
+ * {@link #wipeDataWithReason(int, CharSequence)} to call.
+ *
+ * @see #wipeData(int)
+ * @see #wipeDataWithReason(int, CharSequence)
+ * @hide
+ */
+ private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
+ throwIfParentInstance("wipeDataWithReason");
if (mService != null) {
try {
- mService.wipeData(flags);
+ mService.wipeDataWithReason(flags, wipeReasonForUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -7534,12 +7574,12 @@ public class DevicePolicyManager {
}
/**
- * Called by the device owner or profile owner to set the name of the organization under
- * management.
- * <p>
- * If the organization name needs to be localized, it is the responsibility of the
- * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
- * and set a new version of this string accordingly.
+ * Called by the device owner (since API 26) or profile owner (since API 24) to set the name of
+ * the organization under management.
+ *
+ * <p>If the organization name needs to be localized, it is the responsibility of the {@link
+ * DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set
+ * a new version of this string accordingly.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param title The organization name or {@code null} to clear a previously set name.
diff --git a/android/app/assist/AssistStructure.java b/android/app/assist/AssistStructure.java
index 55c22de5..d9b7cd7e 100644
--- a/android/app/assist/AssistStructure.java
+++ b/android/app/assist/AssistStructure.java
@@ -674,6 +674,7 @@ public class AssistStructure implements Parcelable {
ViewNodeText mText;
int mInputType;
+ String mWebScheme;
String mWebDomain;
Bundle mExtras;
LocaleList mLocaleList;
@@ -751,6 +752,7 @@ public class AssistStructure implements Parcelable {
mInputType = in.readInt();
}
if ((flags&FLAGS_HAS_URL) != 0) {
+ mWebScheme = in.readString();
mWebDomain = in.readString();
}
if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
@@ -813,7 +815,7 @@ public class AssistStructure implements Parcelable {
if (mInputType != 0) {
flags |= FLAGS_HAS_INPUT_TYPE;
}
- if (mWebDomain != null) {
+ if (mWebScheme != null || mWebDomain != null) {
flags |= FLAGS_HAS_URL;
}
if (mLocaleList != null) {
@@ -908,6 +910,7 @@ public class AssistStructure implements Parcelable {
out.writeInt(mInputType);
}
if ((flags&FLAGS_HAS_URL) != 0) {
+ out.writeString(mWebScheme);
out.writeString(mWebDomain);
}
if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
@@ -1260,18 +1263,31 @@ public class AssistStructure implements Parcelable {
* <p>Typically used when the view associated with the view is a container for an HTML
* document.
*
- * <strong>WARNING:</strong> a {@link android.service.autofill.AutofillService} should only
- * use this domain for autofill purposes when it trusts the app generating it (i.e., the app
- * defined by {@link AssistStructure#getActivityComponent()}).
+ * <p><b>Warning:</b> an autofill service cannot trust the value reported by this method
+ * without verifing its authenticity&mdash;see the "Web security" section of
+ * {@link android.service.autofill.AutofillService} for more details.
*
* @return domain-only part of the document. For example, if the full URL is
- * {@code http://my.site/login?user=my_user}, it returns {@code my.site}.
+ * {@code https://example.com/login?user=my_user}, it returns {@code example.com}.
*/
@Nullable public String getWebDomain() {
return mWebDomain;
}
/**
+ * Returns the scheme of the HTML document represented by this view.
+ *
+ * <p>Typically used when the view associated with the view is a container for an HTML
+ * document.
+ *
+ * @return scheme-only part of the document. For example, if the full URL is
+ * {@code https://example.com/login?user=my_user}, it returns {@code https}.
+ */
+ @Nullable public String getWebScheme() {
+ return mWebScheme;
+ }
+
+ /**
* Returns the HTML properties associated with this view.
*
* <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
@@ -1767,10 +1783,13 @@ public class AssistStructure implements Parcelable {
@Override
public void setWebDomain(@Nullable String domain) {
if (domain == null) {
+ mNode.mWebScheme = null;
mNode.mWebDomain = null;
return;
}
- mNode.mWebDomain = Uri.parse(domain).getHost();
+ Uri uri = Uri.parse(domain);
+ mNode.mWebScheme = uri.getScheme();
+ mNode.mWebDomain = uri.getHost();
}
@Override
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index 87e516ca..1434c9ba 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -317,7 +317,8 @@ public class JobInfo implements Parcelable {
}
/**
- * Whether this job needs the device to be plugged in.
+ * Whether this job requires that the device be charging (or be a non-battery-powered
+ * device connected to permanent power, such as Android TV devices).
*/
public boolean isRequireCharging() {
return (constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0;
@@ -331,7 +332,10 @@ public class JobInfo implements Parcelable {
}
/**
- * Whether this job needs the device to be in an Idle maintenance window.
+ * Whether this job requires that the user <em>not</em> be interacting with the device.
+ *
+ * <p class="note">This is <em>not</em> the same as "doze" or "device idle";
+ * it is purely about the user's direct interactions.</p>
*/
public boolean isRequireDeviceIdle() {
return (constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0;
@@ -918,9 +922,19 @@ public class JobInfo implements Parcelable {
}
/**
- * Specify that to run this job, the device needs to be plugged in. This defaults to
- * false.
- * @param requiresCharging Whether or not the device is plugged in.
+ * Specify that to run this job, the device must be charging (or be a
+ * non-battery-powered device connected to permanent power, such as Android TV
+ * devices). This defaults to {@code false}.
+ *
+ * <p class="note">For purposes of running jobs, a battery-powered device
+ * "charging" is not quite the same as simply being connected to power. If the
+ * device is so busy that the battery is draining despite a power connection, jobs
+ * with this constraint will <em>not</em> run. This can happen during some
+ * common use cases such as video chat, particularly if the device is plugged in
+ * to USB rather than to wall power.
+ *
+ * @param requiresCharging Pass {@code true} to require that the device be
+ * charging in order to run the job.
*/
public Builder setRequiresCharging(boolean requiresCharging) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING)
@@ -942,14 +956,22 @@ public class JobInfo implements Parcelable {
}
/**
- * Specify that to run, the job needs the device to be in idle mode. This defaults to
- * false.
- * <p>Idle mode is a loose definition provided by the system, which means that the device
- * is not in use, and has not been in use for some time. As such, it is a good time to
- * perform resource heavy jobs. Bear in mind that battery usage will still be attributed
- * to your application, and surfaced to the user in battery stats.</p>
- * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
- * window.
+ * When set {@code true}, ensure that this job will not run if the device is in active use.
+ * The default state is {@code false}: that is, the for the job to be runnable even when
+ * someone is interacting with the device.
+ *
+ * <p>This state is a loose definition provided by the system. In general, it means that
+ * the device is not currently being used interactively, and has not been in use for some
+ * time. As such, it is a good time to perform resource heavy jobs. Bear in mind that
+ * battery usage will still be attributed to your application, and surfaced to the user in
+ * battery stats.</p>
+ *
+ * <p class="note">Despite the similar naming, this job constraint is <em>not</em>
+ * related to the system's "device idle" or "doze" states. This constraint only
+ * determines whether a job is allowed to run while the device is directly in use.
+ *
+ * @param requiresDeviceIdle Pass {@code true} to prevent the job from running
+ * while the device is being used interactively.
*/
public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE)
diff --git a/android/app/timezone/RulesUpdaterContract.java b/android/app/timezone/RulesUpdaterContract.java
index 9c62f46b..74ed6588 100644
--- a/android/app/timezone/RulesUpdaterContract.java
+++ b/android/app/timezone/RulesUpdaterContract.java
@@ -51,7 +51,7 @@ public final class RulesUpdaterContract {
* applies.
*/
public static final String ACTION_TRIGGER_RULES_UPDATE_CHECK =
- "android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
+ "com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
/**
* The extra containing the {@code byte[]} that should be passed to
@@ -61,7 +61,7 @@ public final class RulesUpdaterContract {
* {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent has been processed.
*/
public static final String EXTRA_CHECK_TOKEN =
- "android.intent.extra.timezone.CHECK_TOKEN";
+ "com.android.intent.extra.timezone.CHECK_TOKEN";
/**
* Creates an intent that would trigger a time zone rules update check.
@@ -83,8 +83,7 @@ public final class RulesUpdaterContract {
Intent intent = createUpdaterIntent(updaterAppPackageName);
intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
context.sendBroadcastAsUser(
- intent,
- UserHandle.of(UserHandle.myUserId()),
+ intent, UserHandle.SYSTEM,
RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
}
}
diff --git a/android/appwidget/AppWidgetHostView.java b/android/appwidget/AppWidgetHostView.java
index 8a1eae2d..dc9970a7 100644
--- a/android/appwidget/AppWidgetHostView.java
+++ b/android/appwidget/AppWidgetHostView.java
@@ -23,17 +23,12 @@ import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.Parcel;
import android.os.Parcelable;
-import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -58,17 +53,17 @@ import java.util.concurrent.Executor;
* {@link RemoteViews}.
*/
public class AppWidgetHostView extends FrameLayout {
+
static final String TAG = "AppWidgetHostView";
+ private static final String KEY_JAILED_ARRAY = "jail";
+
static final boolean LOGD = false;
- static final boolean CROSSFADE = false;
static final int VIEW_MODE_NOINIT = 0;
static final int VIEW_MODE_CONTENT = 1;
static final int VIEW_MODE_ERROR = 2;
static final int VIEW_MODE_DEFAULT = 3;
- static final int FADE_DURATION = 1000;
-
// When we're inflating the initialLayout for a AppWidget, we only allow
// views that are allowed in RemoteViews.
static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
@@ -85,9 +80,6 @@ public class AppWidgetHostView extends FrameLayout {
View mView;
int mViewMode = VIEW_MODE_NOINIT;
int mLayoutId = -1;
- long mFadeStartTime = -1;
- Bitmap mOld;
- Paint mOldPaint = new Paint();
private OnClickHandler mOnClickHandler;
private Executor mAsyncExecutor;
@@ -212,9 +204,12 @@ public class AppWidgetHostView extends FrameLayout {
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
- final ParcelableSparseArray jail = new ParcelableSparseArray();
+ final SparseArray<Parcelable> jail = new SparseArray<>();
super.dispatchSaveInstanceState(jail);
- container.put(generateId(), jail);
+
+ Bundle bundle = new Bundle();
+ bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail);
+ container.put(generateId(), bundle);
}
private int generateId() {
@@ -226,12 +221,12 @@ public class AppWidgetHostView extends FrameLayout {
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
final Parcelable parcelable = container.get(generateId());
- ParcelableSparseArray jail = null;
- if (parcelable != null && parcelable instanceof ParcelableSparseArray) {
- jail = (ParcelableSparseArray) parcelable;
+ SparseArray<Parcelable> jail = null;
+ if (parcelable instanceof Bundle) {
+ jail = ((Bundle) parcelable).getSparseParcelableArray(KEY_JAILED_ARRAY);
}
- if (jail == null) jail = new ParcelableSparseArray();
+ if (jail == null) jail = new SparseArray<>();
try {
super.dispatchRestoreInstanceState(jail);
@@ -383,31 +378,10 @@ public class AppWidgetHostView extends FrameLayout {
* @hide
*/
protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) {
- if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
-
boolean recycled = false;
View content = null;
Exception exception = null;
- // Capture the old view into a bitmap so we can do the crossfade.
- if (CROSSFADE) {
- if (mFadeStartTime < 0) {
- if (mView != null) {
- final int width = mView.getWidth();
- final int height = mView.getHeight();
- try {
- mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- } catch (OutOfMemoryError e) {
- // we just won't do the fade
- mOld = null;
- }
- if (mOld != null) {
- //mView.drawIntoBitmap(mOld);
- }
- }
- }
- }
-
if (mLastExecutionSignal != null) {
mLastExecutionSignal.cancel();
mLastExecutionSignal = null;
@@ -484,16 +458,6 @@ public class AppWidgetHostView extends FrameLayout {
removeView(mView);
mView = content;
}
-
- if (CROSSFADE) {
- if (mFadeStartTime < 0) {
- // if there is already an animation in progress, don't do anything --
- // the new view will pop in on top of the old one during the cross fade,
- // and that looks okay.
- mFadeStartTime = SystemClock.uptimeMillis();
- invalidate();
- }
- }
}
private void updateContentDescription(AppWidgetProviderInfo info) {
@@ -617,45 +581,6 @@ public class AppWidgetHostView extends FrameLayout {
}
}
- @Override
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (CROSSFADE) {
- int alpha;
- int l = child.getLeft();
- int t = child.getTop();
- if (mFadeStartTime > 0) {
- alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
- if (alpha > 255) {
- alpha = 255;
- }
- Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
- + " w=" + child.getWidth());
- if (alpha != 255 && mOld != null) {
- mOldPaint.setAlpha(255-alpha);
- //canvas.drawBitmap(mOld, l, t, mOldPaint);
- }
- } else {
- alpha = 255;
- }
- int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
- Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
- boolean rv = super.drawChild(canvas, child, drawingTime);
- canvas.restoreToCount(restoreTo);
- if (alpha < 255) {
- invalidate();
- } else {
- mFadeStartTime = -1;
- if (mOld != null) {
- mOld.recycle();
- mOld = null;
- }
- }
- return rv;
- } else {
- return super.drawChild(canvas, child, drawingTime);
- }
- }
-
/**
* Prepare the given view to be shown. This might include adjusting
* {@link FrameLayout.LayoutParams} before inserting.
@@ -740,36 +665,4 @@ public class AppWidgetHostView extends FrameLayout {
super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(AppWidgetHostView.class.getName());
}
-
- private static class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable {
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- final int count = size();
- dest.writeInt(count);
- for (int i = 0; i < count; i++) {
- dest.writeInt(keyAt(i));
- dest.writeParcelable(valueAt(i), 0);
- }
- }
-
- public static final Parcelable.Creator<ParcelableSparseArray> CREATOR =
- new Parcelable.Creator<ParcelableSparseArray>() {
- public ParcelableSparseArray createFromParcel(Parcel source) {
- final ParcelableSparseArray array = new ParcelableSparseArray();
- final ClassLoader loader = array.getClass().getClassLoader();
- final int count = source.readInt();
- for (int i = 0; i < count; i++) {
- array.put(source.readInt(), source.readParcelable(loader));
- }
- return array;
- }
-
- public ParcelableSparseArray[] newArray(int size) {
- return new ParcelableSparseArray[size];
- }
- };
- }
}
diff --git a/android/arch/core/executor/AppToolkitTaskExecutor.java b/android/arch/core/executor/ArchTaskExecutor.java
index 7337f74a..2401a730 100644
--- a/android/arch/core/executor/AppToolkitTaskExecutor.java
+++ b/android/arch/core/executor/ArchTaskExecutor.java
@@ -29,8 +29,8 @@ import java.util.concurrent.Executor;
* @hide This API is not final.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class AppToolkitTaskExecutor extends TaskExecutor {
- private static volatile AppToolkitTaskExecutor sInstance;
+public class ArchTaskExecutor extends TaskExecutor {
+ private static volatile ArchTaskExecutor sInstance;
@NonNull
private TaskExecutor mDelegate;
@@ -54,7 +54,7 @@ public class AppToolkitTaskExecutor extends TaskExecutor {
}
};
- private AppToolkitTaskExecutor() {
+ private ArchTaskExecutor() {
mDefaultTaskExecutor = new DefaultTaskExecutor();
mDelegate = mDefaultTaskExecutor;
}
@@ -62,15 +62,15 @@ public class AppToolkitTaskExecutor extends TaskExecutor {
/**
* Returns an instance of the task executor.
*
- * @return The singleton AppToolkitTaskExecutor.
+ * @return The singleton ArchTaskExecutor.
*/
- public static AppToolkitTaskExecutor getInstance() {
+ public static ArchTaskExecutor getInstance() {
if (sInstance != null) {
return sInstance;
}
- synchronized (AppToolkitTaskExecutor.class) {
+ synchronized (ArchTaskExecutor.class) {
if (sInstance == null) {
- sInstance = new AppToolkitTaskExecutor();
+ sInstance = new ArchTaskExecutor();
}
}
return sInstance;
diff --git a/android/arch/core/executor/JunitTaskExecutorRule.java b/android/arch/core/executor/JunitTaskExecutorRule.java
index cd4f8f57..c3366f35 100644
--- a/android/arch/core/executor/JunitTaskExecutorRule.java
+++ b/android/arch/core/executor/JunitTaskExecutorRule.java
@@ -46,11 +46,11 @@ public class JunitTaskExecutorRule implements TestRule {
}
private void beforeStart() {
- AppToolkitTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+ ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
}
private void afterFinished() {
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
public TaskExecutor getTaskExecutor() {
diff --git a/android/arch/core/executor/testing/CountingTaskExecutorRule.java b/android/arch/core/executor/testing/CountingTaskExecutorRule.java
index ad930aa8..77133d5b 100644
--- a/android/arch/core/executor/testing/CountingTaskExecutorRule.java
+++ b/android/arch/core/executor/testing/CountingTaskExecutorRule.java
@@ -16,7 +16,7 @@
package android.arch.core.executor.testing;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.executor.DefaultTaskExecutor;
import android.os.SystemClock;
@@ -39,7 +39,7 @@ public class CountingTaskExecutorRule extends TestWatcher {
@Override
protected void starting(Description description) {
super.starting(description);
- AppToolkitTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
+ ArchTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
@Override
public void executeOnDiskIO(Runnable runnable) {
super.executeOnDiskIO(new CountingRunnable(runnable));
@@ -55,7 +55,7 @@ public class CountingTaskExecutorRule extends TestWatcher {
@Override
protected void finished(Description description) {
super.finished(description);
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
private void increment() {
diff --git a/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java b/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
index ad36b9bc..a6a5b2ee 100644
--- a/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
+++ b/android/arch/core/executor/testing/CountingTaskExecutorRuleTest.java
@@ -19,7 +19,7 @@ package android.arch.core.executor.testing;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
@@ -115,13 +115,13 @@ public class CountingTaskExecutorRuleTest {
private LatchRunnable runOnIO() {
LatchRunnable latchRunnable = new LatchRunnable();
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
+ ArchTaskExecutor.getInstance().executeOnDiskIO(latchRunnable);
return latchRunnable;
}
private LatchRunnable runOnMain() {
LatchRunnable latchRunnable = new LatchRunnable();
- AppToolkitTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
+ ArchTaskExecutor.getInstance().executeOnMainThread(latchRunnable);
return latchRunnable;
}
diff --git a/android/arch/core/executor/testing/InstantTaskExecutorRule.java b/android/arch/core/executor/testing/InstantTaskExecutorRule.java
index 07dcf1fe..f88a3e3f 100644
--- a/android/arch/core/executor/testing/InstantTaskExecutorRule.java
+++ b/android/arch/core/executor/testing/InstantTaskExecutorRule.java
@@ -16,7 +16,7 @@
package android.arch.core.executor.testing;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.executor.TaskExecutor;
import org.junit.rules.TestWatcher;
@@ -32,7 +32,7 @@ public class InstantTaskExecutorRule extends TestWatcher {
@Override
protected void starting(Description description) {
super.starting(description);
- AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+ ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
@Override
public void executeOnDiskIO(Runnable runnable) {
runnable.run();
@@ -53,6 +53,6 @@ public class InstantTaskExecutorRule extends TestWatcher {
@Override
protected void finished(Description description) {
super.finished(description);
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
}
diff --git a/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java b/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
index 4345fd19..0fdcbfbb 100644
--- a/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
+++ b/android/arch/core/executor/testing/InstantTaskExecutorRuleTest.java
@@ -18,7 +18,7 @@ package android.arch.core.executor.testing;
import static org.junit.Assert.assertTrue;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import org.junit.Rule;
import org.junit.Test;
@@ -46,7 +46,7 @@ public class InstantTaskExecutorRuleTest {
return null;
}
});
- AppToolkitTaskExecutor.getInstance().executeOnMainThread(check);
+ ArchTaskExecutor.getInstance().executeOnMainThread(check);
check.get(1, TimeUnit.SECONDS);
}
@@ -60,7 +60,7 @@ public class InstantTaskExecutorRuleTest {
return null;
}
});
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(check);
+ ArchTaskExecutor.getInstance().executeOnDiskIO(check);
check.get(1, TimeUnit.SECONDS);
}
}
diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java
new file mode 100644
index 00000000..f077daed
--- /dev/null
+++ b/android/arch/lifecycle/ClassesInfoCache.java
@@ -0,0 +1,237 @@
+/*
+ * 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.arch.lifecycle;
+
+import android.support.annotation.Nullable;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Reflection is expensive, so we cache information about methods
+ * for {@link ReflectiveGenericLifecycleObserver}, so it can call them,
+ * and for {@link Lifecycling} to determine which observer adapter to use.
+ */
+class ClassesInfoCache {
+
+ static ClassesInfoCache sInstance = new ClassesInfoCache();
+
+ private static final int CALL_TYPE_NO_ARG = 0;
+ private static final int CALL_TYPE_PROVIDER = 1;
+ private static final int CALL_TYPE_PROVIDER_WITH_EVENT = 2;
+
+ private final Map<Class, CallbackInfo> mCallbackMap = new HashMap<>();
+ private final Map<Class, Boolean> mHasLifecycleMethods = new HashMap<>();
+
+ boolean hasLifecycleMethods(Class klass) {
+ if (mHasLifecycleMethods.containsKey(klass)) {
+ return mHasLifecycleMethods.get(klass);
+ }
+
+ Method[] methods = klass.getDeclaredMethods();
+ for (Method method : methods) {
+ OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
+ if (annotation != null) {
+ // Optimization for reflection, we know that this method is called
+ // when there is no generated adapter. But there are methods with @OnLifecycleEvent
+ // so we know that will use ReflectiveGenericLifecycleObserver,
+ // so we createInfo in advance.
+ // CreateInfo always initialize mHasLifecycleMethods for a class, so we don't do it
+ // here.
+ createInfo(klass, methods);
+ return true;
+ }
+ }
+ mHasLifecycleMethods.put(klass, false);
+ return false;
+ }
+
+ CallbackInfo getInfo(Class klass) {
+ CallbackInfo existing = mCallbackMap.get(klass);
+ if (existing != null) {
+ return existing;
+ }
+ existing = createInfo(klass, null);
+ return existing;
+ }
+
+ private void verifyAndPutHandler(Map<MethodReference, Lifecycle.Event> handlers,
+ MethodReference newHandler, Lifecycle.Event newEvent, Class klass) {
+ Lifecycle.Event event = handlers.get(newHandler);
+ if (event != null && newEvent != event) {
+ Method method = newHandler.mMethod;
+ throw new IllegalArgumentException(
+ "Method " + method.getName() + " in " + klass.getName()
+ + " already declared with different @OnLifecycleEvent value: previous"
+ + " value " + event + ", new value " + newEvent);
+ }
+ if (event == null) {
+ handlers.put(newHandler, newEvent);
+ }
+ }
+
+ private CallbackInfo createInfo(Class klass, @Nullable Method[] declaredMethods) {
+ Class superclass = klass.getSuperclass();
+ Map<MethodReference, Lifecycle.Event> handlerToEvent = new HashMap<>();
+ if (superclass != null) {
+ CallbackInfo superInfo = getInfo(superclass);
+ if (superInfo != null) {
+ handlerToEvent.putAll(superInfo.mHandlerToEvent);
+ }
+ }
+
+ Class[] interfaces = klass.getInterfaces();
+ for (Class intrfc : interfaces) {
+ for (Map.Entry<MethodReference, Lifecycle.Event> entry : getInfo(
+ intrfc).mHandlerToEvent.entrySet()) {
+ verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
+ }
+ }
+
+ Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
+ boolean hasLifecycleMethods = false;
+ for (Method method : methods) {
+ OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
+ if (annotation == null) {
+ continue;
+ }
+ hasLifecycleMethods = true;
+ Class<?>[] params = method.getParameterTypes();
+ int callType = CALL_TYPE_NO_ARG;
+ if (params.length > 0) {
+ callType = CALL_TYPE_PROVIDER;
+ if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
+ throw new IllegalArgumentException(
+ "invalid parameter type. Must be one and instanceof LifecycleOwner");
+ }
+ }
+ Lifecycle.Event event = annotation.value();
+
+ if (params.length > 1) {
+ callType = CALL_TYPE_PROVIDER_WITH_EVENT;
+ if (!params[1].isAssignableFrom(Lifecycle.Event.class)) {
+ throw new IllegalArgumentException(
+ "invalid parameter type. second arg must be an event");
+ }
+ if (event != Lifecycle.Event.ON_ANY) {
+ throw new IllegalArgumentException(
+ "Second arg is supported only for ON_ANY value");
+ }
+ }
+ if (params.length > 2) {
+ throw new IllegalArgumentException("cannot have more than 2 params");
+ }
+ MethodReference methodReference = new MethodReference(callType, method);
+ verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
+ }
+ CallbackInfo info = new CallbackInfo(handlerToEvent);
+ mCallbackMap.put(klass, info);
+ mHasLifecycleMethods.put(klass, hasLifecycleMethods);
+ return info;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class CallbackInfo {
+ final Map<Lifecycle.Event, List<MethodReference>> mEventToHandlers;
+ final Map<MethodReference, Lifecycle.Event> mHandlerToEvent;
+
+ CallbackInfo(Map<MethodReference, Lifecycle.Event> handlerToEvent) {
+ mHandlerToEvent = handlerToEvent;
+ mEventToHandlers = new HashMap<>();
+ for (Map.Entry<MethodReference, Lifecycle.Event> entry : handlerToEvent.entrySet()) {
+ Lifecycle.Event event = entry.getValue();
+ List<MethodReference> methodReferences = mEventToHandlers.get(event);
+ if (methodReferences == null) {
+ methodReferences = new ArrayList<>();
+ mEventToHandlers.put(event, methodReferences);
+ }
+ methodReferences.add(entry.getKey());
+ }
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ void invokeCallbacks(LifecycleOwner source, Lifecycle.Event event, Object target) {
+ invokeMethodsForEvent(mEventToHandlers.get(event), source, event, target);
+ invokeMethodsForEvent(mEventToHandlers.get(Lifecycle.Event.ON_ANY), source, event,
+ target);
+ }
+
+ private static void invokeMethodsForEvent(List<MethodReference> handlers,
+ LifecycleOwner source, Lifecycle.Event event, Object mWrapped) {
+ if (handlers != null) {
+ for (int i = handlers.size() - 1; i >= 0; i--) {
+ handlers.get(i).invokeCallback(source, event, mWrapped);
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class MethodReference {
+ final int mCallType;
+ final Method mMethod;
+
+ MethodReference(int callType, Method method) {
+ mCallType = callType;
+ mMethod = method;
+ mMethod.setAccessible(true);
+ }
+
+ void invokeCallback(LifecycleOwner source, Lifecycle.Event event, Object target) {
+ //noinspection TryWithIdenticalCatches
+ try {
+ switch (mCallType) {
+ case CALL_TYPE_NO_ARG:
+ mMethod.invoke(target);
+ break;
+ case CALL_TYPE_PROVIDER:
+ mMethod.invoke(target, source);
+ break;
+ case CALL_TYPE_PROVIDER_WITH_EVENT:
+ mMethod.invoke(target, source, event);
+ break;
+ }
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException("Failed to call observer method", e.getCause());
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ MethodReference that = (MethodReference) o;
+ return mCallType == that.mCallType && mMethod.getName().equals(that.mMethod.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * mCallType + mMethod.getName().hashCode();
+ }
+ }
+}
diff --git a/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java b/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java
new file mode 100644
index 00000000..e8cbe7ca
--- /dev/null
+++ b/android/arch/lifecycle/CompositeGeneratedAdaptersObserver.java
@@ -0,0 +1,44 @@
+/*
+ * 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.arch.lifecycle;
+
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class CompositeGeneratedAdaptersObserver implements GenericLifecycleObserver {
+
+ private final GeneratedAdapter[] mGeneratedAdapters;
+
+ CompositeGeneratedAdaptersObserver(GeneratedAdapter[] generatedAdapters) {
+ mGeneratedAdapters = generatedAdapters;
+ }
+
+ @Override
+ public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+ MethodCallsLogger logger = new MethodCallsLogger();
+ for (GeneratedAdapter mGenerated: mGeneratedAdapters) {
+ mGenerated.callMethods(source, event, false, logger);
+ }
+ for (GeneratedAdapter mGenerated: mGeneratedAdapters) {
+ mGenerated.callMethods(source, event, true, logger);
+ }
+ }
+}
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index fe18243f..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.AppToolkitTaskExecutor;
-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
- AppToolkitTaskExecutor.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.
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- }
- }
- };
-
- /**
- * Invalidates the LiveData.
- * <p>
- * When there are active observers, this will trigger a call to {@link #compute()}.
- */
- public void invalidate() {
- AppToolkitTaskExecutor.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/ComputableLiveDataTest.java b/android/arch/lifecycle/ComputableLiveDataTest.java
index 0a3fbed6..eb89d8da 100644
--- a/android/arch/lifecycle/ComputableLiveDataTest.java
+++ b/android/arch/lifecycle/ComputableLiveDataTest.java
@@ -27,7 +27,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.executor.TaskExecutor;
import android.arch.core.executor.TaskExecutorWithFakeMainThread;
import android.arch.lifecycle.util.InstantTaskExecutor;
@@ -58,12 +58,12 @@ public class ComputableLiveDataTest {
@Before
public void swapExecutorDelegate() {
mTaskExecutor = spy(new InstantTaskExecutor());
- AppToolkitTaskExecutor.getInstance().setDelegate(mTaskExecutor);
+ ArchTaskExecutor.getInstance().setDelegate(mTaskExecutor);
}
@After
public void removeExecutorDelegate() {
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
@Test
@@ -76,7 +76,7 @@ public class ComputableLiveDataTest {
@Test
public void noConcurrentCompute() throws InterruptedException {
TaskExecutorWithFakeMainThread executor = new TaskExecutorWithFakeMainThread(2);
- AppToolkitTaskExecutor.getInstance().setDelegate(executor);
+ ArchTaskExecutor.getInstance().setDelegate(executor);
try {
// # of compute calls
final Semaphore computeCounter = new Semaphore(0);
@@ -121,7 +121,7 @@ public class ComputableLiveDataTest {
// assert no other results arrive
verify(observer, never()).onChanged(anyInt());
} finally {
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
}
diff --git a/android/arch/lifecycle/DefaultLifecycleObserver.java b/android/arch/lifecycle/DefaultLifecycleObserver.java
new file mode 100644
index 00000000..b6f468cf
--- /dev/null
+++ b/android/arch/lifecycle/DefaultLifecycleObserver.java
@@ -0,0 +1,100 @@
+/*
+ * 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.arch.lifecycle;
+
+import android.support.annotation.NonNull;
+
+/**
+ * Callback interface for listening to {@link LifecycleOwner} state changes.
+ * <p>
+ * If you use Java 8 language, <b>always</b> prefer it over annotations.
+ */
+@SuppressWarnings("unused")
+public interface DefaultLifecycleObserver extends FullLifecycleObserver {
+
+ /**
+ * Notifies that {@code ON_CREATE} event occurred.
+ * <p>
+ * This method will be called after the {@link LifecycleOwner}'s {@code onCreate}
+ * method returns.
+ *
+ * @param owner the component, whose state was changed
+ */
+ @Override
+ default void onCreate(@NonNull LifecycleOwner owner) {
+ }
+
+ /**
+ * Notifies that {@code ON_START} event occurred.
+ * <p>
+ * This method will be called after the {@link LifecycleOwner}'s {@code onStart} method returns.
+ *
+ * @param owner the component, whose state was changed
+ */
+ @Override
+ default void onStart(@NonNull LifecycleOwner owner) {
+ }
+
+ /**
+ * Notifies that {@code ON_RESUME} event occurred.
+ * <p>
+ * This method will be called after the {@link LifecycleOwner}'s {@code onResume}
+ * method returns.
+ *
+ * @param owner the component, whose state was changed
+ */
+ @Override
+ default void onResume(@NonNull LifecycleOwner owner) {
+ }
+
+ /**
+ * Notifies that {@code ON_PAUSE} event occurred.
+ * <p>
+ * This method will be called before the {@link LifecycleOwner}'s {@code onPause} method
+ * is called.
+ *
+ * @param owner the component, whose state was changed
+ */
+ @Override
+ default void onPause(@NonNull LifecycleOwner owner) {
+ }
+
+ /**
+ * Notifies that {@code ON_STOP} event occurred.
+ * <p>
+ * This method will be called before the {@link LifecycleOwner}'s {@code onStop} method
+ * is called.
+ *
+ * @param owner the component, whose state was changed
+ */
+ @Override
+ default void onStop(@NonNull LifecycleOwner owner) {
+ }
+
+ /**
+ * Notifies that {@code ON_DESTROY} event occurred.
+ * <p>
+ * This method will be called before the {@link LifecycleOwner}'s {@code onStop} method
+ * is called.
+ *
+ * @param owner the component, whose state was changed
+ */
+ @Override
+ default void onDestroy(@NonNull LifecycleOwner owner) {
+ }
+}
+
diff --git a/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java b/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
index 3397f5fd..f48f7886 100644
--- a/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
+++ b/android/arch/lifecycle/FragmentInBackStackLifecycleTest.java
@@ -37,6 +37,7 @@ import android.arch.lifecycle.testapp.R;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
@@ -58,20 +59,20 @@ public class FragmentInBackStackLifecycleTest {
final ArrayList<Event> collectedEvents = new ArrayList<>();
LifecycleObserver collectingObserver = new LifecycleObserver() {
@OnLifecycleEvent(Event.ON_ANY)
- void onAny(LifecycleOwner owner, Event event) {
+ void onAny(@SuppressWarnings("unused") LifecycleOwner owner, Event event) {
collectedEvents.add(event);
}
};
final FragmentActivity activity = activityTestRule.getActivity();
activityTestRule.runOnUiThread(() -> {
FragmentManager fm = activity.getSupportFragmentManager();
- LifecycleFragment fragment = new LifecycleFragment();
+ Fragment fragment = new Fragment();
fm.beginTransaction().add(R.id.fragment_container, fragment, "tag").addToBackStack(null)
.commit();
fm.executePendingTransactions();
fragment.getLifecycle().addObserver(collectingObserver);
- LifecycleFragment fragment2 = new LifecycleFragment();
+ Fragment fragment2 = new Fragment();
fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
.commit();
fm.executePendingTransactions();
@@ -82,12 +83,13 @@ public class FragmentInBackStackLifecycleTest {
EmptyActivity newActivity = recreateActivity(activityTestRule.getActivity(),
activityTestRule);
+ //noinspection ArraysAsListWithZeroOrOneArgument
assertThat(collectedEvents, is(asList(ON_DESTROY)));
collectedEvents.clear();
EmptyActivity lastActivity = recreateActivity(newActivity, activityTestRule);
activityTestRule.runOnUiThread(() -> {
FragmentManager fm = lastActivity.getSupportFragmentManager();
- LifecycleFragment fragment = (LifecycleFragment) fm.findFragmentByTag("tag");
+ Fragment fragment = fm.findFragmentByTag("tag");
fragment.getLifecycle().addObserver(collectingObserver);
assertThat(collectedEvents, iterableWithSize(0));
fm.popBackStackImmediate();
diff --git a/android/arch/lifecycle/FragmentOperationsLifecycleTest.java b/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
index be062cba..3e61277f 100644
--- a/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
+++ b/android/arch/lifecycle/FragmentOperationsLifecycleTest.java
@@ -34,6 +34,7 @@ import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import org.junit.Rule;
@@ -55,7 +56,7 @@ public class FragmentOperationsLifecycleTest {
@UiThreadTest
public void addRemoveFragment() {
EmptyActivity activity = mActivityTestRule.getActivity();
- LifecycleFragment fragment = new LifecycleFragment();
+ Fragment fragment = new Fragment();
FragmentManager fm = activity.getSupportFragmentManager();
fm.beginTransaction().add(fragment, "tag").commitNow();
CollectingObserver observer = observeAndCollectIn(fragment);
@@ -70,7 +71,7 @@ public class FragmentOperationsLifecycleTest {
@UiThreadTest
public void fragmentInBackstack() {
EmptyActivity activity = mActivityTestRule.getActivity();
- LifecycleFragment fragment1 = new LifecycleFragment();
+ Fragment fragment1 = new Fragment();
FragmentManager fm = activity.getSupportFragmentManager();
fm.beginTransaction().add(R.id.fragment_container, fragment1, "tag").addToBackStack(null)
.commit();
@@ -78,7 +79,7 @@ public class FragmentOperationsLifecycleTest {
CollectingObserver observer1 = observeAndCollectIn(fragment1);
assertThat(observer1.getEventsAndReset(), is(asList(ON_CREATE, ON_START, ON_RESUME)));
- LifecycleFragment fragment2 = new LifecycleFragment();
+ Fragment fragment2 = new Fragment();
fm.beginTransaction().replace(R.id.fragment_container, fragment2).addToBackStack(null)
.commit();
fm.executePendingTransactions();
@@ -95,7 +96,7 @@ public class FragmentOperationsLifecycleTest {
assertThat(observer1.getEventsAndReset(), is(asList(ON_PAUSE, ON_STOP, ON_DESTROY)));
}
- private static CollectingObserver observeAndCollectIn(LifecycleFragment fragment) {
+ private static CollectingObserver observeAndCollectIn(Fragment fragment) {
CollectingObserver observer = new CollectingObserver();
fragment.getLifecycle().addObserver(observer);
return observer;
diff --git a/android/arch/lifecycle/FullLifecycleObserver.java b/android/arch/lifecycle/FullLifecycleObserver.java
new file mode 100644
index 00000000..f1792747
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserver.java
@@ -0,0 +1,32 @@
+/*
+ * 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.arch.lifecycle;
+
+interface FullLifecycleObserver extends LifecycleObserver {
+
+ void onCreate(LifecycleOwner owner);
+
+ void onStart(LifecycleOwner owner);
+
+ void onResume(LifecycleOwner owner);
+
+ void onPause(LifecycleOwner owner);
+
+ void onStop(LifecycleOwner owner);
+
+ void onDestroy(LifecycleOwner owner);
+}
diff --git a/android/arch/lifecycle/FullLifecycleObserverAdapter.java b/android/arch/lifecycle/FullLifecycleObserverAdapter.java
new file mode 100644
index 00000000..0a91a668
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserverAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.arch.lifecycle;
+
+class FullLifecycleObserverAdapter implements GenericLifecycleObserver {
+
+ private final FullLifecycleObserver mObserver;
+
+ FullLifecycleObserverAdapter(FullLifecycleObserver observer) {
+ mObserver = observer;
+ }
+
+ @Override
+ public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+ switch (event) {
+ case ON_CREATE:
+ mObserver.onCreate(source);
+ break;
+ case ON_START:
+ mObserver.onStart(source);
+ break;
+ case ON_RESUME:
+ mObserver.onResume(source);
+ break;
+ case ON_PAUSE:
+ mObserver.onPause(source);
+ break;
+ case ON_STOP:
+ mObserver.onStop(source);
+ break;
+ case ON_DESTROY:
+ mObserver.onDestroy(source);
+ break;
+ case ON_ANY:
+ throw new IllegalArgumentException("ON_ANY must not been send by anybody");
+ }
+ }
+}
diff --git a/android/arch/lifecycle/FullLifecycleObserverTest.java b/android/arch/lifecycle/FullLifecycleObserverTest.java
new file mode 100644
index 00000000..def67557
--- /dev/null
+++ b/android/arch/lifecycle/FullLifecycleObserverTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+import static android.arch.lifecycle.Lifecycle.State.INITIALIZED;
+import static android.arch.lifecycle.Lifecycle.State.RESUMED;
+import static android.arch.lifecycle.Lifecycle.State.STARTED;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+
+@RunWith(JUnit4.class)
+public class FullLifecycleObserverTest {
+ private LifecycleOwner mOwner;
+ private Lifecycle mLifecycle;
+
+ @Before
+ public void initMocks() {
+ mOwner = mock(LifecycleOwner.class);
+ mLifecycle = mock(Lifecycle.class);
+ when(mOwner.getLifecycle()).thenReturn(mLifecycle);
+ }
+
+ @Test
+ public void eachEvent() {
+ FullLifecycleObserver obj = mock(FullLifecycleObserver.class);
+ FullLifecycleObserverAdapter observer = new FullLifecycleObserverAdapter(obj);
+ when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+
+ observer.onStateChanged(mOwner, ON_CREATE);
+ InOrder inOrder = Mockito.inOrder(obj);
+ inOrder.verify(obj).onCreate(mOwner);
+ reset(obj);
+
+ when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+ observer.onStateChanged(mOwner, ON_START);
+ inOrder.verify(obj).onStart(mOwner);
+ reset(obj);
+
+ when(mLifecycle.getCurrentState()).thenReturn(RESUMED);
+ observer.onStateChanged(mOwner, ON_RESUME);
+ inOrder.verify(obj).onResume(mOwner);
+ reset(obj);
+
+ when(mLifecycle.getCurrentState()).thenReturn(STARTED);
+ observer.onStateChanged(mOwner, ON_PAUSE);
+ inOrder.verify(obj).onPause(mOwner);
+ reset(obj);
+
+ when(mLifecycle.getCurrentState()).thenReturn(CREATED);
+ observer.onStateChanged(mOwner, ON_STOP);
+ inOrder.verify(obj).onStop(mOwner);
+ reset(obj);
+
+ when(mLifecycle.getCurrentState()).thenReturn(INITIALIZED);
+ observer.onStateChanged(mOwner, ON_DESTROY);
+ inOrder.verify(obj).onDestroy(mOwner);
+ reset(obj);
+ }
+}
diff --git a/android/arch/lifecycle/GeneratedAdapter.java b/android/arch/lifecycle/GeneratedAdapter.java
new file mode 100644
index 00000000..a8862da4
--- /dev/null
+++ b/android/arch/lifecycle/GeneratedAdapter.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.arch.lifecycle;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public interface GeneratedAdapter {
+
+ /**
+ * Called when a state transition event happens.
+ *
+ * @param source The source of the event
+ * @param event The event
+ * @param onAny approveCall onAny handlers
+ * @param logger if passed, used to track called methods and prevent calling the same method
+ * twice
+ */
+ void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+ MethodCallsLogger logger);
+}
diff --git a/android/arch/lifecycle/GeneratedAdaptersTest.java b/android/arch/lifecycle/GeneratedAdaptersTest.java
new file mode 100644
index 00000000..2abb511c
--- /dev/null
+++ b/android/arch/lifecycle/GeneratedAdaptersTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class GeneratedAdaptersTest {
+
+ private LifecycleOwner mOwner;
+ @SuppressWarnings("FieldCanBeLocal")
+ private Lifecycle mLifecycle;
+
+ @Before
+ public void initMocks() {
+ mOwner = mock(LifecycleOwner.class);
+ mLifecycle = mock(Lifecycle.class);
+ when(mOwner.getLifecycle()).thenReturn(mLifecycle);
+ }
+
+ static class SimpleObserver implements LifecycleObserver {
+ List<String> mLog;
+
+ SimpleObserver(List<String> log) {
+ mLog = log;
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ void onCreate() {
+ mLog.add("onCreate");
+ }
+ }
+
+ @Test
+ public void testSimpleSingleGeneratedAdapter() {
+ List<String> actual = new ArrayList<>();
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new SimpleObserver(actual));
+ callback.onStateChanged(mOwner, Lifecycle.Event.ON_CREATE);
+ assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+ assertThat(actual, is(singletonList("onCreate")));
+ }
+
+ static class TestObserver implements LifecycleObserver {
+ List<String> mLog;
+
+ TestObserver(List<String> log) {
+ mLog = log;
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ void onCreate() {
+ mLog.add("onCreate");
+ }
+
+ @OnLifecycleEvent(ON_ANY)
+ void onAny() {
+ mLog.add("onAny");
+ }
+ }
+
+ @Test
+ public void testOnAny() {
+ List<String> actual = new ArrayList<>();
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new TestObserver(actual));
+ callback.onStateChanged(mOwner, Lifecycle.Event.ON_CREATE);
+ assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+ assertThat(actual, is(asList("onCreate", "onAny")));
+ }
+
+ interface OnPauses extends LifecycleObserver {
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ void onPause();
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ void onPause(LifecycleOwner owner);
+ }
+
+ interface OnPauseResume extends LifecycleObserver {
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ void onPause();
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ void onResume();
+ }
+
+ class Impl1 implements OnPauses, OnPauseResume {
+
+ List<String> mLog;
+
+ Impl1(List<String> log) {
+ mLog = log;
+ }
+
+ @Override
+ public void onPause() {
+ mLog.add("onPause_0");
+ }
+
+ @Override
+ public void onResume() {
+ mLog.add("onResume");
+ }
+
+ @Override
+ public void onPause(LifecycleOwner owner) {
+ mLog.add("onPause_1");
+ }
+ }
+
+ @Test
+ public void testClashingInterfaces() {
+ List<String> actual = new ArrayList<>();
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new Impl1(actual));
+ callback.onStateChanged(mOwner, Lifecycle.Event.ON_PAUSE);
+ assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+ assertThat(actual, is(asList("onPause_0", "onPause_1")));
+ actual.clear();
+ callback.onStateChanged(mOwner, Lifecycle.Event.ON_RESUME);
+ assertThat(actual, is(singletonList("onResume")));
+ }
+
+ class Base implements LifecycleObserver {
+
+ List<String> mLog;
+
+ Base(List<String> log) {
+ mLog = log;
+ }
+
+ @OnLifecycleEvent(ON_ANY)
+ void onAny() {
+ mLog.add("onAny_0");
+ }
+
+ @OnLifecycleEvent(ON_ANY)
+ void onAny(LifecycleOwner owner) {
+ mLog.add("onAny_1");
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ void onResume() {
+ mLog.add("onResume");
+ }
+ }
+
+ interface OnAny extends LifecycleObserver {
+ @OnLifecycleEvent(ON_ANY)
+ void onAny();
+
+ @OnLifecycleEvent(ON_ANY)
+ void onAny(LifecycleOwner owner, Lifecycle.Event event);
+ }
+
+ class Derived extends Base implements OnAny {
+ Derived(List<String> log) {
+ super(log);
+ }
+
+ @Override
+ public void onAny() {
+ super.onAny();
+ }
+
+ @Override
+ public void onAny(LifecycleOwner owner, Lifecycle.Event event) {
+ mLog.add("onAny_2");
+ assertThat(event, is(ON_RESUME));
+ }
+ }
+
+ @Test
+ public void testClashingClassAndInterface() {
+ List<String> actual = new ArrayList<>();
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new Derived(actual));
+ callback.onStateChanged(mOwner, Lifecycle.Event.ON_RESUME);
+ assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+ assertThat(actual, is(asList("onResume", "onAny_0", "onAny_1", "onAny_2")));
+ }
+
+}
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index fcbd50ad..02db5ff9 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -34,21 +34,25 @@ import android.support.annotation.MainThread;
* before {@link android.app.Activity#onStop onStop} is called.
* This gives you certain guarantees on which state the owner is in.
* <p>
- * Lifecycle events are observed using annotations.
+ * If you use <b>Java 8 Language</b>, then observe events with {@link DefaultLifecycleObserver}.
+ * To include it you should add {@code "android.arch.lifecycle:common-java8:<version>"} to your
+ * build.gradle file.
* <pre>
- * class TestObserver implements LifecycleObserver {
- * {@literal @}OnLifecycleEvent(ON_STOP)
- * void onStopped() {}
+ * class TestObserver implements DefaultLifecycleObserver {
+ * {@literal @}Override
+ * public void onCreate(LifecycleOwner owner) {
+ * // your code
+ * }
* }
* </pre>
- * <p>
- * Multiple methods can observe the same event.
+ * If you use <b>Java 7 Language</b>, Lifecycle events are observed using annotations.
+ * Once Java 8 Language becomes mainstream on Android, annotations will be deprecated, so between
+ * {@link DefaultLifecycleObserver} and annotations,
+ * you must always prefer {@code DefaultLifecycleObserver}.
* <pre>
* class TestObserver implements LifecycleObserver {
* {@literal @}OnLifecycleEvent(ON_STOP)
- * void onStoppedFirst() {}
- * {@literal @}OnLifecycleEvent(ON_STOP)
- * void onStoppedSecond() {}
+ * void onStopped() {}
* }
* </pre>
* <p>
diff --git a/android/arch/lifecycle/Lifecycling.java b/android/arch/lifecycle/Lifecycling.java
index 3a5c0b9e..7d6b37fd 100644
--- a/android/arch/lifecycle/Lifecycling.java
+++ b/android/arch/lifecycle/Lifecycling.java
@@ -22,52 +22,61 @@ import android.support.annotation.RestrictTo;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
* Internal class to handle lifecycle conversion etc.
+ *
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class Lifecycling {
- private static Constructor<? extends GenericLifecycleObserver> sREFLECTIVE;
-
- static {
- try {
- sREFLECTIVE = ReflectiveGenericLifecycleObserver.class
- .getDeclaredConstructor(Object.class);
- } catch (NoSuchMethodException ignored) {
+public class Lifecycling {
- }
- }
+ private static final int REFLECTIVE_CALLBACK = 1;
+ private static final int GENERATED_CALLBACK = 2;
- private static Map<Class, Constructor<? extends GenericLifecycleObserver>> sCallbackCache =
+ private static Map<Class, Integer> sCallbackCache = new HashMap<>();
+ private static Map<Class, List<Constructor<? extends GeneratedAdapter>>> sClassToAdapters =
new HashMap<>();
@NonNull
static GenericLifecycleObserver getCallback(Object object) {
+ if (object instanceof FullLifecycleObserver) {
+ return new FullLifecycleObserverAdapter((FullLifecycleObserver) object);
+ }
+
if (object instanceof GenericLifecycleObserver) {
return (GenericLifecycleObserver) object;
}
- //noinspection TryWithIdenticalCatches
- try {
- final Class<?> klass = object.getClass();
- Constructor<? extends GenericLifecycleObserver> cachedConstructor = sCallbackCache.get(
- klass);
- if (cachedConstructor != null) {
- return cachedConstructor.newInstance(object);
+
+ final Class<?> klass = object.getClass();
+ int type = getObserverConstructorType(klass);
+ if (type == GENERATED_CALLBACK) {
+ List<Constructor<? extends GeneratedAdapter>> constructors =
+ sClassToAdapters.get(klass);
+ if (constructors.size() == 1) {
+ GeneratedAdapter generatedAdapter = createGeneratedAdapter(
+ constructors.get(0), object);
+ return new SingleGeneratedAdapterObserver(generatedAdapter);
}
- cachedConstructor = getGeneratedAdapterConstructor(klass);
- if (cachedConstructor != null) {
- if (!cachedConstructor.isAccessible()) {
- cachedConstructor.setAccessible(true);
- }
- } else {
- cachedConstructor = sREFLECTIVE;
+ GeneratedAdapter[] adapters = new GeneratedAdapter[constructors.size()];
+ for (int i = 0; i < constructors.size(); i++) {
+ adapters[i] = createGeneratedAdapter(constructors.get(i), object);
}
- sCallbackCache.put(klass, cachedConstructor);
- return cachedConstructor.newInstance(object);
+ return new CompositeGeneratedAdaptersObserver(adapters);
+ }
+ return new ReflectiveGenericLifecycleObserver(object);
+ }
+
+ private static GeneratedAdapter createGeneratedAdapter(
+ Constructor<? extends GeneratedAdapter> constructor, Object object) {
+ //noinspection TryWithIdenticalCatches
+ try {
+ return constructor.newInstance(object);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
@@ -78,37 +87,95 @@ class Lifecycling {
}
@Nullable
- private static Constructor<? extends GenericLifecycleObserver> getGeneratedAdapterConstructor(
- Class<?> klass) {
- Package aPackage = klass.getPackage();
- final String fullPackage = aPackage != null ? aPackage.getName() : "";
-
- String name = klass.getCanonicalName();
- // anonymous class bug:35073837
- if (name == null) {
- return null;
- }
- final String adapterName = getAdapterName(fullPackage.isEmpty() ? name :
- name.substring(fullPackage.length() + 1));
+ private static Constructor<? extends GeneratedAdapter> generatedConstructor(Class<?> klass) {
try {
- @SuppressWarnings("unchecked")
- final Class<? extends GenericLifecycleObserver> aClass =
- (Class<? extends GenericLifecycleObserver>) Class.forName(
+ Package aPackage = klass.getPackage();
+ String name = klass.getCanonicalName();
+ final String fullPackage = aPackage != null ? aPackage.getName() : "";
+ final String adapterName = getAdapterName(fullPackage.isEmpty() ? name :
+ name.substring(fullPackage.length() + 1));
+
+ @SuppressWarnings("unchecked") final Class<? extends GeneratedAdapter> aClass =
+ (Class<? extends GeneratedAdapter>) Class.forName(
fullPackage.isEmpty() ? adapterName : fullPackage + "." + adapterName);
- return aClass.getDeclaredConstructor(klass);
- } catch (ClassNotFoundException e) {
- final Class<?> superclass = klass.getSuperclass();
- if (superclass != null) {
- return getGeneratedAdapterConstructor(superclass);
+ Constructor<? extends GeneratedAdapter> constructor =
+ aClass.getDeclaredConstructor(klass);
+ if (!constructor.isAccessible()) {
+ constructor.setAccessible(true);
}
+ return constructor;
+ } catch (ClassNotFoundException e) {
+ return null;
} catch (NoSuchMethodException e) {
// this should not happen
throw new RuntimeException(e);
}
- return null;
}
- static String getAdapterName(String className) {
+ private static int getObserverConstructorType(Class<?> klass) {
+ if (sCallbackCache.containsKey(klass)) {
+ return sCallbackCache.get(klass);
+ }
+ int type = resolveObserverCallbackType(klass);
+ sCallbackCache.put(klass, type);
+ return type;
+ }
+
+ private static int resolveObserverCallbackType(Class<?> klass) {
+ // anonymous class bug:35073837
+ if (klass.getCanonicalName() == null) {
+ return REFLECTIVE_CALLBACK;
+ }
+
+ Constructor<? extends GeneratedAdapter> constructor = generatedConstructor(klass);
+ if (constructor != null) {
+ sClassToAdapters.put(klass, Collections
+ .<Constructor<? extends GeneratedAdapter>>singletonList(constructor));
+ return GENERATED_CALLBACK;
+ }
+
+ boolean hasLifecycleMethods = ClassesInfoCache.sInstance.hasLifecycleMethods(klass);
+ if (hasLifecycleMethods) {
+ return REFLECTIVE_CALLBACK;
+ }
+
+ Class<?> superclass = klass.getSuperclass();
+ List<Constructor<? extends GeneratedAdapter>> adapterConstructors = null;
+ if (isLifecycleParent(superclass)) {
+ if (getObserverConstructorType(superclass) == REFLECTIVE_CALLBACK) {
+ return REFLECTIVE_CALLBACK;
+ }
+ adapterConstructors = new ArrayList<>(sClassToAdapters.get(superclass));
+ }
+
+ for (Class<?> intrface : klass.getInterfaces()) {
+ if (!isLifecycleParent(intrface)) {
+ continue;
+ }
+ if (getObserverConstructorType(intrface) == REFLECTIVE_CALLBACK) {
+ return REFLECTIVE_CALLBACK;
+ }
+ if (adapterConstructors == null) {
+ adapterConstructors = new ArrayList<>();
+ }
+ adapterConstructors.addAll(sClassToAdapters.get(intrface));
+ }
+ if (adapterConstructors != null) {
+ sClassToAdapters.put(klass, adapterConstructors);
+ return GENERATED_CALLBACK;
+ }
+
+ return REFLECTIVE_CALLBACK;
+ }
+
+ private static boolean isLifecycleParent(Class<?> klass) {
+ return klass != null && LifecycleObserver.class.isAssignableFrom(klass);
+ }
+
+ /**
+ * Create a name for an adapter class.
+ */
+ public static String getAdapterName(String className) {
return className.replace(".", "_") + "_LifecycleAdapter";
}
}
diff --git a/android/arch/lifecycle/LifecyclingTest.java b/android/arch/lifecycle/LifecyclingTest.java
new file mode 100644
index 00000000..70ce84c3
--- /dev/null
+++ b/android/arch/lifecycle/LifecyclingTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.arch.lifecycle;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.lifecycle.observers.DerivedSequence1;
+import android.arch.lifecycle.observers.DerivedSequence2;
+import android.arch.lifecycle.observers.DerivedWithNewMethods;
+import android.arch.lifecycle.observers.DerivedWithNoNewMethods;
+import android.arch.lifecycle.observers.DerivedWithOverridenMethodsWithLfAnnotation;
+import android.arch.lifecycle.observers.InterfaceImpl1;
+import android.arch.lifecycle.observers.InterfaceImpl2;
+import android.arch.lifecycle.observers.InterfaceImpl3;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class LifecyclingTest {
+
+ @Test
+ public void testDerivedWithNewLfMethodsNoGeneratedAdapter() {
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new DerivedWithNewMethods());
+ assertThat(callback, instanceOf(ReflectiveGenericLifecycleObserver.class));
+ }
+
+ @Test
+ public void testDerivedWithNoNewLfMethodsNoGeneratedAdapter() {
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new DerivedWithNoNewMethods());
+ assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+ }
+
+ @Test
+ public void testDerivedWithOverridenMethodsNoGeneratedAdapter() {
+ GenericLifecycleObserver callback = Lifecycling.getCallback(
+ new DerivedWithOverridenMethodsWithLfAnnotation());
+ // that is not effective but...
+ assertThat(callback, instanceOf(ReflectiveGenericLifecycleObserver.class));
+ }
+
+ @Test
+ public void testInterfaceImpl1NoGeneratedAdapter() {
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl1());
+ assertThat(callback, instanceOf(SingleGeneratedAdapterObserver.class));
+ }
+
+ @Test
+ public void testInterfaceImpl2NoGeneratedAdapter() {
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl2());
+ assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+ }
+
+ @Test
+ public void testInterfaceImpl3NoGeneratedAdapter() {
+ GenericLifecycleObserver callback = Lifecycling.getCallback(new InterfaceImpl3());
+ assertThat(callback, instanceOf(CompositeGeneratedAdaptersObserver.class));
+ }
+
+ @Test
+ public void testDerivedSequence() {
+ GenericLifecycleObserver callback2 = Lifecycling.getCallback(new DerivedSequence2());
+ assertThat(callback2, instanceOf(ReflectiveGenericLifecycleObserver.class));
+ GenericLifecycleObserver callback1 = Lifecycling.getCallback(new DerivedSequence1());
+ assertThat(callback1, instanceOf(SingleGeneratedAdapterObserver.class));
+ }
+}
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 99d859c4..3aea6acb 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,411 +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.AppToolkitTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
-import android.support.annotation.MainThread;
-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 hold 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())) {
- 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(LifecycleOwner owner, 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);
- wrapper.activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
- }
-
- /**
- * 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(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(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(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;
- }
- AppToolkitTaskExecutor.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 LifecycleObserver {
- 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;
- }
-
- @SuppressWarnings("unused")
- @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
- void onStateChange() {
- 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 (!AppToolkitTaskExecutor.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 0be01496..2b25bc9b 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreams.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreams.java
@@ -16,7 +16,8 @@
package android.arch.lifecycle;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.reactivestreams.Publisher;
@@ -85,7 +86,7 @@ public final class LiveDataReactiveStreams {
if (n < 0 || mCanceled) {
return;
}
- AppToolkitTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+ ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
public void run() {
if (mCanceled) {
@@ -110,7 +111,7 @@ public final class LiveDataReactiveStreams {
if (mCanceled) {
return;
}
- AppToolkitTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
+ ArchTaskExecutor.getInstance().executeOnMainThread(new Runnable() {
@Override
public void run() {
if (mCanceled) {
@@ -133,40 +134,101 @@ public final class LiveDataReactiveStreams {
/**
* Creates an Observable {@link LiveData} stream from a ReactiveStreams publisher.
+ *
+ * <p>
+ * When the LiveData becomes active, it subscribes to the emissions from the Publisher.
+ *
+ * <p>
+ * When the LiveData becomes inactive, the subscription is cleared.
+ * LiveData holds the last value emitted by the Publisher when the LiveData was active.
+ * <p>
+ * 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.
+ *
+ * @param <T> The type of data hold by this instance.
*/
public static <T> LiveData<T> fromPublisher(final Publisher<T> publisher) {
- MutableLiveData<T> liveData = new MutableLiveData<>();
- // Since we don't have a way to directly observe cancels, weakly hold the live data.
- final WeakReference<MutableLiveData<T>> liveDataRef = new WeakReference<>(liveData);
+ return new PublisherLiveData<>(publisher);
+ }
- publisher.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.
- s.request(Long.MAX_VALUE);
- }
+ /**
+ * Defines a {@link LiveData} object that wraps a {@link Publisher}.
+ *
+ * <p>
+ * When the LiveData becomes active, it subscribes to the emissions from the Publisher.
+ *
+ * <p>
+ * When the LiveData becomes inactive, the subscription is cleared.
+ * LiveData holds the last value emitted by the Publisher when the LiveData was active.
+ * <p>
+ * 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.
+ *
+ * @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();
+
+ PublisherLiveData(@NonNull final Publisher publisher) {
+ mPublisher = publisher;
+ }
+
+ @Override
+ protected void onActive() {
+ super.onActive();
+
+ 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
- public void onNext(final T t) {
- final LiveData<T> liveData = liveDataRef.get();
- if (liveData != null) {
- liveData.postValue(t);
+ @Override
+ public void onNext(final T t) {
+ postValue(t);
}
- }
- @Override
- public void onError(Throwable t) {
- // Errors should be handled upstream, so propagate as a crash.
- throw new RuntimeException(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 onComplete() {
+ @Override
+ public void onComplete() {
+ synchronized (mLock) {
+ mSubscriptionRef = 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();
+ }
+ mSubscriptionRef = null;
+ }
}
- });
-
- return liveData;
+ }
}
-
}
diff --git a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
index 87fba27c..7278847c 100644
--- a/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
+++ b/android/arch/lifecycle/LiveDataReactiveStreamsTest.java
@@ -16,12 +16,10 @@
package android.arch.lifecycle;
-import static android.arch.lifecycle.Lifecycle.State.RESUMED;
-
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.executor.TaskExecutor;
import android.support.annotation.Nullable;
import android.support.test.filters.SmallTest;
@@ -47,28 +45,7 @@ import io.reactivex.subjects.AsyncSubject;
@SmallTest
public class LiveDataReactiveStreamsTest {
- private static final Lifecycle sLifecycle = new Lifecycle() {
- @Override
- public void addObserver(LifecycleObserver observer) {
- }
-
- @Override
- public void removeObserver(LifecycleObserver observer) {
- }
-
- @Override
- public State getCurrentState() {
- return RESUMED;
- }
- };
- private static final LifecycleOwner S_LIFECYCLE_OWNER = new LifecycleOwner() {
-
- @Override
- public Lifecycle getLifecycle() {
- return sLifecycle;
- }
-
- };
+ private LifecycleOwner mLifecycleOwner;
private final List<String> mLiveDataOutput = new ArrayList<>();
private final Observer<String> mObserver = new Observer<String>() {
@@ -85,8 +62,19 @@ public class LiveDataReactiveStreamsTest {
@Before
public void init() {
+ mLifecycleOwner = new LifecycleOwner() {
+ LifecycleRegistry mRegistry = new LifecycleRegistry(this);
+ {
+ mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ }
+
+ @Override
+ public Lifecycle getLifecycle() {
+ return mRegistry;
+ }
+ };
mTestThread = Thread.currentThread();
- AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+ ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
@Override
public void executeOnDiskIO(Runnable runnable) {
@@ -109,7 +97,7 @@ public class LiveDataReactiveStreamsTest {
@After
public void removeExecutorDelegate() {
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
@Test
@@ -117,7 +105,7 @@ public class LiveDataReactiveStreamsTest {
PublishProcessor<String> processor = PublishProcessor.create();
LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
- liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+ liveData.observe(mLifecycleOwner, mObserver);
processor.onNext("foo");
processor.onNext("bar");
@@ -132,13 +120,13 @@ public class LiveDataReactiveStreamsTest {
PublishProcessor<String> processor = PublishProcessor.create();
LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
- liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+ liveData.observe(mLifecycleOwner, mObserver);
processor.onNext("foo");
processor.onNext("bar");
// The second mObserver should only get the newest value and any later values.
- liveData.observe(S_LIFECYCLE_OWNER, new Observer<String>() {
+ liveData.observe(mLifecycleOwner, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
output2.add(s);
@@ -152,12 +140,44 @@ public class LiveDataReactiveStreamsTest {
}
@Test
+ public void convertsFromPublisherAfterInactive() {
+ PublishProcessor<String> processor = PublishProcessor.create();
+ LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+ liveData.observe(mLifecycleOwner, mObserver);
+ processor.onNext("foo");
+ liveData.removeObserver(mObserver);
+ processor.onNext("bar");
+
+ liveData.observe(mLifecycleOwner, mObserver);
+ processor.onNext("baz");
+
+ assertThat(mLiveDataOutput, is(Arrays.asList("foo", "foo", "baz")));
+ }
+
+ @Test
+ public void convertsFromPublisherManagesSubcriptions() {
+ PublishProcessor<String> processor = PublishProcessor.create();
+ LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(processor);
+
+ assertThat(processor.hasSubscribers(), is(false));
+ liveData.observe(mLifecycleOwner, mObserver);
+
+ // once the live data is active, there's a subscriber
+ assertThat(processor.hasSubscribers(), is(true));
+
+ liveData.removeObserver(mObserver);
+ // once the live data is inactive, the subscriber is removed
+ assertThat(processor.hasSubscribers(), is(false));
+ }
+
+ @Test
public void convertsFromAsyncPublisher() {
Flowable<String> input = Flowable.just("foo")
.concatWith(Flowable.just("bar", "baz").observeOn(sBackgroundScheduler));
LiveData<String> liveData = LiveDataReactiveStreams.fromPublisher(input);
- liveData.observe(S_LIFECYCLE_OWNER, mObserver);
+ liveData.observe(mLifecycleOwner, mObserver);
assertThat(mLiveDataOutput, is(Collections.singletonList("foo")));
sBackgroundScheduler.triggerActions();
@@ -170,7 +190,7 @@ public class LiveDataReactiveStreamsTest {
liveData.setValue("foo");
assertThat(liveData.getValue(), is("foo"));
- Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+ Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
.subscribe(mOutputProcessor);
liveData.setValue("bar");
@@ -188,7 +208,7 @@ public class LiveDataReactiveStreamsTest {
assertThat(liveData.getValue(), is("foo"));
Disposable disposable = Flowable
- .fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+ .fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
.subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
@@ -216,7 +236,7 @@ public class LiveDataReactiveStreamsTest {
final AsyncSubject<Subscription> subscriptionSubject = AsyncSubject.create();
- Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+ Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
.subscribe(new Subscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
@@ -275,7 +295,7 @@ public class LiveDataReactiveStreamsTest {
public void convertsToPublisherWithAsyncData() {
MutableLiveData<String> liveData = new MutableLiveData<>();
- Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(S_LIFECYCLE_OWNER, liveData))
+ Flowable.fromPublisher(LiveDataReactiveStreams.toPublisher(mLifecycleOwner, liveData))
.observeOn(sBackgroundScheduler)
.subscribe(mOutputProcessor);
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index ed2a35dc..9f0b4257 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -36,16 +36,20 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.lifecycle.util.InstantTaskExecutor;
import android.support.annotation.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
import org.mockito.Mockito;
@SuppressWarnings({"unchecked"})
+@RunWith(JUnit4.class)
public class LiveDataTest {
private PublicLiveData<String> mLiveData;
private LifecycleOwner mOwner;
@@ -66,12 +70,12 @@ public class LiveDataTest {
@Before
public void swapExecutorDelegate() {
- AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+ ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
}
@After
public void removeExecutorDelegate() {
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
@Test
@@ -418,6 +422,40 @@ public class LiveDataTest {
verify(mActiveObserversChanged, never()).onCall(anyBoolean());
}
+ @Test
+ public void testRemoveDuringAddition() {
+ mRegistry.handleLifecycleEvent(ON_START);
+ mLiveData.setValue("bla");
+ mLiveData.observeForever(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ mLiveData.removeObserver(this);
+ }
+ });
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ InOrder inOrder = Mockito.inOrder(mActiveObserversChanged);
+ inOrder.verify(mActiveObserversChanged).onCall(true);
+ inOrder.verify(mActiveObserversChanged).onCall(false);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testRemoveDuringBringingUpToState() {
+ mLiveData.setValue("bla");
+ mLiveData.observeForever(new Observer<String>() {
+ @Override
+ public void onChanged(@Nullable String s) {
+ mLiveData.removeObserver(this);
+ }
+ });
+ mRegistry.handleLifecycleEvent(ON_RESUME);
+ assertThat(mLiveData.hasActiveObservers(), is(false));
+ InOrder inOrder = Mockito.inOrder(mActiveObserversChanged);
+ inOrder.verify(mActiveObserversChanged).onCall(true);
+ inOrder.verify(mActiveObserversChanged).onCall(false);
+ inOrder.verifyNoMoreInteractions();
+ }
+
@SuppressWarnings("WeakerAccess")
static class PublicLiveData<T> extends LiveData<T> {
// cannot spy due to internal calls
diff --git a/android/arch/lifecycle/MediatorLiveDataTest.java b/android/arch/lifecycle/MediatorLiveDataTest.java
index 3de3eeeb..e2eadbe8 100644
--- a/android/arch/lifecycle/MediatorLiveDataTest.java
+++ b/android/arch/lifecycle/MediatorLiveDataTest.java
@@ -25,7 +25,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.lifecycle.util.InstantTaskExecutor;
import android.support.annotation.Nullable;
@@ -69,7 +69,7 @@ public class MediatorLiveDataTest {
@Before
public void swapExecutorDelegate() {
- AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+ ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
}
@Test
diff --git a/android/arch/lifecycle/MethodCallsLogger.java b/android/arch/lifecycle/MethodCallsLogger.java
new file mode 100644
index 00000000..031e43e4
--- /dev/null
+++ b/android/arch/lifecycle/MethodCallsLogger.java
@@ -0,0 +1,42 @@
+/*
+ * 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.arch.lifecycle;
+
+import android.support.annotation.RestrictTo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class MethodCallsLogger {
+ private Map<String, Integer> mCalledMethods = new HashMap<>();
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public boolean approveCall(String name, int type) {
+ Integer nullableMask = mCalledMethods.get(name);
+ int mask = nullableMask != null ? nullableMask : 0;
+ boolean wasCalled = (mask & type) != 0;
+ mCalledMethods.put(name, mask | type);
+ return !wasCalled;
+ }
+}
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index e80e11c4..37bdcdb4 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -37,6 +37,7 @@ import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.FragmentActivity;
import org.junit.After;
import org.junit.Rule;
@@ -78,7 +79,7 @@ public class ProcessOwnerTest {
@Test
public void testNavigation() throws Throwable {
- LifecycleActivity firstActivity = setupObserverOnResume();
+ FragmentActivity firstActivity = setupObserverOnResume();
Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
NavigationTestActivitySecond.class.getCanonicalName(), null, false);
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
@@ -88,15 +89,15 @@ public class ProcessOwnerTest {
firstActivity.finish();
firstActivity.startActivity(intent);
- LifecycleActivity secondActivity = (LifecycleActivity) monitor.waitForActivity();
+ FragmentActivity secondActivity = (FragmentActivity) monitor.waitForActivity();
assertThat("Failed to navigate", secondActivity, notNullValue());
checkProcessObserverSilent(secondActivity);
}
@Test
public void testRecreation() throws Throwable {
- LifecycleActivity activity = setupObserverOnResume();
- LifecycleActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
+ FragmentActivity activity = setupObserverOnResume();
+ FragmentActivity recreated = TestUtils.recreateActivity(activity, activityTestRule);
assertThat("Failed to recreate", recreated, notNullValue());
checkProcessObserverSilent(recreated);
}
@@ -112,14 +113,15 @@ public class ProcessOwnerTest {
NavigationTestActivityFirst activity = activityTestRule.getActivity();
activity.startActivity(new Intent(activity, NavigationDialogActivity.class));
- LifecycleActivity dialogActivity = (LifecycleActivity) monitor.waitForActivity();
+ FragmentActivity dialogActivity = (FragmentActivity) monitor.waitForActivity();
checkProcessObserverSilent(dialogActivity);
List<Event> events = Collections.synchronizedList(new ArrayList<>());
LifecycleObserver collectingObserver = new LifecycleObserver() {
@OnLifecycleEvent(Event.ON_ANY)
- public void onStateChanged(LifecycleOwner provider, Event event) {
+ public void onStateChanged(@SuppressWarnings("unused") LifecycleOwner provider,
+ Event event) {
events.add(event);
}
};
@@ -138,8 +140,8 @@ public class ProcessOwnerTest {
dialogActivity.finish();
}
- private LifecycleActivity setupObserverOnResume() throws Throwable {
- LifecycleActivity firstActivity = activityTestRule.getActivity();
+ private FragmentActivity setupObserverOnResume() throws Throwable {
+ FragmentActivity firstActivity = activityTestRule.getActivity();
waitTillResumed(firstActivity, activityTestRule);
addProcessObserver(mObserver);
mObserver.mChangedState = false;
@@ -156,7 +158,7 @@ public class ProcessOwnerTest {
ProcessLifecycleOwner.get().getLifecycle().removeObserver(observer));
}
- private void checkProcessObserverSilent(LifecycleActivity activity) throws Throwable {
+ private void checkProcessObserverSilent(FragmentActivity activity) throws Throwable {
waitTillResumed(activity, activityTestRule);
assertThat(mObserver.mChangedState, is(false));
activityTestRule.runOnUiThread(() ->
diff --git a/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java b/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
index 44815e6d..f010ed84 100644
--- a/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
+++ b/android/arch/lifecycle/ReflectiveGenericLifecycleObserver.java
@@ -16,204 +16,23 @@
package android.arch.lifecycle;
+import android.arch.lifecycle.ClassesInfoCache.CallbackInfo;
import android.arch.lifecycle.Lifecycle.Event;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
/**
* An internal implementation of {@link GenericLifecycleObserver} that relies on reflection.
*/
class ReflectiveGenericLifecycleObserver implements GenericLifecycleObserver {
private final Object mWrapped;
private final CallbackInfo mInfo;
- @SuppressWarnings("WeakerAccess")
- static final Map<Class, CallbackInfo> sInfoCache = new HashMap<>();
ReflectiveGenericLifecycleObserver(Object wrapped) {
mWrapped = wrapped;
- mInfo = getInfo(mWrapped.getClass());
+ mInfo = ClassesInfoCache.sInstance.getInfo(mWrapped.getClass());
}
@Override
public void onStateChanged(LifecycleOwner source, Event event) {
- invokeCallbacks(mInfo, source, event);
- }
-
- private void invokeMethodsForEvent(List<MethodReference> handlers, LifecycleOwner source,
- Event event) {
- if (handlers != null) {
- for (int i = handlers.size() - 1; i >= 0; i--) {
- MethodReference reference = handlers.get(i);
- invokeCallback(reference, source, event);
- }
- }
- }
-
- @SuppressWarnings("ConstantConditions")
- private void invokeCallbacks(CallbackInfo info, LifecycleOwner source, Event event) {
- invokeMethodsForEvent(info.mEventToHandlers.get(event), source, event);
- invokeMethodsForEvent(info.mEventToHandlers.get(Event.ON_ANY), source, event);
- }
-
- private void invokeCallback(MethodReference reference, LifecycleOwner source, Event event) {
- //noinspection TryWithIdenticalCatches
- try {
- switch (reference.mCallType) {
- case CALL_TYPE_NO_ARG:
- reference.mMethod.invoke(mWrapped);
- break;
- case CALL_TYPE_PROVIDER:
- reference.mMethod.invoke(mWrapped, source);
- break;
- case CALL_TYPE_PROVIDER_WITH_EVENT:
- reference.mMethod.invoke(mWrapped, source, event);
- break;
- }
- } catch (InvocationTargetException e) {
- throw new RuntimeException("Failed to call observer method", e.getCause());
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
+ mInfo.invokeCallbacks(source, event, mWrapped);
}
-
- private static CallbackInfo getInfo(Class klass) {
- CallbackInfo existing = sInfoCache.get(klass);
- if (existing != null) {
- return existing;
- }
- existing = createInfo(klass);
- return existing;
- }
-
- private static void verifyAndPutHandler(Map<MethodReference, Event> handlers,
- MethodReference newHandler, Event newEvent, Class klass) {
- Event event = handlers.get(newHandler);
- if (event != null && newEvent != event) {
- Method method = newHandler.mMethod;
- throw new IllegalArgumentException(
- "Method " + method.getName() + " in " + klass.getName()
- + " already declared with different @OnLifecycleEvent value: previous"
- + " value " + event + ", new value " + newEvent);
- }
- if (event == null) {
- handlers.put(newHandler, newEvent);
- }
- }
-
- private static CallbackInfo createInfo(Class klass) {
- Class superclass = klass.getSuperclass();
- Map<MethodReference, Event> handlerToEvent = new HashMap<>();
- if (superclass != null) {
- CallbackInfo superInfo = getInfo(superclass);
- if (superInfo != null) {
- handlerToEvent.putAll(superInfo.mHandlerToEvent);
- }
- }
-
- Method[] methods = klass.getDeclaredMethods();
-
- Class[] interfaces = klass.getInterfaces();
- for (Class intrfc : interfaces) {
- for (Entry<MethodReference, Event> entry : getInfo(intrfc).mHandlerToEvent.entrySet()) {
- verifyAndPutHandler(handlerToEvent, entry.getKey(), entry.getValue(), klass);
- }
- }
-
- for (Method method : methods) {
- OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
- if (annotation == null) {
- continue;
- }
- Class<?>[] params = method.getParameterTypes();
- int callType = CALL_TYPE_NO_ARG;
- if (params.length > 0) {
- callType = CALL_TYPE_PROVIDER;
- if (!params[0].isAssignableFrom(LifecycleOwner.class)) {
- throw new IllegalArgumentException(
- "invalid parameter type. Must be one and instanceof LifecycleOwner");
- }
- }
- Event event = annotation.value();
-
- if (params.length > 1) {
- callType = CALL_TYPE_PROVIDER_WITH_EVENT;
- if (!params[1].isAssignableFrom(Event.class)) {
- throw new IllegalArgumentException(
- "invalid parameter type. second arg must be an event");
- }
- if (event != Event.ON_ANY) {
- throw new IllegalArgumentException(
- "Second arg is supported only for ON_ANY value");
- }
- }
- if (params.length > 2) {
- throw new IllegalArgumentException("cannot have more than 2 params");
- }
- MethodReference methodReference = new MethodReference(callType, method);
- verifyAndPutHandler(handlerToEvent, methodReference, event, klass);
- }
- CallbackInfo info = new CallbackInfo(handlerToEvent);
- sInfoCache.put(klass, info);
- return info;
- }
-
- @SuppressWarnings("WeakerAccess")
- static class CallbackInfo {
- final Map<Event, List<MethodReference>> mEventToHandlers;
- final Map<MethodReference, Event> mHandlerToEvent;
-
- CallbackInfo(Map<MethodReference, Event> handlerToEvent) {
- mHandlerToEvent = handlerToEvent;
- mEventToHandlers = new HashMap<>();
- for (Entry<MethodReference, Event> entry : handlerToEvent.entrySet()) {
- Event event = entry.getValue();
- List<MethodReference> methodReferences = mEventToHandlers.get(event);
- if (methodReferences == null) {
- methodReferences = new ArrayList<>();
- mEventToHandlers.put(event, methodReferences);
- }
- methodReferences.add(entry.getKey());
- }
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- static class MethodReference {
- final int mCallType;
- final Method mMethod;
-
- MethodReference(int callType, Method method) {
- mCallType = callType;
- mMethod = method;
- mMethod.setAccessible(true);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- MethodReference that = (MethodReference) o;
- return mCallType == that.mCallType && mMethod.getName().equals(that.mMethod.getName());
- }
-
- @Override
- public int hashCode() {
- return 31 * mCallType + mMethod.getName().hashCode();
- }
- }
-
- private static final int CALL_TYPE_NO_ARG = 0;
- private static final int CALL_TYPE_PROVIDER = 1;
- private static final int CALL_TYPE_PROVIDER_WITH_EVENT = 2;
}
diff --git a/android/arch/lifecycle/SingleGeneratedAdapterObserver.java b/android/arch/lifecycle/SingleGeneratedAdapterObserver.java
new file mode 100644
index 00000000..d176a3a9
--- /dev/null
+++ b/android/arch/lifecycle/SingleGeneratedAdapterObserver.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.arch.lifecycle;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SingleGeneratedAdapterObserver implements GenericLifecycleObserver {
+
+ private final GeneratedAdapter mGeneratedAdapter;
+
+ SingleGeneratedAdapterObserver(GeneratedAdapter generatedAdapter) {
+ mGeneratedAdapter = generatedAdapter;
+ }
+
+ @Override
+ public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+ mGeneratedAdapter.callMethods(source, event, false, null);
+ mGeneratedAdapter.callMethods(source, event, true, null);
+ }
+}
diff --git a/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java
index c5a520fe..f0214bfb 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -23,15 +23,16 @@ import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
import java.util.concurrent.CountDownLatch;
-public class TestUtils {
+class TestUtils {
private static final long TIMEOUT_MS = 2000;
@SuppressWarnings("unchecked")
- public static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
+ static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
throws Throwable {
ActivityMonitor monitor = new ActivityMonitor(
activity.getClass().getCanonicalName(), null, false);
@@ -60,7 +61,7 @@ public class TestUtils {
return result;
}
- static void waitTillResumed(final LifecycleActivity a, ActivityTestRule<?> activityRule)
+ static void waitTillResumed(final FragmentActivity a, ActivityTestRule<?> activityRule)
throws Throwable {
final CountDownLatch latch = new CountDownLatch(1);
activityRule.runOnUiThread(() -> {
diff --git a/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java
index c316563d..9ce9cbb7 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -22,6 +22,12 @@ import android.support.annotation.Nullable;
/**
* Transformations for a {@link LiveData} class.
+ * <p>
+ * You can use transformation methods to carry information across the observer's lifecycle. The
+ * transformations aren't calculated unless an observer is observing the returned LiveData object.
+ * <p>
+ * Because the transformations are calculated lazily, lifecycle-related behavior is implicitly
+ * passed down without requiring additional explicit calls or dependencies.
*/
@SuppressWarnings("WeakerAccess")
public class Transformations {
@@ -34,6 +40,18 @@ public class Transformations {
* LiveData and returns LiveData, which emits resulting values.
* <p>
* The given function {@code func} will be executed on the main thread.
+ * <p>
+ * Suppose that you have a LiveData, named {@code userLiveData}, that contains user data and you
+ * need to display the user name, created by concatenating the first and the last
+ * name of the user. You can define a function that handles the name creation, that will be
+ * applied to every value emitted by {@code useLiveData}.
+ *
+ * <pre>
+ * LiveData<User> userLiveData = ...;
+ * LiveData<String> userName = Transformations.map(userLiveData, user -> {
+ * return user.firstName + " " + user.lastName
+ * });
+ * </pre>
*
* @param source a {@code LiveData} to listen to
* @param func a function to apply
@@ -63,9 +81,39 @@ public class Transformations {
* <p>
* If the given function returns null, then {@code swLiveData} is not "backed" by any other
* LiveData.
+ *
* <p>
* The given function {@code func} will be executed on the main thread.
*
+ * <p>
+ * Consider the case where you have a LiveData containing a user id. Every time there's a new
+ * user id emitted, you want to trigger a request to get the user object corresponding to that
+ * id, from a repository that also returns a LiveData.
+ * <p>
+ * The {@code userIdLiveData} is the trigger and the LiveData returned by the {@code
+ * repository.getUserById} is the "backing" LiveData.
+ * <p>
+ * In a scenario where the repository contains User(1, "Jane") and User(2, "John"), when the
+ * userIdLiveData value is set to "1", the {@code switchMap} will call {@code getUser(1)},
+ * that will return a LiveData containing the value User(1, "Jane"). So now, the userLiveData
+ * will emit User(1, "Jane"). When the user in the repository gets updated to User(1, "Sarah"),
+ * the {@code userLiveData} gets automatically notified and will emit User(1, "Sarah").
+ * <p>
+ * When the {@code setUserId} method is called with userId = "2", the value of the {@code
+ * userIdLiveData} changes and automatically triggers a request for getting the user with id
+ * "2" from the repository. So, the {@code userLiveData} emits User(2, "John"). The LiveData
+ * returned by {@code repository.getUserById(1)} is removed as a source.
+ *
+ * <pre>
+ * MutableLiveData<String> userIdLiveData = ...;
+ * LiveData<User> userLiveData = Transformations.switchMap(userIdLiveData, id ->
+ * repository.getUserById(id));
+ *
+ * void setUserId(String userId) {
+ * this.userIdLiveData.setValue(userId);
+ * }
+ * </pre>
+ *
* @param trigger a {@code LiveData} to listen to
* @param func a function which creates "backing" LiveData
* @param <X> a type of {@code source} LiveData
diff --git a/android/arch/lifecycle/TransformationsTest.java b/android/arch/lifecycle/TransformationsTest.java
index e92ecca3..940a3e86 100644
--- a/android/arch/lifecycle/TransformationsTest.java
+++ b/android/arch/lifecycle/TransformationsTest.java
@@ -25,7 +25,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.util.Function;
import android.arch.lifecycle.util.InstantTaskExecutor;
@@ -42,7 +42,7 @@ public class TransformationsTest {
@Before
public void swapExecutorDelegate() {
- AppToolkitTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
+ ArchTaskExecutor.getInstance().setDelegate(new InstantTaskExecutor());
}
@Before
diff --git a/android/arch/lifecycle/ViewModelProviderTest.java b/android/arch/lifecycle/ViewModelProviderTest.java
index 61760fc3..8877357a 100644
--- a/android/arch/lifecycle/ViewModelProviderTest.java
+++ b/android/arch/lifecycle/ViewModelProviderTest.java
@@ -21,6 +21,8 @@ import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import android.arch.lifecycle.ViewModelProvider.NewInstanceFactory;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
import org.junit.Assert;
import org.junit.Before;
@@ -82,6 +84,18 @@ public class ViewModelProviderTest {
assertThat(viewModel, is(provider.get(ViewModel1.class)));
}
+ @Test(expected = IllegalStateException.class)
+ public void testNotAttachedActivity() {
+ // This is similar to call ViewModelProviders.of in Activity's constructor
+ ViewModelProviders.of(new FragmentActivity());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testNotAttachedFragment() {
+ // This is similar to call ViewModelProviders.of in Activity's constructor
+ ViewModelProviders.of(new Fragment());
+ }
+
public static class ViewModel1 extends ViewModel {
boolean mCleared;
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index f64365b6..746162a9 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -17,6 +17,7 @@
package android.arch.lifecycle;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.app.Application;
import android.arch.lifecycle.ViewModelProvider.Factory;
import android.support.annotation.MainThread;
@@ -40,6 +41,23 @@ public class ViewModelProviders {
}
}
+ private static Application checkApplication(Activity activity) {
+ Application application = activity.getApplication();
+ if (application == null) {
+ throw new IllegalStateException("Your activity/fragment is not yet attached to "
+ + "Application. You can't request ViewModel before onCreate call.");
+ }
+ return application;
+ }
+
+ private static Activity checkActivity(Fragment fragment) {
+ Activity activity = fragment.getActivity();
+ if (activity == null) {
+ throw new IllegalStateException("Can't create ViewModelProvider for detached fragment");
+ }
+ return activity;
+ }
+
/**
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
* {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
@@ -51,12 +69,7 @@ public class ViewModelProviders {
*/
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment) {
- FragmentActivity activity = fragment.getActivity();
- if (activity == null) {
- throw new IllegalArgumentException(
- "Can't create ViewModelProvider for detached fragment");
- }
- initializeFactoryIfNeeded(activity.getApplication());
+ initializeFactoryIfNeeded(checkApplication(checkActivity(fragment)));
return new ViewModelProvider(ViewModelStores.of(fragment), sDefaultFactory);
}
@@ -71,7 +84,7 @@ public class ViewModelProviders {
*/
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
- initializeFactoryIfNeeded(activity.getApplication());
+ initializeFactoryIfNeeded(checkApplication(activity));
return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}
@@ -87,6 +100,7 @@ public class ViewModelProviders {
*/
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @NonNull Factory factory) {
+ checkApplication(checkActivity(fragment));
return new ViewModelProvider(ViewModelStores.of(fragment), factory);
}
@@ -103,6 +117,7 @@ public class ViewModelProviders {
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@NonNull Factory factory) {
+ checkApplication(activity);
return new ViewModelProvider(ViewModelStores.of(activity), factory);
}
diff --git a/android/arch/lifecycle/ViewModelTest.java b/android/arch/lifecycle/ViewModelTest.java
index 98ce0278..03ebdf36 100644
--- a/android/arch/lifecycle/ViewModelTest.java
+++ b/android/arch/lifecycle/ViewModelTest.java
@@ -32,6 +32,7 @@ import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
@@ -120,7 +121,7 @@ public class ViewModelTest {
void onResume() {
try {
final FragmentManager manager = activity.getSupportFragmentManager();
- LifecycleFragment fragment = new LifecycleFragment();
+ Fragment fragment = new Fragment();
manager.beginTransaction().add(fragment, "temp").commitNow();
ViewModel1 vm = ViewModelProviders.of(fragment).get(ViewModel1.class);
assertThat(vm.mCleared, is(false));
diff --git a/android/arch/lifecycle/activity/EmptyActivity.java b/android/arch/lifecycle/activity/EmptyActivity.java
index 017fff48..c32c8985 100644
--- a/android/arch/lifecycle/activity/EmptyActivity.java
+++ b/android/arch/lifecycle/activity/EmptyActivity.java
@@ -16,13 +16,12 @@
package android.arch.lifecycle.activity;
+import android.arch.lifecycle.extensions.test.R;
import android.os.Bundle;
import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
-import android.arch.lifecycle.LifecycleActivity;
-import android.arch.lifecycle.extensions.test.R;
-
-public class EmptyActivity extends LifecycleActivity {
+public class EmptyActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/android/arch/lifecycle/activity/FragmentLifecycleActivity.java b/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
index f4485e8f..2eb1cc2e 100644
--- a/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
+++ b/android/arch/lifecycle/activity/FragmentLifecycleActivity.java
@@ -17,7 +17,6 @@
package android.arch.lifecycle.activity;
import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleFragment;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
@@ -26,6 +25,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import java.util.ArrayList;
@@ -70,9 +70,9 @@ public class FragmentLifecycleActivity extends AppCompatActivity {
mLoggedEvents.clear();
}
- public static class MainFragment extends LifecycleFragment {
+ public static class MainFragment extends Fragment {
@Nullable
- LifecycleFragment mNestedFragment;
+ Fragment mNestedFragment;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -85,7 +85,7 @@ public class FragmentLifecycleActivity extends AppCompatActivity {
}
}
- public static class NestedFragment extends LifecycleFragment {
+ public static class NestedFragment extends Fragment {
}
public static Intent intentFor(Context context, boolean nested) {
@@ -98,7 +98,8 @@ public class FragmentLifecycleActivity extends AppCompatActivity {
mObservedOwner = provider;
provider.getLifecycle().addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
- public void anyEvent(LifecycleOwner owner, Lifecycle.Event event) {
+ public void anyEvent(@SuppressWarnings("unused") LifecycleOwner owner,
+ Lifecycle.Event event) {
mLoggedEvents.add(event);
}
});
diff --git a/android/arch/lifecycle/observers/Base.java b/android/arch/lifecycle/observers/Base.java
new file mode 100644
index 00000000..08919d46
--- /dev/null
+++ b/android/arch/lifecycle/observers/Base.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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class Base implements LifecycleObserver {
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ public void onCreate() {
+ }
+}
diff --git a/android/arch/lifecycle/observers/Base_LifecycleAdapter.java b/android/arch/lifecycle/observers/Base_LifecycleAdapter.java
new file mode 100644
index 00000000..4218b963
--- /dev/null
+++ b/android/arch/lifecycle/observers/Base_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Base_LifecycleAdapter implements GeneratedAdapter {
+
+ public Base_LifecycleAdapter(Base base) {
+ }
+
+ @Override
+ public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+ MethodCallsLogger logger) {
+
+ }
+}
diff --git a/android/arch/lifecycle/observers/DerivedSequence1.java b/android/arch/lifecycle/observers/DerivedSequence1.java
new file mode 100644
index 00000000..9db37f17
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedSequence1.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.arch.lifecycle.observers;
+
+public class DerivedSequence1 extends Base {
+
+ public void something() {
+ }
+}
diff --git a/android/arch/lifecycle/observers/DerivedSequence2.java b/android/arch/lifecycle/observers/DerivedSequence2.java
new file mode 100644
index 00000000..f2ef943e
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedSequence2.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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedSequence2 extends DerivedSequence1 {
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ void onStop() {
+
+ }
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithNewMethods.java b/android/arch/lifecycle/observers/DerivedWithNewMethods.java
new file mode 100644
index 00000000..b1eaef0e
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithNewMethods.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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedWithNewMethods extends Base {
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ void onStop() {
+
+ }
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java b/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java
new file mode 100644
index 00000000..cb1afb8c
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithNoNewMethods.java
@@ -0,0 +1,20 @@
+/*
+ * 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.arch.lifecycle.observers;
+
+public class DerivedWithNoNewMethods extends Base {
+}
diff --git a/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java b/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
new file mode 100644
index 00000000..40c7c9ac
--- /dev/null
+++ b/android/arch/lifecycle/observers/DerivedWithOverridenMethodsWithLfAnnotation.java
@@ -0,0 +1,29 @@
+/*
+ * 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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedWithOverridenMethodsWithLfAnnotation extends Base {
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+}
diff --git a/android/arch/lifecycle/observers/Interface1.java b/android/arch/lifecycle/observers/Interface1.java
new file mode 100644
index 00000000..e193de98
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface1.java
@@ -0,0 +1,27 @@
+/*
+ * 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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public interface Interface1 extends LifecycleObserver {
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ void onCreate();
+}
diff --git a/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java b/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java
new file mode 100644
index 00000000..c597b1c8
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface1_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Interface1_LifecycleAdapter implements GeneratedAdapter {
+
+ public Interface1_LifecycleAdapter(Interface1 base) {
+ }
+
+ @Override
+ public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+ MethodCallsLogger logger) {
+
+ }
+}
diff --git a/android/arch/lifecycle/observers/Interface2.java b/android/arch/lifecycle/observers/Interface2.java
new file mode 100644
index 00000000..1056fcbe
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface2.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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public interface Interface2 extends LifecycleObserver {
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
+ void onCreate();
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+ void onDestroy();
+}
diff --git a/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java b/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java
new file mode 100644
index 00000000..b05b41a1
--- /dev/null
+++ b/android/arch/lifecycle/observers/Interface2_LifecycleAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.arch.lifecycle.observers;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+
+public class Interface2_LifecycleAdapter implements GeneratedAdapter {
+
+ public Interface2_LifecycleAdapter(Interface2 base) {
+ }
+
+ @Override
+ public void callMethods(LifecycleOwner source, Lifecycle.Event event, boolean onAny,
+ MethodCallsLogger logger) {
+
+ }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl1.java b/android/arch/lifecycle/observers/InterfaceImpl1.java
new file mode 100644
index 00000000..2f033931
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl1.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.arch.lifecycle.observers;
+
+public class InterfaceImpl1 implements Interface1 {
+ @Override
+ public void onCreate() {
+ }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl2.java b/android/arch/lifecycle/observers/InterfaceImpl2.java
new file mode 100644
index 00000000..eef8ce4f
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl2.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.arch.lifecycle.observers;
+
+public class InterfaceImpl2 implements Interface1, Interface2 {
+ @Override
+ public void onCreate() {
+ }
+
+ @Override
+ public void onDestroy() {
+
+ }
+}
diff --git a/android/arch/lifecycle/observers/InterfaceImpl3.java b/android/arch/lifecycle/observers/InterfaceImpl3.java
new file mode 100644
index 00000000..8f31808a
--- /dev/null
+++ b/android/arch/lifecycle/observers/InterfaceImpl3.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.arch.lifecycle.observers;
+
+public class InterfaceImpl3 extends Base implements Interface1 {
+ @Override
+ public void onCreate() {
+ }
+}
diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
index 5972b16c..5f33c282 100644
--- a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
@@ -19,8 +19,8 @@ package android.arch.lifecycle.testapp;
import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleActivity;
import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
import android.util.Pair;
import java.util.ArrayList;
@@ -31,7 +31,7 @@ import java.util.concurrent.TimeUnit;
/**
* Activity for testing full lifecycle
*/
-public class FullLifecycleTestActivity extends LifecycleActivity implements CollectingActivity {
+public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity {
private List<Pair<TestEvent, Lifecycle.Event>> mCollectedEvents = new ArrayList<>();
private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
diff --git a/android/arch/lifecycle/testapp/LifecycleTestActivity.java b/android/arch/lifecycle/testapp/LifecycleTestActivity.java
index 093ec7f8..cf07aeeb 100644
--- a/android/arch/lifecycle/testapp/LifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/LifecycleTestActivity.java
@@ -16,13 +16,13 @@
package android.arch.lifecycle.testapp;
-import android.arch.lifecycle.LifecycleActivity;
import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
/**
* Activity for testing events by themselves
*/
-public class LifecycleTestActivity extends LifecycleActivity {
+public class LifecycleTestActivity extends FragmentActivity {
/**
* identifies that
diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 709bd8dc..0ae94033 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -16,10 +16,10 @@
package android.arch.lifecycle.testapp;
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
/**
* an activity with Dialog theme.
*/
-public class NavigationDialogActivity extends LifecycleActivity {
+public class NavigationDialogActivity extends FragmentActivity {
}
diff --git a/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java b/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
index f1847c9f..69fd478f 100644
--- a/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
+++ b/android/arch/lifecycle/testapp/NavigationTestActivityFirst.java
@@ -16,10 +16,10 @@
package android.arch.lifecycle.testapp;
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
/**
* Activity for ProcessOwnerTest
*/
-public class NavigationTestActivityFirst extends LifecycleActivity {
+public class NavigationTestActivityFirst extends FragmentActivity {
}
diff --git a/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java b/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
index 221e9275..0f9a4c9f 100644
--- a/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
+++ b/android/arch/lifecycle/testapp/NavigationTestActivitySecond.java
@@ -16,10 +16,10 @@
package android.arch.lifecycle.testapp;
-import android.arch.lifecycle.LifecycleActivity;
+import android.support.v4.app.FragmentActivity;
/**
* Activity for ProcessOwnerTest
*/
-public class NavigationTestActivitySecond extends LifecycleActivity {
+public class NavigationTestActivitySecond extends FragmentActivity {
}
diff --git a/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java b/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
index 6d61c5e7..77bd99f3 100644
--- a/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
+++ b/android/arch/lifecycle/testapp/SimpleAppLifecycleTestActivity.java
@@ -17,12 +17,12 @@
package android.arch.lifecycle.testapp;
import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleActivity;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
import android.arch.lifecycle.ProcessLifecycleOwner;
import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
import android.util.Pair;
import java.util.ArrayList;
@@ -33,7 +33,7 @@ import java.util.concurrent.TimeUnit;
/**
* Activity for SimpleAppFullLifecycleTest
*/
-public class SimpleAppLifecycleTestActivity extends LifecycleActivity {
+public class SimpleAppLifecycleTestActivity extends FragmentActivity {
public enum TestEventType {
PROCESS_EVENT,
diff --git a/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java b/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
index 5ef9f161..1f9f100c 100644
--- a/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
+++ b/android/arch/lifecycle/viewmodeltest/ViewModelActivity.java
@@ -16,15 +16,14 @@
package android.arch.lifecycle.viewmodeltest;
+import android.arch.lifecycle.ViewModelProviders;
import android.arch.lifecycle.extensions.test.R;
import android.os.Bundle;
import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
-import android.arch.lifecycle.LifecycleActivity;
-import android.arch.lifecycle.LifecycleFragment;
-import android.arch.lifecycle.ViewModelProviders;
-
-public class ViewModelActivity extends LifecycleActivity {
+public class ViewModelActivity extends FragmentActivity {
public static final String KEY_FRAGMENT_MODEL = "fragment-model";
public static final String KEY_ACTIVITY_MODEL = "activity-model";
public static final String FRAGMENT_TAG_1 = "f1";
@@ -47,7 +46,7 @@ public class ViewModelActivity extends LifecycleActivity {
defaultActivityModel = ViewModelProviders.of(this).get(TestViewModel.class);
}
- public static class ViewModelFragment extends LifecycleFragment {
+ public static class ViewModelFragment extends Fragment {
public TestViewModel fragmentModel;
public TestViewModel activityModel;
public TestViewModel defaultActivityModel;
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
index 96c23fc5..664ab16c 100644
--- a/android/arch/paging/BoundedDataSource.java
+++ b/android/arch/paging/BoundedDataSource.java
@@ -18,6 +18,7 @@ package android.arch.paging;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.annotation.WorkerThread;
import java.util.ArrayList;
import java.util.Collections;
@@ -49,15 +50,18 @@ public abstract class BoundedDataSource<Value> extends PositionalDataSource<Valu
* @return List of loaded items. Null if the BoundedDataSource is no longer valid, and should
* not be queried again.
*/
+ @WorkerThread
@Nullable
public abstract List<Value> loadRange(int startPosition, int loadCount);
+ @WorkerThread
@Nullable
@Override
public List<Value> loadAfter(int startIndex, int pageSize) {
return loadRange(startIndex, pageSize);
}
+ @WorkerThread
@Nullable
@Override
public List<Value> loadBefore(int startIndex, int pageSize) {
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index 9ff11173..afcc208c 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -41,6 +41,8 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
return true;
}
+ /** @hide */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
public abstract NullPaddedList<Value> loadInitial(
@@ -58,7 +60,10 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
* @param pageSize Suggested number of items to load.
* @return List of items, starting at position currentEndIndex + 1. Null if the data source is
* no longer valid, and should not be queried again.
+ *
+ * @hide
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
public final List<Value> loadAfter(int currentEndIndex,
@@ -88,7 +93,10 @@ public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, V
* on item contents.
* @param pageSize Suggested number of items to load.
* @return List of items, in descending order, starting at position currentBeginIndex - 1.
+ *
+ * @hide
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
public final List<Value> loadBefore(int currentBeginIndex,
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 1e815690..48fbec5f 100644
--- a/android/arch/paging/DataSource.java
+++ b/android/arch/paging/DataSource.java
@@ -17,6 +17,7 @@
package android.arch.paging;
import android.support.annotation.AnyThread;
+import android.support.annotation.WorkerThread;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -64,6 +65,7 @@ public abstract class DataSource<Key, Value> {
* @return number of items that this DataSource can provide in total, or
* {@link #COUNT_UNDEFINED} if expensive or undesired to compute.
*/
+ @WorkerThread
public abstract int countItems();
/**
@@ -143,6 +145,7 @@ public abstract class DataSource<Key, Value> {
*
* @return True if the data source is invalid, and can no longer return data.
*/
+ @WorkerThread
public boolean isInvalid() {
return mInvalid.get();
}
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 057eb7f5..8cf6829c 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -16,6 +16,7 @@
package android.arch.paging;
+import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
@@ -54,7 +55,7 @@ import java.util.List;
* {@literal @}SuppressWarnings("FieldCanBeLocal")
* private final InvalidationTracker.Observer mObserver;
*
- * public OffsetUserQueryDataSource(MyDatabase db) {
+ * public KeyedUserQueryDataSource(MyDatabase db) {
* mDb = db;
* mUserDao = db.getUserDao();
* mObserver = new InvalidationTracker.Observer("user") {
@@ -85,11 +86,15 @@ import java.util.List;
*
* {@literal @}Override
* public List&lt;User> loadBefore({@literal @}NonNull String userName, int pageSize) {
+ * // Return items adjacent to 'userName' in reverse order
+ * // it's valid to return a different-sized list of items than pageSize, if it's easier
* return mUserDao.userNameLoadBefore(userName, pageSize);
* }
*
* {@literal @}Override
* public List&lt;User> loadAfter({@literal @}Nullable String userName, int pageSize) {
+ * // Return items adjacent to 'userName'
+ * // it's valid to return a different-sized list of items than pageSize, if it's easier
* return mUserDao.userNameLoadAfter(userName, pageSize);
* }
* }</pre>
@@ -198,13 +203,64 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
return list;
}
+ /**
+ * Return a key associated with the given item.
+ * <p>
+ * If your KeyedDataSource is loading from a source that is sorted and loaded by a unique
+ * integer ID, you would return {@code item.getID()} here. This key can then be passed to
+ * {@link #loadBefore(Key, int)} or {@link #loadAfter(Key, int)} to load additional items
+ * adjacent to the item passed to this function.
+ * <p>
+ * If your key is more complex, such as when you're sorting by name, then resolving collisions
+ * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
+ * such as {@code Pair<String, Integer>} or, in Kotlin,
+ * {@code data class Key(val name: String, val id: Int)}
+ *
+ * @param item Item to get the key from.
+ * @return Key associated with given item.
+ */
@NonNull
+ @AnyThread
public abstract Key getKey(@NonNull Value item);
+ /**
+ * Return the number of items that occur before the item uniquely identified by {@code key} in
+ * the data set.
+ * <p>
+ * For example, if you're loading items sorted by ID, then this would return the total number of
+ * items with ID less than {@code key}.
+ * <p>
+ * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsAfter(Key)}, your
+ * data source will not present placeholder null items in place of unloaded data.
+ *
+ * @param key A unique identifier of an item in the data set.
+ * @return Number of items in the data set before the item identified by {@code key}, or
+ * {@link #COUNT_UNDEFINED}.
+ *
+ * @see #countItemsAfter(Key)
+ */
+ @WorkerThread
public int countItemsBefore(@NonNull Key key) {
return COUNT_UNDEFINED;
}
+ /**
+ * Return the number of items that occur after the item uniquely identified by {@code key} in
+ * the data set.
+ * <p>
+ * For example, if you're loading items sorted by ID, then this would return the total number of
+ * items with ID greater than {@code key}.
+ * <p>
+ * If you return {@link #COUNT_UNDEFINED} here, or from {@link #countItemsBefore(Key)}, your
+ * data source will not present placeholder null items in place of unloaded data.
+ *
+ * @param key A unique identifier of an item in the data set.
+ * @return Number of items in the data set after the item identified by {@code key}, or
+ * {@link #COUNT_UNDEFINED}.
+ *
+ * @see #countItemsBefore(Key)
+ */
+ @WorkerThread
public int countItemsAfter(@NonNull Key key) {
return COUNT_UNDEFINED;
}
@@ -231,10 +287,17 @@ public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<K
public abstract List<Value> loadAfter(@NonNull Key currentEndKey, int pageSize);
/**
- * Load data before the currently loaded content, starting at the provided index.
+ * Load data before the currently loaded content, starting at the provided index,
+ * in reverse-display order.
* <p>
* It's valid to return a different list size than the page size, if it's easier for this data
* source. It is generally safer to increase the number loaded than reduce.
+ * <p class="note"><strong>Note:</strong> Items returned from loadBefore <em>must</em> be in
+ * reverse order from how they will be presented in the list. The first item in the return list
+ * will be prepended immediately before the current beginning of the list. This is so that the
+ * KeyedDataSource may return a different number of items from the requested {@code pageSize} by
+ * shortening or lengthening the return list as it desires.
+ * <p>
*
* @param currentBeginKey Load items before this key.
* @param pageSize Suggested number of items to load.
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index 0d5313dd..6a31b689 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -27,16 +27,65 @@ import java.util.concurrent.Executor;
/**
* Lazy loading list that pages in content from a {@link DataSource}.
* <p>
- * A PagedList is a lazy loaded list, which presents data from a {@link DataSource}. If the
- * DataSource is counted (returns a valid number from its count method(s)), the PagedList will
- * present {@code null} items in place of not-yet-loaded content to serve as placeholders.
+ * A PagedList is a {@link List} which loads its data in chunks (pages) from a {@link DataSource}.
+ * Items can be accessed with {@link #get(int)}, and further loading can be triggered with
+ * {@link #loadAround(int)}. See {@link PagedListAdapter}, which enables the binding of a PagedList
+ * to a {@link android.support.v7.widget.RecyclerView}.
+ * <h4>Loading Data</h4>
* <p>
- * When {@link #loadAround} is called, items will be loaded in near the passed position. If
- * placeholder {@code null}s are present in the list, they will be replaced as content is loaded.
+ * All data in a PagedList is loaded from its {@link DataSource}. Creating a PagedList loads data
+ * from the DataSource immediately, and should for this reason be done on a background thread. The
+ * constructed PagedList may then be passed to and used on the UI thread. This is done to prevent
+ * passing a list with no loaded content to the UI thread, which should generally not be presented
+ * to the user.
* <p>
- * In this way, PagedList can present data for an unbounded, infinite scrolling list, or a very
- * large but countable list. See {@link PagedListAdapter}, which enables the binding of a PagedList
- * to a RecyclerView. Use {@link Config} to control how many items a PagedList loads, and when.
+ * When {@link #loadAround} is called, items will be loaded in near the passed list index. If
+ * placeholder {@code null}s are present in the list, they will be replaced as content is
+ * loaded. If not, newly loaded items will be inserted at the beginning or end of the list.
+ * <p>
+ * PagedList can present data for an unbounded, infinite scrolling list, or a very large but
+ * countable list. Use {@link Config} to control how many items a PagedList loads, and when.
+ * <p>
+ * If you use {@link LivePagedListProvider} to get a
+ * {@link android.arch.lifecycle.LiveData}&lt;PagedList>, it will initialize PagedLists on a
+ * background thread for you.
+ * <h4>Placeholders</h4>
+ * <p>
+ * There are two ways that PagedList can represent its not-yet-loaded data - with or without
+ * {@code null} placeholders.
+ * <p>
+ * With placeholders, the PagedList is always the full size of the data set. {@code get(N)} returns
+ * the {@code N}th item in the data set, or {@code null} if its not yet loaded.
+ * <p>
+ * Without {@code null} placeholders, the PagedList is the sublist of data that has already been
+ * loaded. The size of the PagedList is the number of currently loaded items, and {@code get(N)}
+ * returns the {@code N}th <em>loaded</em> item. This is not necessarily the {@code N}th item in the
+ * data set.
+ * <p>
+ * Placeholders have several benefits:
+ * <ul>
+ * <li>They express the full sized list to the presentation layer (often a
+ * {@link PagedListAdapter}), and so can support scrollbars (without jumping as pages are
+ * loaded) and fast-scrolling to any position, whether loaded or not.
+ * <li>They avoid the need for a loading spinner at the end of the loaded list, since the list
+ * is always full sized.
+ * </ul>
+ * <p>
+ * They also have drawbacks:
+ * <ul>
+ * <li>Your Adapter (or other presentation mechanism) needs to account for {@code null} items.
+ * This often means providing default values in data you bind to a
+ * {@link android.support.v7.widget.RecyclerView.ViewHolder}.
+ * <li>They don't work well if your item views are of different sizes, as this will prevent
+ * loading items from cross-fading nicely.
+ * <li>They require you to count your data set, which can be expensive or impossible, depending
+ * on where your data comes from.
+ * </ul>
+ * <p>
+ * Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
+ * DataSource returns {@link DataSource#COUNT_UNDEFINED} from any item counting method, or if
+ * {@code false} is passed to {@link Config.Builder#setEnablePlaceholders(boolean)} when building a
+ * {@link Config}.
*
* @param <T> The type of the entries in the list.
*/
@@ -92,6 +141,18 @@ public abstract class PagedList<T> extends AbstractList<T> {
* Builder class for PagedList.
* <p>
* DataSource, main thread and background executor, and Config must all be provided.
+ * <p>
+ * A valid PagedList may not be constructed without data, so building a PagedList queries
+ * initial data from the data source. This is done because it's generally undesired to present a
+ * PagedList with no data in it to the UI. It's better to present initial data, so that the UI
+ * doesn't show an empty list, or placeholders for a few frames, just before showing initial
+ * content.
+ * <p>
+ * Because PagedLists are initialized with data, PagedLists must be built on a background
+ * thread.
+ * <p>
+ * {@link LivePagedListProvider} does this creation on a background thread automatically, if you
+ * want to receive a {@code LiveData<PagedList<...>>}.
*
* @param <Key> Type of key used to load data from the DataSource.
* @param <Value> Type of items held and loaded by the PagedList.
@@ -174,11 +235,17 @@ public abstract class PagedList<T> extends AbstractList<T> {
* <p>
* This call will initial data and perform any counting needed to initialize the PagedList,
* therefore it should only be called on a worker thread.
+ * <p>
+ * While build() will always return a PagedList, it's important to note that the PagedList
+ * initial load may fail to acquire data from the DataSource. This can happen for example if
+ * the DataSource is invalidated during its initial load. If this happens, the PagedList
+ * will be immediately {@link PagedList#isDetached() detached}, and you can retry
+ * construction (including setting a new DataSource).
*
* @return The newly constructed PagedList
*/
- @NonNull
@WorkerThread
+ @NonNull
public PagedList<Value> build() {
if (mDataSource == null) {
throw new IllegalArgumentException("DataSource required");
@@ -403,6 +470,16 @@ public abstract class PagedList<T> extends AbstractList<T> {
* Defines the number of items loaded at once from the DataSource.
* <p>
* Should be several times the number of visible items onscreen.
+ * <p>
+ * Configuring your page size depends on how your data is being loaded and used. Smaller
+ * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally
+ * improve loading throughput, to a point
+ * (avoid loading more than 2MB from SQLite at once, since it incurs extra cost).
+ * <p>
+ * If you're loading data for very large, social-media style cards that take up most of
+ * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're
+ * displaying dozens of items in a tiled grid, which can present items during a scroll
+ * much more quickly, consider closer to 100.
*
* @param pageSize Number of items loaded at once from the DataSource.
* @return this
@@ -414,12 +491,15 @@ public abstract class PagedList<T> extends AbstractList<T> {
/**
* Defines how far from the edge of loaded content an access must be to trigger further
- * loading. Defaults to page size.
- * <p>
- * A value of 0 indicates that no list items will be loaded before they are first
- * requested.
+ * loading.
* <p>
* Should be several times the number of visible items onscreen.
+ * <p>
+ * If not set, defaults to page size.
+ * <p>
+ * A value of 0 indicates that no list items will be loaded until they are specifically
+ * requested. This is generally not recommended, so that users don't observe a
+ * placeholder item (with placeholders) or end of list (without) while scrolling.
*
* @param prefetchDistance Distance the PagedList should prefetch.
* @return this
@@ -432,8 +512,10 @@ public abstract class PagedList<T> extends AbstractList<T> {
/**
* Pass false to disable null placeholders in PagedLists using this Config.
* <p>
- * A PagedList will present null placeholders for not yet loaded content if two
- * contitions are met:
+ * If not set, defaults to true.
+ * <p>
+ * A PagedList will present null placeholders for not-yet-loaded content if two
+ * conditions are met:
* <p>
* 1) Its DataSource can count all unloaded items (so that the number of nulls to
* present is known).
@@ -442,6 +524,13 @@ public abstract class PagedList<T> extends AbstractList<T> {
* <p>
* Call {@code setEnablePlaceholders(false)} to ensure the receiver of the PagedList
* (often a {@link PagedListAdapter}) doesn't need to account for null items.
+ * <p>
+ * If placeholders are disabled, not-yet-loaded content will not be present in the list.
+ * Paging will still occur, but as items are loaded or removed, they will be signaled
+ * as inserts to the {@link PagedList.Callback}.
+ * {@link PagedList.Callback#onChanged(int, int)} will not be issued as part of loading,
+ * though a {@link PagedListAdapter} may still receive change events as a result of
+ * PagedList diffing.
*
* @param enablePlaceholders False if null placeholders should be disabled.
* @return this
diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java
index 19a0c558..93c02ea3 100644
--- a/android/arch/paging/PagedListAdapter.java
+++ b/android/arch/paging/PagedListAdapter.java
@@ -51,6 +51,7 @@ import android.support.v7.widget.RecyclerView;
* public final LiveData&lt;PagedList&lt;User>> usersList;
* public MyViewModel(UserDao userDao) {
* usersList = userDao.usersByLastName().create(
+ * /* initial load position {@literal *}/ 0,
* new PagedList.Config.Builder()
* .setPageSize(50)
* .setPrefetchDistance(50)
@@ -72,7 +73,7 @@ import android.support.v7.widget.RecyclerView;
*
* class UserAdapter extends PagedListAdapter&lt;User, UserViewHolder> {
* public UserAdapter() {
- * super(User.DIFF_CALLBACK);
+ * super(DIFF_CALLBACK);
* }
* {@literal @}Override
* public void onBindViewHolder(UserViewHolder holder, int position) {
@@ -85,27 +86,21 @@ import android.support.v7.widget.RecyclerView;
* holder.clear();
* }
* }
- * }
- *
- * {@literal @}Entity
- * class User {
- * // ... simple POJO code omitted ...
- *
- * public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
- * {@literal @}Override
- * public boolean areItemsTheSame(
- * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- * // User properties may have changed if reloaded from the DB, but ID is fixed
- * return oldUser.getId() == newUser.getId();
- * }
- * {@literal @}Override
- * public boolean areContentsTheSame(
- * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- * // NOTE: if you use equals, your object must properly override Object#equals()
- * // Incorrectly returning false here will result in too many animations.
- * return oldUser.equals(newUser);
- * }
- * }
+ * public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
+ * {@literal @}Override
+ * public boolean areItemsTheSame(
+ * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ * // User properties may have changed if reloaded from the DB, but ID is fixed
+ * return oldUser.getId() == newUser.getId();
+ * }
+ * {@literal @}Override
+ * public boolean areContentsTheSame(
+ * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ * // NOTE: if you use equals, your object must properly override Object#equals()
+ * // Incorrectly returning false here will result in too many animations.
+ * return oldUser.equals(newUser);
+ * }
+ * }
* }</pre>
*
* Advanced users that wish for more control over adapter behavior, or to provide a specific base
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index 4bff5fcf..c7b61d9f 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -57,6 +57,7 @@ import java.util.List;
* public final LiveData&lt;PagedList&lt;User>> usersList;
* public MyViewModel(UserDao userDao) {
* usersList = userDao.usersByLastName().create(
+ * /* initial load position {@literal *}/ 0,
* new PagedList.Config.Builder()
* .setPageSize(50)
* .setPrefetchDistance(50)
@@ -79,7 +80,7 @@ import java.util.List;
* class UserAdapter extends RecyclerView.Adapter&lt;UserViewHolder> {
* private final PagedListAdapterHelper&lt;User> mHelper;
* public UserAdapter(PagedListAdapterHelper.Builder&lt;User> builder) {
- * mHelper = new PagedListAdapterHelper(this, User.DIFF_CALLBACK);
+ * mHelper = new PagedListAdapterHelper(this, DIFF_CALLBACK);
* }
* {@literal @}Override
* public int getItemCount() {
@@ -99,13 +100,7 @@ import java.util.List;
* holder.clear();
* }
* }
- * }
- *
- * {@literal @}Entity
- * class User {
- * // ... simple POJO code omitted ...
- *
- * public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
+ * public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
* {@literal @}Override
* public boolean areItemsTheSame(
* {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
diff --git a/android/arch/paging/TestExecutor.java b/android/arch/paging/TestExecutor.java
index 976f7df5..30809c3e 100644
--- a/android/arch/paging/TestExecutor.java
+++ b/android/arch/paging/TestExecutor.java
@@ -30,7 +30,7 @@ public class TestExecutor implements Executor {
mTasks.add(command);
}
- public boolean executeAll() {
+ boolean executeAll() {
boolean consumed = !mTasks.isEmpty();
Runnable task;
while ((task = mTasks.poll()) != null) {
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 56556cd4..36be423d 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -17,6 +17,7 @@
package android.arch.paging;
import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
import java.util.List;
@@ -90,6 +91,7 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
*
* @return Number of items this DataSource can provide. Must be <code>0</code> or greater.
*/
+ @WorkerThread
@Override
public abstract int countItems();
@@ -100,14 +102,20 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
/**
* Called to load items at from the specified position range.
+ * <p>
+ * This method must return a list of requested size, unless at the end of list. Fixed size pages
+ * enable TiledDataSource to navigate tiles efficiently, and quickly accesss any position in the
+ * data set.
+ * <p>
+ * If a list of a different size is returned, but it is not the last list in the data set based
+ * on the return value from {@link #countItems()}, an exception will be thrown.
*
* @param startPosition Index of first item to load.
- * @param count Exact number of items to load. Returning a different number will cause
- * an exception to be thrown.
- * @return List of loaded items. Null if the DataSource is no longer valid, and should
- * not be queried again.
+ * @param count Number of items to load.
+ * @return List of loaded items, of the requested length unless at end of list. Null if the
+ * DataSource is no longer valid, and should not be queried again.
*/
- @SuppressWarnings("WeakerAccess")
+ @WorkerThread
public abstract List<Type> loadRange(int startPosition, int count);
final List<Type> loadRangeWrapper(int startPosition, int count) {
@@ -132,6 +140,7 @@ public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
mTiledDataSource = tiledDataSource;
}
+ @WorkerThread
@Nullable
@Override
public List<Value> loadRange(int startPosition, int loadCount) {
diff --git a/android/arch/persistence/db/SupportSQLiteOpenHelper.java b/android/arch/persistence/db/SupportSQLiteOpenHelper.java
index 5a96e5ac..02e4e7dc 100644
--- a/android/arch/persistence/db/SupportSQLiteOpenHelper.java
+++ b/android/arch/persistence/db/SupportSQLiteOpenHelper.java
@@ -17,13 +17,18 @@
package android.arch.persistence.db;
import android.content.Context;
-import android.database.DatabaseErrorHandler;
-import android.database.DefaultDatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
/**
* An interface to map the behavior of {@link android.database.sqlite.SQLiteOpenHelper}.
@@ -99,10 +104,29 @@ public interface SupportSQLiteOpenHelper {
void close();
/**
- * Matching callback methods from {@link android.database.sqlite.SQLiteOpenHelper}.
+ * Handles various lifecycle events for the SQLite connection, similar to
+ * {@link android.database.sqlite.SQLiteOpenHelper}.
*/
@SuppressWarnings({"unused", "WeakerAccess"})
abstract class Callback {
+ private static final String TAG = "SupportSQLite";
+ /**
+ * Version number of the database (starting at 1); if the database is older,
+ * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
+ * will be used to upgrade the database; if the database is newer,
+ * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
+ * will be used to downgrade the database.
+ */
+ public final int version;
+
+ /**
+ * Creates a new Callback to get database lifecycle events.
+ * @param version The version for the database instance. See {@link #version}.
+ */
+ public Callback(int version) {
+ this.version = version;
+ }
+
/**
* Called when the database connection is being configured, to enable features such as
* write-ahead logging or foreign key support.
@@ -193,6 +217,81 @@ public interface SupportSQLiteOpenHelper {
public void onOpen(SupportSQLiteDatabase db) {
}
+
+ /**
+ * The method invoked when database corruption is detected. Default implementation will
+ * delete the database file.
+ *
+ * @param db the {@link SupportSQLiteDatabase} object representing the database on which
+ * corruption is detected.
+ */
+ public void onCorruption(SupportSQLiteDatabase db) {
+ // the following implementation is taken from {@link DefaultDatabaseErrorHandler}.
+
+ Log.e(TAG, "Corruption reported by sqlite on database: " + db.getPath());
+ // is the corruption detected even before database could be 'opened'?
+ if (!db.isOpen()) {
+ // database files are not even openable. delete this database file.
+ // NOTE if the database has attached databases, then any of them could be corrupt.
+ // and not deleting all of them could cause corrupted database file to remain and
+ // make the application crash on database open operation. To avoid this problem,
+ // the application should provide its own {@link DatabaseErrorHandler} impl class
+ // to delete ALL files of the database (including the attached databases).
+ deleteDatabaseFile(db.getPath());
+ return;
+ }
+
+ List<Pair<String, String>> attachedDbs = null;
+ try {
+ // Close the database, which will cause subsequent operations to fail.
+ // before that, get the attached database list first.
+ try {
+ attachedDbs = db.getAttachedDbs();
+ } catch (SQLiteException e) {
+ /* ignore */
+ }
+ try {
+ db.close();
+ } catch (IOException e) {
+ /* ignore */
+ }
+ } finally {
+ // Delete all files of this corrupt database and/or attached databases
+ if (attachedDbs != null) {
+ for (Pair<String, String> p : attachedDbs) {
+ deleteDatabaseFile(p.second);
+ }
+ } else {
+ // attachedDbs = null is possible when the database is so corrupt that even
+ // "PRAGMA database_list;" also fails. delete the main database file
+ deleteDatabaseFile(db.getPath());
+ }
+ }
+ }
+
+ private void deleteDatabaseFile(String fileName) {
+ if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
+ return;
+ }
+ Log.w(TAG, "deleting the database file: " + fileName);
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ SQLiteDatabase.deleteDatabase(new File(fileName));
+ } else {
+ try {
+ final boolean deleted = new File(fileName).delete();
+ if (!deleted) {
+ Log.e(TAG, "Could not delete the database file " + fileName);
+ }
+ } catch (Exception error) {
+ Log.e(TAG, "error while deleting corrupted database file", error);
+ }
+ }
+ } catch (Exception e) {
+ /* print warning and ignore exception */
+ Log.w(TAG, "delete failed: ", e);
+ }
+ }
}
/**
@@ -211,33 +310,15 @@ public interface SupportSQLiteOpenHelper {
@Nullable
public final String name;
/**
- * Version number of the database (starting at 1); if the database is older,
- * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
- * will be used to upgrade the database; if the database is newer,
- * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
- * will be used to downgrade the database.
- */
- public final int version;
- /**
* The callback class to handle creation, upgrade and downgrade.
*/
@NonNull
public final SupportSQLiteOpenHelper.Callback callback;
- /**
- * The {@link DatabaseErrorHandler} to be used when sqlite reports database
- * corruption, or null to use the default error handler.
- */
- @Nullable
- public final DatabaseErrorHandler errorHandler;
- Configuration(@NonNull Context context, @Nullable String name,
- int version, @Nullable DatabaseErrorHandler errorHandler,
- @NonNull Callback callback) {
+ Configuration(@NonNull Context context, @Nullable String name, @NonNull Callback callback) {
this.context = context;
this.name = name;
- this.version = version;
this.callback = callback;
- this.errorHandler = errorHandler;
}
/**
@@ -255,9 +336,7 @@ public interface SupportSQLiteOpenHelper {
public static class Builder {
Context mContext;
String mName;
- int mVersion = 1;
SupportSQLiteOpenHelper.Callback mCallback;
- DatabaseErrorHandler mErrorHandler;
public Configuration build() {
if (mCallback == null) {
@@ -268,11 +347,7 @@ public interface SupportSQLiteOpenHelper {
throw new IllegalArgumentException("Must set a non-null context to create"
+ " the configuration.");
}
- if (mErrorHandler == null) {
- mErrorHandler = new DefaultDatabaseErrorHandler();
- }
- return new Configuration(mContext, mName, mVersion, mErrorHandler,
- mCallback);
+ return new Configuration(mContext, mName, mCallback);
}
Builder(@NonNull Context context) {
@@ -280,17 +355,6 @@ public interface SupportSQLiteOpenHelper {
}
/**
- * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite
- * reports database corruption, or null to use the default error
- * handler.
- * @return This
- */
- public Builder errorHandler(@Nullable DatabaseErrorHandler errorHandler) {
- mErrorHandler = errorHandler;
- return this;
- }
-
- /**
* @param name Name of the database file, or null for an in-memory database.
* @return This
*/
@@ -307,20 +371,6 @@ public interface SupportSQLiteOpenHelper {
mCallback = callback;
return this;
}
-
- /**
- * @param version Version number of the database (starting at 1); if the database is
- * older,
- * {@link SupportSQLiteOpenHelper.Callback#onUpgrade(SupportSQLiteDatabase, int, int)}
- * will be used to upgrade the database; if the database is newer,
- * {@link SupportSQLiteOpenHelper.Callback#onDowngrade(SupportSQLiteDatabase, int, int)}
- * will be used to downgrade the database.
- * @return this
- */
- public Builder version(int version) {
- mVersion = version;
- return this;
- }
}
}
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java b/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
index 92a58205..e9c2b741 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteDatabase.java
@@ -53,8 +53,7 @@ class FrameworkSQLiteDatabase implements SupportSQLiteDatabase {
*
* @param delegate The delegate to receive all calls.
*/
- @SuppressWarnings("WeakerAccess")
- public FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
+ FrameworkSQLiteDatabase(SQLiteDatabase delegate) {
mDelegate = delegate;
}
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
index aa08fa42..a1690f40 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelper.java
@@ -28,42 +28,14 @@ import android.support.annotation.RequiresApi;
class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
private final OpenHelper mDelegate;
- FrameworkSQLiteOpenHelper(Context context, String name, int version,
- DatabaseErrorHandler errorHandler,
- SupportSQLiteOpenHelper.Callback callback) {
- mDelegate = createDelegate(context, name, version, errorHandler, callback);
+ FrameworkSQLiteOpenHelper(Context context, String name,
+ Callback callback) {
+ mDelegate = createDelegate(context, name, callback);
}
- private OpenHelper createDelegate(Context context, String name,
- int version, DatabaseErrorHandler errorHandler,
- final Callback callback) {
- return new OpenHelper(context, name, null, version, errorHandler) {
- @Override
- public void onCreate(SQLiteDatabase sqLiteDatabase) {
- mWrappedDb = new FrameworkSQLiteDatabase(sqLiteDatabase);
- callback.onCreate(mWrappedDb);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
- callback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
- }
-
- @Override
- public void onConfigure(SQLiteDatabase db) {
- callback.onConfigure(getWrappedDb(db));
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- callback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- callback.onOpen(getWrappedDb(db));
- }
- };
+ private OpenHelper createDelegate(Context context, String name, Callback callback) {
+ final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
+ return new OpenHelper(context, name, dbRef, callback);
}
@Override
@@ -92,14 +64,29 @@ class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
mDelegate.close();
}
- abstract static class OpenHelper extends SQLiteOpenHelper {
-
- FrameworkSQLiteDatabase mWrappedDb;
-
- OpenHelper(Context context, String name,
- SQLiteDatabase.CursorFactory factory, int version,
- DatabaseErrorHandler errorHandler) {
- super(context, name, factory, version, errorHandler);
+ static class OpenHelper extends SQLiteOpenHelper {
+ /**
+ * This is used as an Object reference so that we can access the wrapped database inside
+ * the constructor. SQLiteOpenHelper requires the error handler to be passed in the
+ * constructor.
+ */
+ final FrameworkSQLiteDatabase[] mDbRef;
+ final Callback mCallback;
+
+ OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
+ final Callback callback) {
+ super(context, name, null, callback.version,
+ new DatabaseErrorHandler() {
+ @Override
+ public void onCorruption(SQLiteDatabase dbObj) {
+ FrameworkSQLiteDatabase db = dbRef[0];
+ if (db != null) {
+ callback.onCorruption(db);
+ }
+ }
+ });
+ mCallback = callback;
+ mDbRef = dbRef;
}
SupportSQLiteDatabase getWritableSupportDatabase() {
@@ -113,16 +100,43 @@ class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
}
FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
- if (mWrappedDb == null) {
- mWrappedDb = new FrameworkSQLiteDatabase(sqLiteDatabase);
+ FrameworkSQLiteDatabase dbRef = mDbRef[0];
+ if (dbRef == null) {
+ dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase);
+ mDbRef[0] = dbRef;
}
- return mWrappedDb;
+ return mDbRef[0];
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase sqLiteDatabase) {
+ mCallback.onCreate(getWrappedDb(sqLiteDatabase));
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
+ mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
+ }
+
+ @Override
+ public void onConfigure(SQLiteDatabase db) {
+ mCallback.onConfigure(getWrappedDb(db));
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ mCallback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ mCallback.onOpen(getWrappedDb(db));
}
@Override
public synchronized void close() {
super.close();
- mWrappedDb = null;
+ mDbRef[0] = null;
}
}
}
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
index 2268f45f..ab11d490 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteOpenHelperFactory.java
@@ -27,8 +27,6 @@ public final class FrameworkSQLiteOpenHelperFactory implements SupportSQLiteOpen
@Override
public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
return new FrameworkSQLiteOpenHelper(
- configuration.context, configuration.name,
- configuration.version, configuration.errorHandler, configuration.callback
- );
+ configuration.context, configuration.name, configuration.callback);
}
}
diff --git a/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java b/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
index a2daf12e..53a04bd6 100644
--- a/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
+++ b/android/arch/persistence/db/framework/FrameworkSQLiteStatement.java
@@ -30,8 +30,7 @@ class FrameworkSQLiteStatement implements SupportSQLiteStatement {
*
* @param delegate The SQLiteStatement to delegate calls to.
*/
- @SuppressWarnings("WeakerAccess")
- public FrameworkSQLiteStatement(SQLiteStatement delegate) {
+ FrameworkSQLiteStatement(SQLiteStatement delegate) {
mDelegate = delegate;
}
diff --git a/android/arch/persistence/room/Entity.java b/android/arch/persistence/room/Entity.java
index f54f0f80..94ca3bfc 100644
--- a/android/arch/persistence/room/Entity.java
+++ b/android/arch/persistence/room/Entity.java
@@ -36,6 +36,9 @@ import java.lang.annotation.Target;
* When a class is marked as an Entity, all of its fields are persisted. If you would like to
* exclude some of its fields, you can mark them with {@link Ignore}.
* <p>
+ * If a field is {@code transient}, it is automatically ignored <b>unless</b> it is annotated with
+ * {@link ColumnInfo}, {@link Embedded} or {@link Relation}.
+ * <p>
* Example:
* <pre>
* {@literal @}Entity
diff --git a/android/arch/persistence/room/ForeignKey.java b/android/arch/persistence/room/ForeignKey.java
index 4ba0fb3f..3ba632b5 100644
--- a/android/arch/persistence/room/ForeignKey.java
+++ b/android/arch/persistence/room/ForeignKey.java
@@ -40,7 +40,7 @@ import android.support.annotation.IntDef;
* <a href="https://sqlite.org/pragma.html#pragma_defer_foreign_keys">defer_foreign_keys</a> PRAGMA
* to defer them depending on your transaction.
* <p>
- * Please refer to the SQLite <a href="https://sqlite.org/foreignkeys.html>foreign keys</a>
+ * Please refer to the SQLite <a href="https://sqlite.org/foreignkeys.html">foreign keys</a>
* documentation for details.
*/
public @interface ForeignKey {
diff --git a/android/arch/persistence/room/InvalidationTracker.java b/android/arch/persistence/room/InvalidationTracker.java
index 33bc4ed6..45ec0289 100644
--- a/android/arch/persistence/room/InvalidationTracker.java
+++ b/android/arch/persistence/room/InvalidationTracker.java
@@ -16,7 +16,7 @@
package android.arch.persistence.room;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.internal.SafeIterableMap;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.db.SupportSQLiteStatement;
@@ -166,10 +166,12 @@ public class InvalidationTracker {
private static void appendTriggerName(StringBuilder builder, String tableName,
String triggerType) {
- builder.append("room_table_modification_trigger_")
+ builder.append("`")
+ .append("room_table_modification_trigger_")
.append(tableName)
.append("_")
- .append(triggerType);
+ .append(triggerType)
+ .append("`");
}
private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
@@ -192,9 +194,9 @@ public class InvalidationTracker {
appendTriggerName(stringBuilder, tableName, trigger);
stringBuilder.append(" AFTER ")
.append(trigger)
- .append(" ON ")
+ .append(" ON `")
.append(tableName)
- .append(" BEGIN INSERT OR REPLACE INTO ")
+ .append("` BEGIN INSERT OR REPLACE INTO ")
.append(UPDATE_TABLE_NAME)
.append(" VALUES(null, ")
.append(tableId)
@@ -238,7 +240,7 @@ public class InvalidationTracker {
currentObserver = mObserverMap.putIfAbsent(observer, wrapper);
}
if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) {
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+ ArchTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
}
}
@@ -269,7 +271,7 @@ public class InvalidationTracker {
wrapper = mObserverMap.remove(observer);
}
if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) {
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
+ ArchTaskExecutor.getInstance().executeOnDiskIO(mSyncTriggers);
}
}
@@ -350,11 +352,18 @@ public class InvalidationTracker {
return;
}
- if (mDatabase.inTransaction()
- || !mPendingRefresh.compareAndSet(true, false)) {
+ if (!mPendingRefresh.compareAndSet(true, false)) {
// no pending refresh
return;
}
+
+ if (mDatabase.inTransaction()) {
+ // current thread is in a transaction. when it ends, it will invoke
+ // refreshRunnable again. mPendingRefresh is left as false on purpose
+ // so that the last transaction can flip it on again.
+ return;
+ }
+
mCleanupStatement.executeUpdateDelete();
mQueryArgs[0] = mMaxVersion;
Cursor cursor = mDatabase.query(SELECT_UPDATED_TABLES_SQL, mQueryArgs);
@@ -400,7 +409,7 @@ public class InvalidationTracker {
public void refreshVersionsAsync() {
// TODO we should consider doing this sync instead of async.
if (mPendingRefresh.compareAndSet(false, true)) {
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
+ ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
}
}
diff --git a/android/arch/persistence/room/InvalidationTrackerTest.java b/android/arch/persistence/room/InvalidationTrackerTest.java
index f0b730ad..d7474fd1 100644
--- a/android/arch/persistence/room/InvalidationTrackerTest.java
+++ b/android/arch/persistence/room/InvalidationTrackerTest.java
@@ -247,7 +247,7 @@ public class InvalidationTrackerTest {
mTracker.mRefreshRunnable.run();
}
- @Test
+ // @Test - disabled due to flakiness b/65257997
public void closedDbAfterOpen() throws InterruptedException {
setVersions(3, 1);
mTracker.addObserver(new LatchObserver(1, "a", "b"));
diff --git a/android/arch/persistence/room/RoomDatabase.java b/android/arch/persistence/room/RoomDatabase.java
index e64f2d61..cdad868d 100644
--- a/android/arch/persistence/room/RoomDatabase.java
+++ b/android/arch/persistence/room/RoomDatabase.java
@@ -16,7 +16,7 @@
package android.arch.persistence.room;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.persistence.db.SimpleSQLiteQuery;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.db.SupportSQLiteOpenHelper;
@@ -158,7 +158,7 @@ public abstract class RoomDatabase {
if (mAllowMainThreadQueries) {
return;
}
- if (AppToolkitTaskExecutor.getInstance().isMainThread()) {
+ if (ArchTaskExecutor.getInstance().isMainThread()) {
throw new IllegalStateException("Cannot access database on the main thread since"
+ " it may potentially lock the UI for a long period of time.");
}
@@ -216,7 +216,11 @@ public abstract class RoomDatabase {
*/
public void endTransaction() {
mOpenHelper.getWritableDatabase().endTransaction();
- mInvalidationTracker.refreshVersionsAsync();
+ if (!inTransaction()) {
+ // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
+ // endTransaction call to do it.
+ mInvalidationTracker.refreshVersionsAsync();
+ }
}
/**
@@ -311,7 +315,6 @@ public abstract class RoomDatabase {
private ArrayList<Callback> mCallbacks;
private SupportSQLiteOpenHelper.Factory mFactory;
- private boolean mInMemory;
private boolean mAllowMainThreadQueries;
private boolean mRequireMigration;
/**
@@ -381,6 +384,9 @@ public abstract class RoomDatabase {
}
/**
+ * Allows Room to destructively recreate database tables if {@link Migration}s that would
+ * migrate old database schemas to the latest schema version are not found.
+ * <p>
* When the database version on the device does not match the latest schema version, Room
* runs necessary {@link Migration}s on the database.
* <p>
diff --git a/android/arch/persistence/room/RoomOpenHelper.java b/android/arch/persistence/room/RoomOpenHelper.java
index 8767f065..47279d60 100644
--- a/android/arch/persistence/room/RoomOpenHelper.java
+++ b/android/arch/persistence/room/RoomOpenHelper.java
@@ -44,6 +44,7 @@ public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate,
@NonNull String identityHash) {
+ super(delegate.version);
mConfiguration = configuration;
mDelegate = delegate;
mIdentityHash = identityHash;
@@ -135,6 +136,12 @@ public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public abstract static class Delegate {
+ public final int version;
+
+ public Delegate(int version) {
+ this.version = version;
+ }
+
protected abstract void dropAllTables(SupportSQLiteDatabase database);
protected abstract void createAllTables(SupportSQLiteDatabase database);
diff --git a/android/arch/persistence/room/RoomWarnings.java b/android/arch/persistence/room/RoomWarnings.java
index 91f32e45..c64be967 100644
--- a/android/arch/persistence/room/RoomWarnings.java
+++ b/android/arch/persistence/room/RoomWarnings.java
@@ -117,4 +117,12 @@ public class RoomWarnings {
*/
public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD =
"ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+
+ /**
+ * Reported when a Pojo has multiple constructors, one of which is a no-arg constructor. Room
+ * will pick that one by default but will print this warning in case the constructor choice is
+ * important. You can always guide Room to use the right constructor using the @Ignore
+ * annotation.
+ */
+ public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
}
diff --git a/android/arch/persistence/room/RxRoom.java b/android/arch/persistence/room/RxRoom.java
index adfca27b..285b3f89 100644
--- a/android/arch/persistence/room/RxRoom.java
+++ b/android/arch/persistence/room/RxRoom.java
@@ -16,7 +16,7 @@
package android.arch.persistence.room;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
@@ -133,7 +133,7 @@ public class RxRoom {
public Disposable schedule(@NonNull Runnable run, long delay,
@NonNull TimeUnit unit) {
DisposableRunnable disposable = new DisposableRunnable(run, mDisposed);
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(run);
+ ArchTaskExecutor.getInstance().executeOnDiskIO(run);
return disposable;
}
diff --git a/android/arch/persistence/room/Transaction.java b/android/arch/persistence/room/Transaction.java
new file mode 100644
index 00000000..914e4f41
--- /dev/null
+++ b/android/arch/persistence/room/Transaction.java
@@ -0,0 +1,51 @@
+/*
+ * 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.arch.persistence.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method in an abstract {@link Dao} class as a transaction method.
+ * <p>
+ * The derived implementation of the method will execute the super method in a database transaction.
+ * All the parameters and return types are preserved. The transaction will be marked as successful
+ * unless an exception is thrown in the method body.
+ * <p>
+ * Example:
+ * <pre>
+ * {@literal @}Dao
+ * public abstract class ProductDao {
+ * {@literal @}Insert
+ * public abstract void insert(Product product);
+ * {@literal @}Delete
+ * public abstract void delete(Product product);
+ * {@literal @}Transaction
+ * public void insertAndDeleteInTransaction(Product newProduct, Product oldProduct) {
+ * // Anything inside this method runs in a single transaction.
+ * insert(newProduct);
+ * delete(oldProduct);
+ * }
+ * }
+ * </pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface Transaction {
+}
diff --git a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
index 1f434ad6..320b2cdd 100644
--- a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
@@ -17,7 +17,7 @@
package android.arch.persistence.room.integration.testapp;
import android.app.Application;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.paging.DataSource;
@@ -47,7 +47,7 @@ public class CustomerViewModel extends AndroidViewModel {
mDatabase = Room.databaseBuilder(this.getApplication(),
SampleDatabase.class, "customerDatabase").build();
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
+ ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
@Override
public void run() {
// fill with some simple data
@@ -73,7 +73,7 @@ public class CustomerViewModel extends AndroidViewModel {
}
void insertCustomer() {
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
+ ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
@Override
public void run() {
mDatabase.getCustomerDao().insert(createCustomer());
diff --git a/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java b/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
index e61d808c..63b95072 100644
--- a/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/PKeyTestDatabase.java
@@ -23,13 +23,18 @@ import android.arch.persistence.room.Query;
import android.arch.persistence.room.RoomDatabase;
import android.arch.persistence.room.integration.testapp.vo.IntAutoIncPKeyEntity;
import android.arch.persistence.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.IntegerPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.ObjectPKeyEntity;
import java.util.List;
-@Database(entities = {IntAutoIncPKeyEntity.class, IntegerAutoIncPKeyEntity.class}, version = 1,
+@Database(entities = {IntAutoIncPKeyEntity.class, IntegerAutoIncPKeyEntity.class,
+ ObjectPKeyEntity.class, IntegerPKeyEntity.class}, version = 1,
exportSchema = false)
public abstract class PKeyTestDatabase extends RoomDatabase {
public abstract IntPKeyDao intPKeyDao();
+ public abstract IntegerAutoIncPKeyDao integerAutoIncPKeyDao();
+ public abstract ObjectPKeyDao objectPKeyDao();
public abstract IntegerPKeyDao integerPKeyDao();
@Dao
@@ -50,9 +55,10 @@ public abstract class PKeyTestDatabase extends RoomDatabase {
}
@Dao
- public interface IntegerPKeyDao {
+ public interface IntegerAutoIncPKeyDao {
@Insert
- void insertMe(IntegerAutoIncPKeyEntity items);
+ void insertMe(IntegerAutoIncPKeyEntity item);
+
@Query("select * from IntegerAutoIncPKeyEntity WHERE pKey = :key")
IntegerAutoIncPKeyEntity getMe(int key);
@@ -65,4 +71,19 @@ public abstract class PKeyTestDatabase extends RoomDatabase {
@Query("select data from IntegerAutoIncPKeyEntity WHERE pKey IN(:ids)")
List<String> loadDataById(long... ids);
}
+
+ @Dao
+ public interface ObjectPKeyDao {
+ @Insert
+ void insertMe(ObjectPKeyEntity item);
+ }
+
+ @Dao
+ public interface IntegerPKeyDao {
+ @Insert
+ void insertMe(IntegerPKeyEntity item);
+
+ @Query("select * from IntegerPKeyEntity")
+ List<IntegerPKeyEntity> loadAll();
+ }
}
diff --git a/android/arch/persistence/room/integration/testapp/TestDatabase.java b/android/arch/persistence/room/integration/testapp/TestDatabase.java
index 94172965..2fad7b1f 100644
--- a/android/arch/persistence/room/integration/testapp/TestDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/TestDatabase.java
@@ -21,6 +21,7 @@ import android.arch.persistence.room.RoomDatabase;
import android.arch.persistence.room.TypeConverter;
import android.arch.persistence.room.TypeConverters;
import android.arch.persistence.room.integration.testapp.dao.BlobEntityDao;
+import android.arch.persistence.room.integration.testapp.dao.FunnyNamedDao;
import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
import android.arch.persistence.room.integration.testapp.dao.PetDao;
import android.arch.persistence.room.integration.testapp.dao.ProductDao;
@@ -31,6 +32,7 @@ import android.arch.persistence.room.integration.testapp.dao.UserDao;
import android.arch.persistence.room.integration.testapp.dao.UserPetDao;
import android.arch.persistence.room.integration.testapp.dao.WithClauseDao;
import android.arch.persistence.room.integration.testapp.vo.BlobEntity;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
import android.arch.persistence.room.integration.testapp.vo.Pet;
import android.arch.persistence.room.integration.testapp.vo.PetCouple;
import android.arch.persistence.room.integration.testapp.vo.Product;
@@ -41,7 +43,7 @@ import android.arch.persistence.room.integration.testapp.vo.User;
import java.util.Date;
@Database(entities = {User.class, Pet.class, School.class, PetCouple.class, Toy.class,
- BlobEntity.class, Product.class},
+ BlobEntity.class, Product.class, FunnyNamedEntity.class},
version = 1, exportSchema = false)
@TypeConverters(TestDatabase.Converters.class)
public abstract class TestDatabase extends RoomDatabase {
@@ -55,6 +57,7 @@ public abstract class TestDatabase extends RoomDatabase {
public abstract ProductDao getProductDao();
public abstract SpecificDogDao getSpecificDogDao();
public abstract WithClauseDao getWithClauseDao();
+ public abstract FunnyNamedDao getFunnyNamedDao();
@SuppressWarnings("unused")
public static class Converters {
diff --git a/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java b/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java
new file mode 100644
index 00000000..93b5e72e
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/dao/FunnyNamedDao.java
@@ -0,0 +1,50 @@
+/*
+ * 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.arch.persistence.room.integration.testapp.dao;
+
+import static android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity.COLUMN_ID;
+import static android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity.TABLE_NAME;
+
+import android.arch.lifecycle.LiveData;
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Delete;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Update;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
+
+import java.util.List;
+
+@Dao
+public interface FunnyNamedDao {
+ String SELECT_ONE = "select * from \"" + TABLE_NAME + "\" WHERE \"" + COLUMN_ID + "\" = :id";
+ @Insert
+ void insert(FunnyNamedEntity... entities);
+ @Delete
+ void delete(FunnyNamedEntity... entities);
+ @Update
+ void update(FunnyNamedEntity... entities);
+
+ @Query("select * from \"" + TABLE_NAME + "\" WHERE \"" + COLUMN_ID + "\" IN (:ids)")
+ List<FunnyNamedEntity> loadAll(int... ids);
+
+ @Query(SELECT_ONE)
+ LiveData<FunnyNamedEntity> observableOne(int id);
+
+ @Query(SELECT_ONE)
+ FunnyNamedEntity load(int id);
+}
diff --git a/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java b/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
index 7bb137fe..18e8d93e 100644
--- a/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/SchoolDao.java
@@ -35,16 +35,16 @@ public abstract class SchoolDao {
@Query("SELECT * from School WHERE address_street LIKE '%' || :street || '%'")
public abstract List<School> findByStreet(String street);
- @Query("SELECT mName, manager_mName FROM School")
+ @Query("SELECT mId, mName, manager_mName FROM School")
public abstract List<School> schoolAndManagerNames();
- @Query("SELECT mName, manager_mName FROM School")
+ @Query("SELECT mId, mName, manager_mName FROM School")
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
public abstract List<SchoolRef> schoolAndManagerNamesAsPojo();
@Query("SELECT address_lat as lat, address_lng as lng FROM School WHERE mId = :schoolId")
public abstract Coordinates loadCoordinates(int schoolId);
- @Query("SELECT address_lat, address_lng FROM School WHERE mId = :schoolId")
+ @Query("SELECT mId, address_lat, address_lng FROM School WHERE mId = :schoolId")
public abstract School loadCoordinatesAsSchool(int schoolId);
}
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 337c233f..665a1aeb 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -24,6 +24,7 @@ import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
+import android.arch.persistence.room.Transaction;
import android.arch.persistence.room.Update;
import android.arch.persistence.room.integration.testapp.TestDatabase;
import android.arch.persistence.room.integration.testapp.vo.AvgWeightByAge;
@@ -259,4 +260,10 @@ public abstract class UserDao {
+ " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))"
+ " ORDER BY mLastName ASC, mName DESC, mId ASC")
public abstract int userComplexCountBefore(String lastName, String name, int id);
+
+ @Transaction
+ public void insertBothByAnnotation(final User a, final User b) {
+ insert(a);
+ insert(b);
+ }
}
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java b/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
index 3507aeea..eb159014 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserPetDao.java
@@ -33,6 +33,8 @@ import android.arch.persistence.room.integration.testapp.vo.UserWithPetsAndToys;
import java.util.List;
+import io.reactivex.Flowable;
+
@Dao
public interface UserPetDao {
@Query("SELECT * FROM User u, Pet p WHERE u.mId = p.mUserId")
@@ -62,6 +64,9 @@ public interface UserPetDao {
@Query("SELECT * FROM User u where u.mId = :userId")
LiveData<UserAndAllPets> liveUserWithPets(int userId);
+ @Query("SELECT * FROM User u where u.mId = :userId")
+ Flowable<UserAndAllPets> flowableUserWithPets(int userId);
+
@Query("SELECT * FROM User u where u.mId = :uid")
EmbeddedUserAndAllPets loadUserAndPetsAsEmbedded(int uid);
diff --git a/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java b/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
index b1c38eda..40098ed4 100644
--- a/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/WithClauseDao.java
@@ -16,13 +16,16 @@
package android.arch.persistence.room.integration.testapp.dao;
+import android.annotation.TargetApi;
import android.arch.lifecycle.LiveData;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Query;
+import android.os.Build;
import java.util.List;
@Dao
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public interface WithClauseDao {
@Query("WITH RECURSIVE factorial(n, fact) AS \n"
+ "(SELECT 0, 1 \n"
diff --git a/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java b/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
index eec59f6a..9020eb16 100644
--- a/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
+++ b/android/arch/persistence/room/integration/testapp/database/SampleDatabase.java
@@ -18,10 +18,6 @@ package android.arch.persistence.room.integration.testapp.database;
import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.TypeConverter;
-import android.arch.persistence.room.TypeConverters;
-
-import java.util.Date;
/**
* Sample database of customers.
diff --git a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
index 725d53f8..7fe2bc94 100644
--- a/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
+++ b/android/arch/persistence/room/integration/testapp/migration/MigrationTest.java
@@ -318,6 +318,8 @@ public class MigrationTest {
+ " (`id` INTEGER NOT NULL, `name` TEXT COLLATE NOCASE, PRIMARY KEY(`id`),"
+ " FOREIGN KEY(`name`) REFERENCES `Entity1`(`name`)"
+ " ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)");
+ database.execSQL("CREATE UNIQUE INDEX `index_entity1` ON "
+ + MigrationDb.Entity1.TABLE_NAME + " (`name`)");
}
};
diff --git a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java b/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
index 4c9d73e1..df70a170 100644
--- a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
+++ b/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
@@ -21,17 +21,17 @@ import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.executor.testing.CountingTaskExecutorRule;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
+import android.arch.paging.PagedList;
import android.arch.persistence.room.integration.testapp.test.TestDatabaseTest;
import android.arch.persistence.room.integration.testapp.test.TestUtil;
import android.arch.persistence.room.integration.testapp.vo.User;
-import android.arch.paging.PagedList;
import android.support.annotation.Nullable;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
@@ -131,7 +131,7 @@ public class LivePagedListProviderTest extends TestDatabaseTest {
return null;
}
});
- AppToolkitTaskExecutor.getInstance().executeOnMainThread(futureTask);
+ ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
futureTask.get();
}
@@ -155,7 +155,7 @@ public class LivePagedListProviderTest extends TestDatabaseTest {
private static class PagedListObserver<T> implements Observer<PagedList<T>> {
private PagedList<T> mList;
- public void reset() {
+ void reset() {
mList = null;
}
diff --git a/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java b/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
index 353c2e39..6f44546b 100644
--- a/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/CustomDatabaseTest.java
@@ -55,7 +55,6 @@ public class CustomDatabaseTest {
Customer customer = new Customer();
for (int i = 0; i < 100; i++) {
SampleDatabase db = builder.build();
- customer.setId(i);
db.getCustomerDao().insert(customer);
// Give InvalidationTracker enough time to start #mRefreshRunnable and pass the
// initialization check.
diff --git a/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java b/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
new file mode 100644
index 00000000..f4fca7f2
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/FunnyNamedDaoTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.arch.persistence.room.integration.testapp.test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.core.executor.testing.CountingTaskExecutorRule;
+import android.arch.lifecycle.Observer;
+import android.arch.persistence.room.integration.testapp.vo.FunnyNamedEntity;
+import android.support.annotation.Nullable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class FunnyNamedDaoTest extends TestDatabaseTest {
+ @Rule
+ public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
+
+ @Test
+ public void readWrite() {
+ FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+ mFunnyNamedDao.insert(entity);
+ FunnyNamedEntity loaded = mFunnyNamedDao.load(1);
+ assertThat(loaded, is(entity));
+ }
+
+ @Test
+ public void update() {
+ FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+ mFunnyNamedDao.insert(entity);
+ entity.setValue("b");
+ mFunnyNamedDao.update(entity);
+ FunnyNamedEntity loaded = mFunnyNamedDao.load(1);
+ assertThat(loaded.getValue(), is("b"));
+ }
+
+ @Test
+ public void delete() {
+ FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+ mFunnyNamedDao.insert(entity);
+ assertThat(mFunnyNamedDao.load(1), notNullValue());
+ mFunnyNamedDao.delete(entity);
+ assertThat(mFunnyNamedDao.load(1), nullValue());
+ }
+
+ @Test
+ public void observe() throws TimeoutException, InterruptedException {
+ final FunnyNamedEntity[] item = new FunnyNamedEntity[1];
+ mFunnyNamedDao.observableOne(2).observeForever(new Observer<FunnyNamedEntity>() {
+ @Override
+ public void onChanged(@Nullable FunnyNamedEntity funnyNamedEntity) {
+ item[0] = funnyNamedEntity;
+ }
+ });
+
+ FunnyNamedEntity entity = new FunnyNamedEntity(1, "a");
+ mFunnyNamedDao.insert(entity);
+ mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+ assertThat(item[0], nullValue());
+
+ final FunnyNamedEntity entity2 = new FunnyNamedEntity(2, "b");
+ mFunnyNamedDao.insert(entity2);
+ mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+ assertThat(item[0], is(entity2));
+
+ final FunnyNamedEntity entity3 = new FunnyNamedEntity(2, "c");
+ mFunnyNamedDao.update(entity3);
+ mExecutorRule.drainTasks(1, TimeUnit.MINUTES);
+ assertThat(item[0], is(entity3));
+ }
+}
diff --git a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
index 4787ce52..84f20ec5 100644
--- a/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/InvalidationTest.java
@@ -21,7 +21,7 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.executor.TaskExecutor;
import android.arch.persistence.room.InvalidationTracker;
import android.arch.persistence.room.Room;
@@ -68,7 +68,7 @@ public class InvalidationTest {
@Before
public void setSingleThreadedIO() {
- AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+ ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
ExecutorService mIOExecutor = Executors.newSingleThreadExecutor();
Handler mHandler = new Handler(Looper.getMainLooper());
@@ -91,7 +91,7 @@ public class InvalidationTest {
@After
public void clearExecutor() {
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
private void waitUntilIOThreadIsIdle() {
@@ -101,7 +101,7 @@ public class InvalidationTest {
return null;
}
});
- AppToolkitTaskExecutor.getInstance().executeOnDiskIO(future);
+ ArchTaskExecutor.getInstance().executeOnDiskIO(future);
//noinspection TryWithIdenticalCatches
try {
future.get();
diff --git a/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java b/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
index cae8445b..d78411f8 100644
--- a/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/LiveDataQueryTest.java
@@ -21,7 +21,7 @@ import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.executor.testing.CountingTaskExecutorRule;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleOwner;
@@ -35,9 +35,11 @@ import android.arch.persistence.room.integration.testapp.vo.PetsToys;
import android.arch.persistence.room.integration.testapp.vo.Toy;
import android.arch.persistence.room.integration.testapp.vo.User;
import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
+import android.os.Build;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -235,6 +237,7 @@ public class LiveDataQueryTest extends TestDatabaseTest {
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
public void withWithClause() throws ExecutionException, InterruptedException,
TimeoutException {
LiveData<List<String>> actual =
@@ -322,7 +325,7 @@ public class LiveDataQueryTest extends TestDatabaseTest {
return null;
}
});
- AppToolkitTaskExecutor.getInstance().executeOnMainThread(futureTask);
+ ArchTaskExecutor.getInstance().executeOnMainThread(futureTask);
futureTask.get();
}
diff --git a/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java b/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
index 97ce10c2..fda43732 100644
--- a/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/PrimaryKeyTest.java
@@ -16,29 +16,35 @@
package android.arch.persistence.room.integration.testapp.test;
+import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import static org.junit.Assert.assertNotNull;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.integration.testapp.PKeyTestDatabase;
import android.arch.persistence.room.integration.testapp.vo.IntAutoIncPKeyEntity;
import android.arch.persistence.room.integration.testapp.vo.IntegerAutoIncPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.IntegerPKeyEntity;
+import android.arch.persistence.room.integration.testapp.vo.ObjectPKeyEntity;
+import android.database.sqlite.SQLiteConstraintException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class PrimaryKeyTest {
private PKeyTestDatabase mDatabase;
+
@Before
public void setup() {
mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getTargetContext(),
@@ -49,8 +55,8 @@ public class PrimaryKeyTest {
public void integerTest() {
IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
entity.data = "foo";
- mDatabase.integerPKeyDao().insertMe(entity);
- IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(1);
+ mDatabase.integerAutoIncPKeyDao().insertMe(entity);
+ IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(1);
assertThat(loaded, notNullValue());
assertThat(loaded.data, is(entity.data));
}
@@ -60,8 +66,8 @@ public class PrimaryKeyTest {
IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
entity.pKey = 0;
entity.data = "foo";
- mDatabase.integerPKeyDao().insertMe(entity);
- IntegerAutoIncPKeyEntity loaded = mDatabase.integerPKeyDao().getMe(0);
+ mDatabase.integerAutoIncPKeyDao().insertMe(entity);
+ IntegerAutoIncPKeyEntity loaded = mDatabase.integerAutoIncPKeyDao().getMe(0);
assertThat(loaded, notNullValue());
assertThat(loaded.data, is(entity.data));
}
@@ -98,8 +104,8 @@ public class PrimaryKeyTest {
public void getInsertedIdFromInteger() {
IntegerAutoIncPKeyEntity entity = new IntegerAutoIncPKeyEntity();
entity.data = "foo";
- final long id = mDatabase.integerPKeyDao().insertAndGetId(entity);
- assertThat(mDatabase.integerPKeyDao().getMe((int) id).data, is("foo"));
+ final long id = mDatabase.integerAutoIncPKeyDao().insertAndGetId(entity);
+ assertThat(mDatabase.integerAutoIncPKeyDao().getMe((int) id).data, is("foo"));
}
@Test
@@ -108,7 +114,34 @@ public class PrimaryKeyTest {
entity.data = "foo";
IntegerAutoIncPKeyEntity entity2 = new IntegerAutoIncPKeyEntity();
entity2.data = "foo2";
- final long[] ids = mDatabase.integerPKeyDao().insertAndGetIds(entity, entity2);
- assertThat(mDatabase.integerPKeyDao().loadDataById(ids), is(Arrays.asList("foo", "foo2")));
+ final long[] ids = mDatabase.integerAutoIncPKeyDao().insertAndGetIds(entity, entity2);
+ assertThat(mDatabase.integerAutoIncPKeyDao().loadDataById(ids),
+ is(Arrays.asList("foo", "foo2")));
+ }
+
+ @Test
+ public void insertNullPrimaryKey() throws Exception {
+ ObjectPKeyEntity o1 = new ObjectPKeyEntity(null, "1");
+
+ Throwable throwable = null;
+ try {
+ mDatabase.objectPKeyDao().insertMe(o1);
+ } catch (Throwable t) {
+ throwable = t;
+ }
+ assertNotNull("Was expecting an exception", throwable);
+ assertThat(throwable, instanceOf(SQLiteConstraintException.class));
+ }
+
+ @Test
+ public void insertNullPrimaryKeyForInteger() throws Exception {
+ IntegerPKeyEntity entity = new IntegerPKeyEntity();
+ entity.data = "data";
+ mDatabase.integerPKeyDao().insertMe(entity);
+
+ List<IntegerPKeyEntity> list = mDatabase.integerPKeyDao().loadAll();
+ assertThat(list.size(), is(1));
+ assertThat(list.get(0).data, is("data"));
+ assertNotNull(list.get(0).pKey);
}
}
diff --git a/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java b/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
index 1bbc1406..01d071e7 100644
--- a/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
+++ b/android/arch/persistence/room/integration/testapp/test/RxJava2Test.java
@@ -19,10 +19,12 @@ package android.arch.persistence.room.integration.testapp.test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import android.arch.core.executor.TaskExecutor;
import android.arch.persistence.room.EmptyResultSetException;
+import android.arch.persistence.room.integration.testapp.vo.Pet;
import android.arch.persistence.room.integration.testapp.vo.User;
+import android.arch.persistence.room.integration.testapp.vo.UserAndAllPets;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -38,6 +40,7 @@ import java.util.Collections;
import java.util.List;
import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Predicate;
import io.reactivex.observers.TestObserver;
import io.reactivex.schedulers.TestScheduler;
import io.reactivex.subscribers.TestSubscriber;
@@ -52,7 +55,7 @@ public class RxJava2Test extends TestDatabaseTest {
public void setupSchedulers() {
mTestScheduler = new TestScheduler();
mTestScheduler.start();
- AppToolkitTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+ ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
@Override
public void executeOnDiskIO(Runnable runnable) {
mTestScheduler.scheduleDirect(runnable);
@@ -73,7 +76,7 @@ public class RxJava2Test extends TestDatabaseTest {
@After
public void clearSchedulers() {
mTestScheduler.shutdown();
- AppToolkitTaskExecutor.getInstance().setDelegate(null);
+ ArchTaskExecutor.getInstance().setDelegate(null);
}
private void drain() throws InterruptedException {
@@ -269,4 +272,60 @@ public class RxJava2Test extends TestDatabaseTest {
subscriber.cancel();
subscriber.assertNoErrors();
}
+
+ @Test
+ public void flowableWithRelation() throws InterruptedException {
+ final TestSubscriber<UserAndAllPets> subscriber = new TestSubscriber<>();
+
+ mUserPetDao.flowableUserWithPets(3).subscribe(subscriber);
+ drain();
+ subscriber.assertSubscribed();
+
+ drain();
+ subscriber.assertNoValues();
+
+ final User user = TestUtil.createUser(3);
+ mUserDao.insert(user);
+ drain();
+ subscriber.assertValue(new Predicate<UserAndAllPets>() {
+ @Override
+ public boolean test(UserAndAllPets userAndAllPets) throws Exception {
+ return userAndAllPets.user.equals(user);
+ }
+ });
+ subscriber.assertValueCount(1);
+ final Pet[] pets = TestUtil.createPetsForUser(3, 1, 2);
+ mPetDao.insertAll(pets);
+ drain();
+ subscriber.assertValueAt(1, new Predicate<UserAndAllPets>() {
+ @Override
+ public boolean test(UserAndAllPets userAndAllPets) throws Exception {
+ return userAndAllPets.user.equals(user)
+ && userAndAllPets.pets.equals(Arrays.asList(pets));
+ }
+ });
+ }
+
+ @Test
+ public void flowable_updateInTransaction() throws InterruptedException {
+ // When subscribing to the emissions of the user
+ final TestSubscriber<User> userTestSubscriber = mUserDao
+ .flowableUserById(3)
+ .observeOn(mTestScheduler)
+ .test();
+ drain();
+ userTestSubscriber.assertValueCount(0);
+
+ // When inserting a new user in the data source
+ mDatabase.beginTransaction();
+ try {
+ mUserDao.insert(TestUtil.createUser(3));
+ mDatabase.setTransactionSuccessful();
+
+ } finally {
+ mDatabase.endTransaction();
+ }
+ drain();
+ userTestSubscriber.assertValueCount(1);
+ }
}
diff --git a/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java b/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
new file mode 100644
index 00000000..fcd0b004
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/test/RxJava2WithInstantTaskExecutorTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.arch.persistence.room.integration.testapp.test;
+
+import android.arch.core.executor.testing.InstantTaskExecutorRule;
+import android.arch.persistence.room.Room;
+import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.vo.User;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import io.reactivex.subscribers.TestSubscriber;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RxJava2WithInstantTaskExecutorTest {
+ @Rule
+ public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
+
+ private TestDatabase mDatabase;
+
+ @Before
+ public void initDb() throws Exception {
+ // using an in-memory database because the information stored here disappears when the
+ // process is killed
+ mDatabase = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
+ TestDatabase.class)
+ // allowing main thread queries, just for testing
+ .allowMainThreadQueries()
+ .build();
+ }
+
+ @Test
+ public void testFlowableInTransaction() {
+ // When subscribing to the emissions of the user
+ TestSubscriber<User> subscriber = mDatabase.getUserDao().flowableUserById(3).test();
+ subscriber.assertValueCount(0);
+
+ // When inserting a new user in the data source
+ mDatabase.beginTransaction();
+ try {
+ mDatabase.getUserDao().insert(TestUtil.createUser(3));
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+
+ subscriber.assertValueCount(1);
+ }
+}
diff --git a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index 8861adbc..f8049f35 100644
--- a/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -502,4 +502,26 @@ public class SimpleEntityReadWriteTest {
assertThat(mUserDao.updateByAgeAndIds(3f, 30, Arrays.asList(3, 5)), is(1));
assertThat(mUserDao.loadByIds(3)[0].getWeight(), is(3f));
}
+
+ @Test
+ public void transactionByAnnotation() {
+ User a = TestUtil.createUser(3);
+ User b = TestUtil.createUser(5);
+ mUserDao.insertBothByAnnotation(a, b);
+ assertThat(mUserDao.count(), is(2));
+ }
+
+ @Test
+ public void transactionByAnnotation_failure() {
+ User a = TestUtil.createUser(3);
+ User b = TestUtil.createUser(3);
+ boolean caught = false;
+ try {
+ mUserDao.insertBothByAnnotation(a, b);
+ } catch (SQLiteConstraintException e) {
+ caught = true;
+ }
+ assertTrue("SQLiteConstraintException expected", caught);
+ assertThat(mUserDao.count(), is(0));
+ }
}
diff --git a/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java b/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
index 51d5bb33..ec775617 100644
--- a/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/TestDatabaseTest.java
@@ -18,6 +18,7 @@ package android.arch.persistence.room.integration.testapp.test;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.integration.testapp.TestDatabase;
+import android.arch.persistence.room.integration.testapp.dao.FunnyNamedDao;
import android.arch.persistence.room.integration.testapp.dao.PetCoupleDao;
import android.arch.persistence.room.integration.testapp.dao.PetDao;
import android.arch.persistence.room.integration.testapp.dao.SchoolDao;
@@ -42,6 +43,7 @@ public abstract class TestDatabaseTest {
protected ToyDao mToyDao;
protected SpecificDogDao mSpecificDogDao;
protected WithClauseDao mWithClauseDao;
+ protected FunnyNamedDao mFunnyNamedDao;
@Before
public void createDb() {
@@ -55,5 +57,6 @@ public abstract class TestDatabaseTest {
mToyDao = mDatabase.getToyDao();
mSpecificDogDao = mDatabase.getSpecificDogDao();
mWithClauseDao = mDatabase.getWithClauseDao();
+ mFunnyNamedDao = mDatabase.getFunnyNamedDao();
}
}
diff --git a/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java b/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
index 10897da1..92096380 100644
--- a/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/WithClauseTest.java
@@ -20,6 +20,8 @@ import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import android.arch.persistence.room.integration.testapp.vo.User;
+import android.os.Build;
+import android.support.test.filters.SdkSuppress;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -32,6 +34,7 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
public class WithClauseTest extends TestDatabaseTest{
@Test
public void noSourceOfData() {
diff --git a/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java b/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.java
new file mode 100644
index 00000000..20f3c216
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/FunnyNamedEntity.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.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.ColumnInfo;
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+/**
+ * An entity that was weird names
+ */
+@Entity(tableName = FunnyNamedEntity.TABLE_NAME)
+public class FunnyNamedEntity {
+ public static final String TABLE_NAME = "funny but not so funny";
+ public static final String COLUMN_ID = "_this $is id$";
+ public static final String COLUMN_VALUE = "unlikely-Ωşå¨ıünames";
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COLUMN_ID)
+ private int mId;
+ @ColumnInfo(name = COLUMN_VALUE)
+ private String mValue;
+
+ public FunnyNamedEntity(int id, String value) {
+ mId = id;
+ mValue = value;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public void setId(int id) {
+ mId = id;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+
+ public void setValue(String value) {
+ mValue = value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FunnyNamedEntity entity = (FunnyNamedEntity) o;
+
+ if (mId != entity.mId) return false;
+ return mValue != null ? mValue.equals(entity.mValue) : entity.mValue == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mId;
+ result = 31 * result + (mValue != null ? mValue.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java b/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java
new file mode 100644
index 00000000..cae1843e
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/IntegerPKeyEntity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+
+@Entity
+public class IntegerPKeyEntity {
+ @PrimaryKey
+ public Integer pKey;
+ public String data;
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java b/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java
new file mode 100644
index 00000000..895a35a2
--- /dev/null
+++ b/android/arch/persistence/room/integration/testapp/vo/ObjectPKeyEntity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.arch.persistence.room.integration.testapp.vo;
+
+import android.arch.persistence.room.Entity;
+import android.arch.persistence.room.PrimaryKey;
+import android.support.annotation.NonNull;
+
+@Entity
+public class ObjectPKeyEntity {
+ @PrimaryKey
+ @NonNull
+ public String pKey;
+ public String data;
+
+ public ObjectPKeyEntity(String pKey, String data) {
+ this.pKey = pKey;
+ this.data = data;
+ }
+}
diff --git a/android/arch/persistence/room/integration/testapp/vo/PetCouple.java b/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
index f27b1313..e5208ed7 100644
--- a/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
+++ b/android/arch/persistence/room/integration/testapp/vo/PetCouple.java
@@ -20,11 +20,13 @@ import android.arch.persistence.room.Embedded;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
import android.arch.persistence.room.RoomWarnings;
+import android.support.annotation.NonNull;
@Entity
@SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
public class PetCouple {
@PrimaryKey
+ @NonNull
public String id;
@Embedded(prefix = "male_")
public Pet male;
diff --git a/android/arch/persistence/room/migration/TableInfoTest.java b/android/arch/persistence/room/migration/TableInfoTest.java
index c6eade55..d88c02fd 100644
--- a/android/arch/persistence/room/migration/TableInfoTest.java
+++ b/android/arch/persistence/room/migration/TableInfoTest.java
@@ -37,6 +37,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -179,6 +180,35 @@ public class TableInfoTest {
Collections.<TableInfo.ForeignKey>emptySet())));
}
+ @Test
+ public void readIndices() {
+ mDb = createDatabase(
+ "CREATE TABLE foo (n INTEGER, indexed TEXT, unique_indexed TEXT,"
+ + "a INTEGER, b INTEGER);",
+ "CREATE INDEX foo_indexed ON foo(indexed);",
+ "CREATE UNIQUE INDEX foo_unique_indexed ON foo(unique_indexed COLLATE NOCASE"
+ + " DESC);",
+ "CREATE INDEX " + TableInfo.Index.DEFAULT_PREFIX + "foo_composite_indexed"
+ + " ON foo(a, b);"
+ );
+ TableInfo info = TableInfo.read(mDb, "foo");
+ assertThat(info, is(new TableInfo(
+ "foo",
+ toMap(new TableInfo.Column("n", "INTEGER", false, 0),
+ new TableInfo.Column("indexed", "TEXT", false, 0),
+ new TableInfo.Column("unique_indexed", "TEXT", false, 0),
+ new TableInfo.Column("a", "INTEGER", false, 0),
+ new TableInfo.Column("b", "INTEGER", false, 0)),
+ Collections.<TableInfo.ForeignKey>emptySet(),
+ toSet(new TableInfo.Index("index_foo_blahblah", false,
+ Arrays.asList("a", "b")),
+ new TableInfo.Index("foo_unique_indexed", true,
+ Arrays.asList("unique_indexed")),
+ new TableInfo.Index("foo_indexed", false,
+ Arrays.asList("indexed"))))
+ ));
+ }
+
private static Map<String, TableInfo.Column> toMap(TableInfo.Column... columns) {
Map<String, TableInfo.Column> result = new HashMap<>();
for (TableInfo.Column column : columns) {
@@ -187,6 +217,14 @@ public class TableInfoTest {
return result;
}
+ private static <T> Set<T> toSet(T... ts) {
+ final HashSet<T> result = new HashSet<T>();
+ for (T t : ts) {
+ result.add(t);
+ }
+ return result;
+ }
+
@After
public void closeDb() throws IOException {
if (mDb != null && mDb.isOpen()) {
@@ -199,8 +237,7 @@ public class TableInfoTest {
SupportSQLiteOpenHelper.Configuration
.builder(InstrumentationRegistry.getTargetContext())
.name(null)
- .version(1)
- .callback(new SupportSQLiteOpenHelper.Callback() {
+ .callback(new SupportSQLiteOpenHelper.Callback(1) {
@Override
public void onCreate(SupportSQLiteDatabase db) {
for (String query : queries) {
diff --git a/android/arch/persistence/room/package-info.java b/android/arch/persistence/room/package-info.java
index faaa952b..1dafc1b2 100644
--- a/android/arch/persistence/room/package-info.java
+++ b/android/arch/persistence/room/package-info.java
@@ -39,8 +39,8 @@
* database row. For each {@link android.arch.persistence.room.Entity Entity}, a database table
* is created to hold the items. The Entity class must be referenced in the
* {@link android.arch.persistence.room.Database#entities() Database#entities} array. Each field
- * of the Entity is persisted in the database unless it is annotated with
- * {@link android.arch.persistence.room.Ignore Ignore}. Entities must have no-arg constructors.
+ * of the Entity (and its super class) is persisted in the database unless it is denoted
+ * otherwise (see {@link android.arch.persistence.room.Entity Entity} docs for details).
* </li>
* <li>{@link android.arch.persistence.room.Dao Dao}: This annotation marks a class or interface
* as a Data Access Object. Data access objects are the main component of Room that are
diff --git a/android/arch/persistence/room/testing/MigrationTestHelper.java b/android/arch/persistence/room/testing/MigrationTestHelper.java
index aea3e96e..18e0a146 100644
--- a/android/arch/persistence/room/testing/MigrationTestHelper.java
+++ b/android/arch/persistence/room/testing/MigrationTestHelper.java
@@ -29,6 +29,7 @@ import android.arch.persistence.room.migration.bundle.DatabaseBundle;
import android.arch.persistence.room.migration.bundle.EntityBundle;
import android.arch.persistence.room.migration.bundle.FieldBundle;
import android.arch.persistence.room.migration.bundle.ForeignKeyBundle;
+import android.arch.persistence.room.migration.bundle.IndexBundle;
import android.arch.persistence.room.migration.bundle.SchemaBundle;
import android.arch.persistence.room.util.TableInfo;
import android.content.Context;
@@ -146,7 +147,7 @@ public class MigrationTestHelper extends TestWatcher {
RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
new CreatingDelegate(schemaBundle.getDatabase()),
schemaBundle.getDatabase().getIdentityHash());
- return openDatabase(name, version, roomOpenHelper);
+ return openDatabase(name, roomOpenHelper);
}
/**
@@ -189,17 +190,15 @@ public class MigrationTestHelper extends TestWatcher {
RoomOpenHelper roomOpenHelper = new RoomOpenHelper(configuration,
new MigratingDelegate(schemaBundle.getDatabase(), validateDroppedTables),
schemaBundle.getDatabase().getIdentityHash());
- return openDatabase(name, version, roomOpenHelper);
+ return openDatabase(name, roomOpenHelper);
}
- private SupportSQLiteDatabase openDatabase(String name, int version,
- RoomOpenHelper roomOpenHelper) {
+ private SupportSQLiteDatabase openDatabase(String name, RoomOpenHelper roomOpenHelper) {
SupportSQLiteOpenHelper.Configuration config =
SupportSQLiteOpenHelper.Configuration
.builder(mInstrumentation.getTargetContext())
.callback(roomOpenHelper)
.name(name)
- .version(version)
.build();
SupportSQLiteDatabase db = mOpenFactory.create(config).getWritableDatabase();
mManagedDatabases.add(new WeakReference<>(db));
@@ -287,7 +286,19 @@ public class MigrationTestHelper extends TestWatcher {
private static TableInfo toTableInfo(EntityBundle entityBundle) {
return new TableInfo(entityBundle.getTableName(), toColumnMap(entityBundle),
- toForeignKeys(entityBundle.getForeignKeys()));
+ toForeignKeys(entityBundle.getForeignKeys()), toIndices(entityBundle.getIndices()));
+ }
+
+ private static Set<TableInfo.Index> toIndices(List<IndexBundle> indices) {
+ if (indices == null) {
+ return Collections.emptySet();
+ }
+ Set<TableInfo.Index> result = new HashSet<>();
+ for (IndexBundle bundle : indices) {
+ result.add(new TableInfo.Index(bundle.getName(), bundle.isUnique(),
+ bundle.getColumnNames()));
+ }
+ return result;
}
private static Set<TableInfo.ForeignKey> toForeignKeys(
@@ -401,6 +412,7 @@ public class MigrationTestHelper extends TestWatcher {
final DatabaseBundle mDatabaseBundle;
RoomOpenHelperDelegate(DatabaseBundle databaseBundle) {
+ super(databaseBundle.getVersion());
mDatabaseBundle = databaseBundle;
}
diff --git a/android/arch/persistence/room/util/TableInfo.java b/android/arch/persistence/room/util/TableInfo.java
index bcd2e9ef..a115147d 100644
--- a/android/arch/persistence/room/util/TableInfo.java
+++ b/android/arch/persistence/room/util/TableInfo.java
@@ -20,6 +20,7 @@ import android.arch.persistence.db.SupportSQLiteDatabase;
import android.database.Cursor;
import android.os.Build;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import java.util.ArrayList;
@@ -29,6 +30,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
/**
* A data class that holds the information about a table.
@@ -56,11 +58,70 @@ public class TableInfo {
public final Set<ForeignKey> foreignKeys;
+ /**
+ * Sometimes, Index information is not available (older versions). If so, we skip their
+ * verification.
+ */
+ @Nullable
+ public final Set<Index> indices;
+
@SuppressWarnings("unused")
- public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys) {
+ public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys,
+ Set<Index> indices) {
this.name = name;
this.columns = Collections.unmodifiableMap(columns);
this.foreignKeys = Collections.unmodifiableSet(foreignKeys);
+ this.indices = indices == null ? null : Collections.unmodifiableSet(indices);
+ }
+
+ /**
+ * For backward compatibility with dbs created with older versions.
+ */
+ @SuppressWarnings("unused")
+ public TableInfo(String name, Map<String, Column> columns, Set<ForeignKey> foreignKeys) {
+ this(name, columns, foreignKeys, Collections.<Index>emptySet());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TableInfo tableInfo = (TableInfo) o;
+
+ if (name != null ? !name.equals(tableInfo.name) : tableInfo.name != null) return false;
+ if (columns != null ? !columns.equals(tableInfo.columns) : tableInfo.columns != null) {
+ return false;
+ }
+ if (foreignKeys != null ? !foreignKeys.equals(tableInfo.foreignKeys)
+ : tableInfo.foreignKeys != null) {
+ return false;
+ }
+ if (indices == null || tableInfo.indices == null) {
+ // if one us is missing index information, seems like we couldn't acquire the
+ // information so we better skip.
+ return true;
+ }
+ return indices.equals(tableInfo.indices);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (columns != null ? columns.hashCode() : 0);
+ result = 31 * result + (foreignKeys != null ? foreignKeys.hashCode() : 0);
+ // skip index, it is not reliable for comparison.
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "TableInfo{"
+ + "name='" + name + '\''
+ + ", columns=" + columns
+ + ", foreignKeys=" + foreignKeys
+ + ", indices=" + indices
+ + '}';
}
/**
@@ -74,7 +135,8 @@ public class TableInfo {
public static TableInfo read(SupportSQLiteDatabase database, String tableName) {
Map<String, Column> columns = readColumns(database, tableName);
Set<ForeignKey> foreignKeys = readForeignKeys(database, tableName);
- return new TableInfo(tableName, columns, foreignKeys);
+ Set<Index> indices = readIndices(database, tableName);
+ return new TableInfo(tableName, columns, foreignKeys, indices);
}
private static Set<ForeignKey> readForeignKeys(SupportSQLiteDatabase database,
@@ -167,34 +229,74 @@ public class TableInfo {
return columns;
}
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- TableInfo tableInfo = (TableInfo) o;
-
- if (!name.equals(tableInfo.name)) return false;
- //noinspection SimplifiableIfStatement
- if (!columns.equals(tableInfo.columns)) return false;
- return foreignKeys.equals(tableInfo.foreignKeys);
+ /**
+ * @return null if we cannot read the indices due to older sqlite implementations.
+ */
+ @Nullable
+ private static Set<Index> readIndices(SupportSQLiteDatabase database, String tableName) {
+ Cursor cursor = database.query("PRAGMA index_list(`" + tableName + "`)");
+ try {
+ final int nameColumnIndex = cursor.getColumnIndex("name");
+ final int originColumnIndex = cursor.getColumnIndex("origin");
+ final int uniqueIndex = cursor.getColumnIndex("unique");
+ if (nameColumnIndex == -1 || originColumnIndex == -1 || uniqueIndex == -1) {
+ // we cannot read them so better not validate any index.
+ return null;
+ }
+ HashSet<Index> indices = new HashSet<>();
+ while (cursor.moveToNext()) {
+ String origin = cursor.getString(originColumnIndex);
+ if (!"c".equals(origin)) {
+ // Ignore auto-created indices
+ continue;
+ }
+ String name = cursor.getString(nameColumnIndex);
+ boolean unique = cursor.getInt(uniqueIndex) == 1;
+ Index index = readIndex(database, name, unique);
+ if (index == null) {
+ // we cannot read it properly so better not read it
+ return null;
+ }
+ indices.add(index);
+ }
+ return indices;
+ } finally {
+ cursor.close();
+ }
}
- @Override
- public int hashCode() {
- int result = name.hashCode();
- result = 31 * result + columns.hashCode();
- result = 31 * result + foreignKeys.hashCode();
- return result;
- }
+ /**
+ * @return null if we cannot read the index due to older sqlite implementations.
+ */
+ @Nullable
+ private static Index readIndex(SupportSQLiteDatabase database, String name, boolean unique) {
+ Cursor cursor = database.query("PRAGMA index_xinfo(`" + name + "`)");
+ try {
+ final int seqnoColumnIndex = cursor.getColumnIndex("seqno");
+ final int cidColumnIndex = cursor.getColumnIndex("cid");
+ final int nameColumnIndex = cursor.getColumnIndex("name");
+ if (seqnoColumnIndex == -1 || cidColumnIndex == -1 || nameColumnIndex == -1) {
+ // we cannot read them so better not validate any index.
+ return null;
+ }
+ final TreeMap<Integer, String> results = new TreeMap<>();
- @Override
- public String toString() {
- return "TableInfo{"
- + "name='" + name + '\''
- + ", columns=" + columns
- + ", foreignKeys=" + foreignKeys
- + '}';
+ while (cursor.moveToNext()) {
+ int cid = cursor.getInt(cidColumnIndex);
+ if (cid < 0) {
+ // Ignore SQLite row ID
+ continue;
+ }
+ int seq = cursor.getInt(seqnoColumnIndex);
+ String columnName = cursor.getString(nameColumnIndex);
+ results.put(seq, columnName);
+ }
+ final List<String> columns = new ArrayList<>(results.size());
+ columns.addAll(results.values());
+ return new Index(name, unique, columns);
+ } finally {
+ cursor.close();
+ }
}
/**
@@ -379,4 +481,65 @@ public class TableInfo {
}
}
}
+
+ /**
+ * Holds the information about an SQLite index
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static class Index {
+ // should match the value in Index.kt
+ public static final String DEFAULT_PREFIX = "index_";
+ public final String name;
+ public final boolean unique;
+ public final List<String> columns;
+
+ public Index(String name, boolean unique, List<String> columns) {
+ this.name = name;
+ this.unique = unique;
+ this.columns = columns;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Index index = (Index) o;
+ if (unique != index.unique) {
+ return false;
+ }
+ if (!columns.equals(index.columns)) {
+ return false;
+ }
+ if (name.startsWith(Index.DEFAULT_PREFIX)) {
+ return index.name.startsWith(Index.DEFAULT_PREFIX);
+ } else {
+ return name.equals(index.name);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ if (name.startsWith(DEFAULT_PREFIX)) {
+ result = DEFAULT_PREFIX.hashCode();
+ } else {
+ result = name.hashCode();
+ }
+ result = 31 * result + (unique ? 1 : 0);
+ result = 31 * result + columns.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Index{"
+ + "name='" + name + '\''
+ + ", unique=" + unique
+ + ", columns=" + columns
+ + '}';
+ }
+ }
}
diff --git a/android/bluetooth/BluetoothAdapter.java b/android/bluetooth/BluetoothAdapter.java
index 70591d4d..84765f6d 100644
--- a/android/bluetooth/BluetoothAdapter.java
+++ b/android/bluetooth/BluetoothAdapter.java
@@ -1134,6 +1134,29 @@ public final class BluetoothAdapter {
}
/**
+ * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of
+ * the local Bluetooth adapter.
+ *
+ * @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to.
+ * @return true if successful, false if unsuccessful.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
+ if (getState() != STATE_ON) return false;
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) return mService.setBluetoothClass(bluetoothClass);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return false;
+ }
+
+ /**
* Get the current Bluetooth scan mode of the local Bluetooth adapter.
* <p>The Bluetooth scan mode determines if the local adapter is
* connectable and/or discoverable from remote Bluetooth devices.
diff --git a/android/bluetooth/BluetoothClass.java b/android/bluetooth/BluetoothClass.java
index 57e4abb1..f22ea6e8 100644
--- a/android/bluetooth/BluetoothClass.java
+++ b/android/bluetooth/BluetoothClass.java
@@ -19,6 +19,10 @@ package android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
/**
* Represents a Bluetooth class, which describes general characteristics
* and capabilities of a device. For example, a Bluetooth class will
@@ -275,6 +279,48 @@ public final class BluetoothClass implements Parcelable {
return (mClass & Device.BITMASK);
}
+ /**
+ * Return the Bluetooth Class of Device (CoD) value including the
+ * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and
+ * minor device fields.
+ *
+ * <p>This value is an integer representation of Bluetooth CoD as in
+ * Bluetooth specification.
+ *
+ * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a>
+ *
+ * @hide
+ */
+ public int getClassOfDevice() {
+ return mClass;
+ }
+
+ /**
+ * Return the Bluetooth Class of Device (CoD) value including the
+ * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and
+ * minor device fields.
+ *
+ * <p>This value is a byte array representation of Bluetooth CoD as in
+ * Bluetooth specification.
+ *
+ * <p>Bluetooth COD information is 3 bytes, but stored as an int. Hence the
+ * MSB is useless and needs to be thrown away. The lower 3 bytes are
+ * converted into a byte array MSB to LSB. Hence, using BIG_ENDIAN.
+ *
+ * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a>
+ *
+ * @hide
+ */
+ public byte[] getClassOfDeviceBytes() {
+ byte[] bytes = ByteBuffer.allocate(4)
+ .order(ByteOrder.BIG_ENDIAN)
+ .putInt(mClass)
+ .array();
+
+ // Discard the top byte
+ return Arrays.copyOfRange(bytes, 1, bytes.length);
+ }
+
/** @hide */
public static final int PROFILE_HEADSET = 0;
/** @hide */
diff --git a/android/bluetooth/BluetoothInputHost.java b/android/bluetooth/BluetoothInputHost.java
index 37f04278..e18d9d1b 100644
--- a/android/bluetooth/BluetoothInputHost.java
+++ b/android/bluetooth/BluetoothInputHost.java
@@ -74,7 +74,7 @@ public final class BluetoothInputHost implements BluetoothProfile {
public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
- public static final byte SUBCLASS2_DIGITIZER_TABLED = (byte) 0x05;
+ public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05;
public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
/**
diff --git a/android/bluetooth/BluetoothPbap.java b/android/bluetooth/BluetoothPbap.java
index 19f5198c..a1a9347d 100644
--- a/android/bluetooth/BluetoothPbap.java
+++ b/android/bluetooth/BluetoothPbap.java
@@ -16,6 +16,7 @@
package android.bluetooth;
+import android.annotation.SdkConstant;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -53,35 +54,32 @@ public class BluetoothPbap {
private static final boolean DBG = true;
private static final boolean VDBG = false;
- /** int extra for PBAP_STATE_CHANGED_ACTION */
- public static final String PBAP_STATE =
- "android.bluetooth.pbap.intent.PBAP_STATE";
- /** int extra for PBAP_STATE_CHANGED_ACTION */
- public static final String PBAP_PREVIOUS_STATE =
- "android.bluetooth.pbap.intent.PBAP_PREVIOUS_STATE";
-
/**
- * Indicates the state of a pbap connection state has changed.
- * This intent will always contain PBAP_STATE, PBAP_PREVIOUS_STATE and
- * BluetoothIntent.ADDRESS extras.
+ * Intent used to broadcast the change in connection state of the PBAP
+ * profile.
+ *
+ * <p>This intent will have 3 extras:
+ * <ul>
+ * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
+ * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
+ * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+ * </ul>
+ * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
+ * can be any of {@link BluetoothProfile#STATE_DISCONNECTED},
+ * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED},
+ * {@link BluetoothProfile#STATE_DISCONNECTING}.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+ * receive.
*/
- public static final String PBAP_STATE_CHANGED_ACTION =
- "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED";
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CONNECTION_STATE_CHANGED =
+ "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
private volatile IBluetoothPbap mService;
private final Context mContext;
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
- /** There was an error trying to obtain the state */
- public static final int STATE_ERROR = -1;
- /** No client currently connected */
- public static final int STATE_DISCONNECTED = 0;
- /** Connection attempt in progress */
- public static final int STATE_CONNECTING = 1;
- /** Client is currently connected */
- public static final int STATE_CONNECTED = 2;
-
public static final int RESULT_FAILURE = 0;
public static final int RESULT_SUCCESS = 1;
/** Connection canceled before completion. */
@@ -209,8 +207,8 @@ public class BluetoothPbap {
/**
* Get the current state of the BluetoothPbap service.
*
- * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
- * connected to the Pbap service.
+ * @return One of the STATE_ return codes, or {@link BluetoothProfile#STATE_DISCONNECTED}
+ * if this proxy object is currently not connected to the Pbap service.
*/
public int getState() {
if (VDBG) log("getState()");
@@ -225,7 +223,7 @@ public class BluetoothPbap {
Log.w(TAG, "Proxy not attached to service");
if (DBG) log(Log.getStackTraceString(new Throwable()));
}
- return BluetoothPbap.STATE_ERROR;
+ return BluetoothProfile.STATE_DISCONNECTED;
}
/**
diff --git a/android/bluetooth/BluetoothPbapClient.java b/android/bluetooth/BluetoothPbapClient.java
index 00a15f3f..01b3f6e0 100644
--- a/android/bluetooth/BluetoothPbapClient.java
+++ b/android/bluetooth/BluetoothPbapClient.java
@@ -40,7 +40,7 @@ public final class BluetoothPbapClient implements BluetoothProfile {
private static final boolean VDBG = false;
public static final String ACTION_CONNECTION_STATE_CHANGED =
- "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+ "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED";
private volatile IBluetoothPbapClient mService;
private final Context mContext;
diff --git a/android/bluetooth/BluetoothUuid.java b/android/bluetooth/BluetoothUuid.java
index 5bfc54d2..76cb3f5b 100644
--- a/android/bluetooth/BluetoothUuid.java
+++ b/android/bluetooth/BluetoothUuid.java
@@ -232,7 +232,7 @@ public final class BluetoothUuid {
*/
public static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) {
UUID uuid = parcelUuid.getUuid();
- long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32;
+ long value = (uuid.getMostSignificantBits() & 0xFFFFFFFF00000000L) >>> 32;
return (int) value;
}
diff --git a/android/content/ComponentName.java b/android/content/ComponentName.java
index ea6b7690..0d36bddc 100644
--- a/android/content/ComponentName.java
+++ b/android/content/ComponentName.java
@@ -21,9 +21,9 @@ import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
-import java.lang.Comparable;
/**
* Identifier for a specific application component
@@ -33,7 +33,7 @@ import java.lang.Comparable;
* pieces of information, encapsulated here, are required to identify
* a component: the package (a String) it exists in, and the class (a String)
* name inside of that package.
- *
+ *
*/
public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
private final String mPackage;
@@ -91,7 +91,7 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
/**
* Create a new component identifier.
- *
+ *
* @param pkg The name of the package that the component exists in. Can
* not be null.
* @param cls The name of the class inside of <var>pkg</var> that
@@ -106,7 +106,7 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
/**
* Create a new component identifier from a Context and class name.
- *
+ *
* @param pkg A Context for the package implementing the component,
* from which the actual package name will be retrieved.
* @param cls The name of the class inside of <var>pkg</var> that
@@ -120,7 +120,7 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
/**
* Create a new component identifier from a Context and Class object.
- *
+ *
* @param pkg A Context for the package implementing the component, from
* which the actual package name will be retrieved.
* @param cls The Class object of the desired component, from which the
@@ -141,14 +141,14 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
public @NonNull String getPackageName() {
return mPackage;
}
-
+
/**
* Return the class name of this component.
*/
public @NonNull String getClassName() {
return mClass;
}
-
+
/**
* Return the class name, either fully qualified or in a shortened form
* (with a leading '.') if it is a suffix of the package.
@@ -163,7 +163,7 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
}
return mClass;
}
-
+
private static void appendShortClassName(StringBuilder sb, String packageName,
String className) {
if (className.startsWith(packageName)) {
@@ -195,26 +195,26 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
* class names contained in the ComponentName. You can later recover
* the ComponentName from this string through
* {@link #unflattenFromString(String)}.
- *
+ *
* @return Returns a new String holding the package and class names. This
* is represented as the package name, concatenated with a '/' and then the
* class name.
- *
+ *
* @see #unflattenFromString(String)
*/
public @NonNull String flattenToString() {
return mPackage + "/" + mClass;
}
-
+
/**
* The same as {@link #flattenToString()}, but abbreviates the class
* name if it is a suffix of the package. The result can still be used
* with {@link #unflattenFromString(String)}.
- *
+ *
* @return Returns a new String holding the package and class names. This
* is represented as the package name, concatenated with a '/' and then the
* class name.
- *
+ *
* @see #unflattenFromString(String)
*/
public @NonNull String flattenToShortString() {
@@ -250,11 +250,11 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
* followed by a '.' then the final class name will be the concatenation
* of the package name with the string following the '/'. Thus
* "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
- *
+ *
* @param str The String that was returned by flattenToString().
* @return Returns a new ComponentName containing the package and class
* names that were encoded in <var>str</var>
- *
+ *
* @see #flattenToString()
*/
public static @Nullable ComponentName unflattenFromString(@NonNull String str) {
@@ -269,7 +269,7 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
}
return new ComponentName(pkg, cls);
}
-
+
/**
* Return string representation of this class without the class's name
* as a prefix.
@@ -283,6 +283,12 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
return "ComponentInfo{" + mPackage + "/" + mClass + "}";
}
+ /** Put this here so that individual services don't have to reimplement this. @hide */
+ public void toProto(ProtoOutputStream proto) {
+ proto.write(ComponentNameProto.PACKAGE_NAME, mPackage);
+ proto.write(ComponentNameProto.CLASS_NAME, mClass);
+ }
+
@Override
public boolean equals(Object obj) {
try {
@@ -311,7 +317,7 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
}
return this.mClass.compareTo(that.mClass);
}
-
+
public int describeContents() {
return 0;
}
@@ -324,10 +330,10 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
/**
* Write a ComponentName to a Parcel, handling null pointers. Must be
* read with {@link #readFromParcel(Parcel)}.
- *
+ *
* @param c The ComponentName to be written.
* @param out The Parcel in which the ComponentName will be placed.
- *
+ *
* @see #readFromParcel(Parcel)
*/
public static void writeToParcel(ComponentName c, Parcel out) {
@@ -337,23 +343,23 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
out.writeString(null);
}
}
-
+
/**
* Read a ComponentName from a Parcel that was previously written
* with {@link #writeToParcel(ComponentName, Parcel)}, returning either
* a null or new object as appropriate.
- *
+ *
* @param in The Parcel from which to read the ComponentName
* @return Returns a new ComponentName matching the previously written
* object, or null if a null had been written.
- *
+ *
* @see #writeToParcel(ComponentName, Parcel)
*/
public static ComponentName readFromParcel(Parcel in) {
String pkg = in.readString();
return pkg != null ? new ComponentName(pkg, in) : null;
}
-
+
public static final Parcelable.Creator<ComponentName> CREATOR
= new Parcelable.Creator<ComponentName>() {
public ComponentName createFromParcel(Parcel in) {
@@ -371,7 +377,7 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
* must not use this with data written by
* {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
* to handle a null ComponentObject here.
- *
+ *
* @param in The Parcel containing the previously written ComponentName,
* positioned at the location in the buffer where it was written.
*/
diff --git a/android/content/ContentProvider.java b/android/content/ContentProvider.java
index cdeaea3e..5b2bf456 100644
--- a/android/content/ContentProvider.java
+++ b/android/content/ContentProvider.java
@@ -2099,7 +2099,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public static Uri maybeAddUserId(Uri uri, int userId) {
if (uri == null) return null;
if (userId != UserHandle.USER_CURRENT
- && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ && (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+ || ContentResolver.SCHEME_SLICE.equals(uri.getScheme()))) {
if (!uriHasUserId(uri)) {
//We don't add the user Id if there's already one
Uri.Builder builder = uri.buildUpon();
diff --git a/android/content/ContentResolver.java b/android/content/ContentResolver.java
index 9ccc552f..02e70f55 100644
--- a/android/content/ContentResolver.java
+++ b/android/content/ContentResolver.java
@@ -47,6 +47,8 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.slice.Slice;
+import android.slice.SliceProvider;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -178,6 +180,8 @@ public abstract class ContentResolver {
public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED =
new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
+ /** @hide */
+ public static final String SCHEME_SLICE = "slice";
public static final String SCHEME_CONTENT = "content";
public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
public static final String SCHEME_FILE = "file";
@@ -1718,6 +1722,36 @@ public abstract class ContentResolver {
}
/**
+ * Turns a slice Uri into slice content.
+ *
+ * @param uri The URI to a slice provider
+ * @return The Slice provided by the app or null if none is given.
+ * @see Slice
+ * @hide
+ */
+ public final @Nullable Slice bindSlice(@NonNull Uri uri) {
+ Preconditions.checkNotNull(uri, "uri");
+ IContentProvider provider = acquireProvider(uri);
+ if (provider == null) {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ try {
+ Bundle extras = new Bundle();
+ extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+ final Bundle res = provider.call(mPackageName, SliceProvider.METHOD_SLICE, null,
+ extras);
+ Bundle.setDefusable(res, true);
+ 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 {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
* Returns the content provider for the given content URI.
*
* @param uri The URI to a content provider
@@ -1725,7 +1759,7 @@ public abstract class ContentResolver {
* @hide
*/
public final IContentProvider acquireProvider(Uri uri) {
- if (!SCHEME_CONTENT.equals(uri.getScheme())) {
+ if (!SCHEME_CONTENT.equals(uri.getScheme()) && !SCHEME_SLICE.equals(uri.getScheme())) {
return null;
}
final String auth = uri.getAuthority();
diff --git a/android/content/Context.java b/android/content/Context.java
index 2d8249ac..20fbf046 100644
--- a/android/content/Context.java
+++ b/android/content/Context.java
@@ -64,6 +64,7 @@ import android.view.DisplayAdjustments;
import android.view.View;
import android.view.ViewDebug;
import android.view.WindowManager;
+import android.view.autofill.AutofillManager.AutofillClient;
import android.view.textclassifier.TextClassificationManager;
import java.io.File;
@@ -2991,6 +2992,7 @@ public abstract class Context {
//@hide: CONTEXTHUB_SERVICE,
SYSTEM_HEALTH_SERVICE,
//@hide: INCIDENT_SERVICE,
+ //@hide: STATS_COMPANION_SERVICE,
COMPANION_DEVICE_SERVICE
})
@Retention(RetentionPolicy.SOURCE)
@@ -3033,6 +3035,9 @@ public abstract class Context {
* <dt> {@link #CONNECTIVITY_SERVICE} ("connection")
* <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
* handling management of network connections.
+ * <dt> {@link #IPSEC_SERVICE} ("ipsec")
+ * <dd> A {@link android.net.IpSecManager IpSecManager} for managing IPSec on
+ * sockets and networks.
* <dt> {@link #WIFI_SERVICE} ("wifi")
* <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi
* connectivity. On releases before NYC, it should only be obtained from an application
@@ -3377,7 +3382,6 @@ public abstract class Context {
* {@link android.net.IpSecManager} for encrypting Sockets or Networks with
* IPSec.
*
- * @hide
* @see #getSystemService
*/
public static final String IPSEC_SERVICE = "ipsec";
@@ -3464,6 +3468,19 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.wifi.rtt.WifiRttManager} for ranging devices with wifi
+ *
+ * Note: this is a replacement for WIFI_RTT_SERVICE above. It will
+ * be renamed once final implementation in place.
+ *
+ * @see #getSystemService
+ * @see android.net.wifi.rtt.WifiRttManager
+ * @hide
+ */
+ public static final String WIFI_RTT2_SERVICE = "rttmanager2";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
* android.net.lowpan.LowpanManager} for handling management of
* LoWPAN access.
*
@@ -4020,6 +4037,12 @@ public abstract class Context {
public static final String INCIDENT_SERVICE = "incident";
/**
+ * Service to assist statsd in obtaining general stats.
+ * @hide
+ */
+ public static final String STATS_COMPANION_SERVICE = "statscompanion";
+
+ /**
* Use with {@link #getSystemService} to retrieve a {@link
* android.content.om.OverlayManager} for managing overlay packages.
*
@@ -4765,6 +4788,19 @@ public abstract class Context {
}
/**
+ * @hide
+ */
+ public AutofillClient getAutofillClient() {
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void setAutofillClient(AutofillClient client) {
+ }
+
+ /**
* Throws an exception if the Context is using system resources,
* which are non-runtime-overlay-themable and may show inconsistent UI.
* @hide
diff --git a/android/content/ContextWrapper.java b/android/content/ContextWrapper.java
index a9fd58bc..85acdc6b 100644
--- a/android/content/ContextWrapper.java
+++ b/android/content/ContextWrapper.java
@@ -37,6 +37,7 @@ import android.os.Looper;
import android.os.UserHandle;
import android.view.Display;
import android.view.DisplayAdjustments;
+import android.view.autofill.AutofillManager.AutofillClient;
import java.io.File;
import java.io.FileInputStream;
@@ -967,7 +968,24 @@ public class ContextWrapper extends Context {
/**
* @hide
*/
+ @Override
public int getNextAutofillId() {
return mBase.getNextAutofillId();
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public AutofillClient getAutofillClient() {
+ return mBase.getAutofillClient();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setAutofillClient(AutofillClient client) {
+ mBase.setAutofillClient(client);
+ }
}
diff --git a/android/content/Intent.java b/android/content/Intent.java
index 08acfb65..c9ad9519 100644
--- a/android/content/Intent.java
+++ b/android/content/Intent.java
@@ -9444,7 +9444,7 @@ public class Intent implements Parcelable, Cloneable {
for (int i=0; i<N; i++) {
char c = data.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
- || c == '.' || c == '-') {
+ || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+') {
continue;
}
if (c == ':' && i > 0) {
@@ -10071,6 +10071,27 @@ public class Intent implements Parcelable, Cloneable {
return false;
}
+ /**
+ * Convert the dock state to a human readable format.
+ * @hide
+ */
+ public static String dockStateToString(int dock) {
+ switch (dock) {
+ case EXTRA_DOCK_STATE_HE_DESK:
+ return "EXTRA_DOCK_STATE_HE_DESK";
+ case EXTRA_DOCK_STATE_LE_DESK:
+ return "EXTRA_DOCK_STATE_LE_DESK";
+ case EXTRA_DOCK_STATE_CAR:
+ return "EXTRA_DOCK_STATE_CAR";
+ case EXTRA_DOCK_STATE_DESK:
+ return "EXTRA_DOCK_STATE_DESK";
+ case EXTRA_DOCK_STATE_UNDOCKED:
+ return "EXTRA_DOCK_STATE_UNDOCKED";
+ default:
+ return Integer.toString(dock);
+ }
+ }
+
private static ClipData.Item makeClipItem(ArrayList<Uri> streams, ArrayList<CharSequence> texts,
ArrayList<String> htmlTexts, int which) {
Uri uri = streams != null ? streams.get(which) : null;
diff --git a/android/content/pm/ActivityInfo.java b/android/content/pm/ActivityInfo.java
index 48587b36..41667c4c 100644
--- a/android/content/pm/ActivityInfo.java
+++ b/android/content/pm/ActivityInfo.java
@@ -17,6 +17,7 @@
package android.content.pm;
import android.annotation.IntDef;
+import android.annotation.TestApi;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Configuration.NativeConfig;
@@ -34,8 +35,7 @@ import java.lang.annotation.RetentionPolicy;
* from the AndroidManifest.xml's &lt;activity&gt; and
* &lt;receiver&gt; tags.
*/
-public class ActivityInfo extends ComponentInfo
- implements Parcelable {
+public class ActivityInfo extends ComponentInfo implements Parcelable {
// NOTE: When adding new data members be sure to update the copy-constructor, Parcel
// constructor, and writeToParcel.
@@ -181,6 +181,7 @@ public class ActivityInfo extends ComponentInfo
* Activity explicitly requested to be resizeable.
* @hide
*/
+ @TestApi
public static final int RESIZE_MODE_RESIZEABLE = 2;
/**
* Activity is resizeable and supported picture-in-picture mode. This flag is now deprecated
@@ -1211,6 +1212,67 @@ public class ActivityInfo extends ComponentInfo
return isFloating || isTranslucent || isSwipeToDismiss;
}
+ /**
+ * Convert the screen orientation constant to a human readable format.
+ * @hide
+ */
+ public static String screenOrientationToString(int orientation) {
+ switch (orientation) {
+ case SCREEN_ORIENTATION_UNSET:
+ return "SCREEN_ORIENTATION_UNSET";
+ case SCREEN_ORIENTATION_UNSPECIFIED:
+ return "SCREEN_ORIENTATION_UNSPECIFIED";
+ case SCREEN_ORIENTATION_LANDSCAPE:
+ return "SCREEN_ORIENTATION_LANDSCAPE";
+ case SCREEN_ORIENTATION_PORTRAIT:
+ return "SCREEN_ORIENTATION_PORTRAIT";
+ case SCREEN_ORIENTATION_USER:
+ return "SCREEN_ORIENTATION_USER";
+ case SCREEN_ORIENTATION_BEHIND:
+ return "SCREEN_ORIENTATION_BEHIND";
+ case SCREEN_ORIENTATION_SENSOR:
+ return "SCREEN_ORIENTATION_SENSOR";
+ case SCREEN_ORIENTATION_NOSENSOR:
+ return "SCREEN_ORIENTATION_NOSENSOR";
+ case SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ return "SCREEN_ORIENTATION_SENSOR_LANDSCAPE";
+ case SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ return "SCREEN_ORIENTATION_SENSOR_PORTRAIT";
+ case SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ return "SCREEN_ORIENTATION_REVERSE_LANDSCAPE";
+ case SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ return "SCREEN_ORIENTATION_REVERSE_PORTRAIT";
+ case SCREEN_ORIENTATION_FULL_SENSOR:
+ return "SCREEN_ORIENTATION_FULL_SENSOR";
+ case SCREEN_ORIENTATION_USER_LANDSCAPE:
+ return "SCREEN_ORIENTATION_USER_LANDSCAPE";
+ case SCREEN_ORIENTATION_USER_PORTRAIT:
+ return "SCREEN_ORIENTATION_USER_PORTRAIT";
+ case SCREEN_ORIENTATION_FULL_USER:
+ return "SCREEN_ORIENTATION_FULL_USER";
+ case SCREEN_ORIENTATION_LOCKED:
+ return "SCREEN_ORIENTATION_LOCKED";
+ default:
+ return Integer.toString(orientation);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static String colorModeToString(@ColorMode int colorMode) {
+ switch (colorMode) {
+ case COLOR_MODE_DEFAULT:
+ return "COLOR_MODE_DEFAULT";
+ case COLOR_MODE_WIDE_COLOR_GAMUT:
+ return "COLOR_MODE_WIDE_COLOR_GAMUT";
+ case COLOR_MODE_HDR:
+ return "COLOR_MODE_HDR";
+ default:
+ return Integer.toString(colorMode);
+ }
+ }
+
public static final Parcelable.Creator<ActivityInfo> CREATOR
= new Parcelable.Creator<ActivityInfo>() {
public ActivityInfo createFromParcel(Parcel source) {
diff --git a/android/content/pm/PackageManager.java b/android/content/pm/PackageManager.java
index ef8f84bd..31ca1985 100644
--- a/android/content/pm/PackageManager.java
+++ b/android/content/pm/PackageManager.java
@@ -2330,6 +2330,16 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports Wi-Fi RTT (IEEE 802.11mc).
+ *
+ * @hide RTT_API
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports LoWPAN networking.
* @hide
*/
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
index 4c981cdb..be7f921e 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -16,6 +16,9 @@
package android.content.pm;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager.ApplicationInfoFlags;
@@ -25,6 +28,8 @@ import android.content.pm.PackageManager.ResolveInfoFlags;
import android.os.Bundle;
import android.util.SparseArray;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -33,6 +38,20 @@ import java.util.List;
* @hide Only for use within the system server.
*/
public abstract class PackageManagerInternal {
+ public static final int PACKAGE_SYSTEM = 0;
+ public static final int PACKAGE_SETUP_WIZARD = 1;
+ public static final int PACKAGE_INSTALLER = 2;
+ public static final int PACKAGE_VERIFIER = 3;
+ public static final int PACKAGE_BROWSER = 4;
+ @IntDef(value = {
+ PACKAGE_SYSTEM,
+ PACKAGE_SETUP_WIZARD,
+ PACKAGE_INSTALLER,
+ PACKAGE_VERIFIER,
+ PACKAGE_BROWSER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface KnownPackage {}
/**
* Provider for package names.
@@ -172,6 +191,13 @@ public abstract class PackageManagerInternal {
@ResolveInfoFlags int flags, int filterCallingUid, int userId);
/**
+ * Retrieve all services that can be performed for the given intent.
+ * @see PackageManager#queryIntentServices(Intent, int)
+ */
+ public abstract List<ResolveInfo> queryIntentServices(
+ Intent intent, int flags, int callingUid, int userId);
+
+ /**
* Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}.
*/
public abstract ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
@@ -343,14 +369,19 @@ public abstract class PackageManagerInternal {
* Resolves an activity intent, allowing instant apps to be resolved.
*/
public abstract ResolveInfo resolveIntent(Intent intent, String resolvedType,
- int flags, int userId);
+ int flags, int userId, boolean resolveForStart);
/**
* Resolves a service intent, allowing instant apps to be resolved.
*/
- public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
+ public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
int flags, int userId, int callingUid);
+ /**
+ * Resolves a content provider intent.
+ */
+ public abstract ProviderInfo resolveContentProvider(String name, int flags, int userId);
+
/**
* Track the creator of a new isolated uid.
* @param isolatedUid The newly created isolated uid.
@@ -383,4 +414,59 @@ public abstract class PackageManagerInternal {
* Updates a package last used time.
*/
public abstract void notifyPackageUse(String packageName, int reason);
+
+ /**
+ * Returns a package object for the given package name.
+ */
+ public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
+
+ /**
+ * Returns a package object for the disabled system package name.
+ */
+ public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName);
+
+ /**
+ * Returns whether or not the component is the resolver activity.
+ */
+ public abstract boolean isResolveActivityComponent(@NonNull ComponentInfo component);
+
+ /**
+ * Returns the package name for a known package.
+ */
+ public abstract @Nullable String getKnownPackageName(
+ @KnownPackage int knownPackage, int userId);
+
+ /**
+ * Returns whether the package is an instant app.
+ */
+ public abstract boolean isInstantApp(String packageName, int userId);
+
+ /**
+ * Returns whether the package is an instant app.
+ */
+ public abstract @Nullable String getInstantAppPackageName(int uid);
+
+ /**
+ * Returns whether or not access to the application should be filtered.
+ * <p>
+ * Access may be limited based upon whether the calling or target applications
+ * are instant applications.
+ *
+ * @see #canAccessInstantApps(int)
+ */
+ public abstract boolean filterAppAccess(
+ @Nullable PackageParser.Package pkg, int callingUid, int userId);
+
+ /*
+ * NOTE: The following methods are temporary until permissions are extracted from
+ * the package manager into a component specifically for handling permissions.
+ */
+ /** Returns the flags for the given permission. */
+ public abstract @Nullable int getPermissionFlagsTEMP(@NonNull String permName,
+ @NonNull String packageName, int userId);
+ /** Updates the flags for the given permission. */
+ public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
+ @NonNull String packageName, int flagMask, int flagValues, int userId);
+ /** temporary until mPermissionTrees is moved to PermissionManager */
+ public abstract Object enforcePermissionTreeTEMP(@NonNull String permName, int callingUid);
}
diff --git a/android/content/pm/PermissionInfo.java b/android/content/pm/PermissionInfo.java
index 17b4f871..b45c26ce 100644
--- a/android/content/pm/PermissionInfo.java
+++ b/android/content/pm/PermissionInfo.java
@@ -318,16 +318,19 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
return null;
}
+ @Override
public String toString() {
return "PermissionInfo{"
+ Integer.toHexString(System.identityHashCode(this))
+ " " + name + "}";
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
dest.writeInt(protectionLevel);
@@ -338,11 +341,25 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
}
+ /** @hide */
+ public int calculateFootprint() {
+ int size = name.length();
+ if (nonLocalizedLabel != null) {
+ size += nonLocalizedLabel.length();
+ }
+ if (nonLocalizedDescription != null) {
+ size += nonLocalizedDescription.length();
+ }
+ return size;
+ }
+
public static final Creator<PermissionInfo> CREATOR =
new Creator<PermissionInfo>() {
+ @Override
public PermissionInfo createFromParcel(Parcel source) {
return new PermissionInfo(source);
}
+ @Override
public PermissionInfo[] newArray(int size) {
return new PermissionInfo[size];
}
diff --git a/android/content/pm/ShortcutInfo.java b/android/content/pm/ShortcutInfo.java
index d3a3560c..6b9c7537 100644
--- a/android/content/pm/ShortcutInfo.java
+++ b/android/content/pm/ShortcutInfo.java
@@ -1763,21 +1763,43 @@ public final class ShortcutInfo implements Parcelable {
return 0;
}
+
/**
* Return a string representation, intended for logging. Some fields will be retracted.
*/
@Override
public String toString() {
- return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
+ return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false,
+ /*indent=*/ null);
}
/** @hide */
public String toInsecureString() {
- return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
+ return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true,
+ /*indent=*/ null);
+ }
+
+ /** @hide */
+ public String toDumpString(String indent) {
+ return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
+ }
+
+ private void addIndentOrComma(StringBuilder sb, String indent) {
+ if (indent != null) {
+ sb.append("\n ");
+ sb.append(indent);
+ } else {
+ sb.append(", ");
+ }
}
- private String toStringInner(boolean secure, boolean includeInternalData) {
+ private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
final StringBuilder sb = new StringBuilder();
+
+ if (indent != null) {
+ sb.append(indent);
+ }
+
sb.append("ShortcutInfo {");
sb.append("id=");
@@ -1787,47 +1809,51 @@ public final class ShortcutInfo implements Parcelable {
sb.append(Integer.toHexString(mFlags));
sb.append(" [");
if (!isEnabled()) {
- sb.append("X");
+ sb.append("Dis");
}
if (isImmutable()) {
sb.append("Im");
}
if (isManifestShortcut()) {
- sb.append("M");
+ sb.append("Man");
}
if (isDynamic()) {
- sb.append("D");
+ sb.append("Dyn");
}
if (isPinned()) {
- sb.append("P");
+ sb.append("Pin");
}
if (hasIconFile()) {
- sb.append("If");
+ sb.append("Ic-f");
}
if (isIconPendingSave()) {
- sb.append("^");
+ sb.append("Pens");
}
if (hasIconResource()) {
- sb.append("Ir");
+ sb.append("Ic-r");
}
if (hasKeyFieldsOnly()) {
- sb.append("K");
+ sb.append("Key");
}
if (hasStringResourcesResolved()) {
- sb.append("Sr");
+ sb.append("Str");
}
if (isReturnedByServer()) {
- sb.append("V");
+ sb.append("Rets");
}
sb.append("]");
- sb.append(", packageName=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("packageName=");
sb.append(mPackageName);
sb.append(", activity=");
sb.append(mActivity);
- sb.append(", shortLabel=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("shortLabel=");
sb.append(secure ? "***" : mTitle);
sb.append(", resId=");
sb.append(mTitleResId);
@@ -1835,7 +1861,9 @@ public final class ShortcutInfo implements Parcelable {
sb.append(mTitleResName);
sb.append("]");
- sb.append(", longLabel=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("longLabel=");
sb.append(secure ? "***" : mText);
sb.append(", resId=");
sb.append(mTextResId);
@@ -1843,7 +1871,9 @@ public final class ShortcutInfo implements Parcelable {
sb.append(mTextResName);
sb.append("]");
- sb.append(", disabledMessage=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("disabledMessage=");
sb.append(secure ? "***" : mDisabledMessage);
sb.append(", resId=");
sb.append(mDisabledMessageResId);
@@ -1851,19 +1881,27 @@ public final class ShortcutInfo implements Parcelable {
sb.append(mDisabledMessageResName);
sb.append("]");
- sb.append(", categories=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("categories=");
sb.append(mCategories);
- sb.append(", icon=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("icon=");
sb.append(mIcon);
- sb.append(", rank=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("rank=");
sb.append(mRank);
sb.append(", timestamp=");
sb.append(mLastChangedTimestamp);
- sb.append(", intents=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("intents=");
if (mIntents == null) {
sb.append("null");
} else {
@@ -1885,12 +1923,15 @@ public final class ShortcutInfo implements Parcelable {
}
}
- sb.append(", extras=");
+ addIndentOrComma(sb, indent);
+
+ sb.append("extras=");
sb.append(mExtras);
if (includeInternalData) {
+ addIndentOrComma(sb, indent);
- sb.append(", iconRes=");
+ sb.append("iconRes=");
sb.append(mIconResId);
sb.append("[");
sb.append(mIconResName);
diff --git a/android/content/res/Configuration.java b/android/content/res/Configuration.java
index 780e6f76..dfd3bbf0 100644
--- a/android/content/res/Configuration.java
+++ b/android/content/res/Configuration.java
@@ -16,9 +16,20 @@
package android.content.res;
+import static android.content.ConfigurationProto.DENSITY_DPI;
+import static android.content.ConfigurationProto.FONT_SCALE;
+import static android.content.ConfigurationProto.ORIENTATION;
+import static android.content.ConfigurationProto.SCREEN_HEIGHT_DP;
+import static android.content.ConfigurationProto.SCREEN_LAYOUT;
+import static android.content.ConfigurationProto.SCREEN_WIDTH_DP;
+import static android.content.ConfigurationProto.SMALLEST_SCREEN_WIDTH_DP;
+import static android.content.ConfigurationProto.UI_MODE;
+import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.WindowConfiguration;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -27,6 +38,7 @@ import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
import android.view.View;
import com.android.internal.util.XmlUtils;
@@ -295,11 +307,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public int screenLayout;
/**
- * @hide
* Configuration relating to the windowing state of the object associated with this
* Configuration. Contents of this field are not intended to affect resources, but need to be
* communicated and propagated at the same time as the rest of Configuration.
+ * @hide
*/
+ @TestApi
public final WindowConfiguration windowConfiguration = new WindowConfiguration();
/** @hide */
@@ -1054,6 +1067,55 @@ public final class Configuration implements Parcelable, Comparable<Configuration
}
/**
+ * Write to a protocol buffer output stream.
+ * Protocol buffer message definition at {@link android.content.ConfigurationProto}
+ *
+ * @param protoOutputStream Stream to write the Configuration object to.
+ * @param fieldId Field Id of the Configuration as defined in the parent message
+ * @hide
+ */
+ public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+ final long token = protoOutputStream.start(fieldId);
+ protoOutputStream.write(FONT_SCALE, fontScale);
+ protoOutputStream.write(SCREEN_LAYOUT, screenLayout);
+ protoOutputStream.write(ORIENTATION, orientation);
+ protoOutputStream.write(UI_MODE, uiMode);
+ protoOutputStream.write(SCREEN_WIDTH_DP, screenWidthDp);
+ protoOutputStream.write(SCREEN_HEIGHT_DP, screenHeightDp);
+ protoOutputStream.write(SMALLEST_SCREEN_WIDTH_DP, smallestScreenWidthDp);
+ protoOutputStream.write(DENSITY_DPI, densityDpi);
+ windowConfiguration.writeToProto(protoOutputStream, WINDOW_CONFIGURATION);
+ protoOutputStream.end(token);
+ }
+
+ /**
+ * Convert the UI mode to a human readable format.
+ * @hide
+ */
+ public static String uiModeToString(int uiMode) {
+ switch (uiMode) {
+ case UI_MODE_TYPE_UNDEFINED:
+ return "UI_MODE_TYPE_UNDEFINED";
+ case UI_MODE_TYPE_NORMAL:
+ return "UI_MODE_TYPE_NORMAL";
+ case UI_MODE_TYPE_DESK:
+ return "UI_MODE_TYPE_DESK";
+ case UI_MODE_TYPE_CAR:
+ return "UI_MODE_TYPE_CAR";
+ case UI_MODE_TYPE_TELEVISION:
+ return "UI_MODE_TYPE_TELEVISION";
+ case UI_MODE_TYPE_APPLIANCE:
+ return "UI_MODE_TYPE_APPLIANCE";
+ case UI_MODE_TYPE_WATCH:
+ return "UI_MODE_TYPE_WATCH";
+ case UI_MODE_TYPE_VR_HEADSET:
+ return "UI_MODE_TYPE_VR_HEADSET";
+ default:
+ return Integer.toString(uiMode);
+ }
+ }
+
+ /**
* Set this object to the system defaults.
*/
public void setToDefaults() {
diff --git a/android/content/res/Resources_Theme_Delegate.java b/android/content/res/Resources_Theme_Delegate.java
index f1e8fc21..8aa9216b 100644
--- a/android/content/res/Resources_Theme_Delegate.java
+++ b/android/content/res/Resources_Theme_Delegate.java
@@ -56,7 +56,8 @@ public class Resources_Theme_Delegate {
Resources thisResources, Theme thisTheme,
int[] attrs) {
boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+ 0, attrs);
ta.setTheme(thisTheme);
restoreResources(changed);
return ta;
@@ -68,8 +69,8 @@ public class Resources_Theme_Delegate {
int resid, int[] attrs)
throws NotFoundException {
boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid,
- attrs);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+ resid, attrs);
ta.setTheme(thisTheme);
restoreResources(changed);
return ta;
@@ -80,7 +81,7 @@ public class Resources_Theme_Delegate {
Resources thisResources, Theme thisTheme,
AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set,
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(set,
attrs, defStyleAttr, defStyleRes);
ta.setTheme(thisTheme);
restoreResources(changed);
diff --git a/android/database/sqlite/SQLiteConnection.java b/android/database/sqlite/SQLiteConnection.java
index f894f053..c28583ea 100644
--- a/android/database/sqlite/SQLiteConnection.java
+++ b/android/database/sqlite/SQLiteConnection.java
@@ -104,7 +104,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private PreparedStatement mPreparedStatementPool;
// The recent operations log.
- private final OperationLog mRecentOperations = new OperationLog();
+ private final OperationLog mRecentOperations;
// The native SQLiteConnection pointer. (FOR INTERNAL USE ONLY)
private long mConnectionPtr;
@@ -162,6 +162,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
mPool = pool;
+ mRecentOperations = new OperationLog(mPool);
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
mConnectionId = connectionId;
mIsPrimaryConnection = primaryConnection;
@@ -1298,6 +1299,11 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
private int mIndex;
private int mGeneration;
+ private final SQLiteConnectionPool mPool;
+
+ OperationLog(SQLiteConnectionPool pool) {
+ mPool = pool;
+ }
public int beginOperation(String kind, String sql, Object[] bindArgs) {
synchronized (mOperations) {
@@ -1381,8 +1387,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
}
operation.mEndTime = SystemClock.uptimeMillis();
operation.mFinished = true;
+ final long execTime = operation.mEndTime - operation.mStartTime;
+ mPool.onStatementExecuted(execTime);
return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
- operation.mEndTime - operation.mStartTime);
+ execTime);
}
return false;
}
@@ -1426,11 +1434,16 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
int index = mIndex;
Operation operation = mOperations[index];
if (operation != null) {
+ // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
+ // and is relatively expensive to create during preloading. This method is only
+ // used when dumping a connection, which is a rare (mainly error) case.
+ SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
int n = 0;
do {
StringBuilder msg = new StringBuilder();
msg.append(" ").append(n).append(": [");
- msg.append(operation.getFormattedStartTime());
+ String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
+ msg.append(formattedStartTime);
msg.append("] ");
operation.describe(msg, verbose);
printer.println(msg.toString());
@@ -1518,12 +1531,5 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
return methodName;
}
- private String getFormattedStartTime() {
- // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created, and is
- // relatively expensive to create during preloading. This method is only used
- // when dumping a connection, which is a rare (mainly error) case. So:
- // DO NOT CACHE.
- return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(mStartWallTime));
- }
}
}
diff --git a/android/database/sqlite/SQLiteConnectionPool.java b/android/database/sqlite/SQLiteConnectionPool.java
index b66bf18f..8b0fef4f 100644
--- a/android/database/sqlite/SQLiteConnectionPool.java
+++ b/android/database/sqlite/SQLiteConnectionPool.java
@@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
/**
@@ -102,6 +103,8 @@ public final class SQLiteConnectionPool implements Closeable {
@GuardedBy("mLock")
private IdleConnectionHandler mIdleConnectionHandler;
+ private final AtomicLong mTotalExecutionTimeCounter = new AtomicLong(0);
+
// Describes what should happen to an acquired connection when it is returned to the pool.
enum AcquiredConnectionStatus {
// The connection should be returned to the pool as usual.
@@ -523,6 +526,10 @@ public final class SQLiteConnectionPool implements Closeable {
mConnectionLeaked.set(true);
}
+ void onStatementExecuted(long executionTimeMs) {
+ mTotalExecutionTimeCounter.addAndGet(executionTimeMs);
+ }
+
// Can't throw.
private void closeAvailableConnectionsAndLogExceptionsLocked() {
closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
@@ -1076,6 +1083,7 @@ public final class SQLiteConnectionPool implements Closeable {
printer.println("Connection pool for " + mConfiguration.path + ":");
printer.println(" Open: " + mIsOpen);
printer.println(" Max connections: " + mMaxConnectionPoolSize);
+ printer.println(" Total execution time: " + mTotalExecutionTimeCounter);
if (mConfiguration.isLookasideConfigSet()) {
printer.println(" Lookaside config: sz=" + mConfiguration.lookasideSlotSize
+ " cnt=" + mConfiguration.lookasideSlotCount);
diff --git a/android/ext/services/notification/Assistant.java b/android/ext/services/notification/Assistant.java
new file mode 100644
index 00000000..f535368b
--- /dev/null
+++ b/android/ext/services/notification/Assistant.java
@@ -0,0 +1,165 @@
+/**
+ * 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.ext.services.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.service.notification.NotificationListenerService.Ranking
+ .USER_SENTIMENT_NEGATIVE;
+
+import android.app.INotificationManager;
+import android.content.Context;
+import android.ext.services.R;
+import android.os.Bundle;
+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.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Notification assistant that provides guidance on notification channel blocking
+ */
+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<>();
+ static {
+ DISMISS_WITH_PREJUDICE.add(REASON_CANCEL);
+ DISMISS_WITH_PREJUDICE.add(REASON_LISTENER_CANCEL);
+ }
+
+ // key : impressions tracker
+ // TODO: persist across reboots
+ ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>();
+ // SBN key : channel id
+ ArrayMap<String, String> mLiveNotifications = new ArrayMap<>();
+
+ private Ranking mFakeRanking = null;
+
+ @Override
+ public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
+ if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
+ return null;
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
+ try {
+ Ranking ranking = getRanking(sbn.getKey(), rankingMap);
+ if (ranking != null && ranking.getChannel() != null) {
+ String key = getKey(
+ sbn.getPackageName(), sbn.getUserId(), ranking.getChannel().getId());
+ ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
+ new ChannelImpressions());
+ if (ranking.getImportance() > IMPORTANCE_MIN && ci.shouldTriggerBlock()) {
+ adjustNotification(createNegativeAdjustment(
+ sbn.getPackageName(), sbn.getKey(), sbn.getUserId()));
+ }
+ mkeyToImpressions.put(key, ci);
+ mLiveNotifications.put(sbn.getKey(), ranking.getChannel().getId());
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "Error occurred processing post", e);
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+ NotificationStats stats, int reason) {
+ try {
+ 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();
+ }
+ 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();
+ }
+ mkeyToImpressions.put(key, ci);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Error occurred processing removal", e);
+ }
+ }
+
+ @Override
+ public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
+ String snoozeCriterionId) {
+ }
+
+ @Override
+ public void onListenerConnected() {
+ if (DEBUG) Log.i(TAG, "CONNECTED");
+ try {
+ for (StatusBarNotification sbn : getActiveNotifications()) {
+ onNotificationPosted(sbn);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "Error occurred on connection", e);
+ }
+ }
+
+ private String getKey(String pkg, int userId, String channelId) {
+ return pkg + "|" + userId + "|" + channelId;
+ }
+
+ private Ranking getRanking(String key, RankingMap rankingMap) {
+ if (mFakeRanking != null) {
+ return mFakeRanking;
+ }
+ Ranking ranking = new Ranking();
+ rankingMap.getRanking(key, ranking);
+ return ranking;
+ }
+
+ private Adjustment createNegativeAdjustment(String packageName, String key, int user) {
+ if (DEBUG) Log.d(TAG, "User probably doesn't want " + key);
+ Bundle signals = new Bundle();
+ signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
+ return new Adjustment(packageName, key, signals,
+ getContext().getString(R.string.prompt_block_reason), user);
+ }
+
+ // for testing
+ protected void setFakeRanking(Ranking ranking) {
+ mFakeRanking = ranking;
+ }
+
+ protected void setNoMan(INotificationManager noMan) {
+ mNoMan = noMan;
+ }
+
+ protected void setContext(Context context) {
+ mSystemContext = context;
+ }
+} \ No newline at end of file
diff --git a/android/ext/services/notification/ChannelImpressions.java b/android/ext/services/notification/ChannelImpressions.java
new file mode 100644
index 00000000..30567ccd
--- /dev/null
+++ b/android/ext/services/notification/ChannelImpressions.java
@@ -0,0 +1,137 @@
+/**
+ * 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.ext.services.notification;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+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;
+
+ private int mDismissals = 0;
+ private int mViews = 0;
+ private int mStreak = 0;
+
+ public ChannelImpressions() {
+ }
+
+ public ChannelImpressions(int dismissals, int views) {
+ mDismissals = dismissals;
+ mViews = views;
+ }
+
+ protected ChannelImpressions(Parcel in) {
+ mDismissals = in.readInt();
+ mViews = in.readInt();
+ mStreak = in.readInt();
+ }
+
+ public int getStreak() {
+ return mStreak;
+ }
+
+ public int getDismissals() {
+ return mDismissals;
+ }
+
+ public int getViews() {
+ return mViews;
+ }
+
+ public void incrementDismissals() {
+ mDismissals++;
+ mStreak++;
+ }
+
+ public void incrementViews() {
+ mViews++;
+ }
+
+ public void resetStreak() {
+ mStreak = 0;
+ }
+
+ public boolean shouldTriggerBlock() {
+ if (getViews() == 0) {
+ return false;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "should trigger? " + getDismissals() + " " + getViews() + " " + getStreak());
+ }
+ return ((double) getDismissals() / getViews()) > DISMISS_TO_VIEW_RATIO_LIMIT
+ && getStreak() > STREAK_LIMIT;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mDismissals);
+ dest.writeInt(mViews);
+ dest.writeInt(mStreak);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ChannelImpressions> CREATOR = new Creator<ChannelImpressions>() {
+ @Override
+ public ChannelImpressions createFromParcel(Parcel in) {
+ return new ChannelImpressions(in);
+ }
+
+ @Override
+ public ChannelImpressions[] newArray(int size) {
+ return new ChannelImpressions[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ChannelImpressions that = (ChannelImpressions) o;
+
+ if (mDismissals != that.mDismissals) return false;
+ if (mViews != that.mViews) return false;
+ return mStreak == that.mStreak;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mDismissals;
+ result = 31 * result + mViews;
+ result = 31 * result + mStreak;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ChannelImpressions{");
+ sb.append("mDismissals=").append(mDismissals);
+ sb.append(", mViews=").append(mViews);
+ sb.append(", mStreak=").append(mStreak);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/android/graphics/BidiRenderer.java b/android/graphics/BidiRenderer.java
index 9664a582..7b7dfa6c 100644
--- a/android/graphics/BidiRenderer.java
+++ b/android/graphics/BidiRenderer.java
@@ -200,8 +200,7 @@ public class BidiRenderer {
private static void logFontWarning() {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
- "Some fonts could not be loaded. The rendering may not be perfect. " +
- "Try running the IDE with JRE 7.", null, null);
+ "Some fonts could not be loaded. The rendering may not be perfect.", null, null);
}
/**
@@ -288,15 +287,17 @@ public class BidiRenderer {
@NonNull
private static Font getScriptFont(char[] text, int start, int limit, List<FontInfo> fonts) {
for (FontInfo fontInfo : fonts) {
- if (fontInfo.mFont == null) {
- logFontWarning();
- continue;
- }
if (fontInfo.mFont.canDisplayUpTo(text, start, limit) == -1) {
return fontInfo.mFont;
}
}
+ if (fonts.isEmpty()) {
+ logFontWarning();
+ // Fallback font in case no font can be loaded
+ return Font.getFont(Font.SERIF);
+ }
+
return fonts.get(0).mFont;
}
diff --git a/android/graphics/ImageFormat.java b/android/graphics/ImageFormat.java
index e3527e35..43fd2708 100644
--- a/android/graphics/ImageFormat.java
+++ b/android/graphics/ImageFormat.java
@@ -658,6 +658,14 @@ public class ImageFormat {
* float confidence = floatDepthBuffer.get();
* </pre>
*
+ * For camera devices that support the
+ * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT}
+ * capability, DEPTH_POINT_CLOUD coordinates have units of meters, and the coordinate system is
+ * defined by the camera's pose transforms:
+ * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_TRANSLATION} and
+ * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_ROTATION}. That means the origin is
+ * the optical center of the camera device, and the positive Z axis points along the camera's optical axis,
+ * toward the scene.
*/
public static final int DEPTH_POINT_CLOUD = 0x101;
diff --git a/android/graphics/NinePatch_Delegate.java b/android/graphics/NinePatch_Delegate.java
index 43e5b0f9..ce2c18be 100644
--- a/android/graphics/NinePatch_Delegate.java
+++ b/android/graphics/NinePatch_Delegate.java
@@ -160,8 +160,12 @@ public final class NinePatch_Delegate {
}
@LayoutlibDelegate
- /*package*/ static void nativeFinalize(long chunk) {
- sManager.removeJavaReferenceFor(chunk);
+ /*package*/ static void nativeFinalize(long nativeNinePatch) {
+ NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
+ if (delegate != null && delegate.chunk != null) {
+ sChunkCache.remove(delegate.chunk);
+ }
+ sManager.removeJavaReferenceFor(nativeNinePatch);
}
diff --git a/android/graphics/PixelFormat.java b/android/graphics/PixelFormat.java
index f93886dc..96d6eeec 100644
--- a/android/graphics/PixelFormat.java
+++ b/android/graphics/PixelFormat.java
@@ -185,4 +185,52 @@ public class PixelFormat {
return false;
}
+
+ /**
+ * @hide
+ */
+ public static String formatToString(@Format int format) {
+ switch (format) {
+ case UNKNOWN:
+ return "UNKNOWN";
+ case TRANSLUCENT:
+ return "TRANSLUCENT";
+ case TRANSPARENT:
+ return "TRANSPARENT";
+ case RGBA_8888:
+ return "RGBA_8888";
+ case RGBX_8888:
+ return "RGBX_8888";
+ case RGB_888:
+ return "RGB_888";
+ case RGB_565:
+ return "RGB_565";
+ case RGBA_5551:
+ return "RGBA_5551";
+ case RGBA_4444:
+ return "RGBA_4444";
+ case A_8:
+ return "A_8";
+ case L_8:
+ return "L_8";
+ case LA_88:
+ return "LA_88";
+ case RGB_332:
+ return "RGB_332";
+ case YCbCr_422_SP:
+ return "YCbCr_422_SP";
+ case YCbCr_420_SP:
+ return "YCbCr_420_SP";
+ case YCbCr_422_I:
+ return "YCbCr_422_I";
+ case RGBA_F16:
+ return "RGBA_F16";
+ case RGBA_1010102:
+ return "RGBA_1010102";
+ case JPEG:
+ return "JPEG";
+ default:
+ return Integer.toString(format);
+ }
+ }
}
diff --git a/android/graphics/RadialGradient_Delegate.java b/android/graphics/RadialGradient_Delegate.java
index 1defc901..25521d2c 100644
--- a/android/graphics/RadialGradient_Delegate.java
+++ b/android/graphics/RadialGradient_Delegate.java
@@ -173,6 +173,7 @@ public class RadialGradient_Delegate extends Gradient_Delegate {
int index = 0;
float[] pt1 = new float[2];
float[] pt2 = new float[2];
+
for (int iy = 0 ; iy < h ; iy++) {
for (int ix = 0 ; ix < w ; ix++) {
// handle the canvas transform
@@ -181,12 +182,12 @@ public class RadialGradient_Delegate extends Gradient_Delegate {
mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
// handle the local matrix
- pt1[0] = pt2[0] - mX;
- pt1[1] = pt2[1] - mY;
+ pt1[0] = pt2[0];
+ pt1[1] = pt2[1];
mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
- float _x = pt2[0];
- float _y = pt2[1];
+ float _x = pt2[0] - mX;
+ float _y = pt2[1] - mY;
float distance = (float) Math.hypot(_x, _y);
data[index++] = getGradientColor(distance / mRadius);
diff --git a/android/graphics/Shader.java b/android/graphics/Shader.java
index 0209cea4..40288f5e 100644
--- a/android/graphics/Shader.java
+++ b/android/graphics/Shader.java
@@ -159,8 +159,10 @@ public class Shader {
if (mNativeInstance == 0) {
mNativeInstance = createNativeInstance(mLocalMatrix == null
? 0 : mLocalMatrix.native_instance);
- mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
- this, mNativeInstance);
+ if (mNativeInstance != 0) {
+ mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+ this, mNativeInstance);
+ }
}
return mNativeInstance;
}
diff --git a/android/graphics/drawable/RippleBackground.java b/android/graphics/drawable/RippleBackground.java
index 6bd2646f..3bf4f902 100644
--- a/android/graphics/drawable/RippleBackground.java
+++ b/android/graphics/drawable/RippleBackground.java
@@ -104,7 +104,7 @@ class RippleBackground extends RippleComponent {
final AnimatorSet set = new AnimatorSet();
// Linear exit after enter is completed.
- final ObjectAnimator exit = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 0);
+ final ObjectAnimator exit = ObjectAnimator.ofFloat(this, OPACITY, 0);
exit.setInterpolator(LINEAR_INTERPOLATOR);
exit.setDuration(OPACITY_EXIT_DURATION);
exit.setAutoCancel(true);
@@ -115,7 +115,7 @@ class RippleBackground extends RippleComponent {
final int fastEnterDuration = mIsBounded ?
(int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
if (fastEnterDuration > 0) {
- final ObjectAnimator enter = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 1);
+ final ObjectAnimator enter = ObjectAnimator.ofFloat(this, OPACITY, 1);
enter.setInterpolator(LINEAR_INTERPOLATOR);
enter.setDuration(fastEnterDuration);
enter.setAutoCancel(true);
diff --git a/android/graphics/drawable/RippleDrawable.java b/android/graphics/drawable/RippleDrawable.java
index 8f314c9c..1727eca5 100644
--- a/android/graphics/drawable/RippleDrawable.java
+++ b/android/graphics/drawable/RippleDrawable.java
@@ -266,9 +266,9 @@ public class RippleDrawable extends LayerDrawable {
}
}
- setRippleActive(enabled && pressed);
- setBackgroundActive(hovered || focused || (enabled && pressed), focused || hovered);
+ setRippleActive(focused || (enabled && pressed));
+ setBackgroundActive(hovered, hovered);
return changed;
}
@@ -693,7 +693,9 @@ public class RippleDrawable extends LayerDrawable {
// have a mask or content and the ripple bounds if we're projecting.
final Rect bounds = getDirtyBounds();
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
- canvas.clipRect(bounds);
+ if (isBounded()) {
+ canvas.clipRect(bounds);
+ }
drawContent(canvas);
drawBackgroundAndRipples(canvas);
diff --git a/android/graphics/drawable/RippleForeground.java b/android/graphics/drawable/RippleForeground.java
index 829733e9..a675eaf8 100644
--- a/android/graphics/drawable/RippleForeground.java
+++ b/android/graphics/drawable/RippleForeground.java
@@ -41,7 +41,6 @@ class RippleForeground extends RippleComponent {
// Pixel-based accelerations and velocities.
private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
- private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
// Bounded ripple animation properties.
@@ -80,17 +79,18 @@ class RippleForeground extends RippleComponent {
private float mTweenX = 0;
private float mTweenY = 0;
- /** Whether this ripple is bounded. */
- private boolean mIsBounded;
-
/** Whether this ripple has finished its exit animation. */
private boolean mHasFinishedExit;
+ /**
+ * If we have a bound, don't start from 0. Start from 60% of the max out of width and height.
+ */
+ private float mStartRadius = 0;
+
public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
boolean isBounded, boolean forceSoftware) {
super(owner, bounds, forceSoftware);
- mIsBounded = isBounded;
mStartingX = startingX;
mStartingY = startingY;
@@ -100,6 +100,8 @@ class RippleForeground extends RippleComponent {
} else {
mBoundedRadius = 0;
}
+ // Take 60% of the maximum of the width and height, then divided half to get the radius.
+ mStartRadius = Math.max(bounds.width(), bounds.height()) * 0.3f;
}
@Override
@@ -162,24 +164,18 @@ class RippleForeground extends RippleComponent {
@Override
protected Animator createSoftwareEnter(boolean fast) {
- // Bounded ripples don't have enter animations.
- if (mIsBounded) {
- return null;
- }
-
- final int duration = (int)
- (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensityScale) + 0.5);
+ final int duration = getRadiusDuration();
final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
tweenRadius.setAutoCancel(true);
tweenRadius.setDuration(duration);
- tweenRadius.setInterpolator(LINEAR_INTERPOLATOR);
+ tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
tweenOrigin.setAutoCancel(true);
tweenOrigin.setDuration(duration);
- tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR);
+ tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
@@ -201,45 +197,29 @@ class RippleForeground extends RippleComponent {
return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
}
- private int getRadiusExitDuration() {
+ private int getRadiusDuration() {
final float remainingRadius = mTargetRadius - getCurrentRadius();
- return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
- + WAVE_TOUCH_DOWN_ACCELERATION) * mDensityScale) + 0.5);
+ return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION *
+ mDensityScale) + 0.5);
}
private float getCurrentRadius() {
- return MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+ return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
}
private int getOpacityExitDuration() {
return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
}
- /**
- * Compute target values that are dependent on bounding.
- */
- private void computeBoundedTargetValues() {
- mTargetX = (mClampedStartingX - mBounds.exactCenterX()) * .7f;
- mTargetY = (mClampedStartingY - mBounds.exactCenterY()) * .7f;
- mTargetRadius = mBoundedRadius;
- }
-
@Override
protected Animator createSoftwareExit() {
final int radiusDuration;
final int originDuration;
final int opacityDuration;
- if (mIsBounded) {
- computeBoundedTargetValues();
- radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
- originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
- opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
- } else {
- radiusDuration = getRadiusExitDuration();
- originDuration = radiusDuration;
- opacityDuration = getOpacityExitDuration();
- }
+ radiusDuration = getRadiusDuration();
+ originDuration = radiusDuration;
+ opacityDuration = getOpacityExitDuration();
final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
tweenRadius.setAutoCancel(true);
@@ -268,17 +248,10 @@ class RippleForeground extends RippleComponent {
final int radiusDuration;
final int originDuration;
final int opacityDuration;
- if (mIsBounded) {
- computeBoundedTargetValues();
- radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
- originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
- opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
- } else {
- radiusDuration = getRadiusExitDuration();
- originDuration = radiusDuration;
- opacityDuration = getOpacityExitDuration();
- }
+ radiusDuration = getRadiusDuration();
+ originDuration = radiusDuration;
+ opacityDuration = getOpacityExitDuration();
final float startX = getCurrentX();
final float startY = getCurrentY();
diff --git a/android/graphics/drawable/VectorDrawable_Delegate.java b/android/graphics/drawable/VectorDrawable_Delegate.java
index 5fa71021..00630464 100644
--- a/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -35,6 +35,7 @@ import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Path_Delegate;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.Shader_Delegate;
import android.util.ArrayMap;
@@ -144,6 +145,9 @@ public class VectorDrawable_Delegate {
VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+ Canvas_Delegate.nClipRect(canvasWrapperPtr,
+ bounds.left, bounds.top, bounds.right, bounds.bottom,
+ Region.Op.INTERSECT.nativeInt);
Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top);
if (needsMirroring) {
diff --git a/android/hardware/Camera.java b/android/hardware/Camera.java
index aa35a661..931b5c91 100644
--- a/android/hardware/Camera.java
+++ b/android/hardware/Camera.java
@@ -16,10 +16,11 @@
package android.hardware;
-import android.app.ActivityThread;
+import static android.system.OsConstants.*;
+
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.app.job.JobInfo;
+import android.app.ActivityThread;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Point;
@@ -34,11 +35,11 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.renderscript.Allocation;
import android.renderscript.Element;
-import android.renderscript.RenderScript;
import android.renderscript.RSIllegalArgumentException;
+import android.renderscript.RenderScript;
import android.renderscript.Type;
-import android.util.Log;
import android.text.TextUtils;
+import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
@@ -48,8 +49,6 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
-import static android.system.OsConstants.*;
-
/**
* The Camera class is used to set image capture settings, start/stop preview,
* snap pictures, and retrieve frames for encoding for video. This class is a
@@ -243,12 +242,19 @@ public class Camera {
/**
* Returns the number of physical cameras available on this device.
+ *
+ * @return total number of accessible camera devices, or 0 if there are no
+ * cameras or an error was encountered enumerating them.
*/
public native static int getNumberOfCameras();
/**
* Returns the information about a particular camera.
* If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
+ *
+ * @throws RuntimeException if an invalid ID is provided, or if there is an
+ * error retrieving the information (generally due to a hardware or other
+ * low-level failure).
*/
public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
_getCameraInfo(cameraId, cameraInfo);
@@ -362,7 +368,10 @@ public class Camera {
/**
* Creates a new Camera object to access the first back-facing camera on the
* device. If the device does not have a back-facing camera, this returns
- * null.
+ * null. Otherwise acts like the {@link #open(int)} call.
+ *
+ * @return a new Camera object for the first back-facing camera, or null if there is no
+ * backfacing camera
* @see #open(int)
*/
public static Camera open() {
@@ -609,6 +618,8 @@ public class Camera {
*
* @throws IOException if a connection cannot be re-established (for
* example, if the camera is still in use by another process).
+ * @throws RuntimeException if release() has been called on this Camera
+ * instance.
*/
public native final void reconnect() throws IOException;
@@ -637,6 +648,8 @@ public class Camera {
* or null to remove the preview surface
* @throws IOException if the method fails (for example, if the surface
* is unavailable or unsuitable).
+ * @throws RuntimeException if release() has been called on this Camera
+ * instance.
*/
public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
if (holder != null) {
@@ -684,6 +697,8 @@ public class Camera {
* texture
* @throws IOException if the method fails (for example, if the surface
* texture is unavailable or unsuitable).
+ * @throws RuntimeException if release() has been called on this Camera
+ * instance.
*/
public native final void setPreviewTexture(SurfaceTexture surfaceTexture) throws IOException;
@@ -733,12 +748,20 @@ public class Camera {
* {@link #setPreviewCallbackWithBuffer(Camera.PreviewCallback)} were
* called, {@link Camera.PreviewCallback#onPreviewFrame(byte[], Camera)}
* will be called when preview data becomes available.
+ *
+ * @throws RuntimeException if starting preview fails; usually this would be
+ * because of a hardware or other low-level error, or because release()
+ * has been called on this Camera instance.
*/
public native final void startPreview();
/**
* Stops capturing and drawing preview frames to the surface, and
* resets the camera for a future call to {@link #startPreview()}.
+ *
+ * @throws RuntimeException if stopping preview fails; usually this would be
+ * because of a hardware or other low-level error, or because release()
+ * has been called on this Camera instance.
*/
public final void stopPreview() {
_stopPreview();
@@ -777,6 +800,8 @@ public class Camera {
*
* @param cb a callback object that receives a copy of each preview frame,
* or null to stop receiving callbacks.
+ * @throws RuntimeException if release() has been called on this Camera
+ * instance.
* @see android.media.MediaActionSound
*/
public final void setPreviewCallback(PreviewCallback cb) {
@@ -803,6 +828,8 @@ public class Camera {
*
* @param cb a callback object that receives a copy of the next preview frame,
* or null to stop receiving callbacks.
+ * @throws RuntimeException if release() has been called on this Camera
+ * instance.
* @see android.media.MediaActionSound
*/
public final void setOneShotPreviewCallback(PreviewCallback cb) {
@@ -840,6 +867,8 @@ public class Camera {
*
* @param cb a callback object that receives a copy of the preview frame,
* or null to stop receiving callbacks and clear the buffer queue.
+ * @throws RuntimeException if release() has been called on this Camera
+ * instance.
* @see #addCallbackBuffer(byte[])
* @see android.media.MediaActionSound
*/
@@ -1259,6 +1288,9 @@ public class Camera {
* success sound to the user.</p>
*
* @param cb the callback to run
+ * @throws RuntimeException if starting autofocus fails; usually this would
+ * be because of a hardware or other low-level error, or because
+ * release() has been called on this Camera instance.
* @see #cancelAutoFocus()
* @see android.hardware.Camera.Parameters#setAutoExposureLock(boolean)
* @see android.hardware.Camera.Parameters#setAutoWhiteBalanceLock(boolean)
@@ -1279,6 +1311,9 @@ public class Camera {
* this function will return the focus position to the default.
* If the camera does not support auto-focus, this is a no-op.
*
+ * @throws RuntimeException if canceling autofocus fails; usually this would
+ * be because of a hardware or other low-level error, or because
+ * release() has been called on this Camera instance.
* @see #autoFocus(Camera.AutoFocusCallback)
*/
public final void cancelAutoFocus()
@@ -1333,6 +1368,9 @@ public class Camera {
* Sets camera auto-focus move callback.
*
* @param cb the callback to run
+ * @throws RuntimeException if enabling the focus move callback fails;
+ * usually this would be because of a hardware or other low-level error,
+ * or because release() has been called on this Camera instance.
*/
public void setAutoFocusMoveCallback(AutoFocusMoveCallback cb) {
mAutoFocusMoveCallback = cb;
@@ -1384,7 +1422,7 @@ public class Camera {
};
/**
- * Equivalent to takePicture(shutter, raw, null, jpeg).
+ * Equivalent to <pre>takePicture(Shutter, raw, null, jpeg)</pre>.
*
* @see #takePicture(ShutterCallback, PictureCallback, PictureCallback, PictureCallback)
*/
@@ -1422,6 +1460,9 @@ public class Camera {
* @param raw the callback for raw (uncompressed) image data, or null
* @param postview callback with postview image data, may be null
* @param jpeg the callback for JPEG image data, or null
+ * @throws RuntimeException if starting picture capture fails; usually this
+ * would be because of a hardware or other low-level error, or because
+ * release() has been called on this Camera instance.
*/
public final void takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback postview, PictureCallback jpeg) {
@@ -1534,6 +1575,9 @@ public class Camera {
*
* @param degrees the angle that the picture will be rotated clockwise.
* Valid values are 0, 90, 180, and 270.
+ * @throws RuntimeException if setting orientation fails; usually this would
+ * be because of a hardware or other low-level error, or because
+ * release() has been called on this Camera instance.
* @see #setPreviewDisplay(SurfaceHolder)
*/
public native final void setDisplayOrientation(int degrees);
@@ -1559,6 +1603,9 @@ public class Camera {
* changed. {@code false} if the shutter sound state could not be
* changed. {@code true} is also returned if shutter sound playback
* is already set to the requested state.
+ * @throws RuntimeException if the call fails; usually this would be because
+ * of a hardware or other low-level error, or because release() has been
+ * called on this Camera instance.
* @see #takePicture
* @see CameraInfo#canDisableShutterSound
* @see ShutterCallback
@@ -1903,6 +1950,9 @@ public class Camera {
* If modifications are made to the returned Parameters, they must be passed
* to {@link #setParameters(Camera.Parameters)} to take effect.
*
+ * @throws RuntimeException if reading parameters fails; usually this would
+ * be because of a hardware or other low-level error, or because
+ * release() has been called on this Camera instance.
* @see #setParameters(Camera.Parameters)
*/
public Parameters getParameters() {
diff --git a/android/hardware/LegacySensorManager.java b/android/hardware/LegacySensorManager.java
index f5cf3f74..098121df 100644
--- a/android/hardware/LegacySensorManager.java
+++ b/android/hardware/LegacySensorManager.java
@@ -204,7 +204,7 @@ final class LegacySensorManager {
}
private static final class LegacyListener implements SensorEventListener {
- private float mValues[] = new float[6];
+ private float[] mValues = new float[6];
private SensorListener mTarget;
private int mSensors;
private final LmsFilter mYawfilter = new LmsFilter();
@@ -256,7 +256,7 @@ final class LegacySensorManager {
}
public void onSensorChanged(SensorEvent event) {
- final float v[] = mValues;
+ final float[] v = mValues;
v[0] = event.values[0];
v[1] = event.values[1];
v[2] = event.values[2];
@@ -264,10 +264,10 @@ final class LegacySensorManager {
int legacyType = getLegacySensorType(type);
mapSensorDataToWindow(legacyType, v, LegacySensorManager.getRotation());
if (type == Sensor.TYPE_ORIENTATION) {
- if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW)!=0) {
+ if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW) != 0) {
mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION_RAW, v);
}
- if ((mSensors & SensorManager.SENSOR_ORIENTATION)!=0) {
+ if ((mSensors & SensorManager.SENSOR_ORIENTATION) != 0) {
v[0] = mYawfilter.filter(event.timestamp, v[0]);
mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION, v);
}
@@ -317,7 +317,7 @@ final class LegacySensorManager {
switch (sensor) {
case SensorManager.SENSOR_ACCELEROMETER:
case SensorManager.SENSOR_MAGNETIC_FIELD:
- values[0] =-y;
+ values[0] = -y;
values[1] = x;
values[2] = z;
break;
@@ -337,15 +337,15 @@ final class LegacySensorManager {
switch (sensor) {
case SensorManager.SENSOR_ACCELEROMETER:
case SensorManager.SENSOR_MAGNETIC_FIELD:
- values[0] =-x;
- values[1] =-y;
+ values[0] = -x;
+ values[1] = -y;
values[2] = z;
break;
case SensorManager.SENSOR_ORIENTATION:
case SensorManager.SENSOR_ORIENTATION_RAW:
values[0] = (x >= 180) ? (x - 180) : (x + 180);
- values[1] =-y;
- values[2] =-z;
+ values[1] = -y;
+ values[2] = -z;
break;
}
}
@@ -369,10 +369,11 @@ final class LegacySensorManager {
private static final class LmsFilter {
private static final int SENSORS_RATE_MS = 20;
private static final int COUNT = 12;
- private static final float PREDICTION_RATIO = 1.0f/3.0f;
- private static final float PREDICTION_TIME = (SENSORS_RATE_MS*COUNT/1000.0f)*PREDICTION_RATIO;
- private float mV[] = new float[COUNT*2];
- private long mT[] = new long[COUNT*2];
+ private static final float PREDICTION_RATIO = 1.0f / 3.0f;
+ private static final float PREDICTION_TIME =
+ (SENSORS_RATE_MS * COUNT / 1000.0f) * PREDICTION_RATIO;
+ private float[] mV = new float[COUNT * 2];
+ private long[] mT = new long[COUNT * 2];
private int mIndex;
public LmsFilter() {
@@ -383,9 +384,9 @@ final class LegacySensorManager {
float v = in;
final float ns = 1.0f / 1000000000.0f;
float v1 = mV[mIndex];
- if ((v-v1) > 180) {
+ if ((v - v1) > 180) {
v -= 360;
- } else if ((v1-v) > 180) {
+ } else if ((v1 - v) > 180) {
v += 360;
}
/* Manage the circular buffer, we write the data twice spaced
@@ -393,40 +394,43 @@ final class LegacySensorManager {
* when it's full
*/
mIndex++;
- if (mIndex >= COUNT*2)
+ if (mIndex >= COUNT * 2) {
mIndex = COUNT;
+ }
mV[mIndex] = v;
mT[mIndex] = time;
- mV[mIndex-COUNT] = v;
- mT[mIndex-COUNT] = time;
+ mV[mIndex - COUNT] = v;
+ mT[mIndex - COUNT] = time;
float A, B, C, D, E;
float a, b;
int i;
A = B = C = D = E = 0;
- for (i=0 ; i<COUNT-1 ; i++) {
+ for (i = 0; i < COUNT - 1; i++) {
final int j = mIndex - 1 - i;
final float Z = mV[j];
- final float T = (mT[j]/2 + mT[j+1]/2 - time)*ns;
- float dT = (mT[j] - mT[j+1])*ns;
+ final float T = (mT[j] / 2 + mT[j + 1] / 2 - time) * ns;
+ float dT = (mT[j] - mT[j + 1]) * ns;
dT *= dT;
- A += Z*dT;
- B += T*(T*dT);
- C += (T*dT);
- D += Z*(T*dT);
+ A += Z * dT;
+ B += T * (T * dT);
+ C += (T * dT);
+ D += Z * (T * dT);
E += dT;
}
- b = (A*B + C*D) / (E*B + C*C);
- a = (E*b - A) / C;
- float f = b + PREDICTION_TIME*a;
+ b = (A * B + C * D) / (E * B + C * C);
+ a = (E * b - A) / C;
+ float f = b + PREDICTION_TIME * a;
// Normalize
f *= (1.0f / 360.0f);
- if (((f>=0)?f:-f) >= 0.5f)
- f = f - (float)Math.ceil(f + 0.5f) + 1.0f;
- if (f < 0)
+ if (((f >= 0) ? f : -f) >= 0.5f) {
+ f = f - (float) Math.ceil(f + 0.5f) + 1.0f;
+ }
+ if (f < 0) {
f += 1.0f;
+ }
f *= 360.0f;
return f;
}
diff --git a/android/hardware/Sensor.java b/android/hardware/Sensor.java
index f02e4849..7fb0c89e 100644
--- a/android/hardware/Sensor.java
+++ b/android/hardware/Sensor.java
@@ -794,12 +794,12 @@ public final class Sensor {
1, // SENSOR_TYPE_PICK_UP_GESTURE
1, // SENSOR_TYPE_WRIST_TILT_GESTURE
1, // SENSOR_TYPE_DEVICE_ORIENTATION
- 16,// SENSOR_TYPE_POSE_6DOF
+ 16, // SENSOR_TYPE_POSE_6DOF
1, // SENSOR_TYPE_STATIONARY_DETECT
1, // SENSOR_TYPE_MOTION_DETECT
1, // SENSOR_TYPE_HEART_BEAT
2, // SENSOR_TYPE_DYNAMIC_SENSOR_META
- 16,// skip over additional sensor info type
+ 16, // skip over additional sensor info type
1, // SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT
6, // SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED
};
@@ -857,8 +857,8 @@ public final class Sensor {
static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
// RotationVector length has changed to 3 to 5 for API level 18
// Set it to 3 for backward compatibility.
- if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR &&
- sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR
+ && sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return 3;
}
int offset = sensor.mType;
@@ -1033,9 +1033,9 @@ public final class Sensor {
* Returns true if the sensor is a wake-up sensor.
* <p>
* <b>Application Processor Power modes</b> <p>
- * Application Processor(AP), is the processor on which applications run. When no wake lock is held
- * and the user is not interacting with the device, this processor can enter a “Suspend” mode,
- * reducing the power consumption by 10 times or more.
+ * Application Processor(AP), is the processor on which applications run. When no wake lock is
+ * held and the user is not interacting with the device, this processor can enter a “Suspend”
+ * mode, reducing the power consumption by 10 times or more.
* </p>
* <p>
* <b>Non-wake-up sensors</b> <p>
@@ -1232,6 +1232,6 @@ public final class Sensor {
*/
private void setUuid(long msb, long lsb) {
// TODO(b/29547335): Rename this method to setId.
- mId = (int)msb;
+ mId = (int) msb;
}
}
diff --git a/android/hardware/SensorAdditionalInfo.java b/android/hardware/SensorAdditionalInfo.java
index 0c6a4151..7c876cfc 100644
--- a/android/hardware/SensorAdditionalInfo.java
+++ b/android/hardware/SensorAdditionalInfo.java
@@ -200,7 +200,7 @@ public class SensorAdditionalInfo {
public static final int TYPE_DEBUG_INFO = 0x40000000;
SensorAdditionalInfo(
- Sensor aSensor, int aType, int aSerial, int [] aIntValues, float [] aFloatValues) {
+ Sensor aSensor, int aType, int aSerial, int[] aIntValues, float[] aFloatValues) {
sensor = aSensor;
type = aType;
serial = aSerial;
@@ -222,10 +222,10 @@ public class SensorAdditionalInfo {
null, new float[] { strength, declination, inclination});
}
/** @hide */
- public static SensorAdditionalInfo createCustomInfo(Sensor aSensor, int type, float [] data) {
+ public static SensorAdditionalInfo createCustomInfo(Sensor aSensor, int type, float[] data) {
if (type < TYPE_CUSTOM_INFO || type >= TYPE_DEBUG_INFO || aSensor == null) {
- throw new IllegalArgumentException("invalid parameter(s): type: " + type +
- "; sensor: " + aSensor);
+ throw new IllegalArgumentException(
+ "invalid parameter(s): type: " + type + "; sensor: " + aSensor);
}
return new SensorAdditionalInfo(aSensor, type, 0, null, data);
diff --git a/android/hardware/SensorEvent.java b/android/hardware/SensorEvent.java
index c0bca97e..bbd04a31 100644
--- a/android/hardware/SensorEvent.java
+++ b/android/hardware/SensorEvent.java
@@ -207,8 +207,8 @@ public class SensorEvent {
* timestamp = event.timestamp;
* float[] deltaRotationMatrix = new float[9];
* SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
- * // User code should concatenate the delta rotation we computed with the current rotation
- * // in order to get the updated rotation.
+ * // User code should concatenate the delta rotation we computed with the current
+ * // rotation in order to get the updated rotation.
* // rotationCurrent = rotationCurrent * deltaRotationMatrix;
* }
* </pre>
@@ -244,21 +244,22 @@ public class SensorEvent {
* <h4>{@link android.hardware.Sensor#TYPE_GRAVITY Sensor.TYPE_GRAVITY}:</h4>
* <p>A three dimensional vector indicating the direction and magnitude of gravity. Units
* are m/s^2. The coordinate system is the same as is used by the acceleration sensor.</p>
- * <p><b>Note:</b> When the device is at rest, the output of the gravity sensor should be identical
- * to that of the accelerometer.</p>
- *
- * <h4>{@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:</h4>
- * A three dimensional vector indicating acceleration along each device axis, not including
- * gravity. All values have units of m/s^2. The coordinate system is the same as is used by the
- * acceleration sensor.
+ * <p><b>Note:</b> When the device is at rest, the output of the gravity sensor should be
+ * identical to that of the accelerometer.</p>
+ *
+ * <h4>
+ * {@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:
+ * </h4> A three dimensional vector indicating acceleration along each device axis, not
+ * including gravity. All values have units of m/s^2. The coordinate system is the same as is
+ * used by the acceleration sensor.
* <p>The output of the accelerometer, gravity and linear-acceleration sensors must obey the
* following relation:</p>
- * <p><ul>acceleration = gravity + linear-acceleration</ul></p>
+ * <p><ul>acceleration = gravity + linear-acceleration</ul></p>
*
* <h4>{@link android.hardware.Sensor#TYPE_ROTATION_VECTOR Sensor.TYPE_ROTATION_VECTOR}:</h4>
- * <p>The rotation vector represents the orientation of the device as a combination of an <i>angle</i>
- * and an <i>axis</i>, in which the device has rotated through an angle &#952 around an axis
- * &lt;x, y, z>.</p>
+ * <p>The rotation vector represents the orientation of the device as a combination of an
+ * <i>angle</i> and an <i>axis</i>, in which the device has rotated through an angle &#952
+ * around an axis &lt;x, y, z>.</p>
* <p>The three elements of the rotation vector are
* &lt;x*sin(&#952/2), y*sin(&#952/2), z*sin(&#952/2)>, such that the magnitude of the rotation
* vector is equal to sin(&#952/2), and the direction of the rotation vector is equal to the
diff --git a/android/hardware/SensorListener.java b/android/hardware/SensorListener.java
index c71e968d..e2033b6d 100644
--- a/android/hardware/SensorListener.java
+++ b/android/hardware/SensorListener.java
@@ -19,8 +19,8 @@ package android.hardware;
/**
* Used for receiving notifications from the SensorManager when
* sensor values have changed.
- *
- * @deprecated Use
+ *
+ * @deprecated Use
* {@link android.hardware.SensorEventListener SensorEventListener} instead.
*/
@Deprecated
@@ -36,7 +36,7 @@ public interface SensorListener {
* <p><u>Definition of the coordinate system used below.</u><p>
* <p>The X axis refers to the screen's horizontal axis
* (the small edge in portrait mode, the long edge in landscape mode) and
- * points to the right.
+ * points to the right.
* <p>The Y axis refers to the screen's vertical axis and points towards
* the top of the screen (the origin is in the lower-left corner).
* <p>The Z axis points toward the sky when the device is lying on its back
@@ -44,18 +44,18 @@ public interface SensorListener {
* <p> <b>IMPORTANT NOTE:</b> The axis <b><u>are swapped</u></b> when the
* device's screen orientation changes. To access the unswapped values,
* use indices 3, 4 and 5 in values[].
- *
+ *
* <p>{@link android.hardware.SensorManager#SENSOR_ORIENTATION SENSOR_ORIENTATION},
* {@link android.hardware.SensorManager#SENSOR_ORIENTATION_RAW SENSOR_ORIENTATION_RAW}:<p>
* All values are angles in degrees.
- *
+ *
* <p>values[0]: Azimuth, rotation around the Z axis (0<=azimuth<360).
* 0 = North, 90 = East, 180 = South, 270 = West
- *
+ *
* <p>values[1]: Pitch, rotation around X axis (-180<=pitch<=180), with positive
* values when the z-axis moves toward the y-axis.
*
- * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values
+ * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values
* when the z-axis moves toward the x-axis.
*
* <p>Note that this definition of yaw, pitch and roll is different from the
@@ -64,17 +64,17 @@ public interface SensorListener {
*
* <p>{@link android.hardware.SensorManager#SENSOR_ACCELEROMETER SENSOR_ACCELEROMETER}:<p>
* All values are in SI units (m/s^2) and measure contact forces.
- *
- * <p>values[0]: force applied by the device on the x-axis
- * <p>values[1]: force applied by the device on the y-axis
+ *
+ * <p>values[0]: force applied by the device on the x-axis
+ * <p>values[1]: force applied by the device on the y-axis
* <p>values[2]: force applied by the device on the z-axis
- *
+ *
* <p><u>Examples</u>:
* <li>When the device is pushed on its left side toward the right, the
* x acceleration value is negative (the device applies a reaction force
* to the push toward the left)</li>
- *
- * <li>When the device lies flat on a table, the acceleration value is
+ *
+ * <li>When the device lies flat on a table, the acceleration value is
* {@link android.hardware.SensorManager#STANDARD_GRAVITY -STANDARD_GRAVITY},
* which correspond to the force the device applies on the table in reaction
* to gravity.</li>
@@ -83,7 +83,7 @@ public interface SensorListener {
* All values are in micro-Tesla (uT) and measure the ambient magnetic
* field in the X, Y and -Z axis.
* <p><b><u>Note:</u></b> the magnetic field's Z axis is inverted.
- *
+ *
* @param sensor The ID of the sensor being monitored
* @param values The new values for the sensor.
*/
@@ -97,5 +97,5 @@ public interface SensorListener {
* @param sensor The ID of the sensor being monitored
* @param accuracy The new accuracy of this sensor.
*/
- public void onAccuracyChanged(int sensor, int accuracy);
+ public void onAccuracyChanged(int sensor, int accuracy);
}
diff --git a/android/hardware/SensorManager.java b/android/hardware/SensorManager.java
index e1cd451b..35aaf78b 100644
--- a/android/hardware/SensorManager.java
+++ b/android/hardware/SensorManager.java
@@ -83,7 +83,7 @@ public abstract class SensorManager {
/** @hide */
protected static final String TAG = "SensorManager";
- private static final float[] mTempMatrix = new float[16];
+ private static final float[] sTempMatrix = new float[16];
// Cached lists of sensors by type. Guarded by mSensorListByType.
private final SparseArray<List<Sensor>> mSensorListByType =
@@ -188,7 +188,7 @@ public abstract class SensorManager {
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
- public static final int SENSOR_MAX = ((SENSOR_ALL + 1)>>1);
+ public static final int SENSOR_MAX = ((SENSOR_ALL + 1) >> 1);
/**
@@ -425,8 +425,9 @@ public abstract class SensorManager {
} else {
list = new ArrayList<Sensor>();
for (Sensor i : fullList) {
- if (i.getType() == type)
+ if (i.getType() == type) {
list.add(i);
+ }
}
}
list = Collections.unmodifiableList(list);
@@ -461,8 +462,9 @@ public abstract class SensorManager {
} else {
List<Sensor> list = new ArrayList();
for (Sensor i : fullList) {
- if (i.getType() == type)
+ if (i.getType() == type) {
list.add(i);
+ }
}
return Collections.unmodifiableList(list);
}
@@ -490,10 +492,11 @@ public abstract class SensorManager {
// For the following sensor types, return a wake-up sensor. These types are by default
// defined as wake-up sensors. For the rest of the SDK defined sensor types return a
// non_wake-up version.
- if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION ||
- type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE ||
- type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE ||
- type == Sensor.TYPE_WRIST_TILT_GESTURE || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
+ if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION
+ || type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE
+ || type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE
+ || type == Sensor.TYPE_WRIST_TILT_GESTURE
+ || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
wakeUpSensor = true;
}
@@ -509,12 +512,12 @@ public abstract class SensorManager {
* <p>
* For example,
* <ul>
- * <li>getDefaultSensor({@link Sensor#TYPE_ACCELEROMETER}, true) returns a wake-up accelerometer
- * sensor if it exists. </li>
- * <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, false) returns a non wake-up proximity
- * sensor if it exists. </li>
- * <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, true) returns a wake-up proximity sensor
- * which is the same as the Sensor returned by {@link #getDefaultSensor(int)}. </li>
+ * <li>getDefaultSensor({@link Sensor#TYPE_ACCELEROMETER}, true) returns a wake-up
+ * accelerometer sensor if it exists. </li>
+ * <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, false) returns a non wake-up
+ * proximity sensor if it exists. </li>
+ * <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, true) returns a wake-up proximity
+ * sensor which is the same as the Sensor returned by {@link #getDefaultSensor(int)}. </li>
* </ul>
* </p>
* <p class="note">
@@ -532,8 +535,9 @@ public abstract class SensorManager {
public Sensor getDefaultSensor(int type, boolean wakeUp) {
List<Sensor> l = getSensorList(type);
for (Sensor sensor : l) {
- if (sensor.isWakeUpSensor() == wakeUp)
+ if (sensor.isWakeUpSensor() == wakeUp) {
return sensor;
+ }
}
return null;
}
@@ -842,8 +846,8 @@ public abstract class SensorManager {
* @return <code>true</code> if the sensor is supported and successfully enabled.
* @see #registerListener(SensorEventListener, Sensor, int, int)
*/
- public boolean registerListener(SensorEventListener listener, Sensor sensor, int samplingPeriodUs,
- int maxReportLatencyUs, Handler handler) {
+ public boolean registerListener(SensorEventListener listener, Sensor sensor,
+ int samplingPeriodUs, int maxReportLatencyUs, Handler handler) {
int delayUs = getDelay(samplingPeriodUs);
return registerListenerImpl(listener, sensor, delayUs, handler, maxReportLatencyUs, 0);
}
@@ -953,7 +957,7 @@ public abstract class SensorManager {
* Used for receiving notifications from the SensorManager when dynamic sensors are connected or
* disconnected.
*/
- public static abstract class DynamicSensorCallback {
+ public abstract static class DynamicSensorCallback {
/**
* Called when there is a dynamic sensor being connected to the system.
*
@@ -1180,7 +1184,7 @@ public abstract class SensorManager {
float Ay = gravity[1];
float Az = gravity[2];
- final float normsqA = (Ax*Ax + Ay*Ay + Az*Az);
+ final float normsqA = (Ax * Ax + Ay * Ay + Az * Az);
final float g = 9.81f;
final float freeFallGravitySquared = 0.01f * g * g;
if (normsqA < freeFallGravitySquared) {
@@ -1191,10 +1195,10 @@ public abstract class SensorManager {
final float Ex = geomagnetic[0];
final float Ey = geomagnetic[1];
final float Ez = geomagnetic[2];
- float Hx = Ey*Az - Ez*Ay;
- float Hy = Ez*Ax - Ex*Az;
- float Hz = Ex*Ay - Ey*Ax;
- final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
+ float Hx = Ey * Az - Ez * Ay;
+ float Hy = Ez * Ax - Ex * Az;
+ float Hz = Ex * Ay - Ey * Ax;
+ final float normH = (float) Math.sqrt(Hx * Hx + Hy * Hy + Hz * Hz);
if (normH < 0.1f) {
// device is close to free fall (or in space?), or close to
@@ -1205,13 +1209,13 @@ public abstract class SensorManager {
Hx *= invH;
Hy *= invH;
Hz *= invH;
- final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
+ final float invA = 1.0f / (float) Math.sqrt(Ax * Ax + Ay * Ay + Az * Az);
Ax *= invA;
Ay *= invA;
Az *= invA;
- final float Mx = Ay*Hz - Az*Hy;
- final float My = Az*Hx - Ax*Hz;
- final float Mz = Ax*Hy - Ay*Hx;
+ final float Mx = Ay * Hz - Az * Hy;
+ final float My = Az * Hx - Ax * Hz;
+ final float Mz = Ax * Hy - Ay * Hx;
if (R != null) {
if (R.length == 9) {
R[0] = Hx; R[1] = Hy; R[2] = Hz;
@@ -1228,17 +1232,17 @@ public abstract class SensorManager {
// compute the inclination matrix by projecting the geomagnetic
// vector onto the Z (gravity) and X (horizontal component
// of geomagnetic vector) axes.
- final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
- final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;
- final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;
+ final float invE = 1.0f / (float) Math.sqrt(Ex * Ex + Ey * Ey + Ez * Ez);
+ final float c = (Ex * Mx + Ey * My + Ez * Mz) * invE;
+ final float s = (Ex * Ax + Ey * Ay + Ez * Az) * invE;
if (I.length == 9) {
I[0] = 1; I[1] = 0; I[2] = 0;
I[3] = 0; I[4] = c; I[5] = s;
- I[6] = 0; I[7] =-s; I[8] = c;
+ I[6] = 0; I[7] = -s; I[8] = c;
} else if (I.length == 16) {
I[0] = 1; I[1] = 0; I[2] = 0;
I[4] = 0; I[5] = c; I[6] = s;
- I[8] = 0; I[9] =-s; I[10]= c;
+ I[8] = 0; I[9] = -s; I[10] = c;
I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
I[15] = 1;
}
@@ -1262,9 +1266,9 @@ public abstract class SensorManager {
*/
public static float getInclination(float[] I) {
if (I.length == 9) {
- return (float)Math.atan2(I[5], I[4]);
+ return (float) Math.atan2(I[5], I[4]);
} else {
- return (float)Math.atan2(I[6], I[5]);
+ return (float) Math.atan2(I[6], I[5]);
}
}
@@ -1343,17 +1347,16 @@ public abstract class SensorManager {
* @see #getRotationMatrix(float[], float[], float[], float[])
*/
- public static boolean remapCoordinateSystem(float[] inR, int X, int Y,
- float[] outR)
- {
+ public static boolean remapCoordinateSystem(float[] inR, int X, int Y, float[] outR) {
if (inR == outR) {
- final float[] temp = mTempMatrix;
- synchronized(temp) {
+ final float[] temp = sTempMatrix;
+ synchronized (temp) {
// we don't expect to have a lot of contention
if (remapCoordinateSystemImpl(inR, X, Y, temp)) {
final int size = outR.length;
- for (int i=0 ; i<size ; i++)
+ for (int i = 0; i < size; i++) {
outR[i] = temp[i];
+ }
return true;
}
}
@@ -1361,9 +1364,7 @@ public abstract class SensorManager {
return remapCoordinateSystemImpl(inR, X, Y, outR);
}
- private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y,
- float[] outR)
- {
+ private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y, float[] outR) {
/*
* X and Y define a rotation matrix 'r':
*
@@ -1376,14 +1377,18 @@ public abstract class SensorManager {
*/
final int length = outR.length;
- if (inR.length != length)
+ if (inR.length != length) {
return false; // invalid parameter
- if ((X & 0x7C)!=0 || (Y & 0x7C)!=0)
+ }
+ if ((X & 0x7C) != 0 || (Y & 0x7C) != 0) {
return false; // invalid parameter
- if (((X & 0x3)==0) || ((Y & 0x3)==0))
+ }
+ if (((X & 0x3) == 0) || ((Y & 0x3) == 0)) {
return false; // no axis specified
- if ((X & 0x3) == (Y & 0x3))
+ }
+ if ((X & 0x3) == (Y & 0x3)) {
return false; // same axis specified
+ }
// Z is "the other" axis, its sign is either +/- sign(X)*sign(Y)
// this can be calculated by exclusive-or'ing X and Y; except for
@@ -1391,28 +1396,29 @@ public abstract class SensorManager {
int Z = X ^ Y;
// extract the axis (remove the sign), offset in the range 0 to 2.
- final int x = (X & 0x3)-1;
- final int y = (Y & 0x3)-1;
- final int z = (Z & 0x3)-1;
+ final int x = (X & 0x3) - 1;
+ final int y = (Y & 0x3) - 1;
+ final int z = (Z & 0x3) - 1;
// compute the sign of Z (whether it needs to be inverted)
- final int axis_y = (z+1)%3;
- final int axis_z = (z+2)%3;
- if (((x^axis_y)|(y^axis_z)) != 0)
+ final int axis_y = (z + 1) % 3;
+ final int axis_z = (z + 2) % 3;
+ if (((x ^ axis_y) | (y ^ axis_z)) != 0) {
Z ^= 0x80;
+ }
- final boolean sx = (X>=0x80);
- final boolean sy = (Y>=0x80);
- final boolean sz = (Z>=0x80);
+ final boolean sx = (X >= 0x80);
+ final boolean sy = (Y >= 0x80);
+ final boolean sz = (Z >= 0x80);
// Perform R * r, in avoiding actual muls and adds.
- final int rowLength = ((length==16)?4:3);
- for (int j=0 ; j<3 ; j++) {
- final int offset = j*rowLength;
- for (int i=0 ; i<3 ; i++) {
- if (x==i) outR[offset+i] = sx ? -inR[offset+0] : inR[offset+0];
- if (y==i) outR[offset+i] = sy ? -inR[offset+1] : inR[offset+1];
- if (z==i) outR[offset+i] = sz ? -inR[offset+2] : inR[offset+2];
+ final int rowLength = ((length == 16) ? 4 : 3);
+ for (int j = 0; j < 3; j++) {
+ final int offset = j * rowLength;
+ for (int i = 0; i < 3; i++) {
+ if (x == i) outR[offset + i] = sx ? -inR[offset + 0] : inR[offset + 0];
+ if (y == i) outR[offset + i] = sy ? -inR[offset + 1] : inR[offset + 1];
+ if (z == i) outR[offset + i] = sz ? -inR[offset + 2] : inR[offset + 2];
}
}
if (length == 16) {
@@ -1466,7 +1472,7 @@ public abstract class SensorManager {
* @see #getRotationMatrix(float[], float[], float[], float[])
* @see GeomagneticField
*/
- public static float[] getOrientation(float[] R, float values[]) {
+ public static float[] getOrientation(float[] R, float[] values) {
/*
* 4x4 (length=16) case:
* / R[ 0] R[ 1] R[ 2] 0 \
@@ -1481,13 +1487,13 @@ public abstract class SensorManager {
*
*/
if (R.length == 9) {
- values[0] = (float)Math.atan2(R[1], R[4]);
- values[1] = (float)Math.asin(-R[7]);
- values[2] = (float)Math.atan2(-R[6], R[8]);
+ values[0] = (float) Math.atan2(R[1], R[4]);
+ values[1] = (float) Math.asin(-R[7]);
+ values[2] = (float) Math.atan2(-R[6], R[8]);
} else {
- values[0] = (float)Math.atan2(R[1], R[5]);
- values[1] = (float)Math.asin(-R[9]);
- values[2] = (float)Math.atan2(-R[8], R[10]);
+ values[0] = (float) Math.atan2(R[1], R[5]);
+ values[1] = (float) Math.asin(-R[9]);
+ values[2] = (float) Math.atan2(-R[8], R[10]);
}
return values;
@@ -1524,7 +1530,7 @@ public abstract class SensorManager {
*/
public static float getAltitude(float p0, float p) {
final float coef = 1.0f / 5.255f;
- return 44330.0f * (1.0f - (float)Math.pow(p/p0, coef));
+ return 44330.0f * (1.0f - (float) Math.pow(p / p0, coef));
}
/** Helper function to compute the angle change between two rotation matrices.
@@ -1557,12 +1563,13 @@ public abstract class SensorManager {
* (in radians) is stored
*/
- public static void getAngleChange( float[] angleChange, float[] R, float[] prevR) {
- float rd1=0,rd4=0, rd6=0,rd7=0, rd8=0;
- float ri0=0,ri1=0,ri2=0,ri3=0,ri4=0,ri5=0,ri6=0,ri7=0,ri8=0;
- float pri0=0, pri1=0, pri2=0, pri3=0, pri4=0, pri5=0, pri6=0, pri7=0, pri8=0;
+ public static void getAngleChange(float[] angleChange, float[] R, float[] prevR) {
+ float rd1 = 0, rd4 = 0, rd6 = 0, rd7 = 0, rd8 = 0;
+ float ri0 = 0, ri1 = 0, ri2 = 0, ri3 = 0, ri4 = 0, ri5 = 0, ri6 = 0, ri7 = 0, ri8 = 0;
+ float pri0 = 0, pri1 = 0, pri2 = 0, pri3 = 0, pri4 = 0;
+ float pri5 = 0, pri6 = 0, pri7 = 0, pri8 = 0;
- if(R.length == 9) {
+ if (R.length == 9) {
ri0 = R[0];
ri1 = R[1];
ri2 = R[2];
@@ -1572,7 +1579,7 @@ public abstract class SensorManager {
ri6 = R[6];
ri7 = R[7];
ri8 = R[8];
- } else if(R.length == 16) {
+ } else if (R.length == 16) {
ri0 = R[0];
ri1 = R[1];
ri2 = R[2];
@@ -1584,7 +1591,7 @@ public abstract class SensorManager {
ri8 = R[10];
}
- if(prevR.length == 9) {
+ if (prevR.length == 9) {
pri0 = prevR[0];
pri1 = prevR[1];
pri2 = prevR[2];
@@ -1594,7 +1601,7 @@ public abstract class SensorManager {
pri6 = prevR[6];
pri7 = prevR[7];
pri8 = prevR[8];
- } else if(prevR.length == 16) {
+ } else if (prevR.length == 16) {
pri0 = prevR[0];
pri1 = prevR[1];
pri2 = prevR[2];
@@ -1615,9 +1622,9 @@ public abstract class SensorManager {
rd7 = pri2 * ri1 + pri5 * ri4 + pri8 * ri7; //rd[2][1]
rd8 = pri2 * ri2 + pri5 * ri5 + pri8 * ri8; //rd[2][2]
- angleChange[0] = (float)Math.atan2(rd1, rd4);
- angleChange[1] = (float)Math.asin(-rd7);
- angleChange[2] = (float)Math.atan2(-rd6, rd8);
+ angleChange[0] = (float) Math.atan2(rd1, rd4);
+ angleChange[1] = (float) Math.asin(-rd7);
+ angleChange[2] = (float) Math.atan2(-rd6, rd8);
}
@@ -1650,8 +1657,8 @@ public abstract class SensorManager {
if (rotationVector.length >= 4) {
q0 = rotationVector[3];
} else {
- q0 = 1 - q1*q1 - q2*q2 - q3*q3;
- q0 = (q0 > 0) ? (float)Math.sqrt(q0) : 0;
+ q0 = 1 - q1 * q1 - q2 * q2 - q3 * q3;
+ q0 = (q0 > 0) ? (float) Math.sqrt(q0) : 0;
}
float sq_q1 = 2 * q1 * q1;
@@ -1664,7 +1671,7 @@ public abstract class SensorManager {
float q2_q3 = 2 * q2 * q3;
float q1_q0 = 2 * q1 * q0;
- if(R.length == 9) {
+ if (R.length == 9) {
R[0] = 1 - sq_q2 - sq_q3;
R[1] = q1_q2 - q3_q0;
R[2] = q1_q3 + q2_q0;
@@ -1707,8 +1714,8 @@ public abstract class SensorManager {
if (rv.length >= 4) {
Q[0] = rv[3];
} else {
- Q[0] = 1 - rv[0]*rv[0] - rv[1]*rv[1] - rv[2]*rv[2];
- Q[0] = (Q[0] > 0) ? (float)Math.sqrt(Q[0]) : 0;
+ Q[0] = 1 - rv[0] * rv[0] - rv[1] * rv[1] - rv[2] * rv[2];
+ Q[0] = (Q[0] > 0) ? (float) Math.sqrt(Q[0]) : 0;
}
Q[1] = rv[0];
Q[2] = rv[1];
@@ -1800,7 +1807,7 @@ public abstract class SensorManager {
*/
@SystemApi
public boolean initDataInjection(boolean enable) {
- return initDataInjectionImpl(enable);
+ return initDataInjectionImpl(enable);
}
/**
@@ -1846,9 +1853,9 @@ public abstract class SensorManager {
}
int expectedNumValues = Sensor.getMaxLengthValuesArray(sensor, Build.VERSION_CODES.M);
if (values.length != expectedNumValues) {
- throw new IllegalArgumentException ("Wrong number of values for sensor " +
- sensor.getName() + " actual=" + values.length + " expected=" +
- expectedNumValues);
+ throw new IllegalArgumentException("Wrong number of values for sensor "
+ + sensor.getName() + " actual=" + values.length + " expected="
+ + expectedNumValues);
}
if (accuracy < SENSOR_STATUS_NO_CONTACT || accuracy > SENSOR_STATUS_ACCURACY_HIGH) {
throw new IllegalArgumentException("Invalid sensor accuracy");
diff --git a/android/hardware/SystemSensorManager.java b/android/hardware/SystemSensorManager.java
index 607788d3..1174cb6a 100644
--- a/android/hardware/SystemSensorManager.java
+++ b/android/hardware/SystemSensorManager.java
@@ -28,10 +28,11 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import dalvik.system.CloseGuard;
import com.android.internal.annotations.GuardedBy;
+import dalvik.system.CloseGuard;
+
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.ref.WeakReference;
@@ -40,7 +41,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-
/**
* Sensor manager implementation that communicates with the built-in
* system sensors.
@@ -101,7 +101,7 @@ public class SystemSensorManager extends SensorManager {
/** {@hide} */
public SystemSensorManager(Context context, Looper mainLooper) {
- synchronized(sLock) {
+ synchronized (sLock) {
if (!sNativeClassInited) {
sNativeClassInited = true;
nativeClassInit();
@@ -114,7 +114,7 @@ public class SystemSensorManager extends SensorManager {
mNativeInstance = nativeCreate(context.getOpPackageName());
// initialize the sensor list
- for (int index = 0;;++index) {
+ for (int index = 0;; ++index) {
Sensor sensor = new Sensor();
if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
mFullSensorsList.add(sensor);
@@ -157,9 +157,9 @@ public class SystemSensorManager extends SensorManager {
return false;
}
if (mSensorListeners.size() >= MAX_LISTENER_COUNT) {
- throw new IllegalStateException("register failed, " +
- "the sensor listeners size has exceeded the maximum limit " +
- MAX_LISTENER_COUNT);
+ throw new IllegalStateException("register failed, "
+ + "the sensor listeners size has exceeded the maximum limit "
+ + MAX_LISTENER_COUNT);
}
// Invariants to preserve:
@@ -170,9 +170,10 @@ public class SystemSensorManager extends SensorManager {
SensorEventQueue queue = mSensorListeners.get(listener);
if (queue == null) {
Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
- final String fullClassName = listener.getClass().getEnclosingClass() != null ?
- listener.getClass().getEnclosingClass().getName() :
- listener.getClass().getName();
+ final String fullClassName =
+ listener.getClass().getEnclosingClass() != null
+ ? listener.getClass().getEnclosingClass().getName()
+ : listener.getClass().getName();
queue = new SensorEventQueue(listener, looper, this, fullClassName);
if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {
queue.dispose();
@@ -221,17 +222,18 @@ public class SystemSensorManager extends SensorManager {
if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) return false;
if (mTriggerListeners.size() >= MAX_LISTENER_COUNT) {
- throw new IllegalStateException("request failed, " +
- "the trigger listeners size has exceeded the maximum limit " +
- MAX_LISTENER_COUNT);
+ throw new IllegalStateException("request failed, "
+ + "the trigger listeners size has exceeded the maximum limit "
+ + MAX_LISTENER_COUNT);
}
synchronized (mTriggerListeners) {
TriggerEventQueue queue = mTriggerListeners.get(listener);
if (queue == null) {
- final String fullClassName = listener.getClass().getEnclosingClass() != null ?
- listener.getClass().getEnclosingClass().getName() :
- listener.getClass().getName();
+ final String fullClassName =
+ listener.getClass().getEnclosingClass() != null
+ ? listener.getClass().getEnclosingClass().getName()
+ : listener.getClass().getName();
queue = new TriggerEventQueue(listener, mMainLooper, this, fullClassName);
if (!queue.addSensor(sensor, 0, 0)) {
queue.dispose();
@@ -336,27 +338,27 @@ public class SystemSensorManager extends SensorManager {
mHandleToSensor.remove(sensor.getHandle());
if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
- synchronized(mTriggerListeners) {
+ synchronized (mTriggerListeners) {
HashMap<TriggerEventListener, TriggerEventQueue> triggerListeners =
- new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
+ new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
- for (TriggerEventListener l: triggerListeners.keySet()) {
- if (DEBUG_DYNAMIC_SENSOR){
- Log.i(TAG, "removed trigger listener" + l.toString() +
- " due to sensor disconnection");
+ for (TriggerEventListener l : triggerListeners.keySet()) {
+ if (DEBUG_DYNAMIC_SENSOR) {
+ Log.i(TAG, "removed trigger listener" + l.toString()
+ + " due to sensor disconnection");
}
cancelTriggerSensorImpl(l, sensor, true);
}
}
} else {
- synchronized(mSensorListeners) {
+ synchronized (mSensorListeners) {
HashMap<SensorEventListener, SensorEventQueue> sensorListeners =
- new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
+ new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
for (SensorEventListener l: sensorListeners.keySet()) {
- if (DEBUG_DYNAMIC_SENSOR){
- Log.i(TAG, "removed event listener" + l.toString() +
- " due to sensor disconnection");
+ if (DEBUG_DYNAMIC_SENSOR) {
+ Log.i(TAG, "removed event listener" + l.toString()
+ + " due to sensor disconnection");
}
unregisterListenerImpl(l, sensor);
}
@@ -365,7 +367,7 @@ public class SystemSensorManager extends SensorManager {
}
private void updateDynamicSensorList() {
- synchronized(mFullDynamicSensorsList) {
+ synchronized (mFullDynamicSensorsList) {
if (mDynamicSensorListDirty) {
List<Sensor> list = new ArrayList<>();
nativeGetDynamicSensors(mNativeInstance, list);
@@ -488,15 +490,15 @@ public class SystemSensorManager extends SensorManager {
int i = 0, j = 0;
while (true) {
- if (j < oldList.size() && ( i >= newList.size() ||
- newList.get(i).getHandle() > oldList.get(j).getHandle()) ) {
+ if (j < oldList.size() && (i >= newList.size()
+ || newList.get(i).getHandle() > oldList.get(j).getHandle())) {
changed = true;
if (removed != null) {
removed.add(oldList.get(j));
}
++j;
- } else if (i < newList.size() && ( j >= oldList.size() ||
- newList.get(i).getHandle() < oldList.get(j).getHandle())) {
+ } else if (i < newList.size() && (j >= oldList.size()
+ || newList.get(i).getHandle() < oldList.get(j).getHandle())) {
changed = true;
if (added != null) {
added.add(newList.get(i));
@@ -505,8 +507,8 @@ public class SystemSensorManager extends SensorManager {
updated.add(newList.get(i));
}
++i;
- } else if (i < newList.size() && j < oldList.size() &&
- newList.get(i).getHandle() == oldList.get(j).getHandle()) {
+ } else if (i < newList.size() && j < oldList.size()
+ && newList.get(i).getHandle() == oldList.get(j).getHandle()) {
if (updated != null) {
updated.add(oldList.get(j));
}
@@ -623,7 +625,7 @@ public class SystemSensorManager extends SensorManager {
* associated with any listener and there is one InjectEventQueue associated with a
* SensorManager instance.
*/
- private static abstract class BaseEventQueue {
+ private abstract static class BaseEventQueue {
private static native long nativeInitBaseEventQueue(long nativeManager,
WeakReference<BaseEventQueue> eventQWeak, MessageQueue msgQ,
String packageName, int mode, String opPackageName);
@@ -633,9 +635,9 @@ public class SystemSensorManager extends SensorManager {
private static native void nativeDestroySensorEventQueue(long eventQ);
private static native int nativeFlushSensor(long eventQ);
private static native int nativeInjectSensorData(long eventQ, int handle,
- float[] values,int accuracy, long timestamp);
+ float[] values, int accuracy, long timestamp);
- private long nSensorEventQueue;
+ private long mNativeSensorEventQueue;
private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -646,7 +648,7 @@ public class SystemSensorManager extends SensorManager {
BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
if (packageName == null) packageName = "";
- nSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
+ mNativeSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
new WeakReference<>(this), looper.getQueue(),
packageName, mode, manager.mContext.getOpPackageName());
mCloseGuard.open("dispose");
@@ -668,17 +670,17 @@ public class SystemSensorManager extends SensorManager {
addSensorEvent(sensor);
if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) {
// Try continuous mode if batching fails.
- if (maxBatchReportLatencyUs == 0 ||
- maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
- removeSensor(sensor, false);
- return false;
+ if (maxBatchReportLatencyUs == 0
+ || maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
+ removeSensor(sensor, false);
+ return false;
}
}
return true;
}
public boolean removeAllSensors() {
- for (int i=0 ; i<mActiveSensors.size(); i++) {
+ for (int i = 0; i < mActiveSensors.size(); i++) {
if (mActiveSensors.valueAt(i) == true) {
int handle = mActiveSensors.keyAt(i);
Sensor sensor = mManager.mHandleToSensor.get(handle);
@@ -706,8 +708,8 @@ public class SystemSensorManager extends SensorManager {
}
public int flush() {
- if (nSensorEventQueue == 0) throw new NullPointerException();
- return nativeFlushSensor(nSensorEventQueue);
+ if (mNativeSensorEventQueue == 0) throw new NullPointerException();
+ return nativeFlushSensor(mNativeSensorEventQueue);
}
public boolean hasSensors() {
@@ -731,29 +733,30 @@ public class SystemSensorManager extends SensorManager {
}
mCloseGuard.close();
}
- if (nSensorEventQueue != 0) {
- nativeDestroySensorEventQueue(nSensorEventQueue);
- nSensorEventQueue = 0;
+ if (mNativeSensorEventQueue != 0) {
+ nativeDestroySensorEventQueue(mNativeSensorEventQueue);
+ mNativeSensorEventQueue = 0;
}
}
private int enableSensor(
Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
- if (nSensorEventQueue == 0) throw new NullPointerException();
+ if (mNativeSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
- return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs,
+ return nativeEnableSensor(mNativeSensorEventQueue, sensor.getHandle(), rateUs,
maxBatchReportLatencyUs);
}
protected int injectSensorDataBase(int handle, float[] values, int accuracy,
long timestamp) {
- return nativeInjectSensorData(nSensorEventQueue, handle, values, accuracy, timestamp);
+ return nativeInjectSensorData(
+ mNativeSensorEventQueue, handle, values, accuracy, timestamp);
}
private int disableSensor(Sensor sensor) {
- if (nSensorEventQueue == 0) throw new NullPointerException();
+ if (mNativeSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
- return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
+ return nativeDisableSensor(mNativeSensorEventQueue, sensor.getHandle());
}
protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
long timestamp);
@@ -840,7 +843,7 @@ public class SystemSensorManager extends SensorManager {
// sensor disconnected
return;
}
- ((SensorEventListener2)mListener).onFlushCompleted(sensor);
+ ((SensorEventListener2) mListener).onFlushCompleted(sensor);
}
return;
}
@@ -858,7 +861,7 @@ public class SystemSensorManager extends SensorManager {
}
SensorAdditionalInfo info =
new SensorAdditionalInfo(sensor, type, serial, intValues, floatValues);
- ((SensorEventCallback)mListener).onSensorAdditionalInfo(info);
+ ((SensorEventCallback) mListener).onSensorAdditionalInfo(info);
}
}
}
@@ -930,8 +933,8 @@ public class SystemSensorManager extends SensorManager {
super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
}
- int injectSensorData(int handle, float[] values,int accuracy, long timestamp) {
- return injectSensorDataBase(handle, values, accuracy, timestamp);
+ int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
+ return injectSensorDataBase(handle, values, accuracy, timestamp);
}
@SuppressWarnings("unused")
@@ -959,6 +962,7 @@ public class SystemSensorManager extends SensorManager {
int handle = -1;
if (parameter.sensor != null) handle = parameter.sensor.getHandle();
return nativeSetOperationParameter(
- mNativeInstance, handle, parameter.type, parameter.floatValues, parameter.intValues) == 0;
+ mNativeInstance, handle,
+ parameter.type, parameter.floatValues, parameter.intValues) == 0;
}
}
diff --git a/android/hardware/camera2/DngCreator.java b/android/hardware/camera2/DngCreator.java
index 1a51acd6..cc484eaf 100644
--- a/android/hardware/camera2/DngCreator.java
+++ b/android/hardware/camera2/DngCreator.java
@@ -18,7 +18,6 @@ package android.hardware.camera2;
import android.annotation.IntRange;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.ImageFormat;
@@ -37,6 +36,7 @@ import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
+import java.util.Locale;
import java.util.TimeZone;
/**
@@ -122,7 +122,7 @@ public final class DngCreator implements AutoCloseable {
// Create this fresh each time since the time zone may change while a long-running application
// is active.
final DateFormat dateTimeStampFormat =
- new SimpleDateFormat(TIFF_DATETIME_FORMAT);
+ new SimpleDateFormat(TIFF_DATETIME_FORMAT, Locale.US);
dateTimeStampFormat.setTimeZone(TimeZone.getDefault());
// Format for metadata
@@ -472,7 +472,8 @@ public final class DngCreator implements AutoCloseable {
private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd HH:mm:ss";
- private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+ private static final DateFormat sExifGPSDateStamp =
+ new SimpleDateFormat(GPS_DATE_FORMAT_STR, Locale.US);
private final Calendar mGPSTimeStampCalendar = Calendar
.getInstance(TimeZone.getTimeZone("UTC"));
diff --git a/android/hardware/display/DisplayManager.java b/android/hardware/display/DisplayManager.java
index 6fbacaf3..b2af44ec 100644
--- a/android/hardware/display/DisplayManager.java
+++ b/android/hardware/display/DisplayManager.java
@@ -278,6 +278,19 @@ public final class DisplayManager {
*/
public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
+ /**
+ * Virtual display flag: Indicates that the contents will be destroyed once
+ * the display is removed.
+ *
+ * Public virtual displays without this flag will move their content to main display
+ * stack once they're removed. Private vistual displays will always destroy their
+ * content on removal even without this flag.
+ *
+ * @see #createVirtualDisplay
+ * @hide
+ */
+ public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
+
/** @hide */
public DisplayManager(Context context) {
mContext = context;
diff --git a/android/hardware/location/NanoAppInstanceInfo.java b/android/hardware/location/NanoAppInstanceInfo.java
index ac6d83f6..26238304 100644
--- a/android/hardware/location/NanoAppInstanceInfo.java
+++ b/android/hardware/location/NanoAppInstanceInfo.java
@@ -287,8 +287,10 @@ public class NanoAppInstanceInfo {
mPublisher = in.readString();
mName = in.readString();
+ mHandle = in.readInt();
mAppId = in.readLong();
mAppVersion = in.readInt();
+ mContexthubId = in.readInt();
mNeededReadMemBytes = in.readInt();
mNeededWriteMemBytes = in.readInt();
mNeededExecMemBytes = in.readInt();
@@ -309,6 +311,8 @@ public class NanoAppInstanceInfo {
public void writeToParcel(Parcel out, int flags) {
out.writeString(mPublisher);
out.writeString(mName);
+
+ out.writeInt(mHandle);
out.writeLong(mAppId);
out.writeInt(mAppVersion);
out.writeInt(mContexthubId);
diff --git a/android/media/AmrInputStream.java b/android/media/AmrInputStream.java
index fb91bbbb..efaf2244 100644
--- a/android/media/AmrInputStream.java
+++ b/android/media/AmrInputStream.java
@@ -25,12 +25,12 @@ import android.util.Log;
/**
- * AmrInputStream
+ * DO NOT USE
* @hide
*/
public final class AmrInputStream extends InputStream {
private final static String TAG = "AmrInputStream";
-
+
// frame is 20 msec at 8.000 khz
private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
@@ -51,10 +51,10 @@ public final class AmrInputStream extends InputStream {
private byte[] mOneByte = new byte[1];
/**
- * Create a new AmrInputStream, which converts 16 bit PCM to AMR
- * @param inputStream InputStream containing 16 bit PCM.
+ * DO NOT USE - use MediaCodec instead
*/
public AmrInputStream(InputStream inputStream) {
+ Log.w(TAG, "@@@@ AmrInputStream is not a public API @@@@");
mInputStream = inputStream;
MediaFormat format = new MediaFormat();
@@ -83,17 +83,26 @@ public final class AmrInputStream extends InputStream {
mInfo = new BufferInfo();
}
+ /**
+ * DO NOT USE
+ */
@Override
public int read() throws IOException {
int rtn = read(mOneByte, 0, 1);
return rtn == 1 ? (0xff & mOneByte[0]) : -1;
}
+ /**
+ * DO NOT USE
+ */
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
+ /**
+ * DO NOT USE
+ */
@Override
public int read(byte[] b, int offset, int length) throws IOException {
if (mCodec == null) {
@@ -131,19 +140,15 @@ public final class AmrInputStream extends InputStream {
}
}
- // now read encoded data from the encoder (blocking, since we just filled up the
- // encoder's input with data it should be able to output at least one buffer)
- while (true) {
- int index = mCodec.dequeueOutputBuffer(mInfo, -1);
- if (index >= 0) {
- mBufIn = mInfo.size;
- ByteBuffer out = mCodec.getOutputBuffer(index);
- out.get(mBuf, 0 /* offset */, mBufIn /* length */);
- mCodec.releaseOutputBuffer(index, false /* render */);
- if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- mSawOutputEOS = true;
- }
- break;
+ // now read encoded data from the encoder
+ int index = mCodec.dequeueOutputBuffer(mInfo, 0);
+ if (index >= 0) {
+ mBufIn = mInfo.size;
+ ByteBuffer out = mCodec.getOutputBuffer(index);
+ out.get(mBuf, 0 /* offset */, mBufIn /* length */);
+ mCodec.releaseOutputBuffer(index, false /* render */);
+ if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ mSawOutputEOS = true;
}
}
}
diff --git a/android/media/AudioAttributes.java b/android/media/AudioAttributes.java
index 3b9a5de0..26ead3d1 100644
--- a/android/media/AudioAttributes.java
+++ b/android/media/AudioAttributes.java
@@ -19,12 +19,14 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.media.AudioAttributesProto;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -177,7 +179,7 @@ public final class AudioAttributes implements Parcelable {
/**
* IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
- * if applicable.
+ * if applicable, as well as audioattributes.proto.
*/
/**
@@ -850,6 +852,21 @@ public final class AudioAttributes implements Parcelable {
}
/** @hide */
+ public void toProto(ProtoOutputStream proto) {
+ proto.write(AudioAttributesProto.USAGE, mUsage);
+ proto.write(AudioAttributesProto.CONTENT_TYPE, mContentType);
+ proto.write(AudioAttributesProto.FLAGS, mFlags);
+ // mFormattedTags is never null due to assignment in Builder or unmarshalling.
+ for (String t : mFormattedTags.split(";")) {
+ t = t.trim();
+ if (t != "") {
+ proto.write(AudioAttributesProto.TAGS, t);
+ }
+ }
+ // TODO: is the data in mBundle useful for debugging?
+ }
+
+ /** @hide */
public String usageToString() {
return usageToString(mUsage);
}
diff --git a/android/media/AudioManager.java b/android/media/AudioManager.java
index 186b2650..dab7632a 100644
--- a/android/media/AudioManager.java
+++ b/android/media/AudioManager.java
@@ -4119,7 +4119,15 @@ public class AudioManager {
Log.w(TAG, "updateAudioPortCache: listAudioPatches failed");
return status;
}
- } while (patchGeneration[0] != portGeneration[0]);
+ // Loop until patch generation is the same as port generation unless audio ports
+ // and audio patches are not null.
+ } while (patchGeneration[0] != portGeneration[0]
+ && (ports == null || patches == null));
+ // If the patch generation doesn't equal port generation, return ERROR here in case
+ // of mismatch between audio ports and audio patches.
+ if (patchGeneration[0] != portGeneration[0]) {
+ return ERROR;
+ }
for (int i = 0; i < newPatches.size(); i++) {
for (int j = 0; j < newPatches.get(i).sources().length; j++) {
diff --git a/android/media/AudioPortEventHandler.java b/android/media/AudioPortEventHandler.java
index c152245d..ac3904a2 100644
--- a/android/media/AudioPortEventHandler.java
+++ b/android/media/AudioPortEventHandler.java
@@ -17,6 +17,7 @@
package android.media;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import java.util.ArrayList;
@@ -30,6 +31,7 @@ import java.lang.ref.WeakReference;
class AudioPortEventHandler {
private Handler mHandler;
+ private HandlerThread mHandlerThread;
private final ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners =
new ArrayList<AudioManager.OnAudioPortUpdateListener>();
@@ -40,6 +42,8 @@ class AudioPortEventHandler {
private static final int AUDIOPORT_EVENT_SERVICE_DIED = 3;
private static final int AUDIOPORT_EVENT_NEW_LISTENER = 4;
+ private static final long RESCHEDULE_MESSAGE_DELAY_MS = 100;
+
/**
* Accessed by native methods: JNI Callback context.
*/
@@ -51,11 +55,12 @@ class AudioPortEventHandler {
if (mHandler != null) {
return;
}
- // find the looper for our new event handler
- Looper looper = Looper.getMainLooper();
+ // create a new thread for our new event handler
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
- if (looper != null) {
- mHandler = new Handler(looper) {
+ if (mHandlerThread.getLooper() != null) {
+ mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
@@ -86,6 +91,12 @@ class AudioPortEventHandler {
if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) {
int status = AudioManager.updateAudioPortCache(ports, patches, null);
if (status != AudioManager.SUCCESS) {
+ // Since audio ports and audio patches are not null, the return
+ // value could be ERROR due to inconsistency between port generation
+ // and patch generation. In this case, we need to reschedule the
+ // message to make sure the native callback is done.
+ sendMessageDelayed(obtainMessage(msg.what, msg.obj),
+ RESCHEDULE_MESSAGE_DELAY_MS);
return;
}
}
@@ -132,6 +143,9 @@ class AudioPortEventHandler {
@Override
protected void finalize() {
native_finalize();
+ if (mHandlerThread.isAlive()) {
+ mHandlerThread.quit();
+ }
}
private native void native_finalize();
@@ -168,6 +182,10 @@ class AudioPortEventHandler {
Handler handler = eventHandler.handler();
if (handler != null) {
Message m = handler.obtainMessage(what, arg1, arg2, obj);
+ if (what != AUDIOPORT_EVENT_NEW_LISTENER) {
+ // Except AUDIOPORT_EVENT_NEW_LISTENER, we can only respect the last message.
+ handler.removeMessages(what);
+ }
handler.sendMessage(m);
}
}
diff --git a/android/media/ExifInterface.java b/android/media/ExifInterface.java
index 1f5edfa0..ba41a7bd 100644
--- a/android/media/ExifInterface.java
+++ b/android/media/ExifInterface.java
@@ -2584,22 +2584,21 @@ public class ExifInterface {
ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
}
- // Note that the rotation angle from MediaMetadataRetriever for heif images
- // are CCW, while rotation in ExifInterface orientations are CW.
String rotation = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
if (rotation != null) {
int orientation = ExifInterface.ORIENTATION_NORMAL;
+ // all rotation angles in CW
switch (Integer.parseInt(rotation)) {
case 90:
- orientation = ExifInterface.ORIENTATION_ROTATE_270;
+ orientation = ExifInterface.ORIENTATION_ROTATE_90;
break;
case 180:
orientation = ExifInterface.ORIENTATION_ROTATE_180;
break;
case 270:
- orientation = ExifInterface.ORIENTATION_ROTATE_90;
+ orientation = ExifInterface.ORIENTATION_ROTATE_270;
break;
}
diff --git a/android/media/MediaCodecInfo.java b/android/media/MediaCodecInfo.java
index f85925d8..f41e33f7 100644
--- a/android/media/MediaCodecInfo.java
+++ b/android/media/MediaCodecInfo.java
@@ -2749,8 +2749,8 @@ public final class MediaCodecInfo {
mQualityRange = Utils
.parseIntRange(info.getString("quality-range"), mQualityRange);
}
- if (info.containsKey("feature-bitrate-control")) {
- for (String mode: info.getString("feature-bitrate-control").split(",")) {
+ if (info.containsKey("feature-bitrate-modes")) {
+ for (String mode: info.getString("feature-bitrate-modes").split(",")) {
mBitControl |= parseBitrateMode(mode);
}
}
diff --git a/android/media/MediaRouter.java b/android/media/MediaRouter.java
index fe427a73..70ab8632 100644
--- a/android/media/MediaRouter.java
+++ b/android/media/MediaRouter.java
@@ -184,13 +184,15 @@ public class MediaRouter {
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
boolean audioRoutesChanged = false;
+ boolean forceUseDefaultRoute = false;
+
if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
mCurAudioRoutesInfo.mainType = newRoutes.mainType;
int name;
- if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
- || (newRoutes.mainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
+ || (newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
name = com.android.internal.R.string.default_audio_route_name_headphones;
- } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
} else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
name = com.android.internal.R.string.default_audio_route_name_hdmi;
@@ -201,11 +203,16 @@ public class MediaRouter {
}
mDefaultAudioVideo.mNameResId = name;
dispatchRouteChanged(mDefaultAudioVideo);
+
+ if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
+ | AudioRoutesInfo.MAIN_HEADPHONES | AudioRoutesInfo.MAIN_USB)) != 0) {
+ forceUseDefaultRoute = true;
+ }
audioRoutesChanged = true;
}
- final int mainType = mCurAudioRoutesInfo.mainType;
if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+ forceUseDefaultRoute = false;
mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
if (mCurAudioRoutesInfo.bluetoothName != null) {
if (mBluetoothA2dpRoute == null) {
@@ -231,30 +238,21 @@ public class MediaRouter {
}
if (audioRoutesChanged) {
- selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, getDefaultSystemAudioRoute(), false);
Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn());
+ if (mSelectedRoute == null || mSelectedRoute == mDefaultAudioVideo
+ || mSelectedRoute == mBluetoothA2dpRoute) {
+ if (forceUseDefaultRoute || mBluetoothA2dpRoute == null) {
+ selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
+ } else {
+ selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
+ }
+ }
}
}
- RouteInfo getDefaultSystemAudioRoute() {
- boolean globalBluetoothA2doOn = false;
- try {
- globalBluetoothA2doOn = mMediaRouterService.isGlobalBluetoothA2doOn();
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to call isSystemBluetoothA2doOn.", ex);
- }
- return (globalBluetoothA2doOn && mBluetoothA2dpRoute != null)
- ? mBluetoothA2dpRoute : mDefaultAudioVideo;
- }
-
- RouteInfo getCurrentSystemAudioRoute() {
- return (isBluetoothA2dpOn() && mBluetoothA2dpRoute != null)
- ? mBluetoothA2dpRoute : mDefaultAudioVideo;
- }
-
boolean isBluetoothA2dpOn() {
try {
- return mAudioService.isBluetoothA2dpOn();
+ return mBluetoothA2dpRoute != null && mAudioService.isBluetoothA2dpOn();
} catch (RemoteException e) {
Log.e(TAG, "Error querying Bluetooth A2DP state", e);
return false;
@@ -602,13 +600,20 @@ public class MediaRouter {
@Override
public void onRestoreRoute() {
- // Skip restoring route if the selected route is not a system audio route, or
- // MediaRouter is initializing.
- if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute)
- || mSelectedRoute == null) {
- return;
- }
- mSelectedRoute.select();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Skip restoring route if the selected route is not a system audio route,
+ // MediaRouter is initializing, or mClient was changed.
+ if (Client.this != mClient || mSelectedRoute == null
+ || (mSelectedRoute != mDefaultAudioVideo
+ && mSelectedRoute != mBluetoothA2dpRoute)) {
+ return;
+ }
+ Log.v(TAG, "onRestoreRoute() : route=" + mSelectedRoute);
+ mSelectedRoute.select();
+ }
+ });
}
}
}
@@ -940,10 +945,12 @@ public class MediaRouter {
Log.v(TAG, "Selecting route: " + route);
assert(route != null);
final RouteInfo oldRoute = sStatic.mSelectedRoute;
+ final RouteInfo currentSystemRoute = sStatic.isBluetoothA2dpOn()
+ ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo;
boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
|| oldRoute == sStatic.mBluetoothA2dpRoute);
if (oldRoute == route
- && (!wasDefaultOrBluetoothRoute || route == sStatic.getCurrentSystemAudioRoute())) {
+ && (!wasDefaultOrBluetoothRoute || route == currentSystemRoute)) {
return;
}
if (!route.matchesTypes(types)) {
@@ -1014,8 +1021,7 @@ public class MediaRouter {
static void selectDefaultRouteStatic() {
// TODO: Be smarter about the route types here; this selects for all valid.
- if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
- && sStatic.mBluetoothA2dpRoute != null && sStatic.isBluetoothA2dpOn()) {
+ if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute && sStatic.isBluetoothA2dpOn()) {
selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
} else {
selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
diff --git a/android/media/PlayerBase.java b/android/media/PlayerBase.java
index 4808d7a5..09449a18 100644
--- a/android/media/PlayerBase.java
+++ b/android/media/PlayerBase.java
@@ -127,8 +127,9 @@ public abstract class PlayerBase {
Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
}
synchronized (mLock) {
+ boolean attributesChanged = (mAttributes != attr);
mAttributes = attr;
- updateAppOpsPlayAudio_sync();
+ updateAppOpsPlayAudio_sync(attributesChanged);
}
}
@@ -200,16 +201,13 @@ public abstract class PlayerBase {
}
void baseSetVolume(float leftVolume, float rightVolume) {
- final boolean hasAppOpsPlayAudio;
+ final boolean isRestricted;
synchronized (mLock) {
mLeftVolume = leftVolume;
mRightVolume = rightVolume;
- hasAppOpsPlayAudio = mHasAppOpsPlayAudio;
- if (isRestricted_sync()) {
- return;
- }
+ isRestricted = isRestricted_sync();
}
- playerSetVolume(!hasAppOpsPlayAudio/*muting*/,
+ playerSetVolume(isRestricted/*muting*/,
leftVolume * mPanMultiplierL, rightVolume * mPanMultiplierR);
}
@@ -250,7 +248,7 @@ public abstract class PlayerBase {
private void updateAppOpsPlayAudio() {
synchronized (mLock) {
- updateAppOpsPlayAudio_sync();
+ updateAppOpsPlayAudio_sync(false);
}
}
@@ -258,7 +256,7 @@ public abstract class PlayerBase {
* To be called whenever a condition that might affect audibility of this player is updated.
* Must be called synchronized on mLock.
*/
- void updateAppOpsPlayAudio_sync() {
+ void updateAppOpsPlayAudio_sync(boolean attributesChanged) {
boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
try {
int mode = AppOpsManager.MODE_IGNORED;
@@ -275,9 +273,10 @@ public abstract class PlayerBase {
// AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
// volume used by the player
try {
- if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
+ if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio ||
+ attributesChanged) {
getService().playerHasOpPlayAudio(mPlayerIId, mHasAppOpsPlayAudio);
- if (mHasAppOpsPlayAudio) {
+ if (!isRestricted_sync()) {
if (DEBUG_APP_OPS) {
Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
+ "/" + mRightVolume);
diff --git a/android/mtp/MtpDatabase.java b/android/mtp/MtpDatabase.java
index 80fd5c03..17b23264 100644
--- a/android/mtp/MtpDatabase.java
+++ b/android/mtp/MtpDatabase.java
@@ -845,6 +845,33 @@ public class MtpDatabase implements AutoCloseable {
return MtpConstants.RESPONSE_OK;
}
+ private int moveObject(int handle, int newParent, String newPath) {
+ String[] whereArgs = new String[] { Integer.toString(handle) };
+
+ // do not allow renaming any of the special subdirectories
+ if (isStorageSubDirectory(newPath)) {
+ return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+ }
+
+ // update database
+ ContentValues values = new ContentValues();
+ values.put(Files.FileColumns.DATA, newPath);
+ values.put(Files.FileColumns.PARENT, newParent);
+ int updated = 0;
+ try {
+ // note - we are relying on a special case in MediaProvider.update() to update
+ // the paths for all children in the case where this is a directory.
+ updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in mMediaProvider.update", e);
+ }
+ if (updated == 0) {
+ Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
+ return MtpConstants.RESPONSE_GENERAL_ERROR;
+ }
+ return MtpConstants.RESPONSE_OK;
+ }
+
private int setObjectProperty(int handle, int property,
long intValue, String stringValue) {
switch (property) {
diff --git a/android/multiuser/BenchmarkRunner.java b/android/multiuser/BenchmarkRunner.java
index c7bebf38..629e6f45 100644
--- a/android/multiuser/BenchmarkRunner.java
+++ b/android/multiuser/BenchmarkRunner.java
@@ -17,13 +17,16 @@ package android.multiuser;
import android.os.Bundle;
import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import java.io.IOException;
import java.util.ArrayList;
// Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java
public class BenchmarkRunner {
- private static long COOL_OFF_PERIOD_MS = 2000;
+ private static final long COOL_OFF_PERIOD_MS = 1000;
private static final int NUM_ITERATIONS = 4;
@@ -70,9 +73,13 @@ public class BenchmarkRunner {
}
private void prepareForNextRun() {
- // TODO: Once http://b/63115387 is fixed, look into using "am wait-for-broadcast-idle"
- // command instead of waiting for a fixed amount of time.
SystemClock.sleep(COOL_OFF_PERIOD_MS);
+ try {
+ UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ .executeShellCommand("am wait-for-broadcast-idle");
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot execute shell command", e);
+ }
mStartTimeNs = System.nanoTime();
mPausedDurationNs = 0;
}
diff --git a/android/net/ConnectivityManager.java b/android/net/ConnectivityManager.java
index 744ee8ed..d7ecc81f 100644
--- a/android/net/ConnectivityManager.java
+++ b/android/net/ConnectivityManager.java
@@ -2078,16 +2078,30 @@ public class ConnectivityManager {
* {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
* due to device configuration.
*
+ * <p>If this app does not have permission to use this API, it will always
+ * return false rather than throw an exception.</p>
+ *
+ * <p>If the device has a hotspot provisioning app, the caller is required to hold the
+ * {@link android.Manifest.permission.TETHER_PRIVILEGED} permission.</p>
+ *
+ * <p>Otherwise, this method requires the caller to hold the ability to modify system
+ * settings as determined by {@link android.provider.Settings.System#canWrite}.</p>
+ *
* @return a boolean - {@code true} indicating Tethering is supported.
*
* {@hide}
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ @RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED,
+ android.Manifest.permission.WRITE_SETTINGS})
public boolean isTetheringSupported() {
+ String pkgName = mContext.getOpPackageName();
try {
- String pkgName = mContext.getOpPackageName();
return mService.isTetheringSupported(pkgName);
+ } catch (SecurityException e) {
+ // This API is not available to this caller, but for backward-compatibility
+ // this will just return false instead of throwing.
+ return false;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/net/IpSecAlgorithm.java b/android/net/IpSecAlgorithm.java
index 5ae34003..79310e29 100644
--- a/android/net/IpSecAlgorithm.java
+++ b/android/net/IpSecAlgorithm.java
@@ -19,15 +19,16 @@ import android.annotation.StringDef;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+
import com.android.internal.util.HexDump;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
/**
* IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
* RFC 4301.
- *
- * @hide
*/
public final class IpSecAlgorithm implements Parcelable {
@@ -75,13 +76,7 @@ public final class IpSecAlgorithm implements Parcelable {
public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
/** @hide */
- @StringDef({
- CRYPT_AES_CBC,
- AUTH_HMAC_MD5,
- AUTH_HMAC_SHA1,
- AUTH_HMAC_SHA256,
- AUTH_HMAC_SHA512
- })
+ @StringDef({CRYPT_AES_CBC, AUTH_HMAC_MD5, AUTH_HMAC_SHA1, AUTH_HMAC_SHA256, AUTH_HMAC_SHA512})
@Retention(RetentionPolicy.SOURCE)
public @interface AlgorithmName {}
@@ -197,4 +192,12 @@ public final class IpSecAlgorithm implements Parcelable {
.append("}")
.toString();
}
+
+ /** package */
+ static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
+ if (lhs == null || rhs == null) return (lhs == rhs);
+ return (lhs.mName.equals(rhs.mName)
+ && Arrays.equals(lhs.mKey, rhs.mKey)
+ && lhs.mTruncLenBits == rhs.mTruncLenBits);
+ }
};
diff --git a/android/net/IpSecConfig.java b/android/net/IpSecConfig.java
index 5a5c740c..632b7fc0 100644
--- a/android/net/IpSecConfig.java
+++ b/android/net/IpSecConfig.java
@@ -17,105 +17,170 @@ package android.net;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+
+import com.android.internal.annotations.VisibleForTesting;
/** @hide */
public final class IpSecConfig implements Parcelable {
private static final String TAG = "IpSecConfig";
- //MODE_TRANSPORT or MODE_TUNNEL
- int mode;
+ // MODE_TRANSPORT or MODE_TUNNEL
+ private int mMode = IpSecTransform.MODE_TRANSPORT;
- // For tunnel mode
- InetAddress localAddress;
+ // Needs to be valid only for tunnel mode
+ // Preventing this from being null simplifies Java->Native binder
+ private String mLocalAddress = "";
- InetAddress remoteAddress;
+ // Preventing this from being null simplifies Java->Native binder
+ private String mRemoteAddress = "";
- // Limit selection by network interface
- Network network;
+ // The underlying Network that represents the "gateway" Network
+ // for outbound packets. It may also be used to select packets.
+ private Network mNetwork;
public static class Flow {
// Minimum requirements for identifying a transform
// SPI identifying the IPsec flow in packet processing
// and a remote IP address
- int spiResourceId;
+ private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
// Encryption Algorithm
- IpSecAlgorithm encryption;
+ private IpSecAlgorithm mEncryption;
// Authentication Algorithm
- IpSecAlgorithm authentication;
+ private IpSecAlgorithm mAuthentication;
@Override
public String toString() {
return new StringBuilder()
- .append("{spiResourceId=")
- .append(spiResourceId)
- .append(", encryption=")
- .append(encryption)
- .append(", authentication=")
- .append(authentication)
+ .append("{mSpiResourceId=")
+ .append(mSpiResourceId)
+ .append(", mEncryption=")
+ .append(mEncryption)
+ .append(", mAuthentication=")
+ .append(mAuthentication)
.append("}")
.toString();
}
+
+ static boolean equals(IpSecConfig.Flow lhs, IpSecConfig.Flow rhs) {
+ if (lhs == null || rhs == null) return (lhs == rhs);
+ return (lhs.mSpiResourceId == rhs.mSpiResourceId
+ && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
+ && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication));
+ }
}
- final Flow[] flow = new Flow[] {new Flow(), new Flow()};
+ private final Flow[] mFlow = new Flow[] {new Flow(), new Flow()};
// For tunnel mode IPv4 UDP Encapsulation
// IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
- int encapType;
- int encapLocalPortResourceId;
- int encapRemotePort;
+ private int mEncapType = IpSecTransform.ENCAP_NONE;
+ private int mEncapSocketResourceId = IpSecManager.INVALID_RESOURCE_ID;
+ private int mEncapRemotePort;
// An interval, in seconds between the NattKeepalive packets
- int nattKeepaliveInterval;
+ private int mNattKeepaliveInterval;
+
+ /** Set the mode for this IPsec transform */
+ public void setMode(int mode) {
+ mMode = mode;
+ }
+
+ /** Set the local IP address for Tunnel mode */
+ public void setLocalAddress(String localAddress) {
+ if (localAddress == null) {
+ throw new IllegalArgumentException("localAddress may not be null!");
+ }
+ mLocalAddress = localAddress;
+ }
+
+ /** Set the remote IP address for this IPsec transform */
+ public void setRemoteAddress(String remoteAddress) {
+ if (remoteAddress == null) {
+ throw new IllegalArgumentException("remoteAddress may not be null!");
+ }
+ mRemoteAddress = remoteAddress;
+ }
+
+ /** Set the SPI for a given direction by resource ID */
+ public void setSpiResourceId(int direction, int resourceId) {
+ mFlow[direction].mSpiResourceId = resourceId;
+ }
+
+ /** Set the encryption algorithm for a given direction */
+ public void setEncryption(int direction, IpSecAlgorithm encryption) {
+ mFlow[direction].mEncryption = encryption;
+ }
+
+ /** Set the authentication algorithm for a given direction */
+ public void setAuthentication(int direction, IpSecAlgorithm authentication) {
+ mFlow[direction].mAuthentication = authentication;
+ }
+
+ public void setNetwork(Network network) {
+ mNetwork = network;
+ }
+
+ public void setEncapType(int encapType) {
+ mEncapType = encapType;
+ }
+
+ public void setEncapSocketResourceId(int resourceId) {
+ mEncapSocketResourceId = resourceId;
+ }
+
+ public void setEncapRemotePort(int port) {
+ mEncapRemotePort = port;
+ }
+
+ public void setNattKeepaliveInterval(int interval) {
+ mNattKeepaliveInterval = interval;
+ }
// Transport or Tunnel
public int getMode() {
- return mode;
+ return mMode;
}
- public InetAddress getLocalAddress() {
- return localAddress;
+ public String getLocalAddress() {
+ return mLocalAddress;
}
public int getSpiResourceId(int direction) {
- return flow[direction].spiResourceId;
+ return mFlow[direction].mSpiResourceId;
}
- public InetAddress getRemoteAddress() {
- return remoteAddress;
+ public String getRemoteAddress() {
+ return mRemoteAddress;
}
public IpSecAlgorithm getEncryption(int direction) {
- return flow[direction].encryption;
+ return mFlow[direction].mEncryption;
}
public IpSecAlgorithm getAuthentication(int direction) {
- return flow[direction].authentication;
+ return mFlow[direction].mAuthentication;
}
public Network getNetwork() {
- return network;
+ return mNetwork;
}
public int getEncapType() {
- return encapType;
+ return mEncapType;
}
- public int getEncapLocalResourceId() {
- return encapLocalPortResourceId;
+ public int getEncapSocketResourceId() {
+ return mEncapSocketResourceId;
}
public int getEncapRemotePort() {
- return encapRemotePort;
+ return mEncapRemotePort;
}
public int getNattKeepaliveInterval() {
- return nattKeepaliveInterval;
+ return mNattKeepaliveInterval;
}
// Parcelable Methods
@@ -127,82 +192,70 @@ public final class IpSecConfig implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
- // TODO: Use a byte array or other better method for storing IPs that can also include scope
- out.writeString((localAddress != null) ? localAddress.getHostAddress() : null);
- // TODO: Use a byte array or other better method for storing IPs that can also include scope
- out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null);
- out.writeParcelable(network, flags);
- out.writeInt(flow[IpSecTransform.DIRECTION_IN].spiResourceId);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags);
- out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spiResourceId);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags);
- out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags);
- out.writeInt(encapType);
- out.writeInt(encapLocalPortResourceId);
- out.writeInt(encapRemotePort);
- }
-
- // Package Private: Used by the IpSecTransform.Builder;
- // there should be no public constructor for this object
- IpSecConfig() {}
-
- private static InetAddress readInetAddressFromParcel(Parcel in) {
- String addrString = in.readString();
- if (addrString == null) {
- return null;
- }
- try {
- return InetAddress.getByName(addrString);
- } catch (UnknownHostException e) {
- Log.wtf(TAG, "Invalid IpAddress " + addrString);
- return null;
- }
+ out.writeInt(mMode);
+ out.writeString(mLocalAddress);
+ out.writeString(mRemoteAddress);
+ out.writeParcelable(mNetwork, flags);
+ out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId);
+ out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags);
+ out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags);
+ out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId);
+ out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags);
+ out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags);
+ out.writeInt(mEncapType);
+ out.writeInt(mEncapSocketResourceId);
+ out.writeInt(mEncapRemotePort);
+ out.writeInt(mNattKeepaliveInterval);
}
+ @VisibleForTesting
+ public IpSecConfig() {}
+
private IpSecConfig(Parcel in) {
- localAddress = readInetAddressFromParcel(in);
- remoteAddress = readInetAddressFromParcel(in);
- network = (Network) in.readParcelable(Network.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_IN].spiResourceId = in.readInt();
- flow[IpSecTransform.DIRECTION_IN].encryption =
+ mMode = in.readInt();
+ mLocalAddress = in.readString();
+ mRemoteAddress = in.readString();
+ mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
+ mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId = in.readInt();
+ mFlow[IpSecTransform.DIRECTION_IN].mEncryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_IN].authentication =
+ mFlow[IpSecTransform.DIRECTION_IN].mAuthentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_OUT].spiResourceId = in.readInt();
- flow[IpSecTransform.DIRECTION_OUT].encryption =
+ mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt();
+ mFlow[IpSecTransform.DIRECTION_OUT].mEncryption =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- flow[IpSecTransform.DIRECTION_OUT].authentication =
+ mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication =
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
- encapType = in.readInt();
- encapLocalPortResourceId = in.readInt();
- encapRemotePort = in.readInt();
+ mEncapType = in.readInt();
+ mEncapSocketResourceId = in.readInt();
+ mEncapRemotePort = in.readInt();
+ mNattKeepaliveInterval = in.readInt();
}
@Override
public String toString() {
StringBuilder strBuilder = new StringBuilder();
strBuilder
- .append("{mode=")
- .append(mode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
- .append(", localAddress=")
- .append(localAddress)
- .append(", remoteAddress=")
- .append(remoteAddress)
- .append(", network=")
- .append(network)
- .append(", encapType=")
- .append(encapType)
- .append(", encapLocalPortResourceId=")
- .append(encapLocalPortResourceId)
- .append(", encapRemotePort=")
- .append(encapRemotePort)
- .append(", nattKeepaliveInterval=")
- .append(nattKeepaliveInterval)
- .append(", flow[OUT]=")
- .append(flow[IpSecTransform.DIRECTION_OUT])
- .append(", flow[IN]=")
- .append(flow[IpSecTransform.DIRECTION_IN])
+ .append("{mMode=")
+ .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
+ .append(", mLocalAddress=")
+ .append(mLocalAddress)
+ .append(", mRemoteAddress=")
+ .append(mRemoteAddress)
+ .append(", mNetwork=")
+ .append(mNetwork)
+ .append(", mEncapType=")
+ .append(mEncapType)
+ .append(", mEncapSocketResourceId=")
+ .append(mEncapSocketResourceId)
+ .append(", mEncapRemotePort=")
+ .append(mEncapRemotePort)
+ .append(", mNattKeepaliveInterval=")
+ .append(mNattKeepaliveInterval)
+ .append(", mFlow[OUT]=")
+ .append(mFlow[IpSecTransform.DIRECTION_OUT])
+ .append(", mFlow[IN]=")
+ .append(mFlow[IpSecTransform.DIRECTION_IN])
.append("}");
return strBuilder.toString();
@@ -218,4 +271,23 @@ public final class IpSecConfig implements Parcelable {
return new IpSecConfig[size];
}
};
+
+ @VisibleForTesting
+ /** Equals method used for testing */
+ public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) {
+ if (lhs == null || rhs == null) return (lhs == rhs);
+ return (lhs.mMode == rhs.mMode
+ && lhs.mLocalAddress.equals(rhs.mLocalAddress)
+ && lhs.mRemoteAddress.equals(rhs.mRemoteAddress)
+ && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork))
+ || (lhs.mNetwork == rhs.mNetwork))
+ && lhs.mEncapType == rhs.mEncapType
+ && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId
+ && lhs.mEncapRemotePort == rhs.mEncapRemotePort
+ && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
+ && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_OUT],
+ rhs.mFlow[IpSecTransform.DIRECTION_OUT])
+ && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_IN],
+ rhs.mFlow[IpSecTransform.DIRECTION_IN]));
+ }
}
diff --git a/android/net/IpSecManager.java b/android/net/IpSecManager.java
index 2f791e15..d7b32561 100644
--- a/android/net/IpSecManager.java
+++ b/android/net/IpSecManager.java
@@ -25,7 +25,11 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.AndroidException;
import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
import dalvik.system.CloseGuard;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramSocket;
@@ -36,7 +40,9 @@ import java.net.Socket;
* This class contains methods for managing IPsec sessions, which will perform kernel-space
* encryption and decryption of socket or Network traffic.
*
- * @hide
+ * <p>An IpSecManager may be obtained by calling {@link
+ * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
+ * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
*/
@SystemService(Context.IPSEC_SERVICE)
public final class IpSecManager {
@@ -184,7 +190,8 @@ public final class IpSecManager {
}
/** @hide */
- int getResourceId() {
+ @VisibleForTesting
+ public int getResourceId() {
return mResourceId;
}
}
@@ -485,7 +492,8 @@ public final class IpSecManager {
}
/** @hide */
- int getResourceId() {
+ @VisibleForTesting
+ public int getResourceId() {
return mResourceId;
}
};
diff --git a/android/net/IpSecTransform.java b/android/net/IpSecTransform.java
index cfbac58b..e15a2c67 100644
--- a/android/net/IpSecTransform.java
+++ b/android/net/IpSecTransform.java
@@ -26,9 +26,12 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+
import dalvik.system.CloseGuard;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -43,8 +46,6 @@ import java.net.InetAddress;
*
* <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
* of traffic or may represent a transport mode transform operating on a Socket or Sockets.
- *
- * @hide
*/
public final class IpSecTransform implements AutoCloseable {
private static final String TAG = "IpSecTransform";
@@ -67,10 +68,10 @@ public final class IpSecTransform implements AutoCloseable {
public @interface TransformDirection {}
/** @hide */
- public static final int MODE_TUNNEL = 0;
+ public static final int MODE_TRANSPORT = 0;
/** @hide */
- public static final int MODE_TRANSPORT = 1;
+ public static final int MODE_TUNNEL = 1;
/** @hide */
public static final int ENCAP_NONE = 0;
@@ -112,7 +113,11 @@ public final class IpSecTransform implements AutoCloseable {
return IIpSecService.Stub.asInterface(b);
}
- private void checkResultStatusAndThrow(int status)
+ /**
+ * Checks the result status and throws an appropriate exception if
+ * the status is not Status.OK.
+ */
+ private void checkResultStatus(int status)
throws IOException, IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException {
switch (status) {
@@ -140,7 +145,7 @@ public final class IpSecTransform implements AutoCloseable {
IpSecTransformResponse result =
svc.createTransportModeTransform(mConfig, new Binder());
int status = result.status;
- checkResultStatusAndThrow(status);
+ checkResultStatus(status);
mResourceId = result.resourceId;
/* Keepalive will silently fail if not needed by the config; but, if needed and
@@ -242,61 +247,20 @@ public final class IpSecTransform implements AutoCloseable {
/* Package */
void startKeepalive(Context c) {
- // FIXME: NO_KEEPALIVE needs to be a constant
- if (mConfig.getNattKeepaliveInterval() == 0) {
- return;
- }
-
- ConnectivityManager cm =
- (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
-
- if (mKeepalive != null) {
- Log.wtf(TAG, "Keepalive already started for this IpSecTransform.");
- return;
- }
-
- synchronized (mKeepaliveSyncLock) {
- mKeepalive =
- cm.startNattKeepalive(
- mConfig.getNetwork(),
- mConfig.getNattKeepaliveInterval(),
- mKeepaliveCallback,
- mConfig.getLocalAddress(),
- 0x1234, /* FIXME: get the real port number again,
- which we need to retrieve from the provided
- EncapsulationSocket, and which isn't currently
- stashed in IpSecConfig */
- mConfig.getRemoteAddress());
- try {
- // FIXME: this is still a horrible way to fudge the synchronous callback
- mKeepaliveSyncLock.wait(2000);
- } catch (InterruptedException e) {
- }
- }
- if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) {
- throw new UnsupportedOperationException("Packet Keepalive cannot be started");
+ if (mConfig.getNattKeepaliveInterval() != 0) {
+ Log.wtf(TAG, "Keepalive not yet supported.");
}
}
- /* Package */
- int getResourceId() {
+ /** @hide */
+ @VisibleForTesting
+ public int getResourceId() {
return mResourceId;
}
/* Package */
void stopKeepalive() {
- if (mKeepalive == null) {
- return;
- }
- mKeepalive.stop();
- synchronized (mKeepaliveSyncLock) {
- if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) {
- try {
- mKeepaliveSyncLock.wait(2000);
- } catch (InterruptedException e) {
- }
- }
- }
+ return;
}
/**
@@ -322,7 +286,7 @@ public final class IpSecTransform implements AutoCloseable {
*/
public IpSecTransform.Builder setEncryption(
@TransformDirection int direction, IpSecAlgorithm algo) {
- mConfig.flow[direction].encryption = algo;
+ mConfig.setEncryption(direction, algo);
return this;
}
@@ -337,7 +301,7 @@ public final class IpSecTransform implements AutoCloseable {
*/
public IpSecTransform.Builder setAuthentication(
@TransformDirection int direction, IpSecAlgorithm algo) {
- mConfig.flow[direction].authentication = algo;
+ mConfig.setAuthentication(direction, algo);
return this;
}
@@ -360,9 +324,7 @@ public final class IpSecTransform implements AutoCloseable {
*/
public IpSecTransform.Builder setSpi(
@TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
- // TODO: convert to using the resource Id of the SPI. Then build() can validate
- // the owner in the IpSecService
- mConfig.flow[direction].spiResourceId = spi.getResourceId();
+ mConfig.setSpiResourceId(direction, spi.getResourceId());
return this;
}
@@ -377,7 +339,7 @@ public final class IpSecTransform implements AutoCloseable {
*/
@SystemApi
public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
- mConfig.network = net;
+ mConfig.setNetwork(net);
return this;
}
@@ -394,10 +356,9 @@ public final class IpSecTransform implements AutoCloseable {
*/
public IpSecTransform.Builder setIpv4Encapsulation(
IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
- // TODO: check encap type is valid.
- mConfig.encapType = ENCAP_ESPINUDP;
- mConfig.encapLocalPortResourceId = localSocket.getResourceId();
- mConfig.encapRemotePort = remotePort;
+ mConfig.setEncapType(ENCAP_ESPINUDP);
+ mConfig.setEncapSocketResourceId(localSocket.getResourceId());
+ mConfig.setEncapRemotePort(remotePort);
return this;
}
@@ -415,7 +376,7 @@ public final class IpSecTransform implements AutoCloseable {
*/
@SystemApi
public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
- mConfig.nattKeepaliveInterval = intervalSeconds;
+ mConfig.setNattKeepaliveInterval(intervalSeconds);
return this;
}
@@ -448,10 +409,8 @@ public final class IpSecTransform implements AutoCloseable {
public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
throws IpSecManager.ResourceUnavailableException,
IpSecManager.SpiUnavailableException, IOException {
- //FIXME: argument validation here
- //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
- mConfig.mode = MODE_TRANSPORT;
- mConfig.remoteAddress = remoteAddress;
+ mConfig.setMode(MODE_TRANSPORT);
+ mConfig.setRemoteAddress(remoteAddress.getHostAddress());
return new IpSecTransform(mContext, mConfig).activate();
}
@@ -472,9 +431,9 @@ public final class IpSecTransform implements AutoCloseable {
InetAddress localAddress, InetAddress remoteAddress) {
//FIXME: argument validation here
//throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
- mConfig.localAddress = localAddress;
- mConfig.remoteAddress = remoteAddress;
- mConfig.mode = MODE_TUNNEL;
+ mConfig.setLocalAddress(localAddress.getHostAddress());
+ mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+ mConfig.setMode(MODE_TUNNEL);
return new IpSecTransform(mContext, mConfig);
}
@@ -488,14 +447,5 @@ public final class IpSecTransform implements AutoCloseable {
mContext = context;
mConfig = new IpSecConfig();
}
-
- /**
- * Return an {@link IpSecConfig} object for testing purposes.
- * @hide
- */
- @VisibleForTesting
- public IpSecConfig getIpSecConfig() {
- return mConfig;
- }
}
}
diff --git a/android/net/LocalServerSocket.java b/android/net/LocalServerSocket.java
index 3fcde330..d1f49d20 100644
--- a/android/net/LocalServerSocket.java
+++ b/android/net/LocalServerSocket.java
@@ -16,14 +16,15 @@
package android.net;
-import java.io.IOException;
+import java.io.Closeable;
import java.io.FileDescriptor;
+import java.io.IOException;
/**
* Non-standard class for creating an inbound UNIX-domain socket
* in the Linux abstract namespace.
*/
-public class LocalServerSocket {
+public class LocalServerSocket implements Closeable {
private final LocalSocketImpl impl;
private final LocalSocketAddress localAddress;
@@ -106,7 +107,7 @@ public class LocalServerSocket {
*
* @throws IOException
*/
- public void close() throws IOException
+ @Override public void close() throws IOException
{
impl.close();
}
diff --git a/android/net/ip/ConnectivityPacketTracker.java b/android/net/ip/ConnectivityPacketTracker.java
index 884a8a75..0230f36b 100644
--- a/android/net/ip/ConnectivityPacketTracker.java
+++ b/android/net/ip/ConnectivityPacketTracker.java
@@ -61,11 +61,11 @@ public class ConnectivityPacketTracker {
private static final String MARK_STOP = "--- STOP ---";
private final String mTag;
- private final Handler mHandler;
private final LocalLog mLog;
private final BlockingSocketReader mPacketListener;
+ private boolean mRunning;
- public ConnectivityPacketTracker(NetworkInterface netif, LocalLog log) {
+ public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) {
final String ifname;
final int ifindex;
final byte[] hwaddr;
@@ -81,44 +81,40 @@ public class ConnectivityPacketTracker {
}
mTag = TAG + "." + ifname;
- mHandler = new Handler();
mLog = log;
- mPacketListener = new PacketListener(ifindex, hwaddr, mtu);
+ mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu);
}
public void start() {
- mLog.log(MARK_START);
+ mRunning = true;
mPacketListener.start();
}
public void stop() {
mPacketListener.stop();
- mLog.log(MARK_STOP);
+ mRunning = false;
}
private final class PacketListener extends BlockingSocketReader {
private final int mIfIndex;
private final byte mHwAddr[];
- PacketListener(int ifindex, byte[] hwaddr, int mtu) {
- super(mtu);
+ PacketListener(Handler h, int ifindex, byte[] hwaddr, int mtu) {
+ super(h, mtu);
mIfIndex = ifindex;
mHwAddr = hwaddr;
}
@Override
- protected FileDescriptor createSocket() {
+ protected FileDescriptor createFd() {
FileDescriptor s = null;
try {
- // TODO: Evaluate switching to SOCK_DGRAM and changing the
- // BlockingSocketReader's read() to recvfrom(), so that this
- // might work on non-ethernet-like links (via SLL).
s = Os.socket(AF_PACKET, SOCK_RAW, 0);
NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex));
} catch (ErrnoException | IOException e) {
logError("Failed to create packet tracking socket: ", e);
- closeSocket(s);
+ closeFd(s);
return null;
}
return s;
@@ -136,13 +132,27 @@ public class ConnectivityPacketTracker {
}
@Override
+ protected void onStart() {
+ mLog.log(MARK_START);
+ }
+
+ @Override
+ protected void onStop() {
+ if (mRunning) {
+ mLog.log(MARK_STOP);
+ } else {
+ mLog.log(MARK_STOP + " (packet listener stopped unexpectedly)");
+ }
+ }
+
+ @Override
protected void logError(String msg, Exception e) {
Log.e(mTag, msg, e);
addLogEntry(msg + e);
}
private void addLogEntry(String entry) {
- mHandler.post(() -> mLog.log(entry));
+ mLog.log(entry);
}
}
}
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java
index b1eb0854..bc07b810 100644
--- a/android/net/ip/IpManager.java
+++ b/android/net/ip/IpManager.java
@@ -1515,7 +1515,8 @@ public class IpManager extends StateMachine {
private ConnectivityPacketTracker createPacketTracker() {
try {
- return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog);
+ return new ConnectivityPacketTracker(
+ getHandler(), mNetworkInterface, mConnectivityPacketLog);
} catch (IllegalArgumentException e) {
return null;
}
diff --git a/android/net/metrics/WakeupStats.java b/android/net/metrics/WakeupStats.java
index d520b974..97e83f96 100644
--- a/android/net/metrics/WakeupStats.java
+++ b/android/net/metrics/WakeupStats.java
@@ -35,7 +35,7 @@ public class WakeupStats {
public long systemWakeups = 0;
public long nonApplicationWakeups = 0;
public long applicationWakeups = 0;
- public long unroutedWakeups = 0;
+ public long noUidWakeups = 0;
public long durationSec = 0;
public WakeupStats(String iface) {
@@ -58,7 +58,7 @@ public class WakeupStats {
systemWakeups++;
break;
case NO_UID:
- unroutedWakeups++;
+ noUidWakeups++;
break;
default:
if (ev.uid >= Process.FIRST_APPLICATION_UID) {
@@ -80,7 +80,7 @@ public class WakeupStats {
.append(", system: ").append(systemWakeups)
.append(", apps: ").append(applicationWakeups)
.append(", non-apps: ").append(nonApplicationWakeups)
- .append(", unrouted: ").append(unroutedWakeups)
+ .append(", no uid: ").append(noUidWakeups)
.append(", ").append(durationSec).append("s)")
.toString();
}
diff --git a/android/net/util/BlockingSocketReader.java b/android/net/util/BlockingSocketReader.java
index 12fa1e57..99bf4695 100644
--- a/android/net/util/BlockingSocketReader.java
+++ b/android/net/util/BlockingSocketReader.java
@@ -16,81 +16,106 @@
package android.net.util;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+
import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
-import libcore.io.IoBridge;
+import libcore.io.IoUtils;
import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
import java.io.IOException;
/**
- * A thread that reads from a socket and passes the received packets to a
- * subclass's handlePacket() method. The packet receive buffer is recycled
- * on every read call, so subclasses should make any copies they would like
- * inside their handlePacket() implementation.
+ * This class encapsulates the mechanics of registering a file descriptor
+ * with a thread's Looper and handling read events (and errors).
+ *
+ * Subclasses MUST implement createFd() and SHOULD override handlePacket().
+
+ * Subclasses can expect a call life-cycle like the following:
+ *
+ * [1] start() calls createFd() and (if all goes well) onStart()
+ *
+ * [2] yield, waiting for read event or error notification:
+ *
+ * [a] readPacket() && handlePacket()
*
- * All public methods may be called from any thread.
+ * [b] if (no error):
+ * goto 2
+ * else:
+ * goto 3
+ *
+ * [3] stop() calls onStop() if not previously stopped
+ *
+ * The packet receive buffer is recycled on every read call, so subclasses
+ * should make any copies they would like inside their handlePacket()
+ * implementation.
+ *
+ * All public methods MUST only be called from the same thread with which
+ * the Handler constructor argument is associated.
+ *
+ * TODO: rename this class to something more correctly descriptive (something
+ * like [or less horrible than] FdReadEventsHandler?).
*
* @hide
*/
public abstract class BlockingSocketReader {
+ private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+ private static final int UNREGISTER_THIS_FD = 0;
+
public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
+ private final Handler mHandler;
+ private final MessageQueue mQueue;
private final byte[] mPacket;
- private final Thread mThread;
- private volatile FileDescriptor mSocket;
- private volatile boolean mRunning;
- private volatile long mPacketsReceived;
-
- // Make it slightly easier for subclasses to properly close a socket
- // without having to know this incantation.
- public static final void closeSocket(@Nullable FileDescriptor fd) {
- try {
- IoBridge.closeAndSignalBlockedThreads(fd);
- } catch (IOException ignored) {}
- }
+ private FileDescriptor mFd;
+ private long mPacketsReceived;
- protected BlockingSocketReader() {
- this(DEFAULT_RECV_BUF_SIZE);
+ protected static void closeFd(FileDescriptor fd) {
+ IoUtils.closeQuietly(fd);
}
- protected BlockingSocketReader(int recvbufsize) {
- if (recvbufsize < DEFAULT_RECV_BUF_SIZE) {
- recvbufsize = DEFAULT_RECV_BUF_SIZE;
- }
- mPacket = new byte[recvbufsize];
- mThread = new Thread(() -> { mainLoop(); });
+ protected BlockingSocketReader(Handler h) {
+ this(h, DEFAULT_RECV_BUF_SIZE);
}
- public final boolean start() {
- if (mSocket != null) return false;
+ protected BlockingSocketReader(Handler h, int recvbufsize) {
+ mHandler = h;
+ mQueue = mHandler.getLooper().getQueue();
+ mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
+ }
- try {
- mSocket = createSocket();
- } catch (Exception e) {
- logError("Failed to create socket: ", e);
- return false;
+ public final void start() {
+ if (onCorrectThread()) {
+ createAndRegisterFd();
+ } else {
+ mHandler.post(() -> {
+ logError("start() called from off-thread", null);
+ createAndRegisterFd();
+ });
}
-
- if (mSocket == null) return false;
-
- mRunning = true;
- mThread.start();
- return true;
}
public final void stop() {
- mRunning = false;
- closeSocket(mSocket);
- mSocket = null;
+ if (onCorrectThread()) {
+ unregisterAndDestroyFd();
+ } else {
+ mHandler.post(() -> {
+ logError("stop() called from off-thread", null);
+ unregisterAndDestroyFd();
+ });
+ }
}
- public final boolean isRunning() { return mRunning; }
+ public final int recvBufSize() { return mPacket.length; }
public final long numPacketsReceived() { return mPacketsReceived; }
@@ -98,11 +123,21 @@ public abstract class BlockingSocketReader {
* Subclasses MUST create the listening socket here, including setting
* all desired socket options, interface or address/port binding, etc.
*/
- protected abstract FileDescriptor createSocket();
+ protected abstract FileDescriptor createFd();
+
+ /**
+ * Subclasses MAY override this to change the default read() implementation
+ * in favour of, say, recvfrom().
+ *
+ * Implementations MUST return the bytes read or throw an Exception.
+ */
+ protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
+ return Os.read(fd, packetBuffer, 0, packetBuffer.length);
+ }
/**
* Called by the main loop for every packet. Any desired copies of
- * |recvbuf| should be made in here, and the underlying byte array is
+ * |recvbuf| should be made in here, as the underlying byte array is
* reused across all reads.
*/
protected void handlePacket(byte[] recvbuf, int length) {}
@@ -113,43 +148,102 @@ public abstract class BlockingSocketReader {
protected void logError(String msg, Exception e) {}
/**
- * Called by the main loop just prior to exiting.
+ * Called by start(), if successful, just prior to returning.
+ */
+ protected void onStart() {}
+
+ /**
+ * Called by stop() just prior to returning.
*/
- protected void onExit() {}
+ protected void onStop() {}
+
+ private void createAndRegisterFd() {
+ if (mFd != null) return;
+
+ try {
+ mFd = createFd();
+ if (mFd != null) {
+ // Force the socket to be non-blocking.
+ IoUtils.setBlocking(mFd, false);
+ }
+ } catch (Exception e) {
+ logError("Failed to create socket: ", e);
+ closeFd(mFd);
+ mFd = null;
+ return;
+ }
+
+ if (mFd == null) return;
+
+ mQueue.addOnFileDescriptorEventListener(
+ mFd,
+ FD_EVENTS,
+ new OnFileDescriptorEventListener() {
+ @Override
+ public int onFileDescriptorEvents(FileDescriptor fd, int events) {
+ // Always call handleInput() so read/recvfrom are given
+ // a proper chance to encounter a meaningful errno and
+ // perhaps log a useful error message.
+ if (!isRunning() || !handleInput()) {
+ unregisterAndDestroyFd();
+ return UNREGISTER_THIS_FD;
+ }
+ return FD_EVENTS;
+ }
+ });
+ onStart();
+ }
- private final void mainLoop() {
+ private boolean isRunning() { return (mFd != null) && mFd.valid(); }
+
+ // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
+ private boolean handleInput() {
while (isRunning()) {
final int bytesRead;
try {
- // Blocking read.
- // TODO: See if this can be converted to recvfrom.
- bytesRead = Os.read(mSocket, mPacket, 0, mPacket.length);
+ bytesRead = readPacket(mFd, mPacket);
if (bytesRead < 1) {
if (isRunning()) logError("Socket closed, exiting", null);
break;
}
mPacketsReceived++;
} catch (ErrnoException e) {
- if (e.errno != OsConstants.EINTR) {
- if (isRunning()) logError("read error: ", e);
+ if (e.errno == OsConstants.EAGAIN) {
+ // We've read everything there is to read this time around.
+ return true;
+ } else if (e.errno == OsConstants.EINTR) {
+ continue;
+ } else {
+ if (isRunning()) logError("readPacket error: ", e);
break;
}
- continue;
- } catch (IOException ioe) {
- if (isRunning()) logError("read error: ", ioe);
- continue;
+ } catch (Exception e) {
+ if (isRunning()) logError("readPacket error: ", e);
+ break;
}
try {
handlePacket(mPacket, bytesRead);
} catch (Exception e) {
- logError("Unexpected exception: ", e);
+ logError("handlePacket error: ", e);
break;
}
}
- stop();
- onExit();
+ return false;
+ }
+
+ private void unregisterAndDestroyFd() {
+ if (mFd == null) return;
+
+ mQueue.removeOnFileDescriptorEventListener(mFd);
+ closeFd(mFd);
+ mFd = null;
+ onStop();
+ }
+
+ private boolean onCorrectThread() {
+ return (mHandler.getLooper() == Looper.myLooper());
}
}
diff --git a/android/net/util/NetworkConstants.java b/android/net/util/NetworkConstants.java
index 60652681..5a3a8be9 100644
--- a/android/net/util/NetworkConstants.java
+++ b/android/net/util/NetworkConstants.java
@@ -107,6 +107,20 @@ public final class NetworkConstants {
public static final int RFC6177_MIN_PREFIX_LENGTH = 48;
/**
+ * ICMP common (v4/v6) constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc792
+ * - https://tools.ietf.org/html/rfc4443
+ */
+ public static final int ICMP_HEADER_TYPE_OFFSET = 0;
+ public static final int ICMP_HEADER_CODE_OFFSET = 1;
+ public static final int ICMP_HEADER_CHECKSUM_OFFSET = 2;
+ public static final int ICMP_ECHO_IDENTIFIER_OFFSET = 4;
+ public static final int ICMP_ECHO_SEQUENCE_NUMBER_OFFSET = 6;
+ public static final int ICMP_ECHO_DATA_OFFSET = 8;
+
+ /**
* ICMPv6 constants.
*
* See also:
diff --git a/android/net/wifi/WifiConfiguration.java b/android/net/wifi/WifiConfiguration.java
index a1453278..6438631c 100644
--- a/android/net/wifi/WifiConfiguration.java
+++ b/android/net/wifi/WifiConfiguration.java
@@ -790,6 +790,28 @@ public class WifiConfiguration implements Parcelable {
/**
* @hide
+ * Returns true if this WiFi config is for an open network.
+ */
+ public boolean isOpenNetwork() {
+ final int cardinality = allowedKeyManagement.cardinality();
+ final boolean hasNoKeyMgmt = cardinality == 0
+ || (cardinality == 1 && allowedKeyManagement.get(KeyMgmt.NONE));
+
+ boolean hasNoWepKeys = true;
+ if (wepKeys != null) {
+ for (int i = 0; i < wepKeys.length; i++) {
+ if (wepKeys[i] != null) {
+ hasNoWepKeys = false;
+ break;
+ }
+ }
+ }
+
+ return hasNoKeyMgmt && hasNoWepKeys;
+ }
+
+ /**
+ * @hide
* Setting this value will force scan results associated with this configuration to
* be included in the bucket of networks that are externally scored.
* If not set, associated scan results will be treated as legacy saved networks and
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index 9c8ea88c..b08b4b7c 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -967,8 +967,7 @@ public class WifiManager {
* <li>allowedGroupCiphers</li>
* </ul>
* @return a list of network configurations in the form of a list
- * of {@link WifiConfiguration} objects. Upon failure to fetch or
- * when Wi-Fi is turned off, it can be null.
+ * of {@link WifiConfiguration} objects.
*/
public List<WifiConfiguration> getConfiguredNetworks() {
try {
diff --git a/android/net/wifi/WifiScanner.java b/android/net/wifi/WifiScanner.java
index f47d5caf..e3752ac7 100644
--- a/android/net/wifi/WifiScanner.java
+++ b/android/net/wifi/WifiScanner.java
@@ -1105,8 +1105,6 @@ public class WifiScanner {
private static final int BASE = Protocol.BASE_WIFI_SCANNER;
/** @hide */
- public static final int CMD_SCAN = BASE + 0;
- /** @hide */
public static final int CMD_START_BACKGROUND_SCAN = BASE + 2;
/** @hide */
public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3;
@@ -1115,20 +1113,10 @@ public class WifiScanner {
/** @hide */
public static final int CMD_SCAN_RESULT = BASE + 5;
/** @hide */
- public static final int CMD_AP_FOUND = BASE + 9;
- /** @hide */
- public static final int CMD_AP_LOST = BASE + 10;
- /** @hide */
- public static final int CMD_WIFI_CHANGE_DETECTED = BASE + 15;
- /** @hide */
- public static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 16;
- /** @hide */
public static final int CMD_OP_SUCCEEDED = BASE + 17;
/** @hide */
public static final int CMD_OP_FAILED = BASE + 18;
/** @hide */
- public static final int CMD_PERIOD_CHANGED = BASE + 19;
- /** @hide */
public static final int CMD_FULL_SCAN_RESULT = BASE + 20;
/** @hide */
public static final int CMD_START_SINGLE_SCAN = BASE + 21;
@@ -1359,25 +1347,6 @@ public class WifiScanner {
ScanResult result = (ScanResult) msg.obj;
((ScanListener) listener).onFullResult(result);
return;
- case CMD_PERIOD_CHANGED:
- ((ScanListener) listener).onPeriodChanged(msg.arg1);
- return;
- case CMD_AP_FOUND:
- ((BssidListener) listener).onFound(
- ((ParcelableScanResults) msg.obj).getResults());
- return;
- case CMD_AP_LOST:
- ((BssidListener) listener).onLost(
- ((ParcelableScanResults) msg.obj).getResults());
- return;
- case CMD_WIFI_CHANGE_DETECTED:
- ((WifiChangeListener) listener).onChanging(
- ((ParcelableScanResults) msg.obj).getResults());
- return;
- case CMD_WIFI_CHANGES_STABILIZED:
- ((WifiChangeListener) listener).onQuiescence(
- ((ParcelableScanResults) msg.obj).getResults());
- return;
case CMD_SINGLE_SCAN_COMPLETED:
if (DBG) Log.d(TAG, "removing listener for single scan");
removeListener(msg.arg2);
diff --git a/android/net/wifi/aware/DiscoverySession.java b/android/net/wifi/aware/DiscoverySession.java
index 357f76e3..9f736223 100644
--- a/android/net/wifi/aware/DiscoverySession.java
+++ b/android/net/wifi/aware/DiscoverySession.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.NetworkSpecifier;
-import android.net.wifi.RttManager;
import android.util.Log;
import dalvik.system.CloseGuard;
@@ -224,37 +223,6 @@ public class DiscoverySession implements AutoCloseable {
}
/**
- * Start a ranging operation with the specified peers. The peer IDs are obtained from an
- * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
- * byte[], java.util.List)} or
- * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
- * byte[])} operation - can
- * only range devices which are part of an ongoing discovery session.
- *
- * @param params RTT parameters - each corresponding to a specific peer ID (the array sizes
- * must be identical). The
- * {@link android.net.wifi.RttManager.RttParams#bssid} member must be set to
- * a peer ID - not to a MAC address.
- * @param listener The listener to receive the results of the ranging session.
- * @hide
- * [TODO: b/28847998 - track RTT API & visilibity]
- */
- public void startRanging(RttManager.RttParams[] params, RttManager.RttListener listener) {
- if (mTerminated) {
- Log.w(TAG, "startRanging: called on terminated session");
- return;
- }
-
- WifiAwareManager mgr = mMgr.get();
- if (mgr == null) {
- Log.w(TAG, "startRanging: called post GC on WifiAwareManager");
- return;
- }
-
- mgr.startRanging(mClientId, mSessionId, params, listener);
- }
-
- /**
* Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
* an unencrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
diff --git a/android/net/wifi/aware/PeerHandle.java b/android/net/wifi/aware/PeerHandle.java
index cd45c524..1b0aba15 100644
--- a/android/net/wifi/aware/PeerHandle.java
+++ b/android/net/wifi/aware/PeerHandle.java
@@ -32,4 +32,24 @@ public class PeerHandle {
/** @hide */
public int peerId;
+
+ /** @hide RTT_API */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof PeerHandle)) {
+ return false;
+ }
+
+ return peerId == ((PeerHandle) o).peerId;
+ }
+
+ /** @hide RTT_API */
+ @Override
+ public int hashCode() {
+ return peerId;
+ }
}
diff --git a/android/net/wifi/aware/WifiAwareManager.java b/android/net/wifi/aware/WifiAwareManager.java
index df0d9d23..ed6804d5 100644
--- a/android/net/wifi/aware/WifiAwareManager.java
+++ b/android/net/wifi/aware/WifiAwareManager.java
@@ -26,7 +26,6 @@ import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
-import android.net.wifi.RttManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -35,9 +34,6 @@ import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
import libcore.util.HexEncoding;
@@ -45,7 +41,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.nio.BufferOverflowException;
-import java.util.Arrays;
import java.util.List;
/**
@@ -172,9 +167,6 @@ public class WifiAwareManager {
private final Object mLock = new Object(); // lock access to the following vars
- @GuardedBy("mLock")
- private SparseArray<RttManager.RttListener> mRangingListeners = new SparseArray<>();
-
/** @hide */
public WifiAwareManager(Context context, IWifiAwareManager service) {
mContext = context;
@@ -401,27 +393,6 @@ public class WifiAwareManager {
}
/** @hide */
- public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
- RttManager.RttListener listener) {
- if (VDBG) {
- Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
- + "params=" + Arrays.toString(params) + ", listener=" + listener);
- }
-
- int rangingKey = 0;
- try {
- rangingKey = mService.startRanging(clientId, sessionId,
- new RttManager.ParcelableRttParams(params));
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
-
- synchronized (mLock) {
- mRangingListeners.put(rangingKey, listener);
- }
- }
-
- /** @hide */
public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
if (VDBG) {
@@ -500,29 +471,12 @@ public class WifiAwareManager {
private static final int CALLBACK_CONNECT_SUCCESS = 0;
private static final int CALLBACK_CONNECT_FAIL = 1;
private static final int CALLBACK_IDENTITY_CHANGED = 2;
- private static final int CALLBACK_RANGING_SUCCESS = 3;
- private static final int CALLBACK_RANGING_FAILURE = 4;
- private static final int CALLBACK_RANGING_ABORTED = 5;
private final Handler mHandler;
private final WeakReference<WifiAwareManager> mAwareManager;
private final Binder mBinder;
private final Looper mLooper;
- RttManager.RttListener getAndRemoveRangingListener(int rangingId) {
- WifiAwareManager mgr = mAwareManager.get();
- if (mgr == null) {
- Log.w(TAG, "getAndRemoveRangingListener: called post GC");
- return null;
- }
-
- synchronized (mgr.mLock) {
- RttManager.RttListener listener = mgr.mRangingListeners.get(rangingId);
- mgr.mRangingListeners.delete(rangingId);
- return listener;
- }
- }
-
/**
* Constructs a {@link AttachCallback} using the specified looper.
* All callbacks will delivered on the thread of the specified looper.
@@ -567,37 +521,6 @@ public class WifiAwareManager {
identityChangedListener.onIdentityChanged((byte[]) msg.obj);
}
break;
- case CALLBACK_RANGING_SUCCESS: {
- RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
- if (listener == null) {
- Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
- + ": no listener registered (anymore)");
- } else {
- listener.onSuccess(
- ((RttManager.ParcelableRttResults) msg.obj).mResults);
- }
- break;
- }
- case CALLBACK_RANGING_FAILURE: {
- RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
- if (listener == null) {
- Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
- + ": no listener registered (anymore)");
- } else {
- listener.onFailure(msg.arg2, (String) msg.obj);
- }
- break;
- }
- case CALLBACK_RANGING_ABORTED: {
- RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
- if (listener == null) {
- Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
- + ": no listener registered (anymore)");
- } else {
- listener.onAborted();
- }
- break;
- }
}
}
};
@@ -629,43 +552,6 @@ public class WifiAwareManager {
msg.obj = mac;
mHandler.sendMessage(msg);
}
-
- @Override
- public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
- if (VDBG) {
- Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
- }
-
- Message msg = mHandler.obtainMessage(CALLBACK_RANGING_SUCCESS);
- msg.arg1 = rangingId;
- msg.obj = results;
- mHandler.sendMessage(msg);
- }
-
- @Override
- public void onRangingFailure(int rangingId, int reason, String description) {
- if (VDBG) {
- Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
- + ", description=" + description);
- }
-
- Message msg = mHandler.obtainMessage(CALLBACK_RANGING_FAILURE);
- msg.arg1 = rangingId;
- msg.arg2 = reason;
- msg.obj = description;
- mHandler.sendMessage(msg);
-
- }
-
- @Override
- public void onRangingAborted(int rangingId) {
- if (VDBG) Log.v(TAG, "onRangingAborted: rangingId=" + rangingId);
-
- Message msg = mHandler.obtainMessage(CALLBACK_RANGING_ABORTED);
- msg.arg1 = rangingId;
- mHandler.sendMessage(msg);
-
- }
}
private static class WifiAwareDiscoverySessionCallbackProxy extends
diff --git a/android/net/wifi/rtt/RangingRequest.java b/android/net/wifi/rtt/RangingRequest.java
new file mode 100644
index 00000000..997b6800
--- /dev/null
+++ b/android/net/wifi/rtt/RangingRequest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.wifi.rtt;
+
+import android.net.wifi.ScanResult;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+
+/**
+ * Defines the ranging request to other devices. The ranging request is built using
+ * {@link RangingRequest.Builder}.
+ * A ranging request is executed using
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}.
+ * <p>
+ * The ranging request is a batch request - specifying a set of devices (specified using
+ * {@link RangingRequest.Builder#addAp(ScanResult)} and
+ * {@link RangingRequest.Builder#addAps(List)}).
+ *
+ * @hide RTT_API
+ */
+public final class RangingRequest implements Parcelable {
+ private static final int MAX_PEERS = 10;
+
+ /**
+ * Returns the maximum number of peers to range which can be specified in a single {@code
+ * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
+ * through {@link RangingRequest.Builder#addAp(ScanResult)} or
+ * {@link RangingRequest.Builder#addAps(List)}.
+ *
+ * @return Maximum number of peers.
+ */
+ public static int getMaxPeers() {
+ return MAX_PEERS;
+ }
+
+ /** @hide */
+ public final List<RttPeer> mRttPeers;
+
+ /** @hide */
+ private RangingRequest(List<RttPeer> rttPeers) {
+ mRttPeers = rttPeers;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeList(mRttPeers);
+ }
+
+ public static final Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() {
+ @Override
+ public RangingRequest[] newArray(int size) {
+ return new RangingRequest[size];
+ }
+
+ @Override
+ public RangingRequest createFromParcel(Parcel in) {
+ return new RangingRequest(in.readArrayList(null));
+ }
+ };
+
+ /** @hide */
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", ",");
+ for (RttPeer rp : mRttPeers) {
+ sj.add(rp.toString());
+ }
+ return sj.toString();
+ }
+
+ /** @hide */
+ public void enforceValidity() {
+ if (mRttPeers.size() > MAX_PEERS) {
+ throw new IllegalArgumentException(
+ "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
+ }
+ }
+
+ /**
+ * Builder class used to construct {@link RangingRequest} objects.
+ */
+ public static final class Builder {
+ private List<RttPeer> mRttPeers = new ArrayList<>();
+
+ /**
+ * Add the device specified by the {@link ScanResult} to the list of devices with
+ * which to measure range. The total number of results added to a request cannot exceed the
+ * limit specified by {@link #getMaxPeers()}.
+ *
+ * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addAp(ScanResult apInfo) {
+ if (apInfo == null) {
+ throw new IllegalArgumentException("Null ScanResult!");
+ }
+ mRttPeers.add(new RttPeerAp(apInfo));
+ return this;
+ }
+
+ /**
+ * Add the devices specified by the {@link ScanResult}s to the list of devices with
+ * which to measure range. The total number of results added to a request cannot exceed the
+ * limit specified by {@link #getMaxPeers()}.
+ *
+ * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
+ * @return The builder to facilitate chaining
+ * {@code builder.setXXX(..).setXXX(..)}.
+ */
+ public Builder addAps(List<ScanResult> apInfos) {
+ if (apInfos == null) {
+ throw new IllegalArgumentException("Null list of ScanResults!");
+ }
+ for (ScanResult scanResult : apInfos) {
+ addAp(scanResult);
+ }
+ return this;
+ }
+
+ /**
+ * Build {@link RangingRequest} given the current configurations made on the
+ * builder.
+ */
+ public RangingRequest build() {
+ return new RangingRequest(mRttPeers);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RangingRequest)) {
+ return false;
+ }
+
+ RangingRequest lhs = (RangingRequest) o;
+
+ return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers);
+ }
+
+ @Override
+ public int hashCode() {
+ return mRttPeers.hashCode();
+ }
+
+ /** @hide */
+ public interface RttPeer {
+ // empty (marker interface)
+ }
+
+ /** @hide */
+ public static class RttPeerAp implements RttPeer, Parcelable {
+ public final ScanResult scanResult;
+
+ public RttPeerAp(ScanResult scanResult) {
+ this.scanResult = scanResult;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ scanResult.writeToParcel(dest, flags);
+ }
+
+ public static final Creator<RttPeerAp> CREATOR = new Creator<RttPeerAp>() {
+ @Override
+ public RttPeerAp[] newArray(int size) {
+ return new RttPeerAp[size];
+ }
+
+ @Override
+ public RttPeerAp createFromParcel(Parcel in) {
+ return new RttPeerAp(ScanResult.CREATOR.createFromParcel(in));
+ }
+ };
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RttPeerAp: scanResult=").append(
+ scanResult.toString()).toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RttPeerAp)) {
+ return false;
+ }
+
+ RttPeerAp lhs = (RttPeerAp) o;
+
+ // Note: the only thing which matters for the request identity is the BSSID of the AP
+ return TextUtils.equals(scanResult.BSSID, lhs.scanResult.BSSID);
+ }
+
+ @Override
+ public int hashCode() {
+ return scanResult.hashCode();
+ }
+ }
+} \ No newline at end of file
diff --git a/android/net/wifi/rtt/RangingResult.java b/android/net/wifi/rtt/RangingResult.java
new file mode 100644
index 00000000..918803ef
--- /dev/null
+++ b/android/net/wifi/rtt/RangingResult.java
@@ -0,0 +1,194 @@
+/*
+ * 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.wifi.rtt;
+
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Ranging result for a request started by
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. Results are
+ * returned in {@link RangingResultCallback#onRangingResults(List)}.
+ * <p>
+ * A ranging result is the distance measurement result for a single device specified in the
+ * {@link RangingRequest}.
+ *
+ * @hide RTT_API
+ */
+public final class RangingResult implements Parcelable {
+ private static final String TAG = "RangingResult";
+
+ private final int mStatus;
+ private final byte[] mMac;
+ private final int mDistanceCm;
+ private final int mDistanceStdDevCm;
+ private final int mRssi;
+ private final long mTimestamp;
+
+ /** @hide */
+ public RangingResult(int status, byte[] mac, int distanceCm, int distanceStdDevCm, int rssi,
+ long timestamp) {
+ mStatus = status;
+ mMac = mac;
+ mDistanceCm = distanceCm;
+ mDistanceStdDevCm = distanceStdDevCm;
+ mRssi = rssi;
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * @return The status of ranging measurement: {@link RangingResultCallback#STATUS_SUCCESS} in
+ * case of success, and {@link RangingResultCallback#STATUS_FAIL} in case of failure.
+ */
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * @return The MAC address of the device whose range measurement was requested. Will correspond
+ * to the MAC address of the device in the {@link RangingRequest}.
+ * <p>
+ * Always valid (i.e. when {@link #getStatus()} is either SUCCESS or FAIL.
+ */
+ public byte[] getMacAddress() {
+ return mMac;
+ }
+
+ /**
+ * @return The distance (in cm) to the device specified by {@link #getMacAddress()}.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ */
+ public int getDistanceCm() {
+ if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+ Log.e(TAG, "getDistanceCm(): invalid value retrieved");
+ }
+ return mDistanceCm;
+ }
+
+ /**
+ * @return The standard deviation of the measured distance (in cm) to the device specified by
+ * {@link #getMacAddress()}. The standard deviation is calculated over the measurements
+ * executed in a single RTT burst.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ */
+ public int getDistanceStdDevCm() {
+ if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+ Log.e(TAG, "getDistanceStdDevCm(): invalid value retrieved");
+ }
+ return mDistanceStdDevCm;
+ }
+
+ /**
+ * @return The average RSSI (in units of -0.5dB) observed during the RTT measurement.
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ */
+ public int getRssi() {
+ if (mStatus != RangingResultCallback.STATUS_SUCCESS) {
+ // TODO: should this be an exception?
+ Log.e(TAG, "getRssi(): invalid value retrieved");
+ }
+ return mRssi;
+ }
+
+ /**
+ * @return The timestamp (in us) at which the ranging operation was performed
+ * <p>
+ * Only valid if {@link #getStatus()} returns {@link RangingResultCallback#STATUS_SUCCESS}.
+ */
+ public long getRangingTimestamp() {
+ return mTimestamp;
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ dest.writeByteArray(mMac);
+ dest.writeInt(mDistanceCm);
+ dest.writeInt(mDistanceStdDevCm);
+ dest.writeInt(mRssi);
+ dest.writeLong(mTimestamp);
+ }
+
+ /** @hide */
+ public static final Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
+ @Override
+ public RangingResult[] newArray(int size) {
+ return new RangingResult[size];
+ }
+
+ @Override
+ public RangingResult createFromParcel(Parcel in) {
+ int status = in.readInt();
+ byte[] mac = in.createByteArray();
+ int distanceCm = in.readInt();
+ int distanceStdDevCm = in.readInt();
+ int rssi = in.readInt();
+ long timestamp = in.readLong();
+ return new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi, timestamp);
+ }
+ };
+
+ /** @hide */
+ @Override
+ public String toString() {
+ return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
+ mMac == null ? "<null>" : HexEncoding.encodeToString(mMac)).append(
+ ", distanceCm=").append(mDistanceCm).append(", distanceStdDevCm=").append(
+ mDistanceStdDevCm).append(", rssi=").append(mRssi).append(", timestamp=").append(
+ mTimestamp).append("]").toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof RangingResult)) {
+ return false;
+ }
+
+ RangingResult lhs = (RangingResult) o;
+
+ return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac)
+ && mDistanceCm == lhs.mDistanceCm && mDistanceStdDevCm == lhs.mDistanceStdDevCm
+ && mRssi == lhs.mRssi && mTimestamp == lhs.mTimestamp;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus, mMac, mDistanceCm, mDistanceStdDevCm, mRssi, mTimestamp);
+ }
+} \ No newline at end of file
diff --git a/android/net/wifi/rtt/RangingResultCallback.java b/android/net/wifi/rtt/RangingResultCallback.java
new file mode 100644
index 00000000..d7270ad2
--- /dev/null
+++ b/android/net/wifi/rtt/RangingResultCallback.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.net.wifi.rtt;
+
+import android.os.Handler;
+
+import java.util.List;
+
+/**
+ * Base class for ranging result callbacks. Should be extended by applications and set when calling
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. A single
+ * result from a range request will be called in this object.
+ *
+ * @hide RTT_API
+ */
+public abstract class RangingResultCallback {
+ /**
+ * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
+ * operation was successful and distance value is valid.
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * Individual range request status, {@link RangingResult#getStatus()}. Indicates ranging
+ * operation failed and the distance value is invalid.
+ */
+ public static final int STATUS_FAIL = 1;
+
+ /**
+ * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
+ * devices specified in the request was attempted.
+ */
+ public abstract void onRangingFailure();
+
+ /**
+ * Called when a ranging operation was executed. The list of results corresponds to devices
+ * specified in the ranging request.
+ *
+ * @param results List of range measurements, one per requested device.
+ */
+ public abstract void onRangingResults(List<RangingResult> results);
+}
diff --git a/android/net/wifi/rtt/WifiRttManager.java b/android/net/wifi/rtt/WifiRttManager.java
new file mode 100644
index 00000000..a085de17
--- /dev/null
+++ b/android/net/wifi/rtt/WifiRttManager.java
@@ -0,0 +1,100 @@
+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 android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+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.util.Log;
+
+import java.util.List;
+
+/**
+ * This class provides the primary API for measuring distance (range) to other devices using the
+ * IEEE 802.11mc Wi-Fi Round Trip Time (RTT) technology.
+ * <p>
+ * The devices which can be ranged include:
+ * <li>Access Points (APs)
+ * <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.
+ *
+ * @hide RTT_API
+ */
+@SystemService(Context.WIFI_RTT2_SERVICE)
+public class WifiRttManager {
+ private static final String TAG = "WifiRttManager";
+ private static final boolean VDBG = true;
+
+ private final Context mContext;
+ private final IWifiRttManager mService;
+
+ /** @hide */
+ public WifiRttManager(Context context, IWifiRttManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * 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 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.
+ */
+ @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
+ public void startRanging(RangingRequest request, RangingResultCallback callback,
+ @Nullable Handler handler) {
+ if (VDBG) {
+ Log.v(TAG, "startRanging: 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,
+ new RttCallbackProxy(looper, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class RttCallbackProxy extends IRttCallback.Stub {
+ private final Handler mHandler;
+ private final RangingResultCallback mCallback;
+
+ RttCallbackProxy(Looper looper, RangingResultCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onRangingResults(int status, List<RangingResult> results) throws RemoteException {
+ if (VDBG) {
+ Log.v(TAG, "RttCallbackProxy: onRanginResults: status=" + status + ", results="
+ + results);
+ }
+ mHandler.post(() -> {
+ if (status == RangingResultCallback.STATUS_SUCCESS) {
+ mCallback.onRangingResults(results);
+ } else {
+ mCallback.onRangingFailure();
+ }
+ });
+ }
+ }
+}
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index 66b6b478..98819279 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -19,6 +19,7 @@ package android.os;
import android.app.job.JobParameters;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
import android.telephony.SignalStrength;
import android.text.format.DateFormat;
import android.util.ArrayMap;
@@ -29,11 +30,13 @@ import android.util.Printer;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import android.view.Display;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -78,17 +81,17 @@ public abstract class BatteryStats implements Parcelable {
* A constant indicating a sensor timer.
*/
public static final int SENSOR = 3;
-
+
/**
* A constant indicating a a wifi running timer
*/
public static final int WIFI_RUNNING = 4;
-
+
/**
* A constant indicating a full wifi lock timer
*/
public static final int FULL_WIFI_LOCK = 5;
-
+
/**
* A constant indicating a wifi scan
*/
@@ -190,7 +193,7 @@ public abstract class BatteryStats implements Parcelable {
public static final int STATS_SINCE_UNPLUGGED = 2;
// NOTE: Update this list if you add/change any stats above.
- // These characters are supposed to represent "total", "last", "current",
+ // These characters are supposed to represent "total", "last", "current",
// and "unplugged". They were shortened for efficiency sake.
private static final String[] STAT_NAMES = { "l", "c", "u" };
@@ -217,8 +220,10 @@ public abstract class BatteryStats implements Parcelable {
* - Package wakeup alarms are now on screen-off timebase
* New in version 26:
* - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
+ * New in version 27:
+ * - Always On Display (screen doze mode) time and power
*/
- static final String CHECKIN_VERSION = "26";
+ static final int CHECKIN_VERSION = 27;
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -1381,12 +1386,12 @@ public abstract class BatteryStats implements Parcelable {
public static final int STATE_PHONE_SCANNING_FLAG = 1<<21;
public static final int STATE_SCREEN_ON_FLAG = 1<<20; // consider moving to states2
public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19; // consider moving to states2
- // empty slot
+ public static final int STATE_SCREEN_DOZE_FLAG = 1 << 18;
// empty slot
public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<16;
public static final int MOST_INTERESTING_STATES =
- STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG;
+ STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG | STATE_SCREEN_DOZE_FLAG;
public static final int SETTLE_TO_ZERO_STATES = 0xffff0000 & ~MOST_INTERESTING_STATES;
@@ -1414,8 +1419,8 @@ public abstract class BatteryStats implements Parcelable {
public static final int STATE2_BLUETOOTH_SCAN_FLAG = 1 << 20;
public static final int MOST_INTERESTING_STATES2 =
- STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
- | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG;
+ STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
+ | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG;
public static final int SETTLE_TO_ZERO_STATES2 = 0xffff0000 & ~MOST_INTERESTING_STATES2;
@@ -1863,6 +1868,21 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract int getScreenOnCount(int which);
+ /**
+ * Returns the time in microseconds that the screen has been dozing while the device was
+ * running on battery.
+ *
+ * {@hide}
+ */
+ public abstract long getScreenDozeTime(long elapsedRealtimeUs, int which);
+
+ /**
+ * Returns the number of times the screen was turned dozing.
+ *
+ * {@hide}
+ */
+ public abstract int getScreenDozeCount(int which);
+
public abstract long getInteractiveTime(long elapsedRealtimeUs, int which);
public static final int SCREEN_BRIGHTNESS_DARK = 0;
@@ -2116,8 +2136,7 @@ public abstract class BatteryStats implements Parcelable {
"group", "compl", "dorm", "uninit"
};
- public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS
- = new BitDescription[] {
+ public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor", "s"),
@@ -2131,6 +2150,7 @@ public abstract class BatteryStats implements Parcelable {
new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"),
new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
+ new BitDescription(HistoryItem.STATE_SCREEN_DOZE_FLAG, "screen_doze", "Sd"),
new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
DATA_CONNECTION_NAMES, DATA_CONNECTION_NAMES),
@@ -2467,6 +2487,18 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getDischargeAmountScreenOffSinceCharge();
/**
+ * Get the amount the battery has discharged while the screen was doze,
+ * since the last time power was unplugged.
+ */
+ public abstract int getDischargeAmountScreenDoze();
+
+ /**
+ * Get the amount the battery has discharged while the screen was doze,
+ * since the last time the device was charged.
+ */
+ public abstract int getDischargeAmountScreenDozeSinceCharge();
+
+ /**
* Returns the total, last, or current battery uptime in microseconds.
*
* @param curTime the elapsed realtime in microseconds.
@@ -2483,7 +2515,7 @@ public abstract class BatteryStats implements Parcelable {
public abstract long computeBatteryRealtime(long curTime, int which);
/**
- * Returns the total, last, or current battery screen off uptime in microseconds.
+ * Returns the total, last, or current battery screen off/doze uptime in microseconds.
*
* @param curTime the elapsed realtime in microseconds.
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
@@ -2491,7 +2523,7 @@ public abstract class BatteryStats implements Parcelable {
public abstract long computeBatteryScreenOffUptime(long curTime, int which);
/**
- * Returns the total, last, or current battery screen off realtime in microseconds.
+ * Returns the total, last, or current battery screen off/doze realtime in microseconds.
*
* @param curTime the current elapsed realtime in microseconds.
* @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
@@ -2590,18 +2622,24 @@ public abstract class BatteryStats implements Parcelable {
};
/**
- * Return the counter keeping track of the amount of battery discharge while the screen was off,
- * measured in micro-Ampere-hours. This will be non-zero only if the device's battery has
+ * Return the amount of battery discharge while the screen was off, measured in
+ * micro-Ampere-hours. This will be non-zero only if the device's battery has
* a coulomb counter.
*/
- public abstract LongCounter getDischargeScreenOffCoulombCounter();
+ public abstract long getMahDischargeScreenOff(int which);
/**
- * Return the counter keeping track of the amount of battery discharge measured in
+ * Return the amount of battery discharge while the screen was in doze mode, measured in
* micro-Ampere-hours. This will be non-zero only if the device's battery has
* a coulomb counter.
*/
- public abstract LongCounter getDischargeCoulombCounter();
+ public abstract long getMahDischargeScreenDoze(int which);
+
+ /**
+ * Return the amount of battery discharge measured in micro-Ampere-hours. This will be
+ * non-zero only if the device's battery has a coulomb counter.
+ */
+ public abstract long getMahDischarge(int which);
/**
* Returns the estimated real battery capacity, which may be less than the capacity
@@ -2953,6 +2991,31 @@ public abstract class BatteryStats implements Parcelable {
}
/**
+ * Dump a given timer stat to the proto stream.
+ *
+ * @param proto the ProtoOutputStream to log to
+ * @param fieldId type of data, the field to save to (e.g. AggregatedBatteryStats.WAKELOCK)
+ * @param timer a {@link Timer} to dump stats for
+ * @param rawRealtimeUs the current elapsed realtime of the system in microseconds
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+ */
+ private static void dumpTimer(ProtoOutputStream proto, long fieldId,
+ Timer timer, long rawRealtime, int which) {
+ if (timer == null) {
+ return;
+ }
+ // Convert from microseconds to milliseconds with rounding
+ final long totalTimeMs = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
+ final int count = timer.getCountLocked(which);
+ if (totalTimeMs != 0 || count != 0) {
+ final long token = proto.start(fieldId);
+ proto.write(TimerProto.DURATION_MS, totalTimeMs);
+ proto.write(TimerProto.COUNT, count);
+ proto.end(token);
+ }
+ }
+
+ /**
* Checks if the ControllerActivityCounter has any data worth dumping.
*/
private static boolean controllerActivityHasData(ControllerActivityCounter counter, int which) {
@@ -3004,6 +3067,38 @@ public abstract class BatteryStats implements Parcelable {
pw.println();
}
+ /**
+ * Dumps the ControllerActivityCounter if it has any data worth dumping.
+ */
+ private static void dumpControllerActivityProto(ProtoOutputStream proto, long fieldId,
+ ControllerActivityCounter counter,
+ int which) {
+ if (!controllerActivityHasData(counter, which)) {
+ return;
+ }
+
+ final long cToken = proto.start(fieldId);
+
+ proto.write(ControllerActivityProto.IDLE_DURATION_MS,
+ counter.getIdleTimeCounter().getCountLocked(which));
+ proto.write(ControllerActivityProto.RX_DURATION_MS,
+ counter.getRxTimeCounter().getCountLocked(which));
+ proto.write(ControllerActivityProto.POWER_MAH,
+ counter.getPowerCounter().getCountLocked(which) / (1000 * 60 * 60));
+
+ long tToken;
+ LongCounter[] txCounters = counter.getTxTimeCounters();
+ for (int i = 0; i < txCounters.length; ++i) {
+ LongCounter c = txCounters[i];
+ tToken = proto.start(ControllerActivityProto.TX);
+ proto.write(ControllerActivityProto.TxLevel.LEVEL, i);
+ proto.write(ControllerActivityProto.TxLevel.DURATION_MS, c.getCountLocked(which));
+ proto.end(tToken);
+ }
+
+ proto.end(cToken);
+ }
+
private final void printControllerActivityIfInteresting(PrintWriter pw, StringBuilder sb,
String prefix, String controllerName,
ControllerActivityCounter counter,
@@ -3112,6 +3207,7 @@ public abstract class BatteryStats implements Parcelable {
final long totalRealtime = computeRealtime(rawRealtime, which);
final long totalUptime = computeUptime(rawUptime, which);
final long screenOnTime = getScreenOnTime(rawRealtime, which);
+ final long screenDozeTime = getScreenDozeTime(rawRealtime, which);
final long interactiveTime = getInteractiveTime(rawRealtime, which);
final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
final long deviceIdleModeLightTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT,
@@ -3124,9 +3220,9 @@ public abstract class BatteryStats implements Parcelable {
rawRealtime, which);
final int connChanges = getNumConnectivityChange(which);
final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
- final long dischargeCount = getDischargeCoulombCounter().getCountLocked(which);
- final long dischargeScreenOffCount = getDischargeScreenOffCoulombCounter()
- .getCountLocked(which);
+ final long dischargeCount = getMahDischarge(which);
+ final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
+ final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
final StringBuilder sb = new StringBuilder(128);
@@ -3143,7 +3239,8 @@ public abstract class BatteryStats implements Parcelable {
getStartClockTime(),
whichBatteryScreenOffRealtime / 1000, whichBatteryScreenOffUptime / 1000,
getEstimatedBatteryCapacity(),
- getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity());
+ getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity(),
+ screenDozeTime / 1000);
// Calculate wakelock times across all uids.
@@ -3295,13 +3392,15 @@ public abstract class BatteryStats implements Parcelable {
getDischargeStartLevel()-getDischargeCurrentLevel(),
getDischargeStartLevel()-getDischargeCurrentLevel(),
getDischargeAmountScreenOn(), getDischargeAmountScreenOff(),
- dischargeCount / 1000, dischargeScreenOffCount / 1000);
+ dischargeCount / 1000, dischargeScreenOffCount / 1000,
+ getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000);
} else {
dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
getDischargeAmountScreenOnSinceCharge(),
getDischargeAmountScreenOffSinceCharge(),
- dischargeCount / 1000, dischargeScreenOffCount / 1000);
+ dischargeCount / 1000, dischargeScreenOffCount / 1000,
+ getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000);
}
if (reqUid < 0) {
@@ -3831,6 +3930,7 @@ public abstract class BatteryStats implements Parcelable {
which);
final long batteryTimeRemaining = computeBatteryTimeRemaining(rawRealtime);
final long chargeTimeRemaining = computeChargeTimeRemaining(rawRealtime);
+ final long screenDozeTime = getScreenDozeTime(rawRealtime, which);
final StringBuilder sb = new StringBuilder(128);
@@ -3868,25 +3968,35 @@ public abstract class BatteryStats implements Parcelable {
sb.setLength(0);
sb.append(prefix);
- sb.append(" Time on battery: ");
- formatTimeMs(sb, whichBatteryRealtime / 1000); sb.append("(");
- sb.append(formatRatioLocked(whichBatteryRealtime, totalRealtime));
- sb.append(") realtime, ");
- formatTimeMs(sb, whichBatteryUptime / 1000);
- sb.append("("); sb.append(formatRatioLocked(whichBatteryUptime, totalRealtime));
- sb.append(") uptime");
+ sb.append(" Time on battery: ");
+ formatTimeMs(sb, whichBatteryRealtime / 1000); sb.append("(");
+ sb.append(formatRatioLocked(whichBatteryRealtime, totalRealtime));
+ sb.append(") realtime, ");
+ formatTimeMs(sb, whichBatteryUptime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(whichBatteryUptime, whichBatteryRealtime));
+ sb.append(") uptime");
pw.println(sb.toString());
+
sb.setLength(0);
sb.append(prefix);
- sb.append(" Time on battery screen off: ");
- formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("(");
- sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, totalRealtime));
- sb.append(") realtime, ");
- formatTimeMs(sb, whichBatteryScreenOffUptime / 1000);
- sb.append("(");
- sb.append(formatRatioLocked(whichBatteryScreenOffUptime, totalRealtime));
- sb.append(") uptime");
+ sb.append(" Time on battery screen off: ");
+ formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("(");
+ sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, whichBatteryRealtime));
+ sb.append(") realtime, ");
+ formatTimeMs(sb, whichBatteryScreenOffUptime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(whichBatteryScreenOffUptime, whichBatteryRealtime));
+ sb.append(") uptime");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Time on battery screen doze: ");
+ formatTimeMs(sb, screenDozeTime / 1000); sb.append("(");
+ sb.append(formatRatioLocked(screenDozeTime, whichBatteryRealtime));
+ sb.append(")");
pw.println(sb.toString());
+
sb.setLength(0);
sb.append(prefix);
sb.append(" Total run time: ");
@@ -3910,8 +4020,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final LongCounter dischargeCounter = getDischargeCoulombCounter();
- final long dischargeCount = dischargeCounter.getCountLocked(which);
+ final long dischargeCount = getMahDischarge(which);
if (dischargeCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -3921,8 +4030,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final LongCounter dischargeScreenOffCounter = getDischargeScreenOffCoulombCounter();
- final long dischargeScreenOffCount = dischargeScreenOffCounter.getCountLocked(which);
+ final long dischargeScreenOffCount = getMahDischargeScreenOff(which);
if (dischargeScreenOffCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -3932,7 +4040,18 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
- final long dischargeScreenOnCount = dischargeCount - dischargeScreenOffCount;
+ final long dischargeScreenDozeCount = getMahDischargeScreenDoze(which);
+ if (dischargeScreenDozeCount >= 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Screen doze discharge: ");
+ sb.append(BatteryStatsHelper.makemAh(dischargeScreenDozeCount / 1000.0));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+ }
+
+ final long dischargeScreenOnCount =
+ dischargeCount - dischargeScreenOffCount - dischargeScreenDozeCount;
if (dischargeScreenOnCount >= 0) {
sb.setLength(0);
sb.append(prefix);
@@ -4340,20 +4459,24 @@ public abstract class BatteryStats implements Parcelable {
pw.println(getDischargeCurrentLevel());
}
pw.print(prefix); pw.print(" Amount discharged while screen on: ");
- pw.println(getDischargeAmountScreenOn());
+ pw.println(getDischargeAmountScreenOn());
pw.print(prefix); pw.print(" Amount discharged while screen off: ");
- pw.println(getDischargeAmountScreenOff());
+ pw.println(getDischargeAmountScreenOff());
+ pw.print(prefix); pw.print(" Amount discharged while screen doze: ");
+ pw.println(getDischargeAmountScreenDoze());
pw.println(" ");
} else {
pw.print(prefix); pw.println(" Device battery use since last full charge");
pw.print(prefix); pw.print(" Amount discharged (lower bound): ");
- pw.println(getLowDischargeAmountSinceCharge());
+ pw.println(getLowDischargeAmountSinceCharge());
pw.print(prefix); pw.print(" Amount discharged (upper bound): ");
- pw.println(getHighDischargeAmountSinceCharge());
+ pw.println(getHighDischargeAmountSinceCharge());
pw.print(prefix); pw.print(" Amount discharged while screen on: ");
- pw.println(getDischargeAmountScreenOnSinceCharge());
+ pw.println(getDischargeAmountScreenOnSinceCharge());
pw.print(prefix); pw.print(" Amount discharged while screen off: ");
- pw.println(getDischargeAmountScreenOffSinceCharge());
+ pw.println(getDischargeAmountScreenOffSinceCharge());
+ pw.print(prefix); pw.print(" Amount discharged while screen doze: ");
+ pw.println(getDischargeAmountScreenDozeSinceCharge());
pw.println();
}
@@ -5426,8 +5549,9 @@ public abstract class BatteryStats implements Parcelable {
}
}
}
-
+
public void prepareForDumpLocked() {
+ // We don't need to require subclasses implement this.
}
public static class HistoryPrinter {
@@ -6248,7 +6372,8 @@ public abstract class BatteryStats implements Parcelable {
pw.println();
}
}
-
+
+ // This is called from BatteryStatsService.
@SuppressWarnings("unused")
public void dumpCheckinLocked(Context context, PrintWriter pw,
List<ApplicationInfo> apps, int flags, long histStart) {
@@ -6260,10 +6385,7 @@ public abstract class BatteryStats implements Parcelable {
long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
- final boolean filtering = (flags &
- (DUMP_HISTORY_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0;
-
- if ((flags&DUMP_INCLUDE_HISTORY) != 0 || (flags&DUMP_HISTORY_ONLY) != 0) {
+ if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
if (startIteratingHistoryLocked()) {
try {
for (int i=0; i<getHistoryStringPoolSize(); i++) {
@@ -6287,7 +6409,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
+ if ((flags & DUMP_HISTORY_ONLY) != 0) {
return;
}
@@ -6320,7 +6442,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
}
- if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
+ if ((flags & DUMP_DAILY_ONLY) == 0) {
dumpDurationSteps(pw, "", DISCHARGE_STEP_DATA, getDischargeLevelStepTracker(), true);
String[] lineArgs = new String[1];
long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime() * 1000);
@@ -6340,4 +6462,33 @@ public abstract class BatteryStats implements Parcelable {
(flags&DUMP_DEVICE_WIFI_ONLY) != 0);
}
}
+
+ /** Dump batterystats data to a proto. @hide */
+ public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps,
+ int flags, long historyStart) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ final long bToken = proto.start(BatteryStatsServiceDumpProto.BATTERYSTATS);
+ prepareForDumpLocked();
+
+ proto.write(BatteryStatsProto.REPORT_VERSION, CHECKIN_VERSION);
+ proto.write(BatteryStatsProto.PARCEL_VERSION, getParcelVersion());
+ proto.write(BatteryStatsProto.START_PLATFORM_VERSION, getStartPlatformVersion());
+ proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion());
+
+ long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
+ if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
+ if (startIteratingHistoryLocked()) {
+ // TODO: implement dumpProtoHistoryLocked(proto);
+ }
+ }
+
+ if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
+ // TODO: implement dumpProtoAppsLocked(proto, apps);
+ // TODO: implement dumpProtoSystemLocked(proto);
+ }
+
+ proto.end(bToken);
+ proto.flush();
+ }
}
diff --git a/android/os/Binder.java b/android/os/Binder.java
index 0df6361d..e9e695bb 100644
--- a/android/os/Binder.java
+++ b/android/os/Binder.java
@@ -23,7 +23,6 @@ import android.util.Log;
import android.util.Slog;
import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
@@ -202,7 +201,7 @@ public class Binder implements IBinder {
* then its own pid is returned.
*/
public static final native int getCallingPid();
-
+
/**
* Return the Linux uid assigned to the process that sent you the
* current transaction that is being processed. This uid can be used with
@@ -335,7 +334,7 @@ public class Binder implements IBinder {
* it needs to.
*/
public static final native void flushPendingCommands();
-
+
/**
* Add the calling thread to the IPC thread pool. This function does
* not return until the current process is exiting.
@@ -372,7 +371,7 @@ public class Binder implements IBinder {
}
}
}
-
+
/**
* Convenience method for associating a specific interface with the Binder.
* After calling, queryLocalInterface() will be implemented for you
@@ -383,7 +382,7 @@ public class Binder implements IBinder {
mOwner = owner;
mDescriptor = descriptor;
}
-
+
/**
* Default implementation returns an empty interface name.
*/
@@ -408,7 +407,7 @@ public class Binder implements IBinder {
public boolean isBinderAlive() {
return true;
}
-
+
/**
* Use information supplied to attachInterface() to return the
* associated IInterface if it matches the requested
@@ -630,7 +629,7 @@ public class Binder implements IBinder {
}
return r;
}
-
+
/**
* Local implementation is a no-op.
*/
@@ -643,7 +642,7 @@ public class Binder implements IBinder {
public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
return true;
}
-
+
protected void finalize() throws Throwable {
try {
destroyBinder();
@@ -730,7 +729,15 @@ public class Binder implements IBinder {
}
}
+/**
+ * Java proxy for a native IBinder object.
+ * Allocated and constructed by the native javaObjectforIBinder function. Never allocated
+ * directly from Java code.
+ */
final class BinderProxy implements IBinder {
+ // See android_util_Binder.cpp for the native half of this.
+ // TODO: Consider using NativeAllocationRegistry instead of finalization.
+
// Assume the process-wide default value when created
volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
@@ -789,7 +796,7 @@ final class BinderProxy implements IBinder {
reply.recycle();
}
}
-
+
public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -826,7 +833,7 @@ final class BinderProxy implements IBinder {
BinderProxy() {
mSelf = new WeakReference(this);
}
-
+
@Override
protected void finalize() throws Throwable {
try {
@@ -835,9 +842,9 @@ final class BinderProxy implements IBinder {
super.finalize();
}
}
-
+
private native final void destroy();
-
+
private static final void sendDeathNotice(DeathRecipient recipient) {
if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
try {
@@ -848,8 +855,20 @@ final class BinderProxy implements IBinder {
exc);
}
}
-
+
+ // This WeakReference to "this" is used only by native code to "attach" to the
+ // native IBinder object.
+ // Using WeakGlobalRefs instead currently appears unsafe, in that they can yield a
+ // non-null value after the BinderProxy is enqueued for finalization.
+ // Used only once immediately after construction.
+ // TODO: Consider making the extra native-to-java call to compute this on the fly.
final private WeakReference mSelf;
+
+ // Native pointer to the wrapped native IBinder object. Counted as strong reference.
private long mObject;
+
+ // Native pointer to native DeathRecipientList. Counted as strong reference.
+ // Basically owned by the JavaProxy object. Reference counted only because DeathRecipients
+ // hold a weak reference that can be temporarily promoted.
private long mOrgue;
}
diff --git a/android/os/IServiceManager.java b/android/os/IServiceManager.java
index 7b11c283..87c65ecc 100644
--- a/android/os/IServiceManager.java
+++ b/android/os/IServiceManager.java
@@ -18,12 +18,12 @@ package android.os;
/**
* Basic interface for finding and publishing system services.
- *
+ *
* An implementation of this interface is usually published as the
* global context object, which can be retrieved via
* BinderNative.getContextObject(). An easy way to retrieve this
* is with the static method BnServiceManager.getDefault().
- *
+ *
* @hide
*/
public interface IServiceManager extends IInterface
@@ -33,33 +33,33 @@ public interface IServiceManager extends IInterface
* service manager. Blocks for a few seconds waiting for it to be
* published if it does not already exist.
*/
- public IBinder getService(String name) throws RemoteException;
-
+ IBinder getService(String name) throws RemoteException;
+
/**
* Retrieve an existing service called @a name from the
* service manager. Non-blocking.
*/
- public IBinder checkService(String name) throws RemoteException;
+ IBinder checkService(String name) throws RemoteException;
/**
* Place a new @a service called @a name into the service
* manager.
*/
- public void addService(String name, IBinder service, boolean allowIsolated)
+ void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
throws RemoteException;
/**
* Return a list of all currently running services.
*/
- public String[] listServices() throws RemoteException;
+ String[] listServices(int dumpPriority) throws RemoteException;
/**
* Assign a permission controller to the service manager. After set, this
* interface is checked before any services are added.
*/
- public void setPermissionController(IPermissionController controller)
+ void setPermissionController(IPermissionController controller)
throws RemoteException;
-
+
static final String descriptor = "android.os.IServiceManager";
int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
@@ -68,4 +68,13 @@ public interface IServiceManager extends IInterface
int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
+
+ /*
+ * Must update values in IServiceManager.h
+ */
+ int DUMP_PRIORITY_CRITICAL = 1 << 0;
+ int DUMP_PRIORITY_HIGH = 1 << 1;
+ int DUMP_PRIORITY_NORMAL = 1 << 2;
+ int DUMP_PRIORITY_ALL = DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_HIGH
+ | DUMP_PRIORITY_NORMAL;
}
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
index 031ca91c..857e8a60 100644
--- a/android/os/Parcel.java
+++ b/android/os/Parcel.java
@@ -1340,6 +1340,13 @@ public final class Parcel {
* @see Parcelable
*/
public final <T extends Parcelable> void writeTypedList(List<T> val) {
+ writeTypedList(val, 0);
+ }
+
+ /**
+ * @hide
+ */
+ public <T extends Parcelable> void writeTypedList(List<T> val, int parcelableFlags) {
if (val == null) {
writeInt(-1);
return;
@@ -1348,13 +1355,7 @@ public final class Parcel {
int i=0;
writeInt(N);
while (i < N) {
- T item = val.get(i);
- if (item != null) {
- writeInt(1);
- item.writeToParcel(this, 0);
- } else {
- writeInt(0);
- }
+ writeTypedObject(val.get(i), parcelableFlags);
i++;
}
}
@@ -1456,116 +1457,7 @@ public final class Parcel {
int N = val.length;
writeInt(N);
for (int i = 0; i < N; i++) {
- T item = val[i];
- if (item != null) {
- writeInt(1);
- item.writeToParcel(this, parcelableFlags);
- } else {
- writeInt(0);
- }
- }
- } else {
- writeInt(-1);
- }
- }
-
- /**
- * Write a uniform (all items are null or the same class) array list of
- * parcelables.
- *
- * @param list The list to write.
- *
- * @hide
- */
- public final <T extends Parcelable> void writeTypedArrayList(@Nullable ArrayList<T> list,
- int parcelableFlags) {
- if (list != null) {
- int N = list.size();
- writeInt(N);
- boolean wroteCreator = false;
- for (int i = 0; i < N; i++) {
- T item = list.get(i);
- if (item != null) {
- writeInt(1);
- if (!wroteCreator) {
- writeParcelableCreator(item);
- wroteCreator = true;
- }
- item.writeToParcel(this, parcelableFlags);
- } else {
- writeInt(0);
- }
- }
- } else {
- writeInt(-1);
- }
- }
-
- /**
- * Reads a uniform (all items are null or the same class) array list of
- * parcelables.
- *
- * @return The list or null.
- *
- * @hide
- */
- public final @Nullable <T> ArrayList<T> readTypedArrayList(@Nullable ClassLoader loader) {
- int N = readInt();
- if (N <= 0) {
- return null;
- }
- Parcelable.Creator<?> creator = null;
- ArrayList<T> result = new ArrayList<T>(N);
- for (int i = 0; i < N; i++) {
- if (readInt() != 0) {
- if (creator == null) {
- creator = readParcelableCreator(loader);
- if (creator == null) {
- return null;
- }
- }
- final T parcelable;
- if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
- Parcelable.ClassLoaderCreator<?> classLoaderCreator =
- (Parcelable.ClassLoaderCreator<?>) creator;
- parcelable = (T) classLoaderCreator.createFromParcel(this, loader);
- } else {
- parcelable = (T) creator.createFromParcel(this);
- }
- result.add(parcelable);
- } else {
- result.add(null);
- }
- }
- return result;
- }
-
- /**
- * Write a uniform (all items are null or the same class) array set of
- * parcelables.
- *
- * @param set The set to write.
- *
- * @hide
- */
- public final <T extends Parcelable> void writeTypedArraySet(@Nullable ArraySet<T> set,
- int parcelableFlags) {
- if (set != null) {
- int N = set.size();
- writeInt(N);
- boolean wroteCreator = false;
- for (int i = 0; i < N; i++) {
- T item = set.valueAt(i);
- if (item != null) {
- writeInt(1);
- if (!wroteCreator) {
- writeParcelableCreator(item);
- wroteCreator = true;
- }
- item.writeToParcel(this, parcelableFlags);
- } else {
- writeInt(0);
- }
+ writeTypedObject(val[i], parcelableFlags);
}
} else {
writeInt(-1);
@@ -1573,43 +1465,6 @@ public final class Parcel {
}
/**
- * Reads a uniform (all items are null or the same class) array set of
- * parcelables.
- *
- * @return The set or null.
- *
- * @hide
- */
- public final @Nullable <T> ArraySet<T> readTypedArraySet(@Nullable ClassLoader loader) {
- int N = readInt();
- if (N <= 0) {
- return null;
- }
- Parcelable.Creator<?> creator = null;
- ArraySet<T> result = new ArraySet<T>(N);
- for (int i = 0; i < N; i++) {
- T parcelable = null;
- if (readInt() != 0) {
- if (creator == null) {
- creator = readParcelableCreator(loader);
- if (creator == null) {
- return null;
- }
- }
- if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
- Parcelable.ClassLoaderCreator<?> classLoaderCreator =
- (Parcelable.ClassLoaderCreator<?>) creator;
- parcelable = (T) classLoaderCreator.createFromParcel(this, loader);
- } else {
- parcelable = (T) creator.createFromParcel(this);
- }
- }
- result.append(parcelable);
- }
- return result;
- }
-
- /**
* Flatten the Parcelable object into the parcel.
*
* @param val The Parcelable object to be written.
@@ -2458,11 +2313,7 @@ public final class Parcel {
}
ArrayList<T> l = new ArrayList<T>(N);
while (N > 0) {
- if (readInt() != 0) {
- l.add(c.createFromParcel(this));
- } else {
- l.add(null);
- }
+ l.add(readTypedObject(c));
N--;
}
return l;
@@ -2485,18 +2336,10 @@ public final class Parcel {
int N = readInt();
int i = 0;
for (; i < M && i < N; i++) {
- if (readInt() != 0) {
- list.set(i, c.createFromParcel(this));
- } else {
- list.set(i, null);
- }
+ list.set(i, readTypedObject(c));
}
for (; i<N; i++) {
- if (readInt() != 0) {
- list.add(c.createFromParcel(this));
- } else {
- list.add(null);
- }
+ list.add(readTypedObject(c));
}
for (; i<M; i++) {
list.remove(N);
@@ -2641,9 +2484,7 @@ public final class Parcel {
}
T[] l = c.newArray(N);
for (int i=0; i<N; i++) {
- if (readInt() != 0) {
- l[i] = c.createFromParcel(this);
- }
+ l[i] = readTypedObject(c);
}
return l;
}
@@ -2652,11 +2493,7 @@ public final class Parcel {
int N = readInt();
if (N == val.length) {
for (int i=0; i<N; i++) {
- if (readInt() != 0) {
- val[i] = c.createFromParcel(this);
- } else {
- val[i] = null;
- }
+ val[i] = readTypedObject(c);
}
} else {
throw new RuntimeException("bad array lengths");
diff --git a/android/os/ParcelableException.java b/android/os/ParcelableException.java
index d84d6299..7f71905d 100644
--- a/android/os/ParcelableException.java
+++ b/android/os/ParcelableException.java
@@ -52,10 +52,12 @@ public final class ParcelableException extends RuntimeException implements Parce
final String msg = in.readString();
try {
final Class<?> clazz = Class.forName(name, true, Parcelable.class.getClassLoader());
- return (Throwable) clazz.getConstructor(String.class).newInstance(msg);
+ if (Throwable.class.isAssignableFrom(clazz)) {
+ return (Throwable) clazz.getConstructor(String.class).newInstance(msg);
+ }
} catch (ReflectiveOperationException e) {
- throw new RuntimeException(name + ": " + msg);
}
+ return new RuntimeException(name + ": " + msg);
}
/** {@hide} */
diff --git a/android/os/PowerManager.java b/android/os/PowerManager.java
index 960c9f5c..7f4dee6e 100644
--- a/android/os/PowerManager.java
+++ b/android/os/PowerManager.java
@@ -443,6 +443,20 @@ public final class PowerManager {
public static final String SHUTDOWN_USER_REQUESTED = "userrequested";
/**
+ * The value to pass as the 'reason' argument to android_reboot() when battery temperature
+ * is too high.
+ * @hide
+ */
+ public static final String SHUTDOWN_BATTERY_THERMAL_STATE = "thermal,battery";
+
+ /**
+ * The value to pass as the 'reason' argument to android_reboot() when device is running
+ * critically low on battery.
+ * @hide
+ */
+ public static final String SHUTDOWN_LOW_BATTERY = "battery";
+
+ /**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@@ -451,7 +465,9 @@ public final class PowerManager {
SHUTDOWN_REASON_SHUTDOWN,
SHUTDOWN_REASON_REBOOT,
SHUTDOWN_REASON_USER_REQUESTED,
- SHUTDOWN_REASON_THERMAL_SHUTDOWN
+ SHUTDOWN_REASON_THERMAL_SHUTDOWN,
+ SHUTDOWN_REASON_LOW_BATTERY,
+ SHUTDOWN_REASON_BATTERY_THERMAL
})
public @interface ShutdownReason {}
@@ -485,6 +501,18 @@ public final class PowerManager {
*/
public static final int SHUTDOWN_REASON_THERMAL_SHUTDOWN = 4;
+ /**
+ * constant for shutdown reason being low battery.
+ * @hide
+ */
+ public static final int SHUTDOWN_REASON_LOW_BATTERY = 5;
+
+ /**
+ * constant for shutdown reason being critical battery thermal state.
+ * @hide
+ */
+ public static final int SHUTDOWN_REASON_BATTERY_THERMAL = 6;
+
final Context mContext;
final IPowerManager mService;
final Handler mHandler;
@@ -1384,7 +1412,11 @@ public final class PowerManager {
*/
public void release(int flags) {
synchronized (mToken) {
- mInternalCount--;
+ if (mInternalCount > 0) {
+ // internal count must only be decreased if it is > 0 or state of
+ // the WakeLock object is broken.
+ mInternalCount--;
+ }
if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
mExternalCount--;
}
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index 34c78455..f41848fa 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,29 @@
package android.os;
+import android.util.Log;
+
+import com.android.internal.os.BinderInternal;
+
+import java.util.HashMap;
import java.util.Map;
+/** @hide */
public final class ServiceManager {
+ private static final String TAG = "ServiceManager";
+ private static IServiceManager sServiceManager;
+ private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
+
+ private static IServiceManager getIServiceManager() {
+ if (sServiceManager != null) {
+ return sServiceManager;
+ }
+
+ // Find the service manager
+ sServiceManager = ServiceManagerNative
+ .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
+ return sServiceManager;
+ }
/**
* Returns a reference to a service with the given name.
@@ -27,14 +47,32 @@ public final class ServiceManager {
* @return a reference to the service, or <code>null</code> if the service doesn't exist
*/
public static IBinder getService(String name) {
+ try {
+ IBinder service = sCache.get(name);
+ if (service != null) {
+ return service;
+ } else {
+ return Binder.allowBlocking(getIServiceManager().getService(name));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in getService", e);
+ }
return null;
}
/**
- * Is not supposed to return null, but that is fine for layoutlib.
+ * Returns a reference to a service with the given name, or throws
+ * {@link NullPointerException} if none is found.
+ *
+ * @hide
*/
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
- throw new ServiceNotFoundException(name);
+ final IBinder binder = getService(name);
+ if (binder != null) {
+ return binder;
+ } else {
+ throw new ServiceNotFoundException(name);
+ }
}
/**
@@ -45,7 +83,39 @@ public final class ServiceManager {
* @param service the service object
*/
public static void addService(String name, IBinder service) {
- // pass
+ addService(name, service, false, IServiceManager.DUMP_PRIORITY_NORMAL);
+ }
+
+ /**
+ * Place a new @a service called @a name into the service
+ * manager.
+ *
+ * @param name the name of the new service
+ * @param service the service object
+ * @param allowIsolated set to true to allow isolated sandboxed processes
+ * to access this service
+ */
+ public static void addService(String name, IBinder service, boolean allowIsolated) {
+ addService(name, service, allowIsolated, IServiceManager.DUMP_PRIORITY_NORMAL);
+ }
+
+ /**
+ * Place a new @a service called @a name into the service
+ * manager.
+ *
+ * @param name the name of the new service
+ * @param service the service object
+ * @param allowIsolated set to true to allow isolated sandboxed processes
+ * @param dumpPriority supported dump priority levels as a bitmask
+ * to access this service
+ */
+ public static void addService(String name, IBinder service, boolean allowIsolated,
+ int dumpPriority) {
+ try {
+ getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in addService", e);
+ }
}
/**
@@ -53,7 +123,17 @@ public final class ServiceManager {
* service manager. Non-blocking.
*/
public static IBinder checkService(String name) {
- return null;
+ try {
+ IBinder service = sCache.get(name);
+ if (service != null) {
+ return service;
+ } else {
+ return Binder.allowBlocking(getIServiceManager().checkService(name));
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in checkService", e);
+ return null;
+ }
}
/**
@@ -62,9 +142,12 @@ public final class ServiceManager {
* case of an exception
*/
public static String[] listServices() {
- // actual implementation returns null sometimes, so it's ok
- // to return null instead of an empty list.
- return null;
+ try {
+ return getIServiceManager().listServices(IServiceManager.DUMP_PRIORITY_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in listServices", e);
+ return null;
+ }
}
/**
@@ -76,7 +159,10 @@ public final class ServiceManager {
* @hide
*/
public static void initServiceCache(Map<String, IBinder> cache) {
- // pass
+ if (sCache.size() != 0) {
+ throw new IllegalStateException("setServiceCache may only be called once");
+ }
+ sCache.putAll(cache);
}
/**
@@ -87,7 +173,6 @@ public final class ServiceManager {
* @hide
*/
public static class ServiceNotFoundException extends Exception {
- // identical to the original implementation
public ServiceNotFoundException(String name) {
super("No service published for: " + name);
}
diff --git a/android/os/ServiceManagerNative.java b/android/os/ServiceManagerNative.java
index be244264..589b8c49 100644
--- a/android/os/ServiceManagerNative.java
+++ b/android/os/ServiceManagerNative.java
@@ -40,63 +40,65 @@ public abstract class ServiceManagerNative extends Binder implements IServiceMan
if (in != null) {
return in;
}
-
+
return new ServiceManagerProxy(obj);
}
-
+
public ServiceManagerNative()
{
attachInterface(this, descriptor);
}
-
+
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
{
try {
switch (code) {
- case IServiceManager.GET_SERVICE_TRANSACTION: {
- data.enforceInterface(IServiceManager.descriptor);
- String name = data.readString();
- IBinder service = getService(name);
- reply.writeStrongBinder(service);
- return true;
- }
-
- case IServiceManager.CHECK_SERVICE_TRANSACTION: {
- data.enforceInterface(IServiceManager.descriptor);
- String name = data.readString();
- IBinder service = checkService(name);
- reply.writeStrongBinder(service);
- return true;
- }
-
- case IServiceManager.ADD_SERVICE_TRANSACTION: {
- data.enforceInterface(IServiceManager.descriptor);
- String name = data.readString();
- IBinder service = data.readStrongBinder();
- boolean allowIsolated = data.readInt() != 0;
- addService(name, service, allowIsolated);
- return true;
- }
-
- case IServiceManager.LIST_SERVICES_TRANSACTION: {
- data.enforceInterface(IServiceManager.descriptor);
- String[] list = listServices();
- reply.writeStringArray(list);
- return true;
- }
-
- case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
- data.enforceInterface(IServiceManager.descriptor);
- IPermissionController controller
- = IPermissionController.Stub.asInterface(
- data.readStrongBinder());
- setPermissionController(controller);
- return true;
- }
+ case IServiceManager.GET_SERVICE_TRANSACTION: {
+ data.enforceInterface(IServiceManager.descriptor);
+ String name = data.readString();
+ IBinder service = getService(name);
+ reply.writeStrongBinder(service);
+ return true;
+ }
+
+ case IServiceManager.CHECK_SERVICE_TRANSACTION: {
+ data.enforceInterface(IServiceManager.descriptor);
+ String name = data.readString();
+ IBinder service = checkService(name);
+ reply.writeStrongBinder(service);
+ return true;
+ }
+
+ case IServiceManager.ADD_SERVICE_TRANSACTION: {
+ data.enforceInterface(IServiceManager.descriptor);
+ String name = data.readString();
+ IBinder service = data.readStrongBinder();
+ boolean allowIsolated = data.readInt() != 0;
+ int dumpPriority = data.readInt();
+ addService(name, service, allowIsolated, dumpPriority);
+ return true;
+ }
+
+ case IServiceManager.LIST_SERVICES_TRANSACTION: {
+ data.enforceInterface(IServiceManager.descriptor);
+ int dumpPriority = data.readInt();
+ String[] list = listServices(dumpPriority);
+ reply.writeStringArray(list);
+ return true;
+ }
+
+ case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
+ data.enforceInterface(IServiceManager.descriptor);
+ IPermissionController controller =
+ IPermissionController.Stub.asInterface(
+ data.readStrongBinder());
+ setPermissionController(controller);
+ return true;
+ }
}
} catch (RemoteException e) {
}
-
+
return false;
}
@@ -110,11 +112,11 @@ class ServiceManagerProxy implements IServiceManager {
public ServiceManagerProxy(IBinder remote) {
mRemote = remote;
}
-
+
public IBinder asBinder() {
return mRemote;
}
-
+
public IBinder getService(String name) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -139,7 +141,7 @@ class ServiceManagerProxy implements IServiceManager {
return binder;
}
- public void addService(String name, IBinder service, boolean allowIsolated)
+ public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -147,12 +149,13 @@ class ServiceManagerProxy implements IServiceManager {
data.writeString(name);
data.writeStrongBinder(service);
data.writeInt(allowIsolated ? 1 : 0);
+ data.writeInt(dumpPriority);
mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
reply.recycle();
data.recycle();
}
-
- public String[] listServices() throws RemoteException {
+
+ public String[] listServices(int dumpPriority) throws RemoteException {
ArrayList<String> services = new ArrayList<String>();
int n = 0;
while (true) {
@@ -160,6 +163,7 @@ class ServiceManagerProxy implements IServiceManager {
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeInt(n);
+ data.writeInt(dumpPriority);
n++;
try {
boolean res = mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0);
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index f02631c7..826ec1eb 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.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -153,12 +154,15 @@ public final class StrictMode {
// Byte 1: Thread-policy
/** @hide */
+ @TestApi
public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
/** @hide */
+ @TestApi
public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
/** @hide */
+ @TestApi
public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
/**
@@ -166,6 +170,7 @@ public final class StrictMode {
*
* @hide
*/
+ @TestApi
public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
/**
@@ -173,9 +178,11 @@ public final class StrictMode {
*
* @hide
*/
+ @TestApi
public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
/** @hide */
+ @TestApi
public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
private static final int ALL_THREAD_DETECT_BITS =
@@ -193,6 +200,7 @@ public final class StrictMode {
*
* @hide
*/
+ @TestApi
public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
/**
@@ -200,6 +208,7 @@ public final class StrictMode {
*
* @hide
*/
+ @TestApi
public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
/**
@@ -207,25 +216,32 @@ public final class StrictMode {
*
* @hide
*/
+ @TestApi
public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
/** @hide */
- private 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
/** @hide */
- private 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 */
- private 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 */
- private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
+ @TestApi
+ public static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
/** @hide */
- private 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
@@ -322,16 +338,36 @@ public final class StrictMode {
/** {@hide} */
@TestApi
- public interface ViolationListener {
- public void onViolation(String message);
+ public interface ViolationLogger {
+
+ /** Called when penaltyLog is enabled and a violation needs logging. */
+ void log(ViolationInfo info);
}
- private static volatile ViolationListener sListener;
+ private static final ViolationLogger LOGCAT_LOGGER =
+ info -> {
+ String msg;
+ if (info.durationMillis != -1) {
+ msg = "StrictMode policy violation; ~duration=" + info.durationMillis + " ms:";
+ } else {
+ msg = "StrictMode policy violation:";
+ }
+ if (info.crashInfo != null) {
+ Log.d(TAG, msg + " " + info.crashInfo.stackTrace);
+ } else {
+ Log.d(TAG, msg + " missing stack trace!");
+ }
+ };
+
+ private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
/** {@hide} */
@TestApi
- public static void setViolationListener(ViolationListener listener) {
- sListener = listener;
+ public static void setViolationLogger(ViolationLogger listener) {
+ if (listener == null) {
+ listener = LOGCAT_LOGGER;
+ }
+ sLogger = listener;
}
/**
@@ -1512,28 +1548,16 @@ public final class StrictMode {
lastViolationTime = vtime;
}
} else {
- mLastViolationTime = new ArrayMap<Integer, Long>(1);
+ mLastViolationTime = new ArrayMap<>(1);
}
long now = SystemClock.uptimeMillis();
mLastViolationTime.put(crashFingerprint, now);
long timeSinceLastViolationMillis =
lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime);
- if ((info.policy & PENALTY_LOG) != 0 && sListener != null) {
- sListener.onViolation(info.crashInfo.stackTrace);
- }
if ((info.policy & PENALTY_LOG) != 0
&& timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
- if (info.durationMillis != -1) {
- Log.d(
- TAG,
- "StrictMode policy violation; ~duration="
- + info.durationMillis
- + " ms: "
- + info.crashInfo.stackTrace);
- } else {
- Log.d(TAG, "StrictMode policy violation: " + info.crashInfo.stackTrace);
- }
+ sLogger.log(info);
}
// The violationMaskSubset, passed to ActivityManager, is a
@@ -1849,6 +1873,10 @@ public final class StrictMode {
}
/** @hide */
+ public static final String CLEARTEXT_DETECTED_MSG =
+ "Detected cleartext network traffic from UID ";
+
+ /** @hide */
public static void onCleartextNetworkDetected(byte[] firstPacket) {
byte[] rawAddr = null;
if (firstPacket != null) {
@@ -1864,14 +1892,10 @@ public final class StrictMode {
}
final int uid = android.os.Process.myUid();
- String msg = "Detected cleartext network traffic from UID " + uid;
+ String msg = CLEARTEXT_DETECTED_MSG + uid;
if (rawAddr != null) {
try {
- msg =
- "Detected cleartext network traffic from UID "
- + uid
- + " to "
- + InetAddress.getByAddress(rawAddr);
+ msg += " to " + InetAddress.getByAddress(rawAddr);
} catch (UnknownHostException ignored) {
}
}
@@ -1882,12 +1906,13 @@ public final class StrictMode {
}
/** @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(
- null,
- new Throwable(
- "Untagged socket detected; use"
- + " TrafficStats.setThreadSocketTag() to track all network usage"));
+ onVmPolicyViolation(null, new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
}
// Map from VM violation fingerprint to uptime millis.
@@ -1925,11 +1950,11 @@ public final class StrictMode {
}
}
- if (penaltyLog && sListener != null) {
- sListener.onViolation(originStack.toString());
+ if (penaltyLog && sLogger != null) {
+ sLogger.log(info);
}
if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
- Log.e(TAG, message, originStack);
+ sLogger.log(info);
}
int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicy.mask);
@@ -2339,11 +2364,12 @@ public final class StrictMode {
*
* @hide
*/
- public static class ViolationInfo implements Parcelable {
+ @TestApi
+ public static final class ViolationInfo implements Parcelable {
public final String message;
/** Stack and other stuff info. */
- public final ApplicationErrorReport.CrashInfo crashInfo;
+ @Nullable public final ApplicationErrorReport.CrashInfo crashInfo;
/** The strict mode policy mask at the time of violation. */
public final int policy;
diff --git a/android/os/UserManagerInternal.java b/android/os/UserManagerInternal.java
index 17f00c24..9369eebf 100644
--- a/android/os/UserManagerInternal.java
+++ b/android/os/UserManagerInternal.java
@@ -154,11 +154,21 @@ public abstract class UserManagerInternal {
public abstract boolean isUserUnlocked(int userId);
/**
- * Return whether the given user is running
+ * Returns whether the given user is running
*/
public abstract boolean isUserRunning(int userId);
/**
+ * Returns whether the given user is initialized
+ */
+ public abstract boolean isUserInitialized(int userId);
+
+ /**
+ * Returns whether the given user exists
+ */
+ public abstract boolean exists(int userId);
+
+ /**
* Set user's running state
*/
public abstract void setUserState(int userId, int userState);
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index 40ced6ce..a062db43 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -6966,8 +6966,9 @@ public final class Settings {
public static final String NIGHT_DISPLAY_CUSTOM_END_TIME = "night_display_custom_end_time";
/**
- * Time in milliseconds (since epoch) when Night display was last activated. Use to decide
- * whether to apply the current activated state after a reboot or user change.
+ * A String representing the LocalDateTime when Night display was last activated. Use to
+ * decide whether to apply the current activated state after a reboot or user change. In
+ * legacy cases, this is represented by the time in milliseconds (since epoch).
* @hide
*/
public static final String NIGHT_DISPLAY_LAST_ACTIVATED_TIME =
@@ -9368,16 +9369,6 @@ public final class Settings {
public static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants";
/**
- * Device Idle (Doze) specific settings for watches. See {@code #DEVICE_IDLE_CONSTANTS}
- *
- * <p>
- * Type: string
- * @hide
- * @see com.android.server.DeviceIdleController.Constants
- */
- public static final String DEVICE_IDLE_CONSTANTS_WATCH = "device_idle_constants_watch";
-
- /**
* Battery Saver specific settings
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -9403,9 +9394,12 @@ public final class Settings {
/**
* Battery anomaly detection specific settings
- * This is encoded as a key=value list, separated by commas. Ex:
+ * 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
+ * {@link Uri#encode(String)}, separated by colons. Ex:
*
- * "anomaly_detection_enabled=true,wakelock_threshold=2000"
+ * "anomaly_detection_enabled=true,wakelock_threshold=2000,wakeup_alarm_enabled=true,"
+ * "wakeup_alarm_threshold=10,wakeup_blacklisted_tags=tag1:tag2:with%2Ccomma:with%3Acolon"
*
* The following keys are supported:
*
@@ -9413,6 +9407,11 @@ public final class Settings {
* anomaly_detection_enabled (boolean)
* wakelock_enabled (boolean)
* wakelock_threshold (long)
+ * wakeup_alarm_enabled (boolean)
+ * wakeup_alarm_threshold (long)
+ * wakeup_blacklisted_tags (string)
+ * bluetooth_scan_enabled (boolean)
+ * bluetooth_scan_threshold (long)
* </pre>
* @hide
*/
@@ -10199,7 +10198,7 @@ public final class Settings {
"allow_user_switching_when_system_user_locked";
/**
- * Boot count since the device starts running APK level 24.
+ * Boot count since the device starts running API level 24.
* <p>
* Type: int
*/
@@ -10893,6 +10892,26 @@ public final class Settings {
*/
public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE =
"enable_deletion_helper_no_threshold_toggle";
+
+ /**
+ * The list of snooze options for notifications
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * "default=60,options_array=15:30:60:120"
+ *
+ * The following keys are supported:
+ *
+ * <pre>
+ * default (int)
+ * options_array (string)
+ * </pre>
+ *
+ * All delays in integer minutes. Array order is respected.
+ * Options will be used in order up to the maximum allowed by the UI.
+ * @hide
+ */
+ public static final String NOTIFICATION_SNOOZE_OPTIONS =
+ "notification_snooze_options";
}
/**
diff --git a/android/service/autofill/AutofillService.java b/android/service/autofill/AutofillService.java
index 3e08dcf2..2e59f6c5 100644
--- a/android/service/autofill/AutofillService.java
+++ b/android/service/autofill/AutofillService.java
@@ -187,7 +187,7 @@ import com.android.internal.os.SomeArgs;
* protect a dataset that contains sensitive information by requiring dataset authentication
* (see {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}), and to include
* info about the "primary" field of the partition in the custom presentation for "secondary"
- * fields &mdash; that would prevent a malicious app from getting the "primary" fields without the
+ * fields&mdash;that would prevent a malicious app from getting the "primary" fields without the
* user realizing they're being released (for example, a malicious app could have fields for a
* credit card number, verification code, and expiration date crafted in a way that just the latter
* is visible; by explicitly indicating the expiration date is related to a given credit card
@@ -305,7 +305,7 @@ import com.android.internal.os.SomeArgs;
* <li>Use the {@link android.app.assist.AssistStructure.ViewNode#getWebDomain()} to get the
* source of the document.
* <li>Get the canonical domain using the
- * <a href="https://publicsuffix.org/>Public Suffix List</a> (see example below).
+ * <a href="https://publicsuffix.org/">Public Suffix List</a> (see example below).
* <li>Use <a href="https://developers.google.com/digital-asset-links/">Digital Asset Links</a>
* to obtain the package name and certificate fingerprint of the package corresponding to
* the canonical domain.
@@ -503,13 +503,19 @@ public abstract class AutofillService extends Service {
@NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
/**
- * Called when user requests service to save the fields of a screen.
+ * Called when the user requests the service to save the contents of a screen.
*
* <p>Service must call one of the {@link SaveCallback} methods (like
* {@link SaveCallback#onSuccess()} or {@link SaveCallback#onFailure(CharSequence)})
- * to notify the result of the request.
+ * to notify the Android System of the result of the request.
+ *
+ * <p>If the service could not handle the request right away&mdash;for example, because it must
+ * launch an activity asking the user to authenticate first or because the network is
+ * down&mdash;the service could keep the {@link SaveRequest request} and reuse it later,
+ * but the service must call {@link SaveCallback#onSuccess()} right away.
*
- * <p><b>Note:</b> To retrieve the actual value of the field, the service should call
+ * <p><b>Note:</b> To retrieve the actual value of fields input by the user, the service
+ * should call
* {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}; if it calls
* {@link android.app.assist.AssistStructure.ViewNode#getText()} or other methods, there is no
* guarantee such method will return the most recent value of the field.
diff --git a/android/service/autofill/AutofillServiceInfo.java b/android/service/autofill/AutofillServiceInfo.java
index f1474006..5c7388f7 100644
--- a/android/service/autofill/AutofillServiceInfo.java
+++ b/android/service/autofill/AutofillServiceInfo.java
@@ -146,4 +146,9 @@ public final class AutofillServiceInfo {
public String getSettingsActivity() {
return mSettingsActivity;
}
+
+ @Override
+ public String toString() {
+ return mServiceInfo == null ? "null" : mServiceInfo.toString();
+ }
}
diff --git a/android/service/autofill/Dataset.java b/android/service/autofill/Dataset.java
index cb341b1d..ef9598aa 100644
--- a/android/service/autofill/Dataset.java
+++ b/android/service/autofill/Dataset.java
@@ -29,32 +29,77 @@ import android.widget.RemoteViews;
import com.android.internal.util.Preconditions;
+import java.io.Serializable;
import java.util.ArrayList;
+import java.util.regex.Pattern;
/**
- * A dataset object represents a group of key/value pairs used to autofill parts of a screen.
+ * A dataset object represents a group of fields (key / value pairs) used to autofill parts of a
+ * screen.
*
- * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of
- * {@link AutofillId} and {@link AutofillValue} respectively); and one or more
- * {@link RemoteViews presentation} for these pairs (a pair could have its own
- * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated
- * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse}
+ * <a name="BasicUsage"></a>
+ * <h3>Basic usage</h3>
+ *
+ * <p>In its simplest form, a dataset contains one or more fields (comprised of
+ * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter
+ * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields
+ * (each field could have its own {@link RemoteViews presentation}, or use the default
+ * {@link RemoteViews presentation} associated with the whole dataset).
+ *
+ * <p>When an autofill service returns datasets in a {@link FillResponse}
* and the screen input is focused in a view that is present in at least one of these datasets,
- * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of
+ * the Android System displays a UI containing the {@link RemoteViews presentation} of
* all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
- * dataset from the affordance, all views in that dataset are autofilled.
+ * dataset from the UI, all views in that dataset are autofilled.
+ *
+ * <a name="Authentication"></a>
+ * <h3>Dataset authentication</h3>
+ *
+ * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates
+ * the dataset&mdash;in that case, when a dataset is selected by the user, the Android System
+ * launches an intent set by the service to "unlock" the dataset.
+ *
+ * <p>For example, when a data set contains credit card information (such as number,
+ * expiration date, and verification code), you could provide a dataset presentation saying
+ * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking
+ * the user to enter the credit card code, and if the user enters a valid code, you could then
+ * "unlock" the dataset.
+ *
+ * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example,
+ * if the activity being autofilled is an account creation screen, you could use an authenticated
+ * dataset to automatically generate a random password for the user.
*
- * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates
- * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}.
+ * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset
+ * authentication mechanism.
*
- * @see android.service.autofill.AutofillService for more information and examples about the
- * role of datasets in the autofill workflow.
+ * <a name="Filtering"></a>
+ * <h3>Filtering</h3>
+ * <p>The autofill UI automatically changes which values are shown based on value of the view
+ * anchoring it, following the rules below:
+ * <ol>
+ * <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
+ * {@link AutofillValue#isText() text} or is empty, all datasets are shown.
+ * <li>Datasets that have a filter regex (set through
+ * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
+ * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
+ * regex matches the view's text value converted to lower case are shown.
+ * <li>Datasets that do not require authentication, have a field value that is
+ * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
+ * with the lower case value of the view's text are shown.
+ * <li>All other datasets are hidden.
+ * </ol>
+ *
+ * <a name="MoreInfo"></a>
+ * <h3>More information</h3>
+ * <p>See {@link android.service.autofill.AutofillService} for more information and examples about
+ * the role of datasets in the autofill workflow.
*/
public final class Dataset implements Parcelable {
private final ArrayList<AutofillId> mFieldIds;
private final ArrayList<AutofillValue> mFieldValues;
private final ArrayList<RemoteViews> mFieldPresentations;
+ private final ArrayList<Pattern> mFieldFilters;
private final RemoteViews mPresentation;
private final IntentSender mAuthentication;
@Nullable String mId;
@@ -63,6 +108,7 @@ public final class Dataset implements Parcelable {
mFieldIds = builder.mFieldIds;
mFieldValues = builder.mFieldValues;
mFieldPresentations = builder.mFieldPresentations;
+ mFieldFilters = builder.mFieldFilters;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
mId = builder.mId;
@@ -85,6 +131,12 @@ public final class Dataset implements Parcelable {
}
/** @hide */
+ @Nullable
+ public Pattern getFilter(int index) {
+ return mFieldFilters.get(index);
+ }
+
+ /** @hide */
public @Nullable IntentSender getAuthentication() {
return mAuthentication;
}
@@ -103,6 +155,8 @@ public final class Dataset implements Parcelable {
.append(", fieldValues=").append(mFieldValues)
.append(", fieldPresentations=")
.append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
+ .append(", fieldFilters=")
+ .append(mFieldFilters == null ? 0 : mFieldFilters.size())
.append(", hasPresentation=").append(mPresentation != null)
.append(", hasAuthentication=").append(mAuthentication != null)
.append(']').toString();
@@ -127,6 +181,7 @@ public final class Dataset implements Parcelable {
private ArrayList<AutofillId> mFieldIds;
private ArrayList<AutofillValue> mFieldValues;
private ArrayList<RemoteViews> mFieldPresentations;
+ private ArrayList<Pattern> mFieldFilters;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private boolean mDestroyed;
@@ -182,12 +237,12 @@ public final class Dataset implements Parcelable {
* credit card information without the CVV for the data set in the {@link FillResponse
* response} then the returned data set should contain the CVV entry.
*
- * <p><b>NOTE:</b> Do not make the provided pending intent
+ * <p><b>Note:</b> Do not make the provided pending intent
* immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
* platform needs to fill in the authentication arguments.
*
* @param authentication Intent to an activity with your authentication flow.
- * @return This builder.
+ * @return this builder.
*
* @see android.app.PendingIntent
*/
@@ -214,11 +269,10 @@ public final class Dataset implements Parcelable {
*
* @param id id for this dataset or {@code null} to unset.
*
- * @return This builder.
+ * @return this builder.
*/
public @NonNull Builder setId(@Nullable String id) {
throwIfDestroyed();
-
mId = id;
return this;
}
@@ -230,17 +284,16 @@ public final class Dataset implements Parcelable {
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
* @param value value to be autofilled. Pass {@code null} if you do not have the value
* but the target view is a logical part of the dataset. For example, if
- * the dataset needs an authentication and you have no access to the value.
- * @return This builder.
+ * the dataset needs authentication and you have no access to the value.
+ * @return this builder.
* @throws IllegalStateException if the builder was constructed without a
* {@link RemoteViews presentation}.
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
throwIfDestroyed();
- if (mPresentation == null) {
- throw new IllegalStateException("Dataset presentation not set on constructor");
- }
- setValueAndPresentation(id, value, null);
+ Preconditions.checkState(mPresentation != null,
+ "Dataset presentation not set on constructor");
+ setLifeTheUniverseAndEverything(id, value, null, null);
return this;
}
@@ -250,23 +303,81 @@ public final class Dataset implements Parcelable {
*
* @param id id returned by {@link
* android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
- * @param value value to be auto filled. Pass {@code null} if you do not have the value
+ * @param value the value to be autofilled. Pass {@code null} if you do not have the value
* but the target view is a logical part of the dataset. For example, if
- * the dataset needs an authentication and you have no access to the value.
- * Filtering matches any user typed string to {@code null} values.
- * @param presentation The presentation used to visualize this field.
- * @return This builder.
+ * the dataset needs authentication and you have no access to the value.
+ * @param presentation the presentation used to visualize this field.
+ * @return this builder.
+ *
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
@NonNull RemoteViews presentation) {
throwIfDestroyed();
Preconditions.checkNotNull(presentation, "presentation cannot be null");
- setValueAndPresentation(id, value, presentation);
+ setLifeTheUniverseAndEverything(id, value, presentation, null);
+ return this;
+ }
+
+ /**
+ * Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
+ *
+ * <p>This method is typically used when the dataset is not authenticated and the field
+ * value is not {@link AutofillValue#isText() text} but the service still wants to allow
+ * the user to filter it out.
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if
+ * the dataset needs authentication and you have no access to the value.
+ * @param filter regex used to determine if the dataset should be shown in the autofill UI.
+ *
+ * @return this builder.
+ * @throws IllegalStateException if the builder was constructed without a
+ * {@link RemoteViews presentation}.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+ @NonNull Pattern filter) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(filter, "filter cannot be null");
+ Preconditions.checkState(mPresentation != null,
+ "Dataset presentation not set on constructor");
+ setLifeTheUniverseAndEverything(id, value, null, filter);
+ return this;
+ }
+
+ /**
+ * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+ * visualize it and a <a href="#Filtering">explicit filter</a>.
+ *
+ * <p>Typically used to allow filtering on
+ * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated datasets}. For
+ * example, if the dataset represents a credit card number and the service does not want to
+ * show the "Tap to authenticate" message until the user tapped 4 digits, in which case
+ * the filter would be {@code Pattern.compile("\\d.{4,}")}.
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if
+ * the dataset needs authentication and you have no access to the value.
+ * @param presentation the presentation used to visualize this field.
+ * @param filter regex used to determine if the dataset should be shown in the autofill UI.
+ *
+ * @return this builder.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+ @NonNull Pattern filter, @NonNull RemoteViews presentation) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(filter, "filter cannot be null");
+ Preconditions.checkNotNull(presentation, "presentation cannot be null");
+ setLifeTheUniverseAndEverything(id, value, presentation, filter);
return this;
}
- private void setValueAndPresentation(AutofillId id, AutofillValue value,
- RemoteViews presentation) {
+ private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
+ @Nullable AutofillValue value, @Nullable RemoteViews presentation,
+ @Nullable Pattern filter) {
Preconditions.checkNotNull(id, "id cannot be null");
if (mFieldIds != null) {
final int existingIdx = mFieldIds.indexOf(id);
@@ -279,10 +390,12 @@ public final class Dataset implements Parcelable {
mFieldIds = new ArrayList<>();
mFieldValues = new ArrayList<>();
mFieldPresentations = new ArrayList<>();
+ mFieldFilters = new ArrayList<>();
}
mFieldIds.add(id);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
+ mFieldFilters.add(filter);
}
/**
@@ -290,8 +403,9 @@ public final class Dataset implements Parcelable {
*
* <p>You should not interact with this builder once this method is called.
*
- * <p>It is required that you specify at least one field before calling this method. It's
- * also mandatory to provide a presentation view to visualize the data set in the UI.
+ * @throws IllegalStateException if no field was set (through
+ * {@link #setValue(AutofillId, AutofillValue)} or
+ * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}).
*
* @return The built dataset.
*/
@@ -299,7 +413,7 @@ public final class Dataset implements Parcelable {
throwIfDestroyed();
mDestroyed = true;
if (mFieldIds == null) {
- throw new IllegalArgumentException("at least one value must be set");
+ throw new IllegalStateException("at least one value must be set");
}
return new Dataset(this);
}
@@ -323,9 +437,10 @@ public final class Dataset implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mPresentation, flags);
- parcel.writeTypedArrayList(mFieldIds, flags);
- parcel.writeTypedArrayList(mFieldValues, flags);
+ parcel.writeTypedList(mFieldIds, flags);
+ parcel.writeTypedList(mFieldValues, flags);
parcel.writeParcelableList(mFieldPresentations, flags);
+ parcel.writeSerializable(mFieldFilters);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeString(mId);
}
@@ -340,10 +455,14 @@ public final class Dataset implements Parcelable {
final Builder builder = (presentation == null)
? new Builder()
: new Builder(presentation);
- final ArrayList<AutofillId> ids = parcel.readTypedArrayList(null);
- final ArrayList<AutofillValue> values = parcel.readTypedArrayList(null);
+ final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
+ final ArrayList<AutofillValue> values =
+ parcel.createTypedArrayList(AutofillValue.CREATOR);
final ArrayList<RemoteViews> presentations = new ArrayList<>();
parcel.readParcelableList(presentations, null);
+ @SuppressWarnings("unchecked")
+ final ArrayList<Serializable> filters =
+ (ArrayList<Serializable>) parcel.readSerializable();
final int idCount = (ids != null) ? ids.size() : 0;
final int valueCount = (values != null) ? values.size() : 0;
for (int i = 0; i < idCount; i++) {
@@ -351,7 +470,8 @@ public final class Dataset implements Parcelable {
final AutofillValue value = (valueCount > i) ? values.get(i) : null;
final RemoteViews fieldPresentation = presentations.isEmpty() ? null
: presentations.get(i);
- builder.setValueAndPresentation(id, value, fieldPresentation);
+ final Pattern filter = (Pattern) filters.get(i);
+ builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter);
}
builder.setAuthentication(parcel.readParcelable(null));
builder.setId(parcel.readString());
diff --git a/android/service/autofill/ImageTransformation.java b/android/service/autofill/ImageTransformation.java
index 2151f74f..4afda249 100644
--- a/android/service/autofill/ImageTransformation.java
+++ b/android/service/autofill/ImageTransformation.java
@@ -20,11 +20,12 @@ import static android.view.autofill.Helper.sDebug;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import android.view.autofill.AutofillId;
import android.widget.ImageView;
import android.widget.RemoteViews;
@@ -43,9 +44,9 @@ import java.util.regex.Pattern;
*
* <pre class="prettyprint">
* new ImageTransformation.Builder(ccNumberId, Pattern.compile("^4815.*$"),
- * R.drawable.ic_credit_card_logo1)
- * .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2)
- * .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3)
+ * R.drawable.ic_credit_card_logo1, "Brand 1")
+ * .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2, "Brand 2")
+ * .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3, "Brand 3")
* .build();
* </pre>
*
@@ -59,7 +60,7 @@ public final class ImageTransformation extends InternalTransformation implements
private static final String TAG = "ImageTransformation";
private final AutofillId mId;
- private final ArrayList<Pair<Pattern, Integer>> mOptions;
+ private final ArrayList<Option> mOptions;
private ImageTransformation(Builder builder) {
mId = builder.mId;
@@ -82,17 +83,21 @@ public final class ImageTransformation extends InternalTransformation implements
}
for (int i = 0; i < size; i++) {
- final Pair<Pattern, Integer> option = mOptions.get(i);
+ final Option option = mOptions.get(i);
try {
- if (option.first.matcher(value).matches()) {
+ if (option.pattern.matcher(value).matches()) {
Log.d(TAG, "Found match at " + i + ": " + option);
- parentTemplate.setImageViewResource(childViewId, option.second);
+ parentTemplate.setImageViewResource(childViewId, option.resId);
+ if (option.contentDescription != null) {
+ parentTemplate.setContentDescription(childViewId,
+ option.contentDescription);
+ }
return;
}
} catch (Exception e) {
// Do not log full exception to avoid PII leaking
- Log.w(TAG, "Error matching regex #" + i + "(" + option.first.pattern() + ") on id "
- + option.second + ": " + e.getClass());
+ Log.w(TAG, "Error matching regex #" + i + "(" + option.pattern + ") on id "
+ + option.resId + ": " + e.getClass());
throw e;
}
@@ -105,25 +110,44 @@ public final class ImageTransformation extends InternalTransformation implements
*/
public static class Builder {
private final AutofillId mId;
- private final ArrayList<Pair<Pattern, Integer>> mOptions = new ArrayList<>();
+ private final ArrayList<Option> mOptions = new ArrayList<>();
private boolean mDestroyed;
/**
- * Create a new builder for a autofill id and add a first option.
+ * Creates a new builder for a autofill id and add a first option.
*
* @param id id of the screen field that will be used to evaluate whether the image should
* be used.
* @param regex regular expression defining what should be matched to use this image.
* @param resId resource id of the image (in the autofill service's package). The
* {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+ *
+ * @deprecated use
+ * {@link #ImageTransformation.Builder(AutofillId, Pattern, int, CharSequence)} instead.
*/
+ @Deprecated
public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId) {
mId = Preconditions.checkNotNull(id);
-
addOption(regex, resId);
}
/**
+ * Creates a new builder for a autofill id and add a first option.
+ *
+ * @param id id of the screen field that will be used to evaluate whether the image should
+ * be used.
+ * @param regex regular expression defining what should be matched to use this image.
+ * @param resId resource id of the image (in the autofill service's package). The
+ * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+ * @param contentDescription content description to be applied in the child view.
+ */
+ public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId,
+ @NonNull CharSequence contentDescription) {
+ mId = Preconditions.checkNotNull(id);
+ addOption(regex, resId, contentDescription);
+ }
+
+ /**
* Adds an option to replace the child view with a different image when the regex matches.
*
* @param regex regular expression defining what should be matched to use this image.
@@ -131,17 +155,43 @@ public final class ImageTransformation extends InternalTransformation implements
* {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
*
* @return this build
+ *
+ * @deprecated use {@link #addOption(Pattern, int, CharSequence)} instead.
*/
+ @Deprecated
public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId) {
+ addOptionInternal(regex, resId, null);
+ return this;
+ }
+
+ /**
+ * Adds an option to replace the child view with a different image and content description
+ * when the regex matches.
+ *
+ * @param regex regular expression defining what should be matched to use this image.
+ * @param resId resource id of the image (in the autofill service's package). The
+ * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+ * @param contentDescription content description to be applied in the child view.
+ *
+ * @return this build
+ */
+ public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId,
+ @NonNull CharSequence contentDescription) {
+ addOptionInternal(regex, resId, Preconditions.checkNotNull(contentDescription));
+ return this;
+ }
+
+ private void addOptionInternal(@NonNull Pattern regex, @DrawableRes int resId,
+ @Nullable CharSequence contentDescription) {
throwIfDestroyed();
Preconditions.checkNotNull(regex);
Preconditions.checkArgument(resId != 0);
- mOptions.add(new Pair<>(regex, resId));
- return this;
+ mOptions.add(new Option(regex, resId, contentDescription));
}
+
/**
* Creates a new {@link ImageTransformation} instance.
*/
@@ -178,15 +228,18 @@ public final class ImageTransformation extends InternalTransformation implements
parcel.writeParcelable(mId, flags);
final int size = mOptions.size();
- final Pattern[] regexs = new Pattern[size];
+ final Pattern[] patterns = new Pattern[size];
final int[] resIds = new int[size];
+ final CharSequence[] contentDescriptions = new String[size];
for (int i = 0; i < size; i++) {
- Pair<Pattern, Integer> regex = mOptions.get(i);
- regexs[i] = regex.first;
- resIds[i] = regex.second;
+ final Option option = mOptions.get(i);
+ patterns[i] = option.pattern;
+ resIds[i] = option.resId;
+ contentDescriptions[i] = option.contentDescription;
}
- parcel.writeSerializable(regexs);
+ parcel.writeSerializable(patterns);
parcel.writeIntArray(resIds);
+ parcel.writeCharSequenceArray(contentDescriptions);
}
public static final Parcelable.Creator<ImageTransformation> CREATOR =
@@ -197,15 +250,22 @@ public final class ImageTransformation extends InternalTransformation implements
final Pattern[] regexs = (Pattern[]) parcel.readSerializable();
final int[] resIds = parcel.createIntArray();
+ final CharSequence[] contentDescriptions = parcel.readCharSequenceArray();
// 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.
- final ImageTransformation.Builder builder = new ImageTransformation.Builder(id,
- regexs[0], resIds[0]);
+ final CharSequence contentDescription = contentDescriptions[0];
+ final ImageTransformation.Builder builder = (contentDescription != null)
+ ? new ImageTransformation.Builder(id, regexs[0], resIds[0], contentDescription)
+ : new ImageTransformation.Builder(id, regexs[0], resIds[0]);
final int size = regexs.length;
for (int i = 1; i < size; i++) {
- builder.addOption(regexs[i], resIds[i]);
+ if (contentDescriptions[i] != null) {
+ builder.addOption(regexs[i], resIds[i], contentDescriptions[i]);
+ } else {
+ builder.addOption(regexs[i], resIds[i]);
+ }
}
return builder.build();
@@ -216,4 +276,16 @@ public final class ImageTransformation extends InternalTransformation implements
return new ImageTransformation[size];
}
};
+
+ private static final class Option {
+ public final Pattern pattern;
+ public final int resId;
+ public final CharSequence contentDescription;
+
+ Option(Pattern pattern, int resId, CharSequence contentDescription) {
+ this.pattern = pattern;
+ this.resId = resId;
+ this.contentDescription = TextUtils.trimNoCopySpans(contentDescription);
+ }
+ }
}
diff --git a/android/service/autofill/InternalSanitizer.java b/android/service/autofill/InternalSanitizer.java
new file mode 100644
index 00000000..95d2f660
--- /dev/null
+++ b/android/service/autofill/InternalSanitizer.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.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+import android.view.autofill.AutofillValue;
+
+/**
+ * Superclass of all sanitizers the system understands. As this is not public all public subclasses
+ * have to implement {@link Sanitizer} again.
+ *
+ * @hide
+ */
+@TestApi
+public abstract class InternalSanitizer implements Sanitizer, Parcelable {
+
+ /**
+ * Sanitizes an {@link AutofillValue}.
+ *
+ * @hide
+ */
+ public abstract AutofillValue sanitize(@NonNull AutofillValue value);
+}
diff --git a/android/service/autofill/Sanitizer.java b/android/service/autofill/Sanitizer.java
new file mode 100644
index 00000000..38757ac7
--- /dev/null
+++ b/android/service/autofill/Sanitizer.java
@@ -0,0 +1,26 @@
+/*
+ * 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.service.autofill;
+
+/**
+ * Helper class used to sanitize user input before using it in a save request.
+ *
+ * <p>Typically used to avoid displaying the save UI for values that are autofilled but reformatted
+ * by the app&mdash;for example, if the autofill service sends a credit card number
+ * value as "004815162342108" and the app automatically changes it to "0048 1516 2342 108".
+ */
+public interface Sanitizer {
+}
diff --git a/android/service/autofill/SaveCallback.java b/android/service/autofill/SaveCallback.java
index 3a701384..7207f1df 100644
--- a/android/service/autofill/SaveCallback.java
+++ b/android/service/autofill/SaveCallback.java
@@ -34,9 +34,13 @@ public final class SaveCallback {
/**
* Notifies the Android System that an
- * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully fulfilled
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
* by the service.
*
+ * <p>If the service could not handle the request right away&mdash;for example, because it must
+ * launch an activity asking the user to authenticate first or because the network is
+ * down&mdash;it should still call {@link #onSuccess()}.
+ *
* @throws RuntimeException if an error occurred while calling the Android System.
*/
public void onSuccess() {
@@ -51,9 +55,16 @@ public final class SaveCallback {
/**
* Notifies the Android System that an
- * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be fulfilled
+ * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be handled
* by the service.
*
+ * <p>This method should only be called when the service could not handle the request right away
+ * and could not recover or retry it. If the service could retry or recover, it could keep
+ * the {@link SaveRequest} and call {@link #onSuccess()} instead.
+ *
+ * <p><b>Note:</b> The Android System displays an UI with the supplied error message; if
+ * you prefer to show your own message, call {@link #onSuccess()} instead.
+ *
* @param message error message to be displayed to the user.
*
* @throws RuntimeException if an error occurred while calling the Android System.
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index e0a07305..1b9240cc 100644
--- a/android/service/autofill/SaveInfo.java
+++ b/android/service/autofill/SaveInfo.java
@@ -25,6 +25,8 @@ import android.app.Activity;
import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.DebugUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
@@ -232,6 +234,8 @@ public final class SaveInfo implements Parcelable {
private final int mFlags;
private final CustomDescription mCustomDescription;
private final InternalValidator mValidator;
+ private final InternalSanitizer[] mSanitizerKeys;
+ private final AutofillId[][] mSanitizerValues;
private SaveInfo(Builder builder) {
mType = builder.mType;
@@ -243,6 +247,18 @@ public final class SaveInfo implements Parcelable {
mFlags = builder.mFlags;
mCustomDescription = builder.mCustomDescription;
mValidator = builder.mValidator;
+ if (builder.mSanitizers == null) {
+ mSanitizerKeys = null;
+ mSanitizerValues = null;
+ } else {
+ final int size = builder.mSanitizers.size();
+ mSanitizerKeys = new InternalSanitizer[size];
+ mSanitizerValues = new AutofillId[size][];
+ for (int i = 0; i < size; i++) {
+ mSanitizerKeys[i] = builder.mSanitizers.keyAt(i);
+ mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
+ }
+ }
}
/** @hide */
@@ -292,6 +308,18 @@ public final class SaveInfo implements Parcelable {
return mValidator;
}
+ /** @hide */
+ @Nullable
+ public InternalSanitizer[] getSanitizerKeys() {
+ return mSanitizerKeys;
+ }
+
+ /** @hide */
+ @Nullable
+ public AutofillId[][] getSanitizerValues() {
+ return mSanitizerValues;
+ }
+
/**
* A builder for {@link SaveInfo} objects.
*/
@@ -307,6 +335,9 @@ public final class SaveInfo implements Parcelable {
private int mFlags;
private CustomDescription mCustomDescription;
private InternalValidator mValidator;
+ private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
+ // Set used to validate against duplicate ids.
+ private ArraySet<AutofillId> mSanitizerIds;
/**
* Creates a new builder.
@@ -530,6 +561,61 @@ public final class SaveInfo implements Parcelable {
}
/**
+ * Adds a sanitizer for one or more field.
+ *
+ * <p>When a sanitizer is set for a field, the {@link AutofillValue} is sent to the
+ * sanitizer before a save request is <a href="#TriggeringSaveRequest">triggered</a>.
+ *
+ * <p>Typically used to avoid displaying the save UI for values that are autofilled but
+ * reformattedby the app. For example, to remove spaces between every 4 digits of a
+ * credit card number:
+ *
+ * <pre class="prettyprint">
+ * builder.addSanitizer(
+ * new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"),
+ * "$1$2$3$4"), ccNumberId);
+ * </pre>
+ *
+ * <p>The same sanitizer can be reused to sanitize multiple fields. For example, to trim
+ * both the username and password fields:
+ *
+ * <pre class="prettyprint">
+ * builder.addSanitizer(
+ * new TextValueSanitizer(Pattern.compile("^\\s*(.*)\\s*$"), "$1"),
+ * usernameId, passwordId);
+ * </pre>
+ *
+ * @param sanitizer an implementation provided by the Android System.
+ * @param ids id of fields whose value will be sanitized.
+ * @return this builder.
+ *
+ * @throws IllegalArgumentException if a sanitizer for any of the {@code ids} has already
+ * been added or if {@code ids} is empty.
+ */
+ public @NonNull Builder addSanitizer(@NonNull Sanitizer sanitizer,
+ @NonNull AutofillId... ids) {
+ throwIfDestroyed();
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(ids), "ids cannot be empty or null");
+ Preconditions.checkArgument((sanitizer instanceof InternalSanitizer),
+ "not provided by Android System: " + sanitizer);
+
+ if (mSanitizers == null) {
+ mSanitizers = new ArrayMap<>();
+ mSanitizerIds = new ArraySet<>(ids.length);
+ }
+
+ // Check for duplicates first.
+ for (AutofillId id : ids) {
+ Preconditions.checkArgument(!mSanitizerIds.contains(id), "already added %s", id);
+ mSanitizerIds.add(id);
+ }
+
+ mSanitizers.put((InternalSanitizer) sanitizer, ids);
+
+ return this;
+ }
+
+ /**
* Builds a new {@link SaveInfo} instance.
*
* @throws IllegalStateException if no
@@ -569,6 +655,10 @@ public final class SaveInfo implements Parcelable {
.append(", mFlags=").append(mFlags)
.append(", mCustomDescription=").append(mCustomDescription)
.append(", validation=").append(mValidator)
+ .append(", sanitizerKeys=")
+ .append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length)
+ .append(", sanitizerValues=")
+ .append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length)
.append("]").toString();
}
@@ -591,6 +681,12 @@ public final class SaveInfo implements Parcelable {
parcel.writeCharSequence(mDescription);
parcel.writeParcelable(mCustomDescription, flags);
parcel.writeParcelable(mValidator, flags);
+ parcel.writeParcelableArray(mSanitizerKeys, flags);
+ if (mSanitizerKeys != null) {
+ for (int i = 0; i < mSanitizerValues.length; i++) {
+ parcel.writeParcelableArray(mSanitizerValues[i], flags);
+ }
+ }
parcel.writeInt(mFlags);
}
@@ -621,6 +717,16 @@ public final class SaveInfo implements Parcelable {
if (validator != null) {
builder.setValidator(validator);
}
+ final InternalSanitizer[] sanitizers =
+ parcel.readParcelableArray(null, InternalSanitizer.class);
+ if (sanitizers != null) {
+ final int size = sanitizers.length;
+ for (int i = 0; i < size; i++) {
+ final AutofillId[] autofillIds =
+ parcel.readParcelableArray(null, AutofillId.class);
+ builder.addSanitizer(sanitizers[i], autofillIds);
+ }
+ }
builder.setFlags(parcel.readInt());
return builder.build();
}
diff --git a/android/service/autofill/SaveRequest.java b/android/service/autofill/SaveRequest.java
index 1a6c5b0b..65fdb5c4 100644
--- a/android/service/autofill/SaveRequest.java
+++ b/android/service/autofill/SaveRequest.java
@@ -48,7 +48,8 @@ public final class SaveRequest implements Parcelable {
}
private SaveRequest(@NonNull Parcel parcel) {
- this(parcel.readTypedArrayList(null), parcel.readBundle(), parcel.createStringArrayList());
+ this(parcel.createTypedArrayList(FillContext.CREATOR),
+ parcel.readBundle(), parcel.createStringArrayList());
}
/**
@@ -84,7 +85,7 @@ public final class SaveRequest implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeTypedArrayList(mFillContexts, flags);
+ parcel.writeTypedList(mFillContexts, flags);
parcel.writeBundle(mClientState);
parcel.writeStringList(mDatasetIds);
}
diff --git a/android/service/autofill/TextValueSanitizer.java b/android/service/autofill/TextValueSanitizer.java
new file mode 100644
index 00000000..12e85b1d
--- /dev/null
+++ b/android/service/autofill/TextValueSanitizer.java
@@ -0,0 +1,122 @@
+/*
+ * 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.service.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Sanitizes a text {@link AutofillValue} using a regular expression (regex) substitution.
+ *
+ * <p>For example, to remove spaces from groups of 4-digits in a credit card:
+ *
+ * <pre class="prettyprint">
+ * new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"), "$1$2$3$4")
+ * </pre>
+ */
+public final class TextValueSanitizer extends InternalSanitizer implements
+ Sanitizer, Parcelable {
+ private static final String TAG = "TextValueSanitizer";
+
+ private final Pattern mRegex;
+ private final String mSubst;
+
+ /**
+ * Default constructor.
+ *
+ * @param regex regular expression with groups (delimited by {@code (} and {@code (}) that
+ * are used to substitute parts of the {@link AutofillValue#getTextValue() text value}.
+ * @param subst the string that substitutes the matched regex, using {@code $} for
+ * group substitution ({@code $1} for 1st group match, {@code $2} for 2nd, etc).
+ */
+ public TextValueSanitizer(@NonNull Pattern regex, @NonNull String subst) {
+ mRegex = Preconditions.checkNotNull(regex);
+ mSubst = Preconditions.checkNotNull(subst);
+ }
+
+ /** @hide */
+ @Override
+ @TestApi
+ public AutofillValue sanitize(@NonNull AutofillValue value) {
+ if (value == null) {
+ Slog.w(TAG, "sanitize() called with null value");
+ return null;
+ }
+ if (!value.isText()) return value;
+
+ final CharSequence text = value.getTextValue();
+
+ try {
+ final Matcher matcher = mRegex.matcher(text);
+ if (!matcher.matches()) return value;
+
+ final CharSequence sanitized = matcher.replaceAll(mSubst);
+ return AutofillValue.forText(sanitized);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception evaluating " + mRegex + "/" + mSubst + ": " + e);
+ return value;
+ }
+ }
+
+ /////////////////////////////////////
+ // Object "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public String toString() {
+ if (!sDebug) return super.toString();
+
+ return "TextValueSanitizer: [regex=" + mRegex + ", subst=" + mSubst + "]";
+ }
+
+ /////////////////////////////////////
+ // Parcelable "contract" methods. //
+ /////////////////////////////////////
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeSerializable(mRegex);
+ parcel.writeString(mSubst);
+ }
+
+ public static final Parcelable.Creator<TextValueSanitizer> CREATOR =
+ new Parcelable.Creator<TextValueSanitizer>() {
+ @Override
+ public TextValueSanitizer createFromParcel(Parcel parcel) {
+ return new TextValueSanitizer((Pattern) parcel.readSerializable(), parcel.readString());
+ }
+
+ @Override
+ public TextValueSanitizer[] newArray(int size) {
+ return new TextValueSanitizer[size];
+ }
+ };
+}
diff --git a/android/service/carrier/CarrierService.java b/android/service/carrier/CarrierService.java
index 813acc23..2707f146 100644
--- a/android/service/carrier/CarrierService.java
+++ b/android/service/carrier/CarrierService.java
@@ -17,10 +17,13 @@ package android.service.carrier;
import android.annotation.CallSuper;
import android.app.Service;
import android.content.Intent;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.util.Log;
import com.android.internal.telephony.ITelephonyRegistry;
@@ -48,6 +51,8 @@ import com.android.internal.telephony.ITelephonyRegistry;
*/
public abstract class CarrierService extends Service {
+ private static final String LOG_TAG = "CarrierService";
+
public static final String CARRIER_SERVICE_INTERFACE = "android.service.carrier.CarrierService";
private static ITelephonyRegistry sRegistry;
@@ -133,11 +138,26 @@ public abstract class CarrierService extends Service {
/**
* A wrapper around ICarrierService that forwards calls to implementations of
* {@link CarrierService}.
+ * @hide
*/
- private class ICarrierServiceWrapper extends ICarrierService.Stub {
+ public class ICarrierServiceWrapper extends ICarrierService.Stub {
+ /** @hide */
+ public static final int RESULT_OK = 0;
+ /** @hide */
+ public static final int RESULT_ERROR = 1;
+ /** @hide */
+ public static final String KEY_CONFIG_BUNDLE = "config_bundle";
+
@Override
- public PersistableBundle getCarrierConfig(CarrierIdentifier id) {
- return CarrierService.this.onLoadConfig(id);
+ public void getCarrierConfig(CarrierIdentifier id, ResultReceiver result) {
+ try {
+ Bundle data = new Bundle();
+ data.putParcelable(KEY_CONFIG_BUNDLE, CarrierService.this.onLoadConfig(id));
+ result.send(RESULT_OK, data);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error in onLoadConfig: " + e.getMessage(), e);
+ result.send(RESULT_ERROR, null);
+ }
}
}
}
diff --git a/android/service/notification/Adjustment.java b/android/service/notification/Adjustment.java
index ce678fc8..7348cf68 100644
--- a/android/service/notification/Adjustment.java
+++ b/android/service/notification/Adjustment.java
@@ -56,6 +56,15 @@ public final class Adjustment implements Parcelable {
public static final String KEY_GROUP_KEY = "key_group_key";
/**
+ * Data type: int, one of {@link NotificationListenerService.Ranking#USER_SENTIMENT_POSITIVE},
+ * {@link NotificationListenerService.Ranking#USER_SENTIMENT_NEUTRAL},
+ * {@link NotificationListenerService.Ranking#USER_SENTIMENT_NEGATIVE}. Used to express how
+ * a user feels about notifications in the same {@link android.app.NotificationChannel} as
+ * the notification represented by {@link #getKey()}.
+ */
+ public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+
+ /**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
diff --git a/android/service/notification/NotificationAssistantService.java b/android/service/notification/NotificationAssistantService.java
index d94017cd..8e52bfa8 100644
--- a/android/service/notification/NotificationAssistantService.java
+++ b/android/service/notification/NotificationAssistantService.java
@@ -16,12 +16,9 @@
package android.service.notification;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.NotificationChannel;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
@@ -30,9 +27,9 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+
import com.android.internal.os.SomeArgs;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -79,7 +76,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS
String snoozeCriterionId);
/**
- * A notification was posted by an app. Called before alert.
+ * A notification was posted by an app. Called before post.
*
* @param sbn the new notification
* @return an adjustment or null to take no action, within 100ms.
@@ -87,6 +84,34 @@ public abstract class NotificationAssistantService extends NotificationListenerS
abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn);
/**
+ * Implement this method to learn when notifications are removed, how they were interacted with
+ * before removal, and why they were removed.
+ * <p>
+ * This might occur because the user has dismissed the notification using system UI (or another
+ * notification listener) or because the app has withdrawn the notification.
+ * <p>
+ * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
+ * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
+ * fields such as {@link android.app.Notification#contentView} and
+ * {@link android.app.Notification#largeIcon}. However, all other fields on
+ * {@link StatusBarNotification}, sufficient to match this call with a prior call to
+ * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
+ *
+ ** @param sbn A data structure encapsulating at least the original information (tag and id)
+ * and source (package name) used to post the {@link android.app.Notification} that
+ * was just removed.
+ * @param rankingMap The current ranking map that can be used to retrieve ranking information
+ * for active notifications.
+ * @param stats Stats about how the user interacted with the notification before it was removed.
+ * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
+ */
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+ NotificationStats stats, int reason) {
+ onNotificationRemoved(sbn, rankingMap, reason);
+ }
+
+ /**
* Updates a notification. N.B. this won’t cause
* an existing notification to alert, but might allow a future update to
* this notification to alert.
diff --git a/android/service/notification/NotificationListenerService.java b/android/service/notification/NotificationListenerService.java
index a5223fd8..08d3118b 100644
--- a/android/service/notification/NotificationListenerService.java
+++ b/android/service/notification/NotificationListenerService.java
@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Notification.Builder;
@@ -265,7 +266,10 @@ public abstract class NotificationListenerService extends Service {
@GuardedBy("mLock")
private RankingMap mRankingMap;
- private INotificationManager mNoMan;
+ /**
+ * @hide
+ */
+ protected INotificationManager mNoMan;
/**
* Only valid after a successful call to (@link registerAsService}.
@@ -389,6 +393,18 @@ public abstract class NotificationListenerService extends Service {
}
/**
+ * NotificationStats are not populated for notification listeners, so fall back to
+ * {@link #onNotificationRemoved(StatusBarNotification, RankingMap, int)}.
+ *
+ * @hide
+ */
+ @TestApi
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+ NotificationStats stats, int reason) {
+ onNotificationRemoved(sbn, rankingMap, reason);
+ }
+
+ /**
* Implement this method to learn about when the listener is enabled and connected to
* the notification manager. You are safe to call {@link #getActiveNotifications()}
* at this time.
@@ -1200,7 +1216,7 @@ public abstract class NotificationListenerService extends Service {
@Override
public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
- NotificationRankingUpdate update, int reason) {
+ NotificationRankingUpdate update, NotificationStats stats, int reason) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
@@ -1215,6 +1231,7 @@ public abstract class NotificationListenerService extends Service {
args.arg1 = sbn;
args.arg2 = mRankingMap;
args.arg3 = reason;
+ args.arg4 = stats;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
args).sendToTarget();
}
@@ -1324,6 +1341,26 @@ public abstract class NotificationListenerService extends Service {
* @hide */
public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
+ /**
+ * The user is likely to have a negative reaction to this notification.
+ */
+ public static final int USER_SENTIMENT_NEGATIVE = -1;
+ /**
+ * It is not known how the user will react to this notification.
+ */
+ public static final int USER_SENTIMENT_NEUTRAL = 0;
+ /**
+ * The user is likely to have a positive reaction to this notification.
+ */
+ public static final int USER_SENTIMENT_POSITIVE = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "USER_SENTIMENT_" }, value = {
+ USER_SENTIMENT_NEGATIVE, USER_SENTIMENT_NEUTRAL, USER_SENTIMENT_POSITIVE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserSentiment {}
+
private String mKey;
private int mRank = -1;
private boolean mIsAmbient;
@@ -1341,6 +1378,7 @@ public abstract class NotificationListenerService extends Service {
// Notification assistant snooze criteria.
private ArrayList<SnoozeCriterion> mSnoozeCriteria;
private boolean mShowBadge;
+ private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
public Ranking() {}
@@ -1436,6 +1474,17 @@ public abstract class NotificationListenerService extends Service {
}
/**
+ * Returns how the system thinks the user feels about notifications from the
+ * channel provided by {@link #getChannel()}. You can use this information to expose
+ * controls to help the user block this channel's notifications, if the sentiment is
+ * {@link #USER_SENTIMENT_NEGATIVE}, or emphasize this notification if the sentiment is
+ * {@link #USER_SENTIMENT_POSITIVE}.
+ */
+ public int getUserSentiment() {
+ return mUserSentiment;
+ }
+
+ /**
* If the {@link NotificationAssistantService} has added people to this notification, then
* this will be non-null.
* @hide
@@ -1471,7 +1520,8 @@ public abstract class NotificationListenerService extends Service {
int visibilityOverride, int suppressedVisualEffects, int importance,
CharSequence explanation, String overrideGroupKey,
NotificationChannel channel, ArrayList<String> overridePeople,
- ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
+ ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
+ int userSentiment) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1485,6 +1535,7 @@ public abstract class NotificationListenerService extends Service {
mOverridePeople = overridePeople;
mSnoozeCriteria = snoozeCriteria;
mShowBadge = showBadge;
+ mUserSentiment = userSentiment;
}
/**
@@ -1532,6 +1583,7 @@ public abstract class NotificationListenerService extends Service {
private ArrayMap<String, ArrayList<String>> mOverridePeople;
private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
private ArrayMap<String, Boolean> mShowBadge;
+ private ArrayMap<String, Integer> mUserSentiment;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -1560,7 +1612,7 @@ public abstract class NotificationListenerService extends Service {
getVisibilityOverride(key), getSuppressedVisualEffects(key),
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
- getShowBadge(key));
+ getShowBadge(key), getUserSentiment(key));
return rank >= 0;
}
@@ -1677,6 +1729,17 @@ public abstract class NotificationListenerService extends Service {
return showBadge == null ? false : showBadge.booleanValue();
}
+ private int getUserSentiment(String key) {
+ synchronized (this) {
+ if (mUserSentiment == null) {
+ buildUserSentimentLocked();
+ }
+ }
+ Integer userSentiment = mUserSentiment.get(key);
+ return userSentiment == null
+ ? Ranking.USER_SENTIMENT_NEUTRAL : userSentiment.intValue();
+ }
+
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1776,6 +1839,15 @@ public abstract class NotificationListenerService extends Service {
}
}
+ // Locked by 'this'
+ private void buildUserSentimentLocked() {
+ Bundle userSentiment = mRankingUpdate.getUserSentiment();
+ mUserSentiment = new ArrayMap<>(userSentiment.size());
+ for (String key : userSentiment.keySet()) {
+ mUserSentiment.put(key, userSentiment.getInt(key));
+ }
+ }
+
// ----------- Parcelable
@Override
@@ -1835,8 +1907,9 @@ public abstract class NotificationListenerService extends Service {
StatusBarNotification sbn = (StatusBarNotification) args.arg1;
RankingMap rankingMap = (RankingMap) args.arg2;
int reason = (int) args.arg3;
+ NotificationStats stats = (NotificationStats) args.arg4;
args.recycle();
- onNotificationRemoved(sbn, rankingMap, reason);
+ onNotificationRemoved(sbn, rankingMap, stats, reason);
} break;
case MSG_ON_LISTENER_CONNECTED: {
diff --git a/android/service/notification/NotificationRankingUpdate.java b/android/service/notification/NotificationRankingUpdate.java
index 326b212a..6d51db09 100644
--- a/android/service/notification/NotificationRankingUpdate.java
+++ b/android/service/notification/NotificationRankingUpdate.java
@@ -35,12 +35,13 @@ public class NotificationRankingUpdate implements Parcelable {
private final Bundle mOverridePeople;
private final Bundle mSnoozeCriteria;
private final Bundle mShowBadge;
+ private final Bundle mUserSentiment;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
Bundle visibilityOverrides, Bundle suppressedVisualEffects,
int[] importance, Bundle explanation, Bundle overrideGroupKeys,
Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
- Bundle showBadge) {
+ Bundle showBadge, Bundle userSentiment) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
@@ -52,6 +53,7 @@ public class NotificationRankingUpdate implements Parcelable {
mOverridePeople = overridePeople;
mSnoozeCriteria = snoozeCriteria;
mShowBadge = showBadge;
+ mUserSentiment = userSentiment;
}
public NotificationRankingUpdate(Parcel in) {
@@ -67,6 +69,7 @@ public class NotificationRankingUpdate implements Parcelable {
mOverridePeople = in.readBundle();
mSnoozeCriteria = in.readBundle();
mShowBadge = in.readBundle();
+ mUserSentiment = in.readBundle();
}
@Override
@@ -87,6 +90,7 @@ public class NotificationRankingUpdate implements Parcelable {
out.writeBundle(mOverridePeople);
out.writeBundle(mSnoozeCriteria);
out.writeBundle(mShowBadge);
+ out.writeBundle(mUserSentiment);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -143,4 +147,8 @@ public class NotificationRankingUpdate implements Parcelable {
public Bundle getShowBadge() {
return mShowBadge;
}
+
+ public Bundle getUserSentiment() {
+ return mUserSentiment;
+ }
}
diff --git a/android/service/notification/NotificationStats.java b/android/service/notification/NotificationStats.java
new file mode 100644
index 00000000..76d5328d
--- /dev/null
+++ b/android/service/notification/NotificationStats.java
@@ -0,0 +1,256 @@
+/**
+ * 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.service.notification;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.RemoteInput;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+@TestApi
+@SystemApi
+public final class NotificationStats implements Parcelable {
+
+ private boolean mSeen;
+ private boolean mExpanded;
+ private boolean mDirectReplied;
+ private boolean mSnoozed;
+ private boolean mViewedSettings;
+ private boolean mInteracted;
+
+ /** @hide */
+ @IntDef(prefix = { "DISMISSAL_SURFACE_" }, value = {
+ DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD, DISMISSAL_SHADE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DismissalSurface {}
+
+
+ private @DismissalSurface int mDismissalSurface = DISMISSAL_NOT_DISMISSED;
+
+ /**
+ * Notification has not been dismissed yet.
+ */
+ public static final int DISMISSAL_NOT_DISMISSED = -1;
+ /**
+ * Notification has been dismissed from a {@link NotificationListenerService} or the app
+ * itself.
+ */
+ public static final int DISMISSAL_OTHER = 0;
+ /**
+ * Notification has been dismissed while peeking.
+ */
+ public static final int DISMISSAL_PEEK = 1;
+ /**
+ * Notification has been dismissed from always on display.
+ */
+ public static final int DISMISSAL_AOD = 2;
+ /**
+ * Notification has been dismissed from the notification shade.
+ */
+ public static final int DISMISSAL_SHADE = 3;
+
+ public NotificationStats() {
+ }
+
+ protected NotificationStats(Parcel in) {
+ mSeen = in.readByte() != 0;
+ mExpanded = in.readByte() != 0;
+ mDirectReplied = in.readByte() != 0;
+ mSnoozed = in.readByte() != 0;
+ mViewedSettings = in.readByte() != 0;
+ mInteracted = in.readByte() != 0;
+ mDismissalSurface = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte((byte) (mSeen ? 1 : 0));
+ dest.writeByte((byte) (mExpanded ? 1 : 0));
+ dest.writeByte((byte) (mDirectReplied ? 1 : 0));
+ dest.writeByte((byte) (mSnoozed ? 1 : 0));
+ dest.writeByte((byte) (mViewedSettings ? 1 : 0));
+ dest.writeByte((byte) (mInteracted ? 1 : 0));
+ dest.writeInt(mDismissalSurface);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<NotificationStats> CREATOR = new Creator<NotificationStats>() {
+ @Override
+ public NotificationStats createFromParcel(Parcel in) {
+ return new NotificationStats(in);
+ }
+
+ @Override
+ public NotificationStats[] newArray(int size) {
+ return new NotificationStats[size];
+ }
+ };
+
+ /**
+ * Returns whether the user has seen this notification at least once.
+ */
+ public boolean hasSeen() {
+ return mSeen;
+ }
+
+ /**
+ * Records that the user as seen this notification at least once.
+ */
+ public void setSeen() {
+ mSeen = true;
+ }
+
+ /**
+ * Returns whether the user has expanded this notification at least once.
+ */
+ public boolean hasExpanded() {
+ return mExpanded;
+ }
+
+ /**
+ * Records that the user has expanded this notification at least once.
+ */
+ public void setExpanded() {
+ mExpanded = true;
+ mInteracted = true;
+ }
+
+ /**
+ * Returns whether the user has replied to a notification that has a
+ * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply} at
+ * least once.
+ */
+ public boolean hasDirectReplied() {
+ return mDirectReplied;
+ }
+
+ /**
+ * Records that the user has replied to a notification that has a
+ * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply}
+ * at least once.
+ */
+ public void setDirectReplied() {
+ mDirectReplied = true;
+ mInteracted = true;
+ }
+
+ /**
+ * Returns whether the user has snoozed this notification at least once.
+ */
+ public boolean hasSnoozed() {
+ return mSnoozed;
+ }
+
+ /**
+ * Records that the user has snoozed this notification at least once.
+ */
+ public void setSnoozed() {
+ mSnoozed = true;
+ mInteracted = true;
+ }
+
+ /**
+ * Returns whether the user has viewed the in-shade settings for this notification at least
+ * once.
+ */
+ public boolean hasViewedSettings() {
+ return mViewedSettings;
+ }
+
+ /**
+ * Records that the user has viewed the in-shade settings for this notification at least once.
+ */
+ public void setViewedSettings() {
+ mViewedSettings = true;
+ mInteracted = true;
+ }
+
+ /**
+ * Returns whether the user has interacted with this notification beyond having viewed it.
+ */
+ public boolean hasInteracted() {
+ return mInteracted;
+ }
+
+ /**
+ * Returns from which surface the notification was dismissed.
+ */
+ public @DismissalSurface int getDismissalSurface() {
+ return mDismissalSurface;
+ }
+
+ /**
+ * Returns from which surface the notification was dismissed.
+ */
+ public void setDismissalSurface(@DismissalSurface int dismissalSurface) {
+ mDismissalSurface = dismissalSurface;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NotificationStats that = (NotificationStats) o;
+
+ if (mSeen != that.mSeen) return false;
+ if (mExpanded != that.mExpanded) return false;
+ if (mDirectReplied != that.mDirectReplied) return false;
+ if (mSnoozed != that.mSnoozed) return false;
+ if (mViewedSettings != that.mViewedSettings) return false;
+ if (mInteracted != that.mInteracted) return false;
+ return mDismissalSurface == that.mDismissalSurface;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (mSeen ? 1 : 0);
+ result = 31 * result + (mExpanded ? 1 : 0);
+ result = 31 * result + (mDirectReplied ? 1 : 0);
+ result = 31 * result + (mSnoozed ? 1 : 0);
+ result = 31 * result + (mViewedSettings ? 1 : 0);
+ result = 31 * result + (mInteracted ? 1 : 0);
+ result = 31 * result + mDismissalSurface;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("NotificationStats{");
+ sb.append("mSeen=").append(mSeen);
+ sb.append(", mExpanded=").append(mExpanded);
+ sb.append(", mDirectReplied=").append(mDirectReplied);
+ sb.append(", mSnoozed=").append(mSnoozed);
+ sb.append(", mViewedSettings=").append(mViewedSettings);
+ sb.append(", mInteracted=").append(mInteracted);
+ sb.append(", mDismissalSurface=").append(mDismissalSurface);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/android/service/settings/suggestions/Suggestion.java b/android/service/settings/suggestions/Suggestion.java
index f27cc2eb..cfeb7fce 100644
--- a/android/service/settings/suggestions/Suggestion.java
+++ b/android/service/settings/suggestions/Suggestion.java
@@ -16,12 +16,17 @@
package android.service.settings.suggestions;
+import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Data object that has information about a device suggestion.
*
@@ -30,9 +35,27 @@ import android.text.TextUtils;
@SystemApi
public final class Suggestion implements Parcelable {
+ /**
+ * @hide
+ */
+ @IntDef(flag = true, value = {
+ FLAG_HAS_BUTTON,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {
+ }
+
+ /**
+ * Flag for suggestion type with a single button
+ */
+ public static final int FLAG_HAS_BUTTON = 1 << 0;
+
private final String mId;
private final CharSequence mTitle;
private final CharSequence mSummary;
+ private final Icon mIcon;
+ @Flags
+ private final int mFlags;
private final PendingIntent mPendingIntent;
/**
@@ -57,6 +80,22 @@ public final class Suggestion implements Parcelable {
}
/**
+ * Optional icon for this suggestion.
+ */
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Optional flags for this suggestion. This will influence UI when rendering suggestion in
+ * different style.
+ */
+ @Flags
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
* The Intent to launch when the suggestion is activated.
*/
public PendingIntent getPendingIntent() {
@@ -67,6 +106,8 @@ public final class Suggestion implements Parcelable {
mId = builder.mId;
mTitle = builder.mTitle;
mSummary = builder.mSummary;
+ mIcon = builder.mIcon;
+ mFlags = builder.mFlags;
mPendingIntent = builder.mPendingIntent;
}
@@ -74,6 +115,8 @@ public final class Suggestion implements Parcelable {
mId = in.readString();
mTitle = in.readCharSequence();
mSummary = in.readCharSequence();
+ mIcon = in.readParcelable(Icon.class.getClassLoader());
+ mFlags = in.readInt();
mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
}
@@ -99,6 +142,8 @@ public final class Suggestion implements Parcelable {
dest.writeString(mId);
dest.writeCharSequence(mTitle);
dest.writeCharSequence(mSummary);
+ dest.writeParcelable(mIcon, flags);
+ dest.writeInt(mFlags);
dest.writeParcelable(mPendingIntent, flags);
}
@@ -109,6 +154,9 @@ public final class Suggestion implements Parcelable {
private final String mId;
private CharSequence mTitle;
private CharSequence mSummary;
+ private Icon mIcon;
+ @Flags
+ private int mFlags;
private PendingIntent mPendingIntent;
public Builder(String id) {
@@ -135,6 +183,23 @@ public final class Suggestion implements Parcelable {
}
/**
+ * Sets icon for the suggestion.
+ */
+ public Builder setIcon(Icon icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets a UI type for this suggestion. This will influence UI when rendering suggestion in
+ * different style.
+ */
+ public Builder setFlags(@Flags int flags) {
+ mFlags = flags;
+ return this;
+ }
+
+ /**
* Sets suggestion intent
*/
public Builder setPendingIntent(PendingIntent pendingIntent) {
diff --git a/android/slice/Slice.java b/android/slice/Slice.java
new file mode 100644
index 00000000..57686548
--- /dev/null
+++ b/android/slice/Slice.java
@@ -0,0 +1,347 @@
+/*
+ * 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.slice;
+
+import static android.slice.SliceItem.TYPE_ACTION;
+import static android.slice.SliceItem.TYPE_COLOR;
+import static android.slice.SliceItem.TYPE_IMAGE;
+import static android.slice.SliceItem.TYPE_REMOTE_INPUT;
+import static android.slice.SliceItem.TYPE_REMOTE_VIEW;
+import static android.slice.SliceItem.TYPE_SLICE;
+import static android.slice.SliceItem.TYPE_TEXT;
+import static android.slice.SliceItem.TYPE_TIMESTAMP;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * A slice is a piece of app content and actions that can be surfaced outside of the app.
+ *
+ * <p>They are constructed using {@link Builder} in a tree structure
+ * that provides the OS some information about how the content should be displayed.
+ * @hide
+ */
+public final class Slice implements Parcelable {
+
+ /**
+ * @hide
+ */
+ @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
+ HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT})
+ public @interface SliceHint{ }
+
+ /**
+ * Hint that this content is a title of other content in the slice.
+ */
+ public static final String HINT_TITLE = "title";
+ /**
+ * Hint that all sub-items/sub-slices within this content should be considered
+ * to have {@link #HINT_LIST_ITEM}.
+ */
+ public static final String HINT_LIST = "list";
+ /**
+ * Hint that this item is part of a list and should be formatted as if is part
+ * of a list.
+ */
+ public static final String HINT_LIST_ITEM = "list_item";
+ /**
+ * Hint that this content is important and should be larger when displayed if
+ * possible.
+ */
+ public static final String HINT_LARGE = "large";
+ /**
+ * Hint that this slice contains a number of actions that can be grouped together
+ * in a sort of controls area of the UI.
+ */
+ public static final String HINT_ACTIONS = "actions";
+ /**
+ * Hint indicating that this item (and its sub-items) are the current selection.
+ */
+ public static final String HINT_SELECTED = "selected";
+ /**
+ * Hint to indicate that this is a message as part of a communication
+ * sequence in this slice.
+ */
+ public static final String HINT_MESSAGE = "message";
+ /**
+ * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}.
+ */
+ public static final String HINT_SOURCE = "source";
+ /**
+ * Hint that list items within this slice or subslice would appear better
+ * if organized horizontally.
+ */
+ public static final String HINT_HORIZONTAL = "horizontal";
+ /**
+ * Hint to indicate that this content should not be tinted.
+ */
+ public static final String HINT_NO_TINT = "no_tint";
+
+ // These two are coming over from prototyping, but we probably don't want in
+ // public API, at least not right now.
+ /**
+ * @hide
+ */
+ public static final String HINT_ALT = "alt";
+ /**
+ * @hide
+ */
+ public static final String HINT_PARTIAL = "partial";
+
+ private final SliceItem[] mItems;
+ private final @SliceHint String[] mHints;
+ private Uri mUri;
+
+ /**
+ * @hide
+ */
+ public Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+ mHints = hints;
+ mItems = items.toArray(new SliceItem[items.size()]);
+ mUri = uri;
+ }
+
+ protected Slice(Parcel in) {
+ mHints = in.readStringArray();
+ int n = in.readInt();
+ mItems = new SliceItem[n];
+ for (int i = 0; i < n; i++) {
+ mItems[i] = SliceItem.CREATOR.createFromParcel(in);
+ }
+ mUri = Uri.CREATOR.createFromParcel(in);
+ }
+
+ /**
+ * @return The Uri that this Slice represents.
+ */
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * @return All child {@link SliceItem}s that this Slice contains.
+ */
+ public SliceItem[] getItems() {
+ return mItems;
+ }
+
+ /**
+ * @return All hints associated with this Slice.
+ */
+ public @SliceHint String[] getHints() {
+ return mHints;
+ }
+
+ /**
+ * @hide
+ */
+ public SliceItem getPrimaryIcon() {
+ for (SliceItem item : getItems()) {
+ if (item.getType() == TYPE_IMAGE) {
+ return item;
+ }
+ if (!(item.getType() == TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+ && !item.hasHint(Slice.HINT_ACTIONS)
+ && !item.hasHint(Slice.HINT_LIST_ITEM)
+ && (item.getType() != TYPE_ACTION)) {
+ SliceItem icon = SliceQuery.find(item, TYPE_IMAGE);
+ if (icon != null) return icon;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringArray(mHints);
+ dest.writeInt(mItems.length);
+ for (int i = 0; i < mItems.length; i++) {
+ mItems[i].writeToParcel(dest, flags);
+ }
+ mUri.writeToParcel(dest, 0);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean hasHint(@SliceHint String hint) {
+ return ArrayUtils.contains(mHints, hint);
+ }
+
+ /**
+ * A Builder used to construct {@link Slice}s
+ */
+ public static class Builder {
+
+ private final Uri mUri;
+ private ArrayList<SliceItem> mItems = new ArrayList<>();
+ private @SliceHint ArrayList<String> mHints = new ArrayList<>();
+
+ /**
+ * Create a builder which will construct a {@link Slice} for the Given Uri.
+ * @param uri Uri to tag for this slice.
+ */
+ public Builder(@NonNull Uri uri) {
+ mUri = uri;
+ }
+
+ /**
+ * Create a builder for a {@link Slice} that is a sub-slice of the slice
+ * being constructed by the provided builder.
+ * @param parent The builder constructing the parent slice
+ */
+ public Builder(@NonNull Slice.Builder parent) {
+ mUri = parent.mUri.buildUpon().appendPath("_gen")
+ .appendPath(String.valueOf(mItems.size())).build();
+ }
+
+ /**
+ * Add hints to the Slice being constructed
+ */
+ public Builder addHints(@SliceHint String... hints) {
+ mHints.addAll(Arrays.asList(hints));
+ return this;
+ }
+
+ /**
+ * Add a sub-slice to the slice being constructed
+ */
+ public Builder addSubSlice(@NonNull Slice slice) {
+ mItems.add(new SliceItem(slice, TYPE_SLICE, slice.getHints()));
+ return this;
+ }
+
+ /**
+ * Add an action to the slice being constructed
+ */
+ public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
+ mItems.add(new SliceItem(action, s, TYPE_ACTION, new String[0]));
+ return this;
+ }
+
+ /**
+ * Add text to the slice being constructed
+ */
+ public Builder addText(CharSequence text, @SliceHint String... hints) {
+ mItems.add(new SliceItem(text, TYPE_TEXT, hints));
+ return this;
+ }
+
+ /**
+ * Add an image to the slice being constructed
+ */
+ public Builder addIcon(Icon icon, @SliceHint String... hints) {
+ mItems.add(new SliceItem(icon, TYPE_IMAGE, hints));
+ return this;
+ }
+
+ /**
+ * @hide This isn't final
+ */
+ public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
+ mItems.add(new SliceItem(remoteView, TYPE_REMOTE_VIEW, hints));
+ return this;
+ }
+
+ /**
+ * Add remote input to the slice being constructed
+ */
+ public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
+ mItems.add(new SliceItem(remoteInput, TYPE_REMOTE_INPUT, hints));
+ return this;
+ }
+
+ /**
+ * Add a color to the slice being constructed
+ */
+ public Builder addColor(int color, @SliceHint String... hints) {
+ mItems.add(new SliceItem(color, TYPE_COLOR, hints));
+ return this;
+ }
+
+ /**
+ * Add a timestamp to the slice being constructed
+ */
+ public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
+ mItems.add(new SliceItem(time, TYPE_TIMESTAMP, hints));
+ return this;
+ }
+
+ /**
+ * Construct the slice.
+ */
+ public Slice build() {
+ return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
+ }
+ }
+
+ public static final Creator<Slice> CREATOR = new Creator<Slice>() {
+ @Override
+ public Slice createFromParcel(Parcel in) {
+ return new Slice(in);
+ }
+
+ @Override
+ public Slice[] newArray(int size) {
+ return new Slice[size];
+ }
+ };
+
+ /**
+ * @hide
+ * @return A string representation of this slice.
+ */
+ public String getString() {
+ return getString("");
+ }
+
+ private String getString(String indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mItems.length; i++) {
+ sb.append(indent);
+ if (mItems[i].getType() == TYPE_SLICE) {
+ sb.append("slice:\n");
+ sb.append(mItems[i].getSlice().getString(indent + " "));
+ } else if (mItems[i].getType() == TYPE_TEXT) {
+ sb.append("text: ");
+ sb.append(mItems[i].getText());
+ sb.append("\n");
+ } else {
+ sb.append(SliceItem.typeToString(mItems[i].getType()));
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/android/slice/SliceItem.java b/android/slice/SliceItem.java
new file mode 100644
index 00000000..2827ab9d
--- /dev/null
+++ b/android/slice/SliceItem.java
@@ -0,0 +1,344 @@
+/*
+ * 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.slice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.slice.Slice.SliceHint;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.ArrayUtils;
+
+
+/**
+ * A SliceItem is a single unit in the tree structure of a {@link Slice}.
+ *
+ * A SliceItem a piece of content and some hints about what that content
+ * means or how it should be displayed. The types of content can be:
+ * <li>{@link #TYPE_SLICE}</li>
+ * <li>{@link #TYPE_TEXT}</li>
+ * <li>{@link #TYPE_IMAGE}</li>
+ * <li>{@link #TYPE_ACTION}</li>
+ * <li>{@link #TYPE_COLOR}</li>
+ * <li>{@link #TYPE_TIMESTAMP}</li>
+ * <li>{@link #TYPE_REMOTE_INPUT}</li>
+ *
+ * The hints that a {@link SliceItem} are a set of strings which annotate
+ * the content. The hints that are guaranteed to be understood by the system
+ * are defined on {@link Slice}.
+ * @hide
+ */
+public final class SliceItem implements Parcelable {
+
+ /**
+ * @hide
+ */
+ @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR,
+ TYPE_TIMESTAMP, TYPE_REMOTE_INPUT})
+ public @interface SliceType {}
+
+ /**
+ * A {@link SliceItem} that contains a {@link Slice}
+ */
+ public static final int TYPE_SLICE = 1;
+ /**
+ * A {@link SliceItem} that contains a {@link CharSequence}
+ */
+ public static final int TYPE_TEXT = 2;
+ /**
+ * A {@link SliceItem} that contains an {@link Icon}
+ */
+ public static final int TYPE_IMAGE = 3;
+ /**
+ * A {@link SliceItem} that contains a {@link PendingIntent}
+ *
+ * Note: Actions contain 2 pieces of data, In addition to the pending intent, the
+ * item contains a {@link Slice} that the action applies to.
+ */
+ public static final int TYPE_ACTION = 4;
+ /**
+ * @hide This isn't final
+ */
+ public static final int TYPE_REMOTE_VIEW = 5;
+ /**
+ * A {@link SliceItem} that contains a Color int.
+ */
+ public static final int TYPE_COLOR = 6;
+ /**
+ * A {@link SliceItem} that contains a timestamp.
+ */
+ public static final int TYPE_TIMESTAMP = 8;
+ /**
+ * A {@link SliceItem} that contains a {@link RemoteInput}.
+ */
+ public static final int TYPE_REMOTE_INPUT = 9;
+
+ /**
+ * @hide
+ */
+ protected @SliceHint String[] mHints;
+ private final int mType;
+ private final Object mObj;
+
+ /**
+ * @hide
+ */
+ public SliceItem(Object obj, @SliceType int type, @SliceHint String[] hints) {
+ mHints = hints;
+ mType = type;
+ mObj = obj;
+ }
+
+ /**
+ * @hide
+ */
+ public SliceItem(PendingIntent intent, Slice slice, int type, @SliceHint String[] hints) {
+ this(new Pair<>(intent, slice), type, hints);
+ }
+
+ /**
+ * Gets all hints associated with this SliceItem.
+ * @return Array of hints.
+ */
+ public @NonNull @SliceHint String[] getHints() {
+ return mHints;
+ }
+
+ /**
+ * @hide
+ */
+ public void addHint(@SliceHint String hint) {
+ mHints = ArrayUtils.appendElement(String.class, mHints, hint);
+ }
+
+ /**
+ * @hide
+ */
+ public void removeHint(String hint) {
+ ArrayUtils.removeElement(String.class, mHints, hint);
+ }
+
+ public @SliceType int getType() {
+ return mType;
+ }
+
+ /**
+ * @return The text held by this {@link #TYPE_TEXT} SliceItem
+ */
+ public CharSequence getText() {
+ return (CharSequence) mObj;
+ }
+
+ /**
+ * @return The icon held by this {@link #TYPE_IMAGE} SliceItem
+ */
+ public Icon getIcon() {
+ return (Icon) mObj;
+ }
+
+ /**
+ * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem
+ */
+ public PendingIntent getAction() {
+ return ((Pair<PendingIntent, Slice>) mObj).first;
+ }
+
+ /**
+ * @hide This isn't final
+ */
+ public RemoteViews getRemoteView() {
+ return (RemoteViews) mObj;
+ }
+
+ /**
+ * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem
+ */
+ public RemoteInput getRemoteInput() {
+ return (RemoteInput) mObj;
+ }
+
+ /**
+ * @return The color held by this {@link #TYPE_COLOR} SliceItem
+ */
+ public int getColor() {
+ return (Integer) mObj;
+ }
+
+ /**
+ * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem
+ */
+ public Slice getSlice() {
+ if (getType() == TYPE_ACTION) {
+ return ((Pair<PendingIntent, Slice>) mObj).second;
+ }
+ return (Slice) mObj;
+ }
+
+ /**
+ * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem
+ */
+ public long getTimestamp() {
+ return (Long) mObj;
+ }
+
+ /**
+ * @param hint The hint to check for
+ * @return true if this item contains the given hint
+ */
+ public boolean hasHint(@SliceHint String hint) {
+ return ArrayUtils.contains(mHints, hint);
+ }
+
+ /**
+ * @hide
+ */
+ public SliceItem(Parcel in) {
+ mHints = in.readStringArray();
+ mType = in.readInt();
+ mObj = readObj(mType, in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringArray(mHints);
+ dest.writeInt(mType);
+ writeObj(dest, flags, mObj, mType);
+ }
+
+ /**
+ * @hide
+ */
+ public boolean hasHints(@SliceHint String[] hints) {
+ if (hints == null) return true;
+ for (String hint : hints) {
+ if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean hasAnyHints(@SliceHint String[] hints) {
+ if (hints == null) return false;
+ for (String hint : hints) {
+ if (ArrayUtils.contains(mHints, hint)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void writeObj(Parcel dest, int flags, Object obj, int type) {
+ switch (type) {
+ case TYPE_SLICE:
+ case TYPE_REMOTE_VIEW:
+ case TYPE_IMAGE:
+ case TYPE_REMOTE_INPUT:
+ ((Parcelable) obj).writeToParcel(dest, flags);
+ break;
+ case TYPE_ACTION:
+ ((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
+ ((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
+ break;
+ case TYPE_TEXT:
+ TextUtils.writeToParcel((CharSequence) mObj, dest, flags);
+ break;
+ case TYPE_COLOR:
+ dest.writeInt((Integer) mObj);
+ break;
+ case TYPE_TIMESTAMP:
+ dest.writeLong((Long) mObj);
+ break;
+ }
+ }
+
+ private static Object readObj(int type, Parcel in) {
+ switch (type) {
+ case TYPE_SLICE:
+ return Slice.CREATOR.createFromParcel(in);
+ case TYPE_TEXT:
+ return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ case TYPE_IMAGE:
+ return Icon.CREATOR.createFromParcel(in);
+ case TYPE_ACTION:
+ return new Pair<PendingIntent, Slice>(
+ PendingIntent.CREATOR.createFromParcel(in),
+ Slice.CREATOR.createFromParcel(in));
+ case TYPE_REMOTE_VIEW:
+ return RemoteViews.CREATOR.createFromParcel(in);
+ case TYPE_COLOR:
+ return in.readInt();
+ case TYPE_TIMESTAMP:
+ return in.readLong();
+ case TYPE_REMOTE_INPUT:
+ return RemoteInput.CREATOR.createFromParcel(in);
+ }
+ throw new RuntimeException("Unsupported type " + type);
+ }
+
+ public static final Creator<SliceItem> CREATOR = new Creator<SliceItem>() {
+ @Override
+ public SliceItem createFromParcel(Parcel in) {
+ return new SliceItem(in);
+ }
+
+ @Override
+ public SliceItem[] newArray(int size) {
+ return new SliceItem[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static String typeToString(int type) {
+ switch (type) {
+ case TYPE_SLICE:
+ return "Slice";
+ case TYPE_TEXT:
+ return "Text";
+ case TYPE_IMAGE:
+ return "Image";
+ case TYPE_ACTION:
+ return "Action";
+ case TYPE_REMOTE_VIEW:
+ return "RemoteView";
+ case TYPE_COLOR:
+ return "Color";
+ case TYPE_TIMESTAMP:
+ return "Timestamp";
+ case TYPE_REMOTE_INPUT:
+ return "RemoteInput";
+ }
+ return "Unrecognized type: " + type;
+ }
+}
diff --git a/android/slice/SliceProvider.java b/android/slice/SliceProvider.java
new file mode 100644
index 00000000..4e21371b
--- /dev/null
+++ b/android/slice/SliceProvider.java
@@ -0,0 +1,156 @@
+/*
+ * 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.slice;
+
+import android.Manifest.permission;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+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.
+ *
+ * <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>
+ *
+ * @see Slice
+ * @hide
+ */
+public abstract class SliceProvider extends ContentProvider {
+
+ private static final String TAG = "SliceProvider";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_BIND_URI = "slice_uri";
+ /**
+ * @hide
+ */
+ public static final String METHOD_SLICE = "bind_slice";
+ /**
+ * @hide
+ */
+ public static final String EXTRA_SLICE = "slice";
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * Implemented to create a slice. Will be called on the main thread.
+ * @see {@link Slice}.
+ */
+ public abstract Slice onBindSlice(Uri sliceUri);
+
+ @Override
+ public final int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ if (DEBUG) Log.d(TAG, "update " + uri);
+ return 0;
+ }
+
+ @Override
+ public final int delete(Uri uri, String selection, String[] selectionArgs) {
+ if (DEBUG) Log.d(TAG, "delete " + uri);
+ return 0;
+ }
+
+ @Override
+ public final Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ if (DEBUG) Log.d(TAG, "query " + uri);
+ return null;
+ }
+
+ @Override
+ public final Cursor query(Uri uri, String[] projection, String selection, String[]
+ selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+ if (DEBUG) Log.d(TAG, "query " + uri);
+ return null;
+ }
+
+ @Override
+ public final Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+ CancellationSignal cancellationSignal) {
+ if (DEBUG) Log.d(TAG, "query " + uri);
+ return null;
+ }
+
+ @Override
+ public final Uri insert(Uri uri, ContentValues values) {
+ if (DEBUG) Log.d(TAG, "insert " + uri);
+ return null;
+ }
+
+ @Override
+ public final String getType(Uri uri) {
+ if (DEBUG) Log.d(TAG, "getType " + uri);
+ return null;
+ }
+
+ @Override
+ public final 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);
+
+ Slice s = handleBindSlice(uri);
+ Bundle b = new Bundle();
+ b.putParcelable(EXTRA_SLICE, s);
+ return b;
+ }
+ return super.call(method, arg, extras);
+ }
+
+ private Slice handleBindSlice(Uri sliceUri) {
+ Slice[] output = new Slice[1];
+ CountDownLatch latch = new CountDownLatch(1);
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+ mainHandler.post(() -> {
+ output[0] = onBindSlice(sliceUri);
+ latch.countDown();
+ });
+ try {
+ latch.await();
+ return output[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/android/slice/SliceQuery.java b/android/slice/SliceQuery.java
new file mode 100644
index 00000000..d99b26a5
--- /dev/null
+++ b/android/slice/SliceQuery.java
@@ -0,0 +1,151 @@
+/*
+ * 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.slice;
+
+import static android.slice.SliceItem.TYPE_ACTION;
+import static android.slice.SliceItem.TYPE_SLICE;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * A bunch of utilities for searching the contents of a slice.
+ * @hide
+ */
+public class SliceQuery {
+ private static final String TAG = "SliceQuery";
+
+ /**
+ * @hide
+ */
+ public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
+ SliceItem ret = null;
+ while (ret == null && list.size() != 0) {
+ SliceItem remove = list.remove(0);
+ if (!contains(container, remove)) {
+ ret = remove;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * @hide
+ */
+ private static boolean contains(SliceItem container, SliceItem item) {
+ if (container == null || item == null) return false;
+ return stream(container).filter(s -> (s == item)).findAny().isPresent();
+ }
+
+ /**
+ * @hide
+ */
+ public static List<SliceItem> findAll(SliceItem s, int type) {
+ return findAll(s, type, (String[]) null, null);
+ }
+
+ /**
+ * @hide
+ */
+ public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+ return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+ }
+
+ /**
+ * @hide
+ */
+ public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+ String[] nonHints) {
+ return stream(s).filter(item -> (type == -1 || item.getType() == type)
+ && (item.hasHints(hints) && !item.hasAnyHints(nonHints)))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * @hide
+ */
+ public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+ return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+ }
+
+ /**
+ * @hide
+ */
+ public static SliceItem find(Slice s, int type) {
+ return find(s, type, (String[]) null, null);
+ }
+
+ /**
+ * @hide
+ */
+ public static SliceItem find(SliceItem s, int type) {
+ return find(s, type, (String[]) null, null);
+ }
+
+ /**
+ * @hide
+ */
+ public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+ return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+ }
+
+ /**
+ * @hide
+ */
+ public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
+ return find(new SliceItem(s, TYPE_SLICE, s.getHints()), type, hints, nonHints);
+ }
+
+ /**
+ * @hide
+ */
+ public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
+ return stream(s).filter(item -> (item.getType() == type || type == -1)
+ && (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null);
+ }
+
+ /**
+ * @hide
+ */
+ public static Stream<SliceItem> stream(SliceItem slice) {
+ Queue<SliceItem> items = new LinkedList();
+ items.add(slice);
+ Iterator<SliceItem> iterator = new Iterator<SliceItem>() {
+ @Override
+ public boolean hasNext() {
+ return items.size() != 0;
+ }
+
+ @Override
+ public SliceItem next() {
+ SliceItem item = items.poll();
+ if (item.getType() == TYPE_SLICE || item.getType() == TYPE_ACTION) {
+ items.addAll(Arrays.asList(item.getSlice().getItems()));
+ }
+ return item;
+ }
+ };
+ return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
+ }
+}
diff --git a/android/slice/views/ActionRow.java b/android/slice/views/ActionRow.java
new file mode 100644
index 00000000..93e9c035
--- /dev/null
+++ b/android/slice/views/ActionRow.java
@@ -0,0 +1,201 @@
+/*
+ * 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.slice.views;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class ActionRow extends FrameLayout {
+
+ private static final int MAX_ACTIONS = 5;
+ private final int mSize;
+ private final int mIconPadding;
+ private final LinearLayout mActionsGroup;
+ private final boolean mFullActions;
+ private int mColor = Color.BLACK;
+
+ public ActionRow(Context context, boolean fullActions) {
+ super(context);
+ mFullActions = fullActions;
+ mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
+ context.getResources().getDisplayMetrics());
+ mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
+ context.getResources().getDisplayMetrics());
+ mActionsGroup = new LinearLayout(context);
+ mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
+ mActionsGroup.setLayoutParams(
+ new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ addView(mActionsGroup);
+ }
+
+ private void setColor(int color) {
+ mColor = color;
+ for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+ View view = mActionsGroup.getChildAt(i);
+ SliceItem item = (SliceItem) view.getTag();
+ boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
+ if (tint) {
+ ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
+ }
+ }
+ }
+
+ private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
+ ImageView imageView = new ImageView(getContext());
+ imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+ imageView.setScaleType(ScaleType.FIT_CENTER);
+ imageView.setImageIcon(icon);
+ if (allowTint) {
+ imageView.setImageTintList(ColorStateList.valueOf(mColor));
+ }
+ imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
+ android.R.attr.selectableItemBackground));
+ imageView.setTag(image);
+ addAction(imageView);
+ return imageView;
+ }
+
+ /**
+ * Set the actions and color for this action row.
+ */
+ public void setActions(SliceItem actionRow, SliceItem defColor) {
+ removeAllViews();
+ mActionsGroup.removeAllViews();
+ addView(mActionsGroup);
+
+ SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
+ if (color == null) {
+ color = defColor;
+ }
+ if (color != null) {
+ setColor(color.getColor());
+ }
+ SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
+ if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
+ return;
+ }
+ SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
+ if (image == null) {
+ return;
+ }
+ boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
+ SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
+ if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
+ addAction(image.getIcon(), tint, image).setOnClickListener(
+ v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
+ createRemoteInputView(mColor, getContext());
+ } else {
+ addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
+ () -> {
+ try {
+ action.getAction().send();
+ } catch (CanceledException e) {
+ e.printStackTrace();
+ }
+ }));
+ }
+ });
+ setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
+ }
+
+ private void addAction(View child) {
+ mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
+ }
+
+ private void createRemoteInputView(int color, Context context) {
+ View riv = RemoteInputView.inflate(context, this);
+ riv.setVisibility(View.INVISIBLE);
+ addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ riv.setBackgroundColor(color);
+ }
+
+ private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
+ RemoteInput input) {
+ if (input == null) {
+ return false;
+ }
+
+ ViewParent p = view.getParent().getParent();
+ RemoteInputView riv = null;
+ while (p != null) {
+ if (p instanceof View) {
+ View pv = (View) p;
+ riv = findRemoteInputView(pv);
+ if (riv != null) {
+ break;
+ }
+ }
+ p = p.getParent();
+ }
+ if (riv == null) {
+ return false;
+ }
+
+ int width = view.getWidth();
+ if (view instanceof TextView) {
+ // Center the reveal on the text which might be off-center from the TextView
+ TextView tv = (TextView) view;
+ if (tv.getLayout() != null) {
+ int innerWidth = (int) tv.getLayout().getLineWidth(0);
+ innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+ width = Math.min(width, innerWidth);
+ }
+ }
+ int cx = view.getLeft() + width / 2;
+ int cy = view.getTop() + view.getHeight() / 2;
+ int w = riv.getWidth();
+ int h = riv.getHeight();
+ int r = Math.max(
+ Math.max(cx + cy, cx + (h - cy)),
+ Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+ riv.setRevealParameters(cx, cy, r);
+ riv.setPendingIntent(pendingIntent);
+ riv.setRemoteInput(new RemoteInput[] {
+ input
+ }, input);
+ riv.focusAnimated();
+ return true;
+ }
+
+ private RemoteInputView findRemoteInputView(View v) {
+ if (v == null) {
+ return null;
+ }
+ return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+ }
+}
diff --git a/android/slice/views/GridView.java b/android/slice/views/GridView.java
new file mode 100644
index 00000000..18a90f7d
--- /dev/null
+++ b/android/slice/views/GridView.java
@@ -0,0 +1,186 @@
+/*
+ * 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.slice.views;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.views.LargeSliceAdapter.SliceListView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class GridView extends LinearLayout implements SliceListView {
+
+ private static final String TAG = "GridView";
+
+ private static final int MAX_IMAGES = 3;
+ private static final int MAX_ALL = 5;
+ private boolean mIsAllImages;
+
+ public GridView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mIsAllImages) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = width / getChildCount();
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,
+ height);
+ getLayoutParams().height = height;
+ for (int i = 0; i < getChildCount(); i++) {
+ getChildAt(i).getLayoutParams().height = height;
+ }
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public void setSliceItem(SliceItem slice) {
+ mIsAllImages = true;
+ removeAllViews();
+ int total = 1;
+ if (slice.getType() == SliceItem.TYPE_SLICE) {
+ SliceItem[] items = slice.getSlice().getItems();
+ total = items.length;
+ for (int i = 0; i < total; i++) {
+ SliceItem item = items[i];
+ if (isFull()) {
+ continue;
+ }
+ if (!addItem(item)) {
+ mIsAllImages = false;
+ }
+ }
+ } else {
+ if (!isFull()) {
+ if (!addItem(slice)) {
+ mIsAllImages = false;
+ }
+ }
+ }
+ if (total > getChildCount() && mIsAllImages) {
+ addExtraCount(total - getChildCount());
+ }
+ }
+
+ private void addExtraCount(int numExtra) {
+ View last = getChildAt(getChildCount() - 1);
+ FrameLayout frame = new FrameLayout(getContext());
+ frame.setLayoutParams(last.getLayoutParams());
+
+ removeView(last);
+ frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ TextView v = new TextView(getContext());
+ v.setTextColor(Color.WHITE);
+ v.setBackgroundColor(0x4d000000);
+ v.setText(getResources().getString(R.string.slice_more_content, numExtra));
+ v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
+ v.setGravity(Gravity.CENTER);
+ frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ addView(frame);
+ }
+
+ private boolean isFull() {
+ return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
+ }
+
+ /**
+ * Returns true if this item is just an image.
+ */
+ private boolean addItem(SliceItem item) {
+ if (item.getType() == SliceItem.TYPE_IMAGE) {
+ ImageView v = new ImageView(getContext());
+ v.setImageIcon(item.getIcon());
+ v.setScaleType(ScaleType.CENTER_CROP);
+ addView(v, new LayoutParams(0, MATCH_PARENT, 1));
+ return true;
+ } else {
+ LinearLayout v = new LinearLayout(getContext());
+ int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ 12, getContext().getResources().getDisplayMetrics());
+ v.setPadding(0, s, 0, 0);
+ v.setOrientation(LinearLayout.VERTICAL);
+ v.setGravity(Gravity.CENTER_HORIZONTAL);
+ // TODO: Unify sporadic inflates that happen throughout the code.
+ ArrayList<SliceItem> items = new ArrayList<>();
+ if (item.getType() == SliceItem.TYPE_SLICE) {
+ items.addAll(Arrays.asList(item.getSlice().getItems()));
+ }
+ items.forEach(i -> {
+ Context context = getContext();
+ switch (i.getType()) {
+ case SliceItem.TYPE_TEXT:
+ boolean title = false;
+ if ((item.hasAnyHints(new String[] {
+ Slice.HINT_LARGE, Slice.HINT_TITLE
+ }))) {
+ title = true;
+ }
+ TextView tv = (TextView) LayoutInflater.from(context).inflate(
+ title ? R.layout.slice_title : R.layout.slice_secondary_text, null);
+ tv.setText(i.getText());
+ v.addView(tv);
+ break;
+ case SliceItem.TYPE_IMAGE:
+ ImageView iv = new ImageView(context);
+ iv.setImageIcon(i.getIcon());
+ if (item.hasHint(Slice.HINT_LARGE)) {
+ iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ } else {
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ 48, context.getResources().getDisplayMetrics());
+ iv.setLayoutParams(new LayoutParams(size, size));
+ }
+ v.addView(iv);
+ break;
+ case SliceItem.TYPE_REMOTE_VIEW:
+ v.addView(i.getRemoteView().apply(context, v));
+ break;
+ case SliceItem.TYPE_COLOR:
+ // TODO: Support color to tint stuff here.
+ break;
+ }
+ });
+ addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
+ return false;
+ }
+ }
+}
diff --git a/android/slice/views/LargeSliceAdapter.java b/android/slice/views/LargeSliceAdapter.java
new file mode 100644
index 00000000..e77a1b2a
--- /dev/null
+++ b/android/slice/views/LargeSliceAdapter.java
@@ -0,0 +1,224 @@
+/*
+ * 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.slice.views;
+
+import android.content.Context;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.LargeSliceAdapter.SliceViewHolder;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.widget.RecyclerView;
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @hide
+ */
+public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
+
+ public static final int TYPE_DEFAULT = 1;
+ public static final int TYPE_HEADER = 2;
+ public static final int TYPE_GRID = 3;
+ public static final int TYPE_MESSAGE = 4;
+ public static final int TYPE_MESSAGE_LOCAL = 5;
+ public static final int TYPE_REMOTE_VIEWS = 6;
+
+ private final IdGenerator mIdGen = new IdGenerator();
+ private final Context mContext;
+ private List<SliceWrapper> mSlices = new ArrayList<>();
+ private SliceItem mColor;
+
+ public LargeSliceAdapter(Context context) {
+ mContext = context;
+ setHasStableIds(true);
+ }
+
+ /**
+ * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
+ */
+ public void setSliceItems(List<SliceItem> slices, SliceItem color) {
+ mColor = color;
+ mIdGen.resetUsage();
+ mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
+ .collect(Collectors.toList());
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = inflateforType(viewType);
+ v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ return new SliceViewHolder(v);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mSlices.get(position).mType;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mSlices.get(position).mId;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSlices.size();
+ }
+
+ @Override
+ public void onBindViewHolder(SliceViewHolder holder, int position) {
+ SliceWrapper slice = mSlices.get(position);
+ if (holder.mSliceView != null) {
+ holder.mSliceView.setColor(mColor);
+ holder.mSliceView.setSliceItem(slice.mItem);
+ } else if (slice.mType == TYPE_REMOTE_VIEWS) {
+ FrameLayout frame = (FrameLayout) holder.itemView;
+ frame.removeAllViews();
+ frame.addView(slice.mItem.getRemoteView().apply(mContext, frame));
+ }
+ }
+
+ private View inflateforType(int viewType) {
+ switch (viewType) {
+ case TYPE_REMOTE_VIEWS:
+ return new FrameLayout(mContext);
+ case TYPE_GRID:
+ return LayoutInflater.from(mContext).inflate(R.layout.slice_grid, null);
+ case TYPE_MESSAGE:
+ return LayoutInflater.from(mContext).inflate(R.layout.slice_message, null);
+ case TYPE_MESSAGE_LOCAL:
+ return LayoutInflater.from(mContext).inflate(R.layout.slice_message_local, null);
+ }
+ return new SmallTemplateView(mContext);
+ }
+
+ protected static class SliceWrapper {
+ private final SliceItem mItem;
+ private final int mType;
+ private final long mId;
+
+ public SliceWrapper(SliceItem item, IdGenerator idGen) {
+ mItem = item;
+ mType = getType(item);
+ mId = idGen.getId(item);
+ }
+
+ public static int getType(SliceItem item) {
+ if (item.getType() == SliceItem.TYPE_REMOTE_VIEW) {
+ return TYPE_REMOTE_VIEWS;
+ }
+ if (item.hasHint(Slice.HINT_MESSAGE)) {
+ // TODO: Better way to determine me or not? Something more like Messaging style.
+ if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
+ return TYPE_MESSAGE;
+ } else {
+ return TYPE_MESSAGE_LOCAL;
+ }
+ }
+ if (item.hasHint(Slice.HINT_HORIZONTAL)) {
+ return TYPE_GRID;
+ }
+ return TYPE_DEFAULT;
+ }
+ }
+
+ /**
+ * A {@link ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
+ */
+ public static class SliceViewHolder extends ViewHolder {
+ public final SliceListView mSliceView;
+
+ public SliceViewHolder(View itemView) {
+ super(itemView);
+ mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
+ }
+ }
+
+ /**
+ * View slices being displayed in {@link LargeSliceAdapter}.
+ */
+ public interface SliceListView {
+ /**
+ * Set the slice item for this view.
+ */
+ void setSliceItem(SliceItem slice);
+
+ /**
+ * Set the color for the items in this view.
+ */
+ default void setColor(SliceItem color) {
+
+ }
+ }
+
+ private static class IdGenerator {
+ private long mNextLong = 0;
+ private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
+ private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
+
+ public long getId(SliceItem item) {
+ String str = genString(item);
+ if (!mCurrentIds.containsKey(str)) {
+ mCurrentIds.put(str, mNextLong++);
+ }
+ long id = mCurrentIds.get(str);
+ int index = mUsedIds.getOrDefault(str, 0);
+ mUsedIds.put(str, index + 1);
+ return id + index * 10000;
+ }
+
+ private String genString(SliceItem item) {
+ StringBuilder builder = new StringBuilder();
+ SliceQuery.stream(item).forEach(i -> {
+ builder.append(i.getType());
+ i.removeHint(Slice.HINT_SELECTED);
+ builder.append(i.getHints());
+ switch (i.getType()) {
+ case SliceItem.TYPE_REMOTE_VIEW:
+ builder.append(i.getRemoteView());
+ break;
+ case SliceItem.TYPE_IMAGE:
+ builder.append(i.getIcon());
+ break;
+ case SliceItem.TYPE_TEXT:
+ builder.append(i.getText());
+ break;
+ case SliceItem.TYPE_COLOR:
+ builder.append(i.getColor());
+ break;
+ }
+ });
+ return builder.toString();
+ }
+
+ public void resetUsage() {
+ mUsedIds.clear();
+ }
+ }
+}
diff --git a/android/slice/views/LargeTemplateView.java b/android/slice/views/LargeTemplateView.java
new file mode 100644
index 00000000..d53e8fcb
--- /dev/null
+++ b/android/slice/views/LargeTemplateView.java
@@ -0,0 +1,116 @@
+/*
+ * 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.slice.views;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.content.Context;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.SliceView.SliceModeView;
+import android.util.TypedValue;
+
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class LargeTemplateView extends SliceModeView {
+ private final LargeSliceAdapter mAdapter;
+ private final RecyclerView mRecyclerView;
+ private final int mDefaultHeight;
+ private final int mMaxHeight;
+ private Slice mSlice;
+
+ public LargeTemplateView(Context context) {
+ super(context);
+
+ mRecyclerView = new RecyclerView(getContext());
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+ mAdapter = new LargeSliceAdapter(context);
+ mRecyclerView.setAdapter(mAdapter);
+ addView(mRecyclerView);
+ int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
+ getResources().getDisplayMetrics());
+ setLayoutParams(new LayoutParams(width, WRAP_CONTENT));
+ mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+ getResources().getDisplayMetrics());
+ mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+ getResources().getDisplayMetrics());
+ }
+
+ @Override
+ public String getMode() {
+ return SliceView.MODE_LARGE;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mRecyclerView.getMeasuredHeight() > mMaxHeight
+ || mSlice.hasHint(Slice.HINT_PARTIAL)) {
+ mRecyclerView.getLayoutParams().height = mDefaultHeight;
+ } else {
+ mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public void setSlice(Slice slice) {
+ SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+ mSlice = slice;
+ List<SliceItem> items = new ArrayList<>();
+ boolean[] hasHeader = new boolean[1];
+ if (slice.hasHint(Slice.HINT_LIST)) {
+ addList(slice, items);
+ } else {
+ Arrays.asList(slice.getItems()).forEach(item -> {
+ if (item.hasHint(Slice.HINT_ACTIONS)) {
+ return;
+ } else if (item.getType() == SliceItem.TYPE_COLOR) {
+ return;
+ } else if (item.getType() == SliceItem.TYPE_SLICE
+ && item.hasHint(Slice.HINT_LIST)) {
+ addList(item.getSlice(), items);
+ } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
+ items.add(item);
+ } else if (!hasHeader[0]) {
+ hasHeader[0] = true;
+ items.add(0, item);
+ } else {
+ item.addHint(Slice.HINT_LIST_ITEM);
+ items.add(item);
+ }
+ });
+ }
+ mAdapter.setSliceItems(items, color);
+ }
+
+ private void addList(Slice slice, List<SliceItem> items) {
+ List<SliceItem> sliceItems = Arrays.asList(slice.getItems());
+ sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
+ items.addAll(sliceItems);
+ }
+}
diff --git a/android/slice/views/MessageView.java b/android/slice/views/MessageView.java
new file mode 100644
index 00000000..7b03e0bd
--- /dev/null
+++ b/android/slice/views/MessageView.java
@@ -0,0 +1,77 @@
+/*
+ * 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.slice.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.LargeSliceAdapter.SliceListView;
+import android.text.SpannableStringBuilder;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class MessageView extends LinearLayout implements SliceListView {
+
+ private TextView mDetails;
+ private ImageView mIcon;
+
+ public MessageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDetails = findViewById(android.R.id.summary);
+ mIcon = findViewById(android.R.id.icon);
+ }
+
+ @Override
+ public void setSliceItem(SliceItem slice) {
+ SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
+ if (source != null) {
+ final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ 24, getContext().getResources().getDisplayMetrics());
+ // TODO try and turn this into a drawable
+ Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+ Canvas iconCanvas = new Canvas(iconBm);
+ Drawable d = source.getIcon().loadDrawable(getContext());
+ d.setBounds(0, 0, iconSize, iconSize);
+ d.draw(iconCanvas);
+ mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
+ }
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
+ if (builder.length() != 0) {
+ builder.append('\n');
+ }
+ builder.append(text.getText());
+ });
+ mDetails.setText(builder.toString());
+ }
+
+}
diff --git a/android/slice/views/RemoteInputView.java b/android/slice/views/RemoteInputView.java
new file mode 100644
index 00000000..a29bb5c0
--- /dev/null
+++ b/android/slice/views/RemoteInputView.java
@@ -0,0 +1,445 @@
+/*
+ * 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.slice.views;
+
+import android.animation.Animator;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutManager;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.R;
+
+/**
+ * Host for the remote input.
+ *
+ * @hide
+ */
+// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
+public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
+
+ private static final String TAG = "RemoteInput";
+
+ /**
+ * A marker object that let's us easily find views of this class.
+ */
+ public static final Object VIEW_TAG = new Object();
+
+ private RemoteEditText mEditText;
+ private ImageButton mSendButton;
+ private ProgressBar mProgressBar;
+ private PendingIntent mPendingIntent;
+ private RemoteInput[] mRemoteInputs;
+ private RemoteInput mRemoteInput;
+
+ private int mRevealCx;
+ private int mRevealCy;
+ private int mRevealR;
+ private boolean mResetting;
+
+ public RemoteInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mProgressBar = findViewById(R.id.remote_input_progress);
+ mSendButton = findViewById(R.id.remote_input_send);
+ mSendButton.setOnClickListener(this);
+
+ mEditText = (RemoteEditText) getChildAt(0);
+ mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ final boolean isSoftImeEvent = event == null
+ && (actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT
+ || actionId == EditorInfo.IME_ACTION_SEND);
+ final boolean isKeyboardEnterKey = event != null
+ && KeyEvent.isConfirmKey(event.getKeyCode())
+ && event.getAction() == KeyEvent.ACTION_DOWN;
+
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ if (mEditText.length() > 0) {
+ sendRemoteInput();
+ }
+ // Consume action to prevent IME from closing.
+ return true;
+ }
+ return false;
+ }
+ });
+ mEditText.addTextChangedListener(this);
+ mEditText.setInnerFocusable(false);
+ mEditText.mRemoteInputView = this;
+ }
+
+ private void sendRemoteInput() {
+ Bundle results = new Bundle();
+ results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+ Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
+ results);
+
+ mEditText.setEnabled(false);
+ mSendButton.setVisibility(INVISIBLE);
+ mProgressBar.setVisibility(VISIBLE);
+ mEditText.mShowImeOnInputConnection = false;
+
+ // Tell ShortcutManager that this package has been "activated". ShortcutManager
+ // will reset the throttling for this package.
+ // Strictly speaking, the intent receiver may be different from the intent creator,
+ // but that's an edge case, and also because we can't always know which package will receive
+ // an intent, so we just reset for the creator.
+ getContext().getSystemService(ShortcutManager.class).onApplicationActive(
+ mPendingIntent.getCreatorPackage(),
+ getContext().getUserId());
+
+ try {
+ mPendingIntent.send(mContext, 0, fillInIntent);
+ reset();
+ } catch (PendingIntent.CanceledException e) {
+ Log.i(TAG, "Unable to send remote input result", e);
+ Toast.makeText(mContext, "Failure sending pending intent for inline reply :(",
+ Toast.LENGTH_SHORT).show();
+ reset();
+ }
+ }
+
+ /**
+ * Creates a remote input view.
+ */
+ public static RemoteInputView inflate(Context context, ViewGroup root) {
+ RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
+ R.layout.slice_remote_input, root, false);
+ v.setTag(VIEW_TAG);
+ return v;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mSendButton) {
+ sendRemoteInput();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ super.onTouchEvent(event);
+
+ // We never want for a touch to escape to an outer view or one we covered.
+ return true;
+ }
+
+ private void onDefocus() {
+ setVisibility(INVISIBLE);
+ }
+
+ /**
+ * Set the pending intent for remote input.
+ */
+ public void setPendingIntent(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ }
+
+ /**
+ * Set the remote inputs for this view.
+ */
+ public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+ mRemoteInputs = remoteInputs;
+ mRemoteInput = remoteInput;
+ mEditText.setHint(mRemoteInput.getLabel());
+ }
+
+ /**
+ * Focuses the remote input view.
+ */
+ public void focusAnimated() {
+ if (getVisibility() != VISIBLE) {
+ Animator animator = ViewAnimationUtils.createCircularReveal(
+ this, mRevealCx, mRevealCy, 0, mRevealR);
+ animator.setDuration(200);
+ animator.start();
+ }
+ focus();
+ }
+
+ private void focus() {
+ setVisibility(VISIBLE);
+ mEditText.setInnerFocusable(true);
+ mEditText.mShowImeOnInputConnection = true;
+ mEditText.setSelection(mEditText.getText().length());
+ mEditText.requestFocus();
+ updateSendButton();
+ }
+
+ private void reset() {
+ mResetting = true;
+
+ mEditText.getText().clear();
+ mEditText.setEnabled(true);
+ mSendButton.setVisibility(VISIBLE);
+ mProgressBar.setVisibility(INVISIBLE);
+ updateSendButton();
+ onDefocus();
+
+ mResetting = false;
+ }
+
+ @Override
+ public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+ if (mResetting && child == mEditText) {
+ // Suppress text events if it happens during resetting. Ideally this would be
+ // suppressed by the text view not being shown, but that doesn't work here because it
+ // needs to stay visible for the animation.
+ return false;
+ }
+ return super.onRequestSendAccessibilityEvent(child, event);
+ }
+
+ private void updateSendButton() {
+ mSendButton.setEnabled(mEditText.getText().length() != 0);
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ updateSendButton();
+ }
+
+ /**
+ * Tries to find an action that matches the current pending intent of this view and updates its
+ * state to that of the found action
+ *
+ * @return true if a matching action was found, false otherwise
+ */
+ public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
+ if (mPendingIntent == null || actions == null) {
+ return false;
+ }
+ Intent current = mPendingIntent.getIntent();
+ if (current == null) {
+ return false;
+ }
+
+ for (Notification.Action a : actions) {
+ RemoteInput[] inputs = a.getRemoteInputs();
+ if (a.actionIntent == null || inputs == null) {
+ continue;
+ }
+ Intent candidate = a.actionIntent.getIntent();
+ if (!current.filterEquals(candidate)) {
+ continue;
+ }
+
+ RemoteInput input = null;
+ for (RemoteInput i : inputs) {
+ if (i.getAllowFreeFormInput()) {
+ input = i;
+ }
+ }
+ if (input == null) {
+ continue;
+ }
+ setPendingIntent(a.actionIntent);
+ setRemoteInput(inputs, input);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ public void setRevealParameters(int cx, int cy, int r) {
+ mRevealCx = cx;
+ mRevealCy = cy;
+ mRevealR = r;
+ }
+
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ super.dispatchStartTemporaryDetach();
+ // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
+ // won't lose IME focus.
+ detachViewFromParent(mEditText);
+ }
+
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ if (isAttachedToWindow()) {
+ attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
+ } else {
+ removeDetachedView(mEditText, false /* animate */);
+ }
+ super.dispatchFinishTemporaryDetach();
+ }
+
+ /**
+ * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
+ * whenever the user navigates away from it or it becomes invisible.
+ */
+ public static class RemoteEditText extends EditText {
+
+ private final Drawable mBackground;
+ private RemoteInputView mRemoteInputView;
+ boolean mShowImeOnInputConnection;
+
+ public RemoteEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mBackground = getBackground();
+ }
+
+ private void defocusIfNeeded(boolean animate) {
+ if (mRemoteInputView != null || isTemporarilyDetached()) {
+ if (isTemporarilyDetached()) {
+ // We might get reattached but then the other one of HUN / expanded might steal
+ // our focus, so we'll need to save our text here.
+ }
+ return;
+ }
+ if (isFocusable() && isEnabled()) {
+ setInnerFocusable(false);
+ if (mRemoteInputView != null) {
+ mRemoteInputView.onDefocus();
+ }
+ mShowImeOnInputConnection = false;
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (!isShown()) {
+ defocusIfNeeded(false /* animate */);
+ }
+ }
+
+ @Override
+ protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ if (!focused) {
+ defocusIfNeeded(true /* animate */);
+ }
+ }
+
+ @Override
+ public void getFocusedRect(Rect r) {
+ super.getFocusedRect(r);
+ r.top = mScrollY;
+ r.bottom = mScrollY + (mBottom - mTop);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ // Eat the DOWN event here to prevent any default behavior.
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ defocusIfNeeded(true /* animate */);
+ return true;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
+
+ if (mShowImeOnInputConnection && inputConnection != null) {
+ final InputMethodManager imm = InputMethodManager.getInstance();
+ if (imm != null) {
+ // onCreateInputConnection is called by InputMethodManager in the middle of
+ // setting up the connection to the IME; wait with requesting the IME until that
+ // work has completed.
+ post(new Runnable() {
+ @Override
+ public void run() {
+ imm.viewClicked(RemoteEditText.this);
+ imm.showSoftInput(RemoteEditText.this, 0);
+ }
+ });
+ }
+ }
+
+ return inputConnection;
+ }
+
+ @Override
+ public void onCommitCompletion(CompletionInfo text) {
+ clearComposingText();
+ setText(text.getText());
+ setSelection(getText().length());
+ }
+
+ void setInnerFocusable(boolean focusable) {
+ setFocusableInTouchMode(focusable);
+ setFocusable(focusable);
+ setCursorVisible(focusable);
+
+ if (focusable) {
+ requestFocus();
+ setBackground(mBackground);
+ } else {
+ setBackground(null);
+ }
+
+ }
+ }
+}
diff --git a/android/slice/views/ShortcutView.java b/android/slice/views/ShortcutView.java
new file mode 100644
index 00000000..8fe2f1ac
--- /dev/null
+++ b/android/slice/views/ShortcutView.java
@@ -0,0 +1,110 @@
+/*
+ * 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.slice.views;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.net.Uri;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.SliceView.SliceModeView;
+import android.view.ViewGroup;
+
+import com.android.internal.R;
+
+/**
+ * @hide
+ */
+public class ShortcutView extends SliceModeView {
+
+ private static final String TAG = "ShortcutView";
+
+ private PendingIntent mAction;
+ private Uri mUri;
+ private int mLargeIconSize;
+ private int mSmallIconSize;
+
+ public ShortcutView(Context context) {
+ super(context);
+ mLargeIconSize = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.slice_shortcut_size);
+ mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+ setLayoutParams(new ViewGroup.LayoutParams(mLargeIconSize, mLargeIconSize));
+ }
+
+ @Override
+ public void setSlice(Slice slice) {
+ removeAllViews();
+ SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+ SliceItem iconItem = slice.getPrimaryIcon();
+ 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);
+ if (colorItem == null) {
+ colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+ }
+ // TODO: pick better default colour
+ final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
+ ShapeDrawable circle = new ShapeDrawable(new OvalShape());
+ circle.setTint(color);
+ setBackground(circle);
+ if (iconItem != null) {
+ final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE);
+ final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
+ SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(),
+ isLarge, this /* parent */);
+ mAction = sliceItem != null ? sliceItem.getAction()
+ : null;
+ mUri = slice.getUri();
+ setClickable(true);
+ } else {
+ setClickable(false);
+ }
+ }
+
+ @Override
+ public String getMode() {
+ return SliceView.MODE_SHORTCUT;
+ }
+
+ @Override
+ public boolean performClick() {
+ if (!callOnClick()) {
+ try {
+ if (mAction != null) {
+ mAction.send();
+ } else {
+ Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ }
+ } catch (CanceledException e) {
+ e.printStackTrace();
+ }
+ }
+ return true;
+ }
+}
diff --git a/android/slice/views/SliceView.java b/android/slice/views/SliceView.java
new file mode 100644
index 00000000..f3792481
--- /dev/null
+++ b/android/slice/views/SliceView.java
@@ -0,0 +1,249 @@
+/*
+ * 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.slice.views;
+
+import android.annotation.StringDef;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+/**
+ * A view that can display a {@link Slice} in different {@link SliceMode}'s.
+ *
+ * @hide
+ */
+public class SliceView extends LinearLayout {
+
+ private static final String TAG = "SliceView";
+
+ /**
+ * @hide
+ */
+ public abstract static class SliceModeView extends FrameLayout {
+
+ public SliceModeView(Context context) {
+ super(context);
+ }
+
+ /**
+ * @return the {@link SliceMode} of the slice being presented.
+ */
+ public abstract String getMode();
+
+ /**
+ * @param slice the slice to show in this view.
+ */
+ public abstract void setSlice(Slice slice);
+ }
+
+ /**
+ * @hide
+ */
+ @StringDef({
+ MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+ })
+ public @interface SliceMode {}
+
+ /**
+ * Mode indicating this slice should be presented in small template format.
+ */
+ public static final String MODE_SMALL = "SLICE_SMALL";
+ /**
+ * Mode indicating this slice should be presented in large template format.
+ */
+ public static final String MODE_LARGE = "SLICE_LARGE";
+ /**
+ * Mode indicating this slice should be presented as an icon.
+ */
+ public static final String MODE_SHORTCUT = "SLICE_ICON";
+
+ /**
+ * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+ * that selection.
+ */
+ private static final String MODE_AUTO = "auto";
+
+ private String mMode = MODE_AUTO;
+ private SliceModeView mCurrentView;
+ private final ActionRow mActions;
+ private Slice mCurrentSlice;
+ private boolean mShowActions = true;
+
+ /**
+ * Simple constructor to create a slice view from code.
+ *
+ * @param context The context the view is running in.
+ */
+ public SliceView(Context context) {
+ super(context);
+ setOrientation(LinearLayout.VERTICAL);
+ mActions = new ActionRow(mContext, true);
+ mActions.setBackground(new ColorDrawable(0xffeeeeee));
+ mCurrentView = new LargeTemplateView(mContext);
+ addView(mCurrentView);
+ addView(mActions);
+ }
+
+ /**
+ * @hide
+ */
+ public void bindSlice(Intent intent) {
+ // TODO
+ }
+
+ /**
+ * Binds this view to the {@link Slice} associated with the provided {@link Uri}.
+ */
+ public void bindSlice(Uri sliceUri) {
+ validate(sliceUri);
+ Slice s = mContext.getContentResolver().bindSlice(sliceUri);
+ bindSlice(s);
+ }
+
+ /**
+ * Binds this view to the provided {@link Slice}.
+ */
+ public void bindSlice(Slice slice) {
+ mCurrentSlice = slice;
+ if (mCurrentSlice != null) {
+ reinflate();
+ }
+ }
+
+ /**
+ * Call to clean up the view.
+ */
+ public void unbindSlice() {
+ mCurrentSlice = null;
+ }
+
+ /**
+ * Set the {@link SliceMode} this view should present in.
+ */
+ public void setMode(@SliceMode String mode) {
+ setMode(mode, false /* animate */);
+ }
+
+ /**
+ * @hide
+ */
+ public void setMode(@SliceMode String mode, boolean animate) {
+ if (animate) {
+ Log.e(TAG, "Animation not supported yet");
+ }
+ mMode = mode;
+ reinflate();
+ }
+
+ /**
+ * @return the {@link SliceMode} this view is presenting in.
+ */
+ public @SliceMode String getMode() {
+ if (mMode.equals(MODE_AUTO)) {
+ return MODE_LARGE;
+ }
+ return mMode;
+ }
+
+ /**
+ * @hide
+ *
+ * Whether this view should show a row of actions with it.
+ */
+ public void setShowActionRow(boolean show) {
+ mShowActions = show;
+ reinflate();
+ }
+
+ private SliceModeView createView(String mode) {
+ switch (mode) {
+ case MODE_SHORTCUT:
+ return new ShortcutView(getContext());
+ case MODE_SMALL:
+ return new SmallTemplateView(getContext());
+ }
+ return new LargeTemplateView(getContext());
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ unbindSlice();
+ }
+
+ private void reinflate() {
+ if (mCurrentSlice == null) {
+ return;
+ }
+ // TODO: Smarter mapping here from one state to the next.
+ SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+ SliceItem[] items = mCurrentSlice.getItems();
+ SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
+ Slice.HINT_ACTIONS,
+ Slice.HINT_ALT);
+ String mode = getMode();
+ if (!mode.equals(mCurrentView.getMode())) {
+ removeAllViews();
+ mCurrentView = createView(mode);
+ addView(mCurrentView);
+ addView(mActions);
+ }
+ if (items.length > 1 || (items.length != 0 && items[0] != actionRow)) {
+ mCurrentView.setVisibility(View.VISIBLE);
+ mCurrentView.setSlice(mCurrentSlice);
+ } else {
+ mCurrentView.setVisibility(View.GONE);
+ }
+
+ boolean showActions = mShowActions && actionRow != null
+ && !mode.equals(MODE_SHORTCUT);
+ if (showActions) {
+ mActions.setActions(actionRow, color);
+ mActions.setVisibility(View.VISIBLE);
+ } else {
+ mActions.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // TODO -- may need to rethink for AGSA
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ requestDisallowInterceptTouchEvent(true);
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ private static void validate(Uri sliceUri) {
+ if (!ContentResolver.SCHEME_SLICE.equals(sliceUri.getScheme())) {
+ throw new RuntimeException("Invalid uri " + sliceUri);
+ }
+ if (sliceUri.getPathSegments().size() == 0) {
+ throw new RuntimeException("Invalid uri " + sliceUri);
+ }
+ }
+}
diff --git a/android/slice/views/SliceViewUtil.java b/android/slice/views/SliceViewUtil.java
new file mode 100644
index 00000000..1b5a6d1e
--- /dev/null
+++ b/android/slice/views/SliceViewUtil.java
@@ -0,0 +1,182 @@
+/*
+ * 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.slice.views;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/**
+ * A bunch of utilities for slice UI.
+ *
+ * @hide
+ */
+public class SliceViewUtil {
+
+ /**
+ * @hide
+ */
+ @ColorInt
+ public static int getColorAccent(Context context) {
+ return getColorAttr(context, android.R.attr.colorAccent);
+ }
+
+ /**
+ * @hide
+ */
+ @ColorInt
+ public static int getColorError(Context context) {
+ return getColorAttr(context, android.R.attr.colorError);
+ }
+
+ /**
+ * @hide
+ */
+ @ColorInt
+ public static int getDefaultColor(Context context, int resId) {
+ final ColorStateList list = context.getResources().getColorStateList(resId,
+ context.getTheme());
+
+ return list.getDefaultColor();
+ }
+
+ /**
+ * @hide
+ */
+ @ColorInt
+ public static int getDisabled(Context context, int inputColor) {
+ return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+ }
+
+ /**
+ * @hide
+ */
+ @ColorInt
+ public static int applyAlphaAttr(Context context, int attr, int inputColor) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {
+ attr
+ });
+ float alpha = ta.getFloat(0, 0);
+ ta.recycle();
+ return applyAlpha(alpha, inputColor);
+ }
+
+ /**
+ * @hide
+ */
+ @ColorInt
+ public static int applyAlpha(float alpha, int inputColor) {
+ alpha *= Color.alpha(inputColor);
+ return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+ Color.blue(inputColor));
+ }
+
+ /**
+ * @hide
+ */
+ @ColorInt
+ public static int getColorAttr(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {
+ attr
+ });
+ @ColorInt
+ int colorAccent = ta.getColor(0, 0);
+ ta.recycle();
+ return colorAccent;
+ }
+
+ /**
+ * @hide
+ */
+ public static int getThemeAttr(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {
+ attr
+ });
+ int theme = ta.getResourceId(0, 0);
+ ta.recycle();
+ return theme;
+ }
+
+ /**
+ * @hide
+ */
+ public static Drawable getDrawable(Context context, int attr) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {
+ attr
+ });
+ Drawable drawable = ta.getDrawable(0);
+ ta.recycle();
+ return drawable;
+ }
+
+ /**
+ * @hide
+ */
+ public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
+ boolean isLarge, ViewGroup parent) {
+ ImageView v = new ImageView(context);
+ v.setImageIcon(icon);
+ parent.addView(v);
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+ if (isLarge) {
+ // XXX better way to convert from icon -> bitmap or crop an icon (?)
+ Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+ Canvas iconCanvas = new Canvas(iconBm);
+ v.layout(0, 0, iconSize, iconSize);
+ v.draw(iconCanvas);
+ v.setImageBitmap(getCircularBitmap(iconBm));
+ } else {
+ v.setColorFilter(Color.WHITE);
+ }
+ lp.width = iconSize;
+ lp.height = iconSize;
+ lp.gravity = Gravity.CENTER;
+ }
+
+ /**
+ * @hide
+ */
+ public static Bitmap getCircularBitmap(Bitmap bitmap) {
+ Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
+ bitmap.getHeight(), Config.ARGB_8888);
+ Canvas canvas = new Canvas(output);
+ final Paint paint = new Paint();
+ final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+ paint.setAntiAlias(true);
+ canvas.drawARGB(0, 0, 0, 0);
+ canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
+ bitmap.getWidth() / 2, paint);
+ paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+ canvas.drawBitmap(bitmap, rect, rect, paint);
+ return output;
+ }
+}
diff --git a/android/slice/views/SmallTemplateView.java b/android/slice/views/SmallTemplateView.java
new file mode 100644
index 00000000..b0b181ed
--- /dev/null
+++ b/android/slice/views/SmallTemplateView.java
@@ -0,0 +1,211 @@
+/*
+ * 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.slice.views;
+
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.slice.Slice;
+import android.slice.SliceItem;
+import android.slice.SliceQuery;
+import android.slice.views.LargeSliceAdapter.SliceListView;
+import android.slice.views.SliceView.SliceModeView;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Small template is also used to construct list items for use with {@link LargeTemplateView}.
+ *
+ * @hide
+ */
+public class SmallTemplateView extends SliceModeView implements SliceListView {
+
+ private static final String TAG = "SmallTemplateView";
+
+ private int mIconSize;
+ private int mPadding;
+
+ private LinearLayout mStartContainer;
+ private TextView mTitleText;
+ private TextView mSecondaryText;
+ private LinearLayout mEndContainer;
+
+ public SmallTemplateView(Context context) {
+ super(context);
+ inflate(context, R.layout.slice_small_template, this);
+ mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+ mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.slice_padding);
+
+ mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
+ mTitleText = (TextView) findViewById(android.R.id.title);
+ mSecondaryText = (TextView) findViewById(android.R.id.summary);
+ mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
+ }
+
+ @Override
+ public String getMode() {
+ return SliceView.MODE_SMALL;
+ }
+
+ @Override
+ public void setSliceItem(SliceItem slice) {
+ resetViews();
+ SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+ int color = colorItem != null ? colorItem.getColor() : -1;
+
+ // Look for any title elements
+ List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
+ null);
+ boolean hasTitleText = false;
+ boolean hasTitleItem = false;
+ for (int i = 0; i < titleItems.size(); i++) {
+ SliceItem item = titleItems.get(i);
+ if (!hasTitleItem) {
+ // icon, action icon, or timestamp
+ if (item.getType() == SliceItem.TYPE_ACTION) {
+ hasTitleItem = addIcon(item, color, mStartContainer);
+ } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+ addIcon(item, color, mStartContainer);
+ hasTitleItem = true;
+ } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+ TextView tv = new TextView(getContext());
+ tv.setText(convertTimeToString(item.getTimestamp()));
+ hasTitleItem = true;
+ }
+ }
+ if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
+ mTitleText.setText(item.getText());
+ hasTitleText = true;
+ }
+ if (hasTitleText && hasTitleItem) {
+ break;
+ }
+ }
+ mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
+ mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
+
+ if (slice.getType() != SliceItem.TYPE_SLICE) {
+ return;
+ }
+
+ // Deal with remaining items
+ int itemCount = 0;
+ boolean hasSummary = false;
+ ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
+ Arrays.asList(slice.getSlice().getItems()));
+ for (int i = 0; i < sliceItems.size(); i++) {
+ SliceItem item = sliceItems.get(i);
+ if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
+ && !item.hasHint(Slice.HINT_TITLE)) {
+ // TODO -- Should combine all text items?
+ mSecondaryText.setText(item.getText());
+ hasSummary = true;
+ }
+ if (itemCount <= 3) {
+ if (item.getType() == SliceItem.TYPE_ACTION) {
+ if (addIcon(item, color, mEndContainer)) {
+ itemCount++;
+ }
+ } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+ addIcon(item, color, mEndContainer);
+ itemCount++;
+ } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+ TextView tv = new TextView(getContext());
+ tv.setText(convertTimeToString(item.getTimestamp()));
+ mEndContainer.addView(tv);
+ itemCount++;
+ } else if (item.getType() == SliceItem.TYPE_SLICE) {
+ SliceItem[] subItems = item.getSlice().getItems();
+ for (int j = 0; j < subItems.length; j++) {
+ sliceItems.add(subItems[j]);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setSlice(Slice slice) {
+ setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints()));
+ }
+
+ /**
+ * @return Whether an icon was added.
+ */
+ private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
+ SliceItem image = null;
+ SliceItem action = null;
+ if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
+ image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
+ action = sliceItem;
+ } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
+ image = sliceItem;
+ }
+ if (image != null) {
+ ImageView iv = new ImageView(getContext());
+ iv.setImageIcon(image.getIcon());
+ if (action != null) {
+ final SliceItem sliceAction = action;
+ iv.setOnClickListener(v -> AsyncTask.execute(
+ () -> {
+ try {
+ sliceAction.getAction().send();
+ } catch (CanceledException e) {
+ e.printStackTrace();
+ }
+ }));
+ iv.setBackground(SliceViewUtil.getDrawable(getContext(),
+ android.R.attr.selectableItemBackground));
+ }
+ if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
+ iv.setColorFilter(color);
+ }
+ container.addView(iv);
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
+ lp.width = mIconSize;
+ lp.height = mIconSize;
+ lp.setMarginStart(mPadding);
+ return true;
+ }
+ return false;
+ }
+
+ private String convertTimeToString(long time) {
+ // TODO -- figure out what format(s) we support
+ Date date = new Date(time);
+ Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
+ return format.format(date);
+ }
+
+ private void resetViews() {
+ mStartContainer.removeAllViews();
+ mEndContainer.removeAllViews();
+ mTitleText.setText(null);
+ mSecondaryText.setText(null);
+ }
+}
diff --git a/android/support/LibraryVersions.java b/android/support/LibraryVersions.java
index f8e6e81a..a046d95e 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-alpha9-1");
+ private static final Version FLATFOOT_1_0_BATCH = new Version("1.0.0-beta2");
/**
* Version code for Room
@@ -43,12 +43,12 @@ public class LibraryVersions {
/**
* Version code for RecyclerView & Room paging
*/
- public static final Version PAGING = new Version("1.0.0-alpha1");
+ public static final Version PAGING = new Version("1.0.0-alpha3");
/**
* Version code for Lifecycle libs that are required by the support library
*/
- public static final Version LIFECYCLES_CORE = new Version("1.0.0");
+ public static final Version LIFECYCLES_CORE = new Version("1.0.2");
/**
* Version code for Lifecycle runtime libs that are required by the support library
diff --git a/android/support/Version.java b/android/support/Version.java
index b88d8cfe..69b7f5e2 100644
--- a/android/support/Version.java
+++ b/android/support/Version.java
@@ -42,7 +42,29 @@ public class Version implements Comparable<Version> {
@Override
public int compareTo(Version version) {
- return mMajor != version.mMajor ? mMajor - version.mMajor : mMinor - version.mMinor;
+ if (mMajor != version.mMajor) {
+ return mMajor - version.mMajor;
+ }
+ if (mMinor != version.mMinor) {
+ return mMinor - version.mMinor;
+ }
+ if (mPatch != version.mPatch) {
+ return mPatch - version.mPatch;
+ }
+ if (mExtra == null) {
+ if (version.mExtra == null) {
+ return 0;
+ }
+ // not having any extra is always a later version
+ return 1;
+ } else {
+ if (version.mExtra == null) {
+ // not having any extra is always a later version
+ return -1;
+ }
+ // gradle uses lexicographic ordering
+ return mExtra.compareTo(version.mExtra);
+ }
}
public boolean isPatch() {
@@ -73,4 +95,26 @@ public class Version implements Comparable<Version> {
public String toString() {
return mMajor + "." + mMinor + "." + mPatch + (mExtra != null ? mExtra : "");
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Version version = (Version) o;
+
+ if (mMajor != version.mMajor) return false;
+ if (mMinor != version.mMinor) return false;
+ if (mPatch != version.mPatch) return false;
+ return mExtra != null ? mExtra.equals(version.mExtra) : version.mExtra == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mMajor;
+ result = 31 * result + mMinor;
+ result = 31 * result + mPatch;
+ result = 31 * result + (mExtra != null ? mExtra.hashCode() : 0);
+ return result;
+ }
}
diff --git a/android/support/VersionFileWriterTask.java b/android/support/VersionFileWriterTask.java
new file mode 100644
index 00000000..aafa0236
--- /dev/null
+++ b/android/support/VersionFileWriterTask.java
@@ -0,0 +1,109 @@
+/*
+ * 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.support;
+
+import com.android.build.gradle.LibraryExtension;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Task that allows to write a version to a given output file.
+ */
+public class VersionFileWriterTask extends DefaultTask {
+ public static final String RESOURCE_DIRECTORY = "generatedResources";
+ public static final String VERSION_FILE_PATH =
+ RESOURCE_DIRECTORY + "/META-INF/%s_%s.version";
+
+ private String mVersion;
+ private File mOutputFile;
+
+ /**
+ * Sets up Android Library project to have a task that generates a version file.
+ *
+ * @param project an Android Library project.
+ */
+ public static void setUpAndroidLibrary(Project project) {
+ project.afterEvaluate(new Action<Project>() {
+ @Override
+ public void execute(Project project) {
+ LibraryExtension library =
+ project.getExtensions().findByType(LibraryExtension.class);
+
+ String group = (String) project.getProperties().get("group");
+ String artifactId = (String) project.getProperties().get("name");
+ String version = (String) project.getProperties().get("version");
+
+ // Add a java resource file to the library jar for version tracking purposes.
+ File artifactName = new File(project.getBuildDir(),
+ String.format(VersionFileWriterTask.VERSION_FILE_PATH,
+ group, artifactId));
+
+ VersionFileWriterTask writeVersionFile =
+ project.getTasks().create("writeVersionFile", VersionFileWriterTask.class);
+ writeVersionFile.setVersion(version);
+ writeVersionFile.setOutputFile(artifactName);
+
+ library.getLibraryVariants().all(
+ libraryVariant -> libraryVariant.getProcessJavaResources().dependsOn(
+ writeVersionFile));
+
+ library.getSourceSets().getByName("main").getResources().srcDir(
+ new File(project.getBuildDir(), VersionFileWriterTask.RESOURCE_DIRECTORY)
+ );
+ }
+ });
+ }
+
+ @Input
+ public String getVersion() {
+ return mVersion;
+ }
+
+ public void setVersion(String version) {
+ mVersion = version;
+ }
+
+ @OutputFile
+ public File getOutputFile() {
+ return mOutputFile;
+ }
+
+ public void setOutputFile(File outputFile) {
+ mOutputFile = outputFile;
+ }
+
+ /**
+ * The main method for actually writing out the file.
+ *
+ * @throws IOException
+ */
+ @TaskAction
+ public void run() throws IOException {
+ PrintWriter writer = new PrintWriter(mOutputFile);
+ writer.println(mVersion);
+ writer.close();
+ }
+}
diff --git a/android/support/annotation/NavigationRes.java b/android/support/annotation/NavigationRes.java
new file mode 100644
index 00000000..a0510268
--- /dev/null
+++ b/android/support/annotation/NavigationRes.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.support.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a navigation resource reference (e.g. {@code R.navigation.flow}).
+ */
+@Documented
+@Retention(CLASS)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+public @interface NavigationRes {
+}
diff --git a/android/support/car/utils/ColumnCalculator.java b/android/support/car/utils/ColumnCalculator.java
new file mode 100644
index 00000000..96e081b9
--- /dev/null
+++ b/android/support/car/utils/ColumnCalculator.java
@@ -0,0 +1,141 @@
+/*
+ * 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.support.car.utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.car.R;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+
+/**
+ * Utility class that calculates the size of the columns that will fit on the screen. A column's
+ * width is determined by the size of the margins and gutters (space between the columns) that fit
+ * on-screen.
+ *
+ * <p>Refer to the appropriate dimens and integers for the size of the margins and number of
+ * columns.
+ */
+public class ColumnCalculator {
+ private static final String TAG = "ColumnCalculator";
+
+ private static ColumnCalculator sInstance;
+ private static int sScreenWidth;
+
+ private int mNumOfColumns;
+ private int mNumOfGutters;
+ private int mColumnWidth;
+ private int mGutterSize;
+
+ /**
+ * Gets an instance of the {@link ColumnCalculator}. If this is the first time that this
+ * method has been called, then the given {@link Context} will be used to retrieve resources.
+ *
+ * @param context The current calling Context.
+ * @return An instance of {@link ColumnCalculator}.
+ */
+ public static ColumnCalculator getInstance(Context context) {
+ if (sInstance == null) {
+ WindowManager windowManager = (WindowManager) context.getSystemService(
+ Context.WINDOW_SERVICE);
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getMetrics(displayMetrics);
+ sScreenWidth = displayMetrics.widthPixels;
+
+ sInstance = new ColumnCalculator(context);
+ }
+
+ return sInstance;
+ }
+
+ private ColumnCalculator(Context context) {
+ Resources res = context.getResources();
+ int marginSize = res.getDimensionPixelSize(R.dimen.car_screen_margin_size);
+ mGutterSize = res.getDimensionPixelSize(R.dimen.car_screen_gutter_size);
+ mNumOfColumns = res.getInteger(R.integer.car_screen_num_of_columns);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("marginSize: %d; numOfColumns: %d; gutterSize: %d",
+ marginSize, mNumOfColumns, mGutterSize));
+ }
+
+ // The gutters appear between each column. As a result, the number of gutters is one less
+ // than the number of columns.
+ mNumOfGutters = mNumOfColumns - 1;
+
+ // Determine the spacing that is allowed to be filled by the columns by subtracting margins
+ // on both size of the screen and the space taken up by the gutters.
+ int spaceForColumns = sScreenWidth - (2 * marginSize) - (mNumOfGutters * mGutterSize);
+
+ mColumnWidth = spaceForColumns / mNumOfColumns;
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "mColumnWidth: " + mColumnWidth);
+ }
+ }
+
+ /**
+ * Returns the total number of columns that fit on the current screen.
+ *
+ * @return The total number of columns that fit on the screen.
+ */
+ public int getNumOfColumns() {
+ return mNumOfColumns;
+ }
+
+ /**
+ * Returns the size in pixels of each column. The column width is determined by the size of the
+ * screen divided by the number of columns, size of gutters and margins.
+ *
+ * @return The width of a single column in pixels.
+ */
+ public int getColumnWidth() {
+ return mColumnWidth;
+ }
+
+ /**
+ * Returns the total number of gutters that fit on screen. A gutter is the space between each
+ * column. This value is always one less than the number of columns.
+ *
+ * @return The number of gutters on screen.
+ */
+ public int getNumOfGutters() {
+ return mNumOfGutters;
+ }
+
+ /**
+ * Returns the size of each gutter in pixels. A gutter is the space between each column.
+ *
+ * @return The size of a single gutter in pixels.
+ */
+ public int getGutterSize() {
+ return mGutterSize;
+ }
+
+ /**
+ * Returns the size in pixels for the given number of columns. This value takes into account
+ * the size of the gutter between the columns as well. For example, for a column span of four,
+ * the size returned is the sum of four columns and three gutters.
+ *
+ * @return The size in pixels for a given column span.
+ */
+ public int getSizeForColumnSpan(int columnSpan) {
+ int gutterSpan = columnSpan - 1;
+ return columnSpan * mColumnWidth + gutterSpan * mGutterSize;
+ }
+}
diff --git a/android/support/car/widget/CarItemAnimator.java b/android/support/car/widget/CarItemAnimator.java
new file mode 100644
index 00000000..4dd32127
--- /dev/null
+++ b/android/support/car/widget/CarItemAnimator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.support.car.widget;
+
+import android.support.v7.widget.DefaultItemAnimator;
+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;
+
+ public CarItemAnimator(CarLayoutManager layoutManager) {
+ mLayoutManager = layoutManager;
+ }
+
+ @Override
+ public boolean animateChange(RecyclerView.ViewHolder oldHolder,
+ RecyclerView.ViewHolder newHolder,
+ int fromX,
+ int fromY,
+ int toX,
+ int toY) {
+ // The default behavior will cross fade the old view and the new one. However, if we
+ // have a card on a colored background, it will make it appear as if a changing card
+ // fades in and out.
+ float alpha = 0f;
+ if (newHolder != null) {
+ alpha = newHolder.itemView.getAlpha();
+ }
+ boolean ret = super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
+ if (newHolder != null) {
+ newHolder.itemView.setAlpha(alpha);
+ }
+ return ret;
+ }
+
+ @Override
+ public void onMoveFinished(RecyclerView.ViewHolder item) {
+ // The item animator uses translation heavily internally. However, we also use translation
+ // to create the paging affect. When an item's move is animated, it will mess up the
+ // translation we have set on it so we must re-offset the rows once the animations finish.
+
+ // isRunning(ItemAnimationFinishedListener) is the awkward API used to determine when all
+ // animations have finished.
+ isRunning(mFinishedListener);
+ }
+
+ private final ItemAnimatorFinishedListener mFinishedListener =
+ new ItemAnimatorFinishedListener() {
+ @Override
+ public void onAnimationsFinished() {
+ mLayoutManager.offsetRows();
+ }
+ };
+}
diff --git a/android/support/car/widget/CarLayoutManager.java b/android/support/car/widget/CarLayoutManager.java
new file mode 100644
index 00000000..d0d3a9e1
--- /dev/null
+++ b/android/support/car/widget/CarLayoutManager.java
@@ -0,0 +1,1636 @@
+/*
+ * 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.support.car.widget;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.support.car.R;
+import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Recycler;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.LruCache;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Custom {@link RecyclerView.LayoutManager} that behaves similar to LinearLayoutManager except that
+ * it has a few tricks up its sleeve.
+ *
+ * <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)}.
+ * <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
+ * last page can be properly aligned.
+ * </ol>
+ *
+ * This LayoutManger should be used with {@link CarRecyclerView}.
+ */
+public class CarLayoutManager extends RecyclerView.LayoutManager {
+ private static final String TAG = "CarLayoutManager";
+
+ /**
+ * Any fling below the threshold will just scroll to the top fully visible row. The units is
+ * whatever {@link android.widget.Scroller} would return.
+ *
+ * <p>A reasonable value is ~200
+ *
+ * <p>This can be disabled by setting the threshold to -1.
+ */
+ private static final int FLING_THRESHOLD_TO_PAGINATE = -1;
+
+ /**
+ * Any fling shorter than this threshold (in px) will just scroll to the top fully visible row.
+ *
+ * <p>A reasonable value is 15.
+ *
+ * <p>This can be disabled by setting the distance to -1.
+ */
+ private static final int DRAG_DISTANCE_TO_PAGINATE = -1;
+
+ /**
+ * If you scroll really quickly, you can hit the end of the laid out rows before Android has a
+ * chance to layout more. To help counter this, we can layout a number of extra rows past
+ * wherever the focus is if necessary.
+ */
+ private static final int NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS = 2;
+
+ /**
+ * Scroll bar calculation is a bit complicated. This basically defines the granularity we want
+ * our scroll bar to move. Set this to 1 means our scrollbar will have really jerky movement.
+ * Setting it too big will risk an overflow (although there is no performance impact). Ideally
+ * we want to set this higher than the height of our list view. We can't use our list view
+ * height directly though because we might run into situations where getHeight() returns 0,
+ * for example, when the view is not yet measured.
+ */
+ private static final int SCROLL_RANGE = 1000;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({BEFORE, AFTER})
+ private @interface LayoutDirection {}
+
+ private static final int BEFORE = 0;
+ private static final int AFTER = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ROW_OFFSET_MODE_INDIVIDUAL, ROW_OFFSET_MODE_PAGE})
+ public @interface RowOffsetMode {}
+
+ public static final int ROW_OFFSET_MODE_INDIVIDUAL = 0;
+ public static final int ROW_OFFSET_MODE_PAGE = 1;
+
+ private final AccelerateInterpolator mDanglingRowInterpolator = new AccelerateInterpolator(2);
+ private final Context mContext;
+
+ /** Determines whether or not rows will be offset as they slide off screen * */
+ private boolean mOffsetRows;
+
+ /** Determines whether rows will be offset individually or a page at a time * */
+ @RowOffsetMode private int mRowOffsetMode = ROW_OFFSET_MODE_PAGE;
+
+ /**
+ * The LayoutManager only gets {@link #onScrollStateChanged(int)} updates. This enables the
+ * scroll state to be used anywhere.
+ */
+ private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
+
+ /** Used to inspect the current scroll state to help with the various calculations. */
+ private CarSmoothScroller mSmoothScroller;
+
+ private PagedListView.OnScrollListener mOnScrollListener;
+
+ /** The distance that the list has actually scrolled in the most recent drag gesture. */
+ private int mLastDragDistance = 0;
+
+ /** {@code True} if the current drag was limited/capped because it was at some boundary. */
+ private boolean mReachedLimitOfDrag;
+
+ /** The index of the first item on the current page. */
+ private int mAnchorPageBreakPosition = 0;
+
+ /** The index of the first item on the previous page. */
+ private int mUpperPageBreakPosition = -1;
+
+ /** The index of the first item on the next page. */
+ private int mLowerPageBreakPosition = -1;
+
+ /** Used in the bookkeeping of mario style scrolling to prevent extra calculations. */
+ private int mLastChildPositionToRequestFocus = -1;
+
+ private int mSampleViewHeight = -1;
+
+ /** Used for onPageUp and onPageDown */
+ private int mViewsPerPage = 1;
+
+ private int mCurrentPage = 0;
+
+ private static final int MAX_ANIMATIONS_IN_CACHE = 30;
+ /**
+ * Cache of TranslateAnimation per child view. These are needed since using a single animation
+ * for all children doesn't apply the animation effect multiple times. Key = the view the
+ * animation will transform.
+ */
+ private LruCache<View, TranslateAnimation> mFlyOffscreenAnimations;
+
+ /** Set the anchor to the following position on the next layout pass. */
+ private int mPendingScrollPosition = -1;
+
+ public CarLayoutManager(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+ return new RecyclerView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ public boolean canScrollVertically() {
+ return true;
+ }
+
+ /**
+ * onLayoutChildren is sort of like a "reset" for the layout state. At a high level, it should:
+ *
+ * <ol>
+ * <li>Check the current views to get the current state of affairs
+ * <li>Detach all views from the window (a lightweight operation) so that rows not re-added
+ * will be removed after onLayoutChildren.
+ * <li>Re-add rows as necessary.
+ * </ol>
+ *
+ * @see super#onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)
+ */
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ /*
+ * The anchor view is the first fully visible view on screen at the beginning of
+ * onLayoutChildren (or 0 if there is none). This row will be laid out first. After that,
+ * layoutNextRow will layout rows above and below it until the boundaries of what should be
+ * laid out have been reached. See shouldLayoutNextRow(View, int) for more info.
+ */
+ int anchorPosition = 0;
+ int anchorTop = -1;
+ if (mPendingScrollPosition == -1) {
+ View anchor = getFirstFullyVisibleChild();
+ if (anchor != null) {
+ anchorPosition = getPosition(anchor);
+ anchorTop = getDecoratedTop(anchor);
+ }
+ } else {
+ anchorPosition = mPendingScrollPosition;
+ mPendingScrollPosition = -1;
+ mAnchorPageBreakPosition = anchorPosition;
+ mUpperPageBreakPosition = -1;
+ mLowerPageBreakPosition = -1;
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(
+ TAG,
+ String.format(
+ ":: onLayoutChildren anchorPosition:%s, anchorTop:%s,"
+ + " mPendingScrollPosition: %s, mAnchorPageBreakPosition:%s,"
+ + " mUpperPageBreakPosition:%s, mLowerPageBreakPosition:%s",
+ anchorPosition,
+ anchorTop,
+ mPendingScrollPosition,
+ mAnchorPageBreakPosition,
+ mUpperPageBreakPosition,
+ mLowerPageBreakPosition));
+ }
+
+ /*
+ * Detach all attached view for 2 reasons:
+ *
+ * 1) So that views are put in the scrap heap. This enables us to call {@link
+ * RecyclerView.Recycler#getViewForPosition(int)} which will either return one of these
+ * detached views if it is in the scrap heap, one from the recycled pool (will only call
+ * onBind in the adapter), or create an entirely new row if needed (will call onCreate
+ * and onBind in the adapter).
+ * 2) So that views are automatically removed if they are not manually re-added.
+ */
+ detachAndScrapAttachedViews(recycler);
+
+ /*
+ * Layout the views recursively.
+ *
+ * It's possible that this re-layout is triggered because an item gets removed. If the
+ * anchor view is at the end of the list, the anchor view position will be bigger than the
+ * number of available items. Correct that, and only start the layout if the anchor
+ * position is valid.
+ */
+ anchorPosition = Math.min(anchorPosition, getItemCount() - 1);
+ if (anchorPosition >= 0) {
+ View anchor = layoutAnchor(recycler, anchorPosition, anchorTop);
+ View adjacentRow = anchor;
+ while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
+ adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
+ }
+ adjacentRow = anchor;
+ while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
+ adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
+ }
+ }
+
+ updatePageBreakPositions();
+ offsetRows();
+
+ if (Log.isLoggable(TAG, Log.VERBOSE) && getChildCount() > 1) {
+ Log.v(TAG, "Currently showing "
+ + getChildCount()
+ + " views "
+ + getPosition(getChildAt(0))
+ + " to "
+ + getPosition(getChildAt(getChildCount() - 1))
+ + " anchor "
+ + anchorPosition);
+ }
+ // Should be at least 1
+ mViewsPerPage =
+ Math.max(getLastFullyVisibleChildIndex() + 1 - getFirstFullyVisibleChildIndex(), 1);
+ mCurrentPage = getFirstFullyVisibleChildPosition() / mViewsPerPage;
+ Log.v(TAG, "viewsPerPage " + mViewsPerPage);
+ }
+
+ /**
+ * scrollVerticallyBy does the work of what should happen when the list scrolls in addition to
+ * handling cases where the list hits the end. It should be lighter weight than
+ * onLayoutChildren. It doesn't have to detach all views. It only looks at the end of the list
+ * and removes views that have gone out of bounds and lays out new ones that scroll in.
+ *
+ * @param dy The amount that the list is supposed to scroll. > 0 means the list is scrolling
+ * down. < 0 means the list is scrolling up.
+ * @param recycler The recycler that enables views to be reused or created as they scroll in.
+ * @param state Various information about the current state of affairs.
+ * @return The amount the list actually scrolled.
+ * @see super#scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)
+ */
+ @Override
+ public int scrollVerticallyBy(
+ int dy, @NonNull RecyclerView.Recycler recycler, @NonNull RecyclerView.State state) {
+ // If the list is empty, we can prevent the overscroll glow from showing by just
+ // telling RecycerView that we scrolled.
+ if (getItemCount() == 0) {
+ return dy;
+ }
+
+ // Prevent redundant computations if there is definitely nowhere to scroll to.
+ if (getChildCount() <= 1 || dy == 0) {
+ mReachedLimitOfDrag = true;
+ return 0;
+ }
+
+ View firstChild = getChildAt(0);
+ if (firstChild == null) {
+ mReachedLimitOfDrag = true;
+ return 0;
+ }
+ int firstChildPosition = getPosition(firstChild);
+ RecyclerView.LayoutParams firstChildParams = getParams(firstChild);
+ int firstChildTopWithMargin = getDecoratedTop(firstChild) - firstChildParams.topMargin;
+
+ View lastFullyVisibleView = getChildAt(getLastFullyVisibleChildIndex());
+ if (lastFullyVisibleView == null) {
+ mReachedLimitOfDrag = true;
+ return 0;
+ }
+ boolean isLastViewVisible = getPosition(lastFullyVisibleView) == getItemCount() - 1;
+
+ View firstFullyVisibleChild = getFirstFullyVisibleChild();
+ if (firstFullyVisibleChild == null) {
+ mReachedLimitOfDrag = true;
+ return 0;
+ }
+ int firstFullyVisiblePosition = getPosition(firstFullyVisibleChild);
+ RecyclerView.LayoutParams firstFullyVisibleChildParams = getParams(firstFullyVisibleChild);
+ int topRemainingSpace =
+ getDecoratedTop(firstFullyVisibleChild)
+ - firstFullyVisibleChildParams.topMargin
+ - getPaddingTop();
+
+ if (isLastViewVisible
+ && firstFullyVisiblePosition == mAnchorPageBreakPosition
+ && dy > topRemainingSpace
+ && dy > 0) {
+ // Prevent dragging down more than 1 page. As a side effect, this also prevents you
+ // from dragging past the bottom because if you are on the second to last page, it
+ // prevents you from dragging past the last page.
+ dy = topRemainingSpace;
+ mReachedLimitOfDrag = true;
+ } else if (dy < 0
+ && firstChildPosition == 0
+ && firstChildTopWithMargin + Math.abs(dy) > getPaddingTop()) {
+ // Prevent scrolling past the beginning
+ dy = firstChildTopWithMargin - getPaddingTop();
+ mReachedLimitOfDrag = true;
+ } else {
+ mReachedLimitOfDrag = false;
+ }
+
+ boolean isDragging = mScrollState == RecyclerView.SCROLL_STATE_DRAGGING;
+ if (isDragging) {
+ mLastDragDistance += dy;
+ }
+ // We offset by -dy because the views translate in the opposite direction that the
+ // list scrolls (think about it.)
+ offsetChildrenVertical(-dy);
+
+ // The last item in the layout should never scroll above the viewport
+ View view = getChildAt(getChildCount() - 1);
+ if (view.getTop() < 0) {
+ view.setTop(0);
+ }
+
+ // This is the meat of this function. We remove views on the trailing edge of the scroll
+ // and add views at the leading edge as necessary.
+ View adjacentRow;
+ if (dy > 0) {
+ recycleChildrenFromStart(recycler);
+ adjacentRow = getChildAt(getChildCount() - 1);
+ while (shouldLayoutNextRow(state, adjacentRow, AFTER)) {
+ adjacentRow = layoutNextRow(recycler, adjacentRow, AFTER);
+ }
+ } else {
+ recycleChildrenFromEnd(recycler);
+ adjacentRow = getChildAt(0);
+ while (shouldLayoutNextRow(state, adjacentRow, BEFORE)) {
+ adjacentRow = layoutNextRow(recycler, adjacentRow, BEFORE);
+ }
+ }
+ // Now that the correct views are laid out, offset rows as necessary so we can do whatever
+ // fancy animation we want such as having the top view fly off the screen as the next one
+ // settles in to place.
+ updatePageBreakPositions();
+ offsetRows();
+
+ if (getChildCount() > 1) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(
+ TAG,
+ String.format(
+ "Currently showing %d views (%d to %d)",
+ getChildCount(),
+ getPosition(getChildAt(0)),
+ getPosition(getChildAt(getChildCount() - 1))));
+ }
+ }
+ updatePagedState();
+ return dy;
+ }
+
+ private void updatePagedState() {
+ int page = getFirstFullyVisibleChildPosition() / mViewsPerPage;
+ if (mOnScrollListener != null) {
+ if (page > mCurrentPage) {
+ mOnScrollListener.onPageDown();
+ } else if (page < mCurrentPage) {
+ mOnScrollListener.onPageUp();
+ }
+ }
+ mCurrentPage = page;
+ }
+
+ @Override
+ public void scrollToPosition(int position) {
+ mPendingScrollPosition = position;
+ requestLayout();
+ }
+
+ @Override
+ public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+ int position) {
+ /*
+ * startSmoothScroll will handle stopping the old one if there is one. We only keep a copy
+ * of it to handle the translation of rows as they slide off the screen in
+ * offsetRowsWithPageBreak().
+ */
+ mSmoothScroller = new CarSmoothScroller(mContext, position);
+ mSmoothScroller.setTargetPosition(position);
+ startSmoothScroll(mSmoothScroller);
+ }
+
+ /** Miscellaneous bookkeeping. */
+ @Override
+ public void onScrollStateChanged(int state) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, ":: onScrollStateChanged " + state);
+ }
+ if (state == RecyclerView.SCROLL_STATE_IDLE) {
+ // If the focused view is off screen, give focus to one that is.
+ // If the first fully visible view is first in the list, focus the first item.
+ // Otherwise, focus the second so that you have the first item as scrolling context.
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null
+ && (getDecoratedTop(focusedChild) >= getHeight() - getPaddingBottom()
+ || getDecoratedBottom(focusedChild) <= getPaddingTop())) {
+ focusedChild.clearFocus();
+ requestLayout();
+ }
+
+ } else if (state == RecyclerView.SCROLL_STATE_DRAGGING) {
+ mLastDragDistance = 0;
+ }
+
+ if (state != RecyclerView.SCROLL_STATE_SETTLING) {
+ mSmoothScroller = null;
+ }
+
+ mScrollState = state;
+ updatePageBreakPositions();
+ }
+
+ @Override
+ public void onItemsChanged(RecyclerView recyclerView) {
+ super.onItemsChanged(recyclerView);
+ // When item changed, our sample view height is no longer accurate, and need to be
+ // recomputed.
+ mSampleViewHeight = -1;
+ }
+
+ /**
+ * Gives us the opportunity to override the order of the focused views. By default, it will just
+ * go from top to bottom. However, if there is no focused views, we take over the logic and
+ * start the focused views from the middle of what is visible and move from there until the
+ * end of the laid out views in the specified direction.
+ */
+ @Override
+ public boolean onAddFocusables(
+ RecyclerView recyclerView, ArrayList<View> views, int direction, int focusableMode) {
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null) {
+ // If there is a view that already has focus, we can just return false and the normal
+ // Android addFocusables will work fine.
+ return false;
+ }
+
+ // Now we know that there isn't a focused view. We need to set up focusables such that
+ // instead of just focusing the first item that has been laid out, it focuses starting
+ // from a visible item.
+
+ int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+ if (firstFullyVisibleChildIndex == -1) {
+ // Somehow there is a focused view but there is no fully visible view. There shouldn't
+ // be a way for this to happen but we'd better stop here and return instead of
+ // continuing on with -1.
+ Log.w(TAG, "There is a focused child but no first fully visible child.");
+ return false;
+ }
+ View firstFullyVisibleChild = getChildAt(firstFullyVisibleChildIndex);
+ int firstFullyVisibleChildPosition = getPosition(firstFullyVisibleChild);
+
+ int firstFocusableChildIndex = firstFullyVisibleChildIndex;
+ if (firstFullyVisibleChildPosition > 0 && firstFocusableChildIndex + 1 < getItemCount()) {
+ // We are somewhere in the middle of the list. Instead of starting focus on the first
+ // item, start focus on the second item to give some context that we aren't at
+ // the beginning.
+ firstFocusableChildIndex++;
+ }
+
+ if (direction == View.FOCUS_FORWARD) {
+ // Iterate from the first focusable view to the end.
+ for (int i = firstFocusableChildIndex; i < getChildCount(); i++) {
+ views.add(getChildAt(i));
+ }
+ return true;
+ } else if (direction == View.FOCUS_BACKWARD) {
+ // Iterate from the first focusable view to the beginning.
+ for (int i = firstFocusableChildIndex; i >= 0; i--) {
+ views.add(getChildAt(i));
+ }
+ return true;
+ } else if (direction == View.FOCUS_DOWN) {
+ // Framework calls onAddFocusables with FOCUS_DOWN direction when the focus is first
+ // gained. Thereafter, it calls onAddFocusables with FOCUS_FORWARD or FOCUS_BACKWARD.
+ // First we try to put the focus back on the last focused item, if it is visible
+ int lastFocusedVisibleChildIndex = getLastFocusedChildIndexIfVisible();
+ if (lastFocusedVisibleChildIndex != -1) {
+ views.add(getChildAt(lastFocusedVisibleChildIndex));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public View onFocusSearchFailed(
+ View focused, int direction, RecyclerView.Recycler recycler, RecyclerView.State state) {
+ // This doesn't seem to get called the way focus is handled in gearhead...
+ return null;
+ }
+
+ /**
+ * This is the function that decides where to scroll to when a new view is focused. You can get
+ * the position of the currently focused child through the child parameter. Once you have that,
+ * determine where to smooth scroll to and scroll there.
+ *
+ * @param parent The RecyclerView hosting this LayoutManager
+ * @param state Current state of RecyclerView
+ * @param child Direct child of the RecyclerView containing the newly focused view
+ * @param focused The newly focused view. This may be the same view as child or it may be null
+ * @return {@code true} if the default scroll behavior should be suppressed
+ */
+ @Override
+ public boolean onRequestChildFocus(
+ RecyclerView parent, RecyclerView.State state, View child, View focused) {
+ if (child == null) {
+ Log.w(TAG, "onRequestChildFocus with a null child!");
+ return true;
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, String.format(":: onRequestChildFocus child: %s, focused: %s", child,
+ focused));
+ }
+
+ return onRequestChildFocusMarioStyle(parent, child);
+ }
+
+ /**
+ * Goal: the scrollbar maintains the same size throughout scrolling and that the scrollbar
+ * reaches the bottom of the screen when the last item is fully visible. This is because there
+ * are multiple points that could be considered the bottom since the last item can scroll past
+ * the bottom edge of the screen.
+ *
+ * <p>To find the extent, we divide the number of items that can fit on screen by the number of
+ * items in total.
+ */
+ @Override
+ public int computeVerticalScrollExtent(RecyclerView.State state) {
+ if (getChildCount() <= 1) {
+ return 0;
+ }
+
+ int sampleViewHeight = getSampleViewHeight();
+ int availableHeight = getAvailableHeight();
+ int sampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
+
+ if (state.getItemCount() <= sampleViewsThatCanFitOnScreen) {
+ return SCROLL_RANGE;
+ } else {
+ return SCROLL_RANGE * sampleViewsThatCanFitOnScreen / state.getItemCount();
+ }
+ }
+
+ /**
+ * The scrolling offset is calculated by determining what position is at the top of the list.
+ * However, instead of using fixed integer positions for each row, the scroll position is
+ * factored in and the position is recalculated as a float that takes in to account the
+ * current scroll state. This results in a smooth animation for the scrollbar when the user
+ * scrolls the list.
+ */
+ @Override
+ public int computeVerticalScrollOffset(RecyclerView.State state) {
+ View firstChild = getFirstFullyVisibleChild();
+ if (firstChild == null) {
+ return 0;
+ }
+
+ RecyclerView.LayoutParams params = getParams(firstChild);
+ int firstChildPosition = getPosition(firstChild);
+ float previousChildHieght = (float) (getDecoratedMeasuredHeight(firstChild)
+ + params.topMargin + params.bottomMargin);
+
+ // Assume the previous view is the same height as the current one.
+ float percentOfPreviousViewShowing = (getDecoratedTop(firstChild) - params.topMargin)
+ / previousChildHieght;
+ // If the previous view is actually larger than the current one then this the percent
+ // can be greater than 1.
+ percentOfPreviousViewShowing = Math.min(percentOfPreviousViewShowing, 1);
+
+ float currentPosition = (float) firstChildPosition - percentOfPreviousViewShowing;
+
+ int sampleViewHeight = getSampleViewHeight();
+ int availableHeight = getAvailableHeight();
+ int numberOfSampleViewsThatCanFitOnScreen = availableHeight / sampleViewHeight;
+ int positionWhenLastItemIsVisible =
+ state.getItemCount() - numberOfSampleViewsThatCanFitOnScreen;
+
+ if (positionWhenLastItemIsVisible <= 0) {
+ return 0;
+ }
+
+ if (currentPosition >= positionWhenLastItemIsVisible) {
+ return SCROLL_RANGE;
+ }
+
+ return (int) (SCROLL_RANGE * currentPosition / positionWhenLastItemIsVisible);
+ }
+
+ /**
+ * The range of the scrollbar can be understood as the granularity of how we want the scrollbar
+ * to scroll.
+ */
+ @Override
+ public int computeVerticalScrollRange(RecyclerView.State state) {
+ return SCROLL_RANGE;
+ }
+
+ @Override
+ public void onAttachedToWindow(RecyclerView view) {
+ super.onAttachedToWindow(view);
+ // The purpose of calling this is so that any animation offsets are re-applied. These are
+ // cleared in View.onDetachedFromWindow().
+ // This fixes b/27672379
+ updatePageBreakPositions();
+ offsetRows();
+ }
+
+ @Override
+ public void onDetachedFromWindow(RecyclerView recyclerView, Recycler recycler) {
+ super.onDetachedFromWindow(recyclerView, recycler);
+ }
+
+ /**
+ * @return The first view that starts on screen. It assumes that it fully fits on the screen
+ * though. If the first fully visible child is also taller than the screen then it will
+ * still be returned. However, since the LayoutManager snaps to view starts, having a row
+ * that tall would lead to a broken experience anyways.
+ */
+ public int getFirstFullyVisibleChildIndex() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ RecyclerView.LayoutParams params = getParams(child);
+ if (getDecoratedTop(child) - params.topMargin >= getPaddingTop()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @return The position of first visible child in the list. -1 will be returned if there is no
+ * child.
+ */
+ public int getFirstFullyVisibleChildPosition() {
+ View child = getFirstFullyVisibleChild();
+ if (child == null) {
+ return -1;
+ }
+ return getPosition(child);
+ }
+
+ /**
+ * @return The position of last visible child in the list. -1 will be returned if there is no
+ * child.
+ */
+ public int getLastFullyVisibleChildPosition() {
+ View child = getLastFullyVisibleChild();
+ if (child == null) {
+ return -1;
+ }
+ return getPosition(child);
+ }
+
+ /** @return The first View that is completely visible on-screen. */
+ public View getFirstFullyVisibleChild() {
+ int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+ View firstChild = null;
+ if (firstFullyVisibleChildIndex != -1) {
+ firstChild = getChildAt(firstFullyVisibleChildIndex);
+ }
+ return firstChild;
+ }
+
+ /** @return The last View that is completely visible on-screen. */
+ public View getLastFullyVisibleChild() {
+ int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex();
+ View lastChild = null;
+ if (lastFullyVisibleChildIndex != -1) {
+ lastChild = getChildAt(lastFullyVisibleChildIndex);
+ }
+ return lastChild;
+ }
+
+ /**
+ * @return The last view that ends on screen. It assumes that the start is also on screen
+ * though. If the last fully visible child is also taller than the screen then it will
+ * still be returned. However, since the LayoutManager snaps to view starts, having a row
+ * that tall would lead to a broken experience anyways.
+ */
+ public int getLastFullyVisibleChildIndex() {
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ RecyclerView.LayoutParams params = getParams(child);
+ int childBottom = getDecoratedBottom(child) + params.bottomMargin;
+ int listBottom = getHeight() - getPaddingBottom();
+ if (childBottom <= listBottom) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the child in the list that was last focused and is currently visible to
+ * the user. If no child is found, returns -1.
+ */
+ public int getLastFocusedChildIndexIfVisible() {
+ if (mLastChildPositionToRequestFocus == -1) {
+ return -1;
+ }
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (getPosition(child) == mLastChildPositionToRequestFocus) {
+ RecyclerView.LayoutParams params = getParams(child);
+ int childBottom = getDecoratedBottom(child) + params.bottomMargin;
+ int listBottom = getHeight() - getPaddingBottom();
+ if (childBottom <= listBottom) {
+ return i;
+ }
+ break;
+ }
+ }
+ return -1;
+ }
+
+ /** @return Whether or not the first view is fully visible. */
+ public boolean isAtTop() {
+ // getFirstFullyVisibleChildIndex() can return -1 which indicates that there are no views
+ // and also means that the list is at the top.
+ return getFirstFullyVisibleChildIndex() <= 0;
+ }
+
+ /** @return Whether or not the last view is fully visible. */
+ public boolean isAtBottom() {
+ int lastFullyVisibleChildIndex = getLastFullyVisibleChildIndex();
+ if (lastFullyVisibleChildIndex == -1) {
+ return true;
+ }
+ View lastFullyVisibleChild = getChildAt(lastFullyVisibleChildIndex);
+ return getPosition(lastFullyVisibleChild) == getItemCount() - 1;
+ }
+
+ /**
+ * Sets whether or not the rows have an offset animation when it scrolls off-screen. The type
+ * of offset is determined by {@link #setRowOffsetMode(int)}.
+ *
+ * <p>A row being offset means that when they reach the top of the screen, the row is flung off
+ * respectively to the rest of the list. This creates a gap between the offset row(s) and the
+ * list.
+ *
+ * @param offsetRows {@code true} if the rows should be offset.
+ */
+ public void setOffsetRows(boolean offsetRows) {
+ mOffsetRows = offsetRows;
+ if (offsetRows) {
+ // Card animation offsets are only needed when we use the flying off the screen effect
+ if (mFlyOffscreenAnimations == null) {
+ mFlyOffscreenAnimations = new LruCache<>(MAX_ANIMATIONS_IN_CACHE);
+ }
+ offsetRows();
+ } else {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ setCardFlyingEffectOffset(getChildAt(i), 0);
+ }
+ mFlyOffscreenAnimations = null;
+ }
+ }
+
+ /**
+ * Sets the manner of offsetting the rows when they are scrolled off-screen. The rows are either
+ * offset individually or the entire page being scrolled off is offset.
+ *
+ * @param mode One of {@link #ROW_OFFSET_MODE_INDIVIDUAL} or {@link #ROW_OFFSET_MODE_PAGE}.
+ */
+ public void setRowOffsetMode(@RowOffsetMode int mode) {
+ if (mode == mRowOffsetMode) {
+ return;
+ }
+
+ mRowOffsetMode = mode;
+ offsetRows();
+ }
+
+ /**
+ * Sets the listener that will be notified of various scroll events in the list.
+ *
+ * @param listener The on-scroll listener.
+ */
+ public void setOnScrollListener(PagedListView.OnScrollListener listener) {
+ mOnScrollListener = listener;
+ }
+
+ /**
+ * Finish the pagination taking into account where the gesture started (not where we are now).
+ *
+ * @return Whether the list was scrolled as a result of the fling.
+ */
+ public boolean settleScrollForFling(RecyclerView parent, int flingVelocity) {
+ if (getChildCount() == 0) {
+ return false;
+ }
+
+ if (mReachedLimitOfDrag) {
+ return false;
+ }
+
+ // If the fling was too slow or too short, settle on the first fully visible row instead.
+ if (Math.abs(flingVelocity) <= FLING_THRESHOLD_TO_PAGINATE
+ || Math.abs(mLastDragDistance) <= DRAG_DISTANCE_TO_PAGINATE) {
+ int firstFullyVisibleChildIndex = getFirstFullyVisibleChildIndex();
+ if (firstFullyVisibleChildIndex != -1) {
+ int scrollPosition = getPosition(getChildAt(firstFullyVisibleChildIndex));
+ parent.smoothScrollToPosition(scrollPosition);
+ return true;
+ }
+ return false;
+ }
+
+ // Finish the pagination taking into account where the gesture
+ // started (not where we are now).
+ boolean isDownGesture = flingVelocity > 0 || (flingVelocity == 0 && mLastDragDistance >= 0);
+ boolean isUpGesture = flingVelocity < 0 || (flingVelocity == 0 && mLastDragDistance < 0);
+ if (isDownGesture && mLowerPageBreakPosition != -1) {
+ // If the last view is fully visible then only settle on the first fully visible view
+ // instead of the original page down position. However, don't page down if the last
+ // item has come fully into view.
+ parent.smoothScrollToPosition(mAnchorPageBreakPosition);
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onGestureDown();
+ }
+ return true;
+ } else if (isUpGesture && mUpperPageBreakPosition != -1) {
+ parent.smoothScrollToPosition(mUpperPageBreakPosition);
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onGestureUp();
+ }
+ return true;
+ } else {
+ Log.e(
+ TAG,
+ "Error setting scroll for fling! flingVelocity: \t"
+ + flingVelocity
+ + "\tlastDragDistance: "
+ + mLastDragDistance
+ + "\tpageUpAtStartOfDrag: "
+ + mUpperPageBreakPosition
+ + "\tpageDownAtStartOfDrag: "
+ + mLowerPageBreakPosition);
+ // As a last resort, at the last smooth scroller target position if there is one.
+ if (mSmoothScroller != null) {
+ parent.smoothScrollToPosition(mSmoothScroller.getTargetPosition());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @return The position that paging up from the current position would settle at. */
+ public int getPageUpPosition() {
+ return mUpperPageBreakPosition;
+ }
+
+ /** @return The position that paging down from the current position would settle at. */
+ public int getPageDownPosition() {
+ return mLowerPageBreakPosition;
+ }
+
+ /**
+ * Layout the anchor row. The anchor row is the first fully visible row.
+ *
+ * @param anchorTop The decorated top of the anchor. If it is not known or should be reset to
+ * the top, pass -1.
+ */
+ private View layoutAnchor(RecyclerView.Recycler recycler, int anchorPosition, int anchorTop) {
+ View anchor = recycler.getViewForPosition(anchorPosition);
+ RecyclerView.LayoutParams params = getParams(anchor);
+ measureChildWithMargins(anchor, 0, 0);
+ int left = getPaddingLeft() + params.leftMargin;
+ int top = (anchorTop == -1) ? params.topMargin : anchorTop;
+ int right = left + getDecoratedMeasuredWidth(anchor);
+ int bottom = top + getDecoratedMeasuredHeight(anchor);
+ layoutDecorated(anchor, left, top, right, bottom);
+ addView(anchor);
+ return anchor;
+ }
+
+ /**
+ * Lays out the next row in the specified direction next to the specified adjacent row.
+ *
+ * @param recycler The recycler from which a new view can be created.
+ * @param adjacentRow The View of the adjacent row which will be used to position the new one.
+ * @param layoutDirection The side of the adjacent row that the new row will be laid out on.
+ * @return The new row that was laid out.
+ */
+ private View layoutNextRow(RecyclerView.Recycler recycler, View adjacentRow,
+ @LayoutDirection int layoutDirection) {
+ int adjacentRowPosition = getPosition(adjacentRow);
+ int newRowPosition = adjacentRowPosition;
+ if (layoutDirection == BEFORE) {
+ newRowPosition = adjacentRowPosition - 1;
+ } else if (layoutDirection == AFTER) {
+ newRowPosition = adjacentRowPosition + 1;
+ }
+
+ // Because we detach all rows in onLayoutChildren, this will often just return a view from
+ // the scrap heap.
+ View newRow = recycler.getViewForPosition(newRowPosition);
+
+ measureChildWithMargins(newRow, 0, 0);
+ RecyclerView.LayoutParams newRowParams =
+ (RecyclerView.LayoutParams) newRow.getLayoutParams();
+ RecyclerView.LayoutParams adjacentRowParams =
+ (RecyclerView.LayoutParams) adjacentRow.getLayoutParams();
+ int left = getPaddingLeft() + newRowParams.leftMargin;
+ int right = left + getDecoratedMeasuredWidth(newRow);
+ int top;
+ int bottom;
+ if (layoutDirection == BEFORE) {
+ bottom = adjacentRow.getTop() - adjacentRowParams.topMargin - newRowParams.bottomMargin;
+ top = bottom - getDecoratedMeasuredHeight(newRow);
+ } else {
+ top = getDecoratedBottom(adjacentRow) + adjacentRowParams.bottomMargin
+ + newRowParams.topMargin;
+ bottom = top + getDecoratedMeasuredHeight(newRow);
+ }
+ layoutDecorated(newRow, left, top, right, bottom);
+
+ if (layoutDirection == BEFORE) {
+ addView(newRow, 0);
+ } else {
+ addView(newRow);
+ }
+
+ return newRow;
+ }
+
+ /** @return Whether another row should be laid out in the specified direction. */
+ private boolean shouldLayoutNextRow(
+ RecyclerView.State state, View adjacentRow, @LayoutDirection int layoutDirection) {
+ int adjacentRowPosition = getPosition(adjacentRow);
+
+ if (layoutDirection == BEFORE) {
+ if (adjacentRowPosition == 0) {
+ // We already laid out the first row.
+ return false;
+ }
+ } else if (layoutDirection == AFTER) {
+ if (adjacentRowPosition >= state.getItemCount() - 1) {
+ // We already laid out the last row.
+ return false;
+ }
+ }
+
+ // If we are scrolling layout views until the target position.
+ if (mSmoothScroller != null) {
+ if (layoutDirection == BEFORE
+ && adjacentRowPosition >= mSmoothScroller.getTargetPosition()) {
+ return true;
+ } else if (layoutDirection == AFTER
+ && adjacentRowPosition <= mSmoothScroller.getTargetPosition()) {
+ return true;
+ }
+ }
+
+ View focusedRow = getFocusedChild();
+ if (focusedRow != null) {
+ int focusedRowPosition = getPosition(focusedRow);
+ if (layoutDirection == BEFORE && adjacentRowPosition
+ >= focusedRowPosition - NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
+ return true;
+ } else if (layoutDirection == AFTER && adjacentRowPosition
+ <= focusedRowPosition + NUM_EXTRA_ROWS_TO_LAYOUT_PAST_FOCUS) {
+ return true;
+ }
+ }
+
+ RecyclerView.LayoutParams params = getParams(adjacentRow);
+ int adjacentRowTop = getDecoratedTop(adjacentRow) - params.topMargin;
+ int adjacentRowBottom = getDecoratedBottom(adjacentRow) - params.bottomMargin;
+ if (layoutDirection == BEFORE && adjacentRowTop < getPaddingTop() - getHeight()) {
+ // View is more than 1 page past the top of the screen and also past where the user has
+ // scrolled to. We want to keep one page past the top to make the scroll up calculation
+ // easier and scrolling smoother.
+ return false;
+ } else if (layoutDirection == AFTER
+ && adjacentRowBottom > getHeight() - getPaddingBottom()) {
+ // View is off of the bottom and also past where the user has scrolled to.
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Remove and recycle views that are no longer needed. */
+ private void recycleChildrenFromStart(RecyclerView.Recycler recycler) {
+ // Start laying out children one page before the top of the viewport.
+ int childrenStart = getPaddingTop() - getHeight();
+
+ int focusedChildPosition = Integer.MAX_VALUE;
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null) {
+ focusedChildPosition = getPosition(focusedChild);
+ }
+
+ // Count the number of views that should be removed.
+ int detachedCount = 0;
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ int childEnd = getDecoratedBottom(child);
+ int childPosition = getPosition(child);
+
+ if (childEnd >= childrenStart || childPosition >= focusedChildPosition - 1) {
+ break;
+ }
+
+ detachedCount++;
+ }
+
+ // Remove the number of views counted above. Done by removing the first child n times.
+ while (--detachedCount >= 0) {
+ final View child = getChildAt(0);
+ removeAndRecycleView(child, recycler);
+ }
+ }
+
+ /** Remove and recycle views that are no longer needed. */
+ private void recycleChildrenFromEnd(RecyclerView.Recycler recycler) {
+ // Layout views until the end of the viewport.
+ int childrenEnd = getHeight();
+
+ int focusedChildPosition = Integer.MIN_VALUE + 1;
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null) {
+ focusedChildPosition = getPosition(focusedChild);
+ }
+
+ // Count the number of views that should be removed.
+ int firstDetachedPos = 0;
+ int detachedCount = 0;
+ int childCount = getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ int childStart = getDecoratedTop(child);
+ int childPosition = getPosition(child);
+
+ if (childStart <= childrenEnd || childPosition <= focusedChildPosition - 1) {
+ break;
+ }
+
+ firstDetachedPos = i;
+ detachedCount++;
+ }
+
+ while (--detachedCount >= 0) {
+ final View child = getChildAt(firstDetachedPos);
+ removeAndRecycleView(child, recycler);
+ }
+ }
+
+ /**
+ * Offset rows to do fancy animations. If offset rows was not enabled with
+ * {@link #setOffsetRows}, this will do nothing.
+ *
+ * @see #offsetRowsIndividually
+ * @see #offsetRowsByPage
+ * @see #setOffsetRows
+ */
+ public void offsetRows() {
+ if (!mOffsetRows) {
+ return;
+ }
+
+ if (mRowOffsetMode == ROW_OFFSET_MODE_PAGE) {
+ offsetRowsByPage();
+ } else if (mRowOffsetMode == ROW_OFFSET_MODE_INDIVIDUAL) {
+ offsetRowsIndividually();
+ }
+ }
+
+ /**
+ * Offset the single row that is scrolling off the screen such that by the time the next row
+ * reaches the top, it will have accelerated completely off of the screen.
+ */
+ private void offsetRowsIndividually() {
+ if (getChildCount() == 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, ":: offsetRowsIndividually getChildCount=0");
+ }
+ return;
+ }
+
+ // Identify the dangling row. It will be the first row that is at the top of the
+ // list or above.
+ int danglingChildIndex = -1;
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ if (getDecoratedTop(child) - getParams(child).topMargin <= getPaddingTop()) {
+ danglingChildIndex = i;
+ break;
+ }
+ }
+
+ mAnchorPageBreakPosition = danglingChildIndex;
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, ":: offsetRowsIndividually danglingChildIndex: " + danglingChildIndex);
+ }
+
+ // Calculate the total amount that the view will need to scroll in order to go completely
+ // off screen.
+ RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
+ int[] locs = new int[2];
+ rv.getLocationInWindow(locs);
+ int listTopInWindow = locs[1] + rv.getPaddingTop();
+ int maxDanglingViewTranslation;
+
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ RecyclerView.LayoutParams params = getParams(child);
+
+ maxDanglingViewTranslation = listTopInWindow;
+ // If the child has a negative margin, we'll actually need to translate the view a
+ // little but further to get it completely off screen.
+ if (params.topMargin < 0) {
+ maxDanglingViewTranslation -= params.topMargin;
+ }
+ if (params.bottomMargin < 0) {
+ maxDanglingViewTranslation -= params.bottomMargin;
+ }
+
+ if (i < danglingChildIndex) {
+ child.setAlpha(0f);
+ } else if (i > danglingChildIndex) {
+ child.setAlpha(1f);
+ setCardFlyingEffectOffset(child, 0);
+ } else {
+ int totalScrollDistance =
+ getDecoratedMeasuredHeight(child) + params.topMargin + params.bottomMargin;
+
+ int distanceLeftInScroll =
+ getDecoratedBottom(child) + params.bottomMargin - getPaddingTop();
+ float percentageIntoScroll = 1 - distanceLeftInScroll / (float) totalScrollDistance;
+ float interpolatedPercentage =
+ mDanglingRowInterpolator.getInterpolation(percentageIntoScroll);
+
+ child.setAlpha(1f);
+ setCardFlyingEffectOffset(child, -(maxDanglingViewTranslation
+ * interpolatedPercentage));
+ }
+ }
+ }
+
+ /**
+ * When the list scrolls, the entire page of rows will offset in one contiguous block. This
+ * significantly reduces the amount of extra motion at the top of the screen.
+ */
+ private void offsetRowsByPage() {
+ View anchorView = findViewByPosition(mAnchorPageBreakPosition);
+ if (anchorView == null) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, ":: offsetRowsByPage anchorView null");
+ }
+ return;
+ }
+ int anchorViewTop = getDecoratedTop(anchorView) - getParams(anchorView).topMargin;
+
+ View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
+ int upperViewTop =
+ getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin;
+
+ int scrollDistance = upperViewTop - anchorViewTop;
+
+ int distanceLeft = anchorViewTop - getPaddingTop();
+ float scrollPercentage =
+ (Math.abs(scrollDistance) - distanceLeft) / (float) Math.abs(scrollDistance);
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, String.format(":: offsetRowsByPage scrollDistance:%s, distanceLeft:%s, "
+ + "scrollPercentage:%s",
+ scrollDistance, distanceLeft, scrollPercentage));
+ }
+
+ // Calculate the total amount that the view will need to scroll in order to go completely
+ // off screen.
+ RecyclerView rv = (RecyclerView) getChildAt(0).getParent();
+ int[] locs = new int[2];
+ rv.getLocationInWindow(locs);
+ int listTopInWindow = locs[1] + rv.getPaddingTop();
+
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ int position = getPosition(child);
+ if (position < mUpperPageBreakPosition) {
+ child.setAlpha(0f);
+ setCardFlyingEffectOffset(child, -listTopInWindow);
+ } else if (position < mAnchorPageBreakPosition) {
+ // If the child has a negative margin, we need to offset the row by a little bit
+ // extra so that it moves completely off screen.
+ RecyclerView.LayoutParams params = getParams(child);
+ int extraTranslation = 0;
+ if (params.topMargin < 0) {
+ extraTranslation -= params.topMargin;
+ }
+ if (params.bottomMargin < 0) {
+ extraTranslation -= params.bottomMargin;
+ }
+ int translation = (int) ((listTopInWindow + extraTranslation)
+ * mDanglingRowInterpolator.getInterpolation(scrollPercentage));
+ child.setAlpha(1f);
+ setCardFlyingEffectOffset(child, -translation);
+ } else {
+ child.setAlpha(1f);
+ setCardFlyingEffectOffset(child, 0);
+ }
+ }
+ }
+
+ /**
+ * Apply an offset to this view. This offset is applied post-layout so it doesn't affect when
+ * views are recycled
+ *
+ * @param child The view to apply this to
+ * @param verticalOffset The offset for this child.
+ */
+ private void setCardFlyingEffectOffset(View child, float verticalOffset) {
+ // Ideally instead of doing all this, we could use View.setTranslationY(). However, the
+ // default RecyclerView.ItemAnimator also uses this method which causes layout issues.
+ // See: http://b/25977087
+ TranslateAnimation anim = mFlyOffscreenAnimations.get(child);
+ if (anim == null) {
+ anim = new TranslateAnimation();
+ anim.setFillEnabled(true);
+ anim.setFillAfter(true);
+ anim.setDuration(0);
+ mFlyOffscreenAnimations.put(child, anim);
+ } else if (anim.verticalOffset == verticalOffset) {
+ return;
+ }
+
+ anim.reset();
+ anim.verticalOffset = verticalOffset;
+ anim.setStartTime(Animation.START_ON_FIRST_FRAME);
+ child.setAnimation(anim);
+ anim.startNow();
+ }
+
+ /**
+ * Update the page break positions based on the position of the views on screen. This should be
+ * called whenever view move or change such as during a scroll or layout.
+ */
+ private void updatePageBreakPositions() {
+ if (getChildCount() == 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, ":: updatePageBreakPosition getChildCount: 0");
+ }
+ return;
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, String.format(":: #BEFORE updatePageBreakPositions "
+ + "mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s, "
+ + "mLowerPageBreakPosition:%s",
+ mAnchorPageBreakPosition, mUpperPageBreakPosition,
+ mLowerPageBreakPosition));
+ }
+
+ mAnchorPageBreakPosition = getPosition(getFirstFullyVisibleChild());
+
+ if (mAnchorPageBreakPosition == -1) {
+ Log.w(TAG, "Unable to update anchor positions. There is no anchor position.");
+ return;
+ }
+
+ View anchorPageBreakView = findViewByPosition(mAnchorPageBreakPosition);
+ if (anchorPageBreakView == null) {
+ return;
+ }
+ int topMargin = getParams(anchorPageBreakView).topMargin;
+ int anchorTop = getDecoratedTop(anchorPageBreakView) - topMargin;
+ View upperPageBreakView = findViewByPosition(mUpperPageBreakPosition);
+ int upperPageBreakTop = upperPageBreakView == null
+ ? Integer.MIN_VALUE
+ : getDecoratedTop(upperPageBreakView) - getParams(upperPageBreakView).topMargin;
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, String.format(":: #MID updatePageBreakPositions topMargin:%s, anchorTop:%s"
+ + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s,"
+ + " mLowerPageBreakPosition:%s",
+ topMargin,
+ anchorTop,
+ mAnchorPageBreakPosition,
+ mUpperPageBreakPosition,
+ mLowerPageBreakPosition));
+ }
+
+ if (anchorTop < getPaddingTop()) {
+ // The anchor has moved above the viewport. We are now on the next page. Shift the page
+ // break positions and calculate a new lower one.
+ mUpperPageBreakPosition = mAnchorPageBreakPosition;
+ mAnchorPageBreakPosition = mLowerPageBreakPosition;
+ mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
+ } else if (mAnchorPageBreakPosition > 0 && upperPageBreakTop >= getPaddingTop()) {
+ // The anchor has moved below the viewport. We are now on the previous page. Shift
+ // the page break positions and calculate a new upper one.
+ mLowerPageBreakPosition = mAnchorPageBreakPosition;
+ mAnchorPageBreakPosition = mUpperPageBreakPosition;
+ mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
+ } else {
+ mUpperPageBreakPosition = calculatePreviousPageBreakPosition(mAnchorPageBreakPosition);
+ mLowerPageBreakPosition = calculateNextPageBreakPosition(mAnchorPageBreakPosition);
+ }
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, String.format(":: #AFTER updatePageBreakPositions"
+ + " mAnchorPageBreakPosition:%s, mUpperPageBreakPosition:%s,"
+ + " mLowerPageBreakPosition:%s",
+ mAnchorPageBreakPosition, mUpperPageBreakPosition,
+ mLowerPageBreakPosition));
+ }
+ }
+
+ /**
+ * @return The page break position of the page before the anchor page break position. However,
+ * if it reaches the end of the laid out children or position 0, it will just return that.
+ */
+ @VisibleForTesting
+ int calculatePreviousPageBreakPosition(int position) {
+ if (position == -1) {
+ return -1;
+ }
+ View referenceView = findViewByPosition(position);
+ int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
+
+ int previousPagePosition = position;
+ while (previousPagePosition > 0) {
+ previousPagePosition--;
+ View child = findViewByPosition(previousPagePosition);
+ if (child == null) {
+ // View has not been laid out yet.
+ return previousPagePosition + 1;
+ }
+
+ int childTop = getDecoratedTop(child) - getParams(child).topMargin;
+ if (childTop < referenceViewTop - getHeight()) {
+ return previousPagePosition + 1;
+ }
+ }
+ // Beginning of the list.
+ return 0;
+ }
+
+ /**
+ * @return The page break position of the next page after the anchor page break position.
+ * However, if it reaches the end of the laid out children or end of the list, it will just
+ * return that.
+ */
+ @VisibleForTesting
+ int calculateNextPageBreakPosition(int position) {
+ if (position == -1) {
+ return -1;
+ }
+
+ View referenceView = findViewByPosition(position);
+ if (referenceView == null) {
+ return position;
+ }
+ int referenceViewTop = getDecoratedTop(referenceView) - getParams(referenceView).topMargin;
+
+ int nextPagePosition = position;
+
+ // Search for the first child item after the referenceView that didn't fully fit on to the
+ // screen. The next page should start from the item before this child, so that users have
+ // a visual anchoring point of the page change.
+ while (nextPagePosition < getItemCount() - 1) {
+ nextPagePosition++;
+ View child = findViewByPosition(nextPagePosition);
+ if (child == null) {
+ // The next view has not been laid out yet.
+ return nextPagePosition - 1;
+ }
+
+ int childTop = getDecoratedTop(child) - getParams(child).topMargin;
+ if (childTop > referenceViewTop + getHeight()) {
+ // If choosing the previous child causes the view to snap back to the referenceView
+ // position, then skip that and go directly to the child. This avoids the case
+ // where a tall card in the layout causes the view to constantly snap back to
+ // the top when scrolled.
+ return nextPagePosition - 1 == position ? nextPagePosition : nextPagePosition - 1;
+ }
+ }
+ // End of the list.
+ return nextPagePosition;
+ }
+
+ /**
+ * In this style, the focus will scroll down to the middle of the screen and lock there so that
+ * moving in either direction will move the entire list by 1.
+ */
+ private boolean onRequestChildFocusMarioStyle(RecyclerView parent, View child) {
+ int focusedPosition = getPosition(child);
+ if (focusedPosition == mLastChildPositionToRequestFocus) {
+ return true;
+ }
+ mLastChildPositionToRequestFocus = focusedPosition;
+
+ int availableHeight = getAvailableHeight();
+ int focusedChildTop = getDecoratedTop(child);
+ int focusedChildBottom = getDecoratedBottom(child);
+
+ int childIndex = parent.indexOfChild(child);
+ // Iterate through children starting at the focused child to find the child above it to
+ // smooth scroll to such that the focused child will be as close to the middle of the screen
+ // as possible.
+ for (int i = childIndex; i >= 0; i--) {
+ View childAtI = getChildAt(i);
+ if (childAtI == null) {
+ Log.e(TAG, "Child is null at index " + i);
+ continue;
+ }
+ // We haven't found a view that is more than half of the recycler view height above it
+ // but we've reached the top so we can't go any further.
+ if (i == 0) {
+ parent.smoothScrollToPosition(getPosition(childAtI));
+ break;
+ }
+
+ // Because we want to scroll to the first view that is less than half of the screen
+ // away from the focused view, we "look ahead" one view. When the look ahead view
+ // is more than availableHeight / 2 away, the current child at i is the one we want to
+ // scroll to. However, sometimes, that view can be null (ie, if the view is in
+ // transition). In that case, just skip that view.
+
+ View childBefore = getChildAt(i - 1);
+ if (childBefore == null) {
+ continue;
+ }
+ int distanceToChildBeforeFromTop = focusedChildTop - getDecoratedTop(childBefore);
+ int distanceToChildBeforeFromBottom = focusedChildBottom - getDecoratedTop(childBefore);
+
+ if (distanceToChildBeforeFromTop > availableHeight / 2
+ || distanceToChildBeforeFromBottom > availableHeight) {
+ parent.smoothScrollToPosition(getPosition(childAtI));
+ break;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * We don't actually know the size of every single view, only what is currently laid out. This
+ * makes it difficult to do accurate scrollbar calculations. However, lists in the car often
+ * consist of views with identical heights. Because of that, we can use a single sample view to
+ * do our calculations for. The main exceptions are in the first items of a list (hero card,
+ * last call card, etc) so if the first view is at position 0, we pick the next one.
+ *
+ * @return The decorated measured height of the sample view plus its margins.
+ */
+ private int getSampleViewHeight() {
+ if (mSampleViewHeight != -1) {
+ return mSampleViewHeight;
+ }
+ int sampleViewIndex = getFirstFullyVisibleChildIndex();
+ View sampleView = getChildAt(sampleViewIndex);
+ if (getPosition(sampleView) == 0 && sampleViewIndex < getChildCount() - 1) {
+ sampleView = getChildAt(++sampleViewIndex);
+ }
+ RecyclerView.LayoutParams params = getParams(sampleView);
+ int height = getDecoratedMeasuredHeight(sampleView) + params.topMargin
+ + params.bottomMargin;
+ if (height == 0) {
+ // This can happen if the view isn't measured yet.
+ Log.w(
+ TAG,
+ "The sample view has a height of 0. Returning a dummy value for now "
+ + "that won't be cached.");
+ height = mContext.getResources().getDimensionPixelSize(R.dimen.car_sample_row_height);
+ } else {
+ mSampleViewHeight = height;
+ }
+ return height;
+ }
+
+ /** @return The height of the RecyclerView excluding padding. */
+ private int getAvailableHeight() {
+ return getHeight() - getPaddingTop() - getPaddingBottom();
+ }
+
+ /**
+ * @return {@link RecyclerView.LayoutParams} for the given view or null if it isn't a child of
+ * {@link RecyclerView}.
+ */
+ private static RecyclerView.LayoutParams getParams(View view) {
+ return (RecyclerView.LayoutParams) view.getLayoutParams();
+ }
+
+ /**
+ * Custom {@link LinearSmoothScroller} that has: a) Custom control over the speed of scrolls. b)
+ * Scrolling snaps to start. All of our scrolling logic depends on that. c) Keeps track of some
+ * state of the current scroll so that can aid in things like the scrollbar calculations.
+ */
+ private final class CarSmoothScroller extends LinearSmoothScroller {
+ /** This value (150) was hand tuned by UX for what felt right. * */
+ private static final float MILLISECONDS_PER_INCH = 150f;
+ /** This value (0.45) was hand tuned by UX for what felt right. * */
+ private static final float DECELERATION_TIME_DIVISOR = 0.45f;
+
+ /** This value (1.8) was hand tuned by UX for what felt right. * */
+ private final Interpolator mInterpolator = new DecelerateInterpolator(1.8f);
+
+ private final int mTargetPosition;
+
+ CarSmoothScroller(Context context, int targetPosition) {
+ super(context);
+ mTargetPosition = targetPosition;
+ }
+
+ @Override
+ public PointF computeScrollVectorForPosition(int i) {
+ if (getChildCount() == 0) {
+ return null;
+ }
+ final int firstChildPos = getPosition(getChildAt(getFirstFullyVisibleChildIndex()));
+ final int direction = (mTargetPosition < firstChildPos) ? -1 : 1;
+ return new PointF(0, direction);
+ }
+
+ @Override
+ protected int getVerticalSnapPreference() {
+ // This is key for most of the scrolling logic that guarantees that scrolling
+ // will settle with a view aligned to the top.
+ return LinearSmoothScroller.SNAP_TO_START;
+ }
+
+ @Override
+ protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+ int dy = calculateDyToMakeVisible(targetView, SNAP_TO_START);
+ if (dy == 0) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Scroll distance is 0");
+ }
+ return;
+ }
+
+ final int time = calculateTimeForDeceleration(dy);
+ if (time > 0) {
+ action.update(0, -dy, time, mInterpolator);
+ }
+ }
+
+ @Override
+ protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+ return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+ }
+
+ @Override
+ protected int calculateTimeForDeceleration(int dx) {
+ return (int) Math.ceil(calculateTimeForScrolling(dx) / DECELERATION_TIME_DIVISOR);
+ }
+
+ @Override
+ public int getTargetPosition() {
+ return mTargetPosition;
+ }
+ }
+
+ /**
+ * Animation that translates a view by the specified amount. Used for card flying off the screen
+ * effect.
+ */
+ private static class TranslateAnimation extends Animation {
+ public float verticalOffset;
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ super.applyTransformation(interpolatedTime, t);
+ t.getMatrix().setTranslate(0, verticalOffset);
+ }
+ }
+}
diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java
new file mode 100644
index 00000000..edc32415
--- /dev/null
+++ b/android/support/car/widget/CarRecyclerView.java
@@ -0,0 +1,204 @@
+/*
+ * 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.support.car.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Custom {@link RecyclerView} that helps {@link CarLayoutManager} 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)}.
+ */
+public class CarRecyclerView extends RecyclerView {
+ private static final String PARCEL_CLASS = "android.os.Parcel";
+ private static final String SAVED_STATE_CLASS =
+ "android.support.v7.widget.RecyclerView.SavedState";
+ private boolean mFadeLastItem;
+ private Constructor<?> mSavedStateConstructor;
+ /**
+ * If the user releases the list with a velocity of 0, {@link #fling(int, int)} will not be
+ * called. However, we want to make sure that the list still snaps to the next page when this
+ * happens.
+ */
+ private boolean mWasFlingCalledForGesture;
+
+ public CarRecyclerView(Context context) {
+ this(context, null);
+ }
+
+ public CarRecyclerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CarRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setFocusableInTouchMode(false);
+ setFocusable(false);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state.getClass().getClassLoader() != getClass().getClassLoader()) {
+ if (mSavedStateConstructor == null) {
+ mSavedStateConstructor = getSavedStateConstructor();
+ }
+ // Class loader mismatch, recreate from parcel.
+ Parcel obtain = Parcel.obtain();
+ state.writeToParcel(obtain, 0);
+ try {
+ Parcelable newState = (Parcelable) mSavedStateConstructor.newInstance(obtain);
+ super.onRestoreInstanceState(newState);
+ } catch (InstantiationException
+ | IllegalAccessException
+ | IllegalArgumentException
+ | InvocationTargetException e) {
+ // Fail loudy here.
+ throw new RuntimeException(e);
+ }
+ } else {
+ super.onRestoreInstanceState(state);
+ }
+ }
+
+ @Override
+ public boolean fling(int velocityX, int velocityY) {
+ mWasFlingCalledForGesture = true;
+ return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ // We want the parent to handle all touch events. There's a lot going on there,
+ // and there is no reason to overwrite that functionality. If we do, bad things will happen.
+ final boolean ret = super.onTouchEvent(e);
+
+ int action = e.getActionMasked();
+ if (action == MotionEvent.ACTION_UP) {
+ if (!mWasFlingCalledForGesture) {
+ ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, 0);
+ }
+ mWasFlingCalledForGesture = false;
+ }
+
+ return ret;
+ }
+
+ @Override
+ public boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
+ if (mFadeLastItem) {
+ float onScreen = 1f;
+ if ((child.getTop() < getBottom() && child.getBottom() > getBottom())) {
+ onScreen = ((float) (getBottom() - child.getTop())) / (float) child.getHeight();
+ } else if ((child.getTop() < getTop() && child.getBottom() > getTop())) {
+ onScreen = ((float) (child.getBottom() - getTop())) / (float) child.getHeight();
+ }
+ float alpha = 1 - (1 - onScreen) * (1 - onScreen);
+ fadeChild(child, alpha);
+ }
+
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ public void setFadeLastItem(boolean fadeLastItem) {
+ mFadeLastItem = fadeLastItem;
+ }
+
+ /**
+ * Scrolls the contents of this {@link CarRecyclerView} up one page. A page is defined as the
+ * number of items that fit completely on the screen.
+ */
+ public void pageUp() {
+ CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+ int pageUpPosition = lm.getPageUpPosition();
+ if (pageUpPosition == -1) {
+ return;
+ }
+
+ smoothScrollToPosition(pageUpPosition);
+ }
+
+ /**
+ * Scrolls the contents of this {@link CarRecyclerView} down one page. A page is defined as the
+ * number of items that fit completely on the screen.
+ */
+ public void pageDown() {
+ CarLayoutManager lm = (CarLayoutManager) getLayoutManager();
+ int pageDownPosition = lm.getPageDownPosition();
+ if (pageDownPosition == -1) {
+ return;
+ }
+
+ smoothScrollToPosition(pageDownPosition);
+ }
+
+ /** Sets {@link #mSavedStateConstructor} to private SavedState constructor. */
+ private Constructor<?> getSavedStateConstructor() {
+ Class<?> savedStateClass = null;
+ // Find package private subclass RecyclerView$SavedState.
+ for (Class<?> c : RecyclerView.class.getDeclaredClasses()) {
+ if (c.getCanonicalName().equals(SAVED_STATE_CLASS)) {
+ savedStateClass = c;
+ break;
+ }
+ }
+ if (savedStateClass == null) {
+ throw new RuntimeException("RecyclerView$SavedState not found!");
+ }
+ // Find constructor that takes a {@link Parcel}.
+ for (Constructor<?> c : savedStateClass.getDeclaredConstructors()) {
+ Class<?>[] parameterTypes = c.getParameterTypes();
+ if (parameterTypes.length == 1
+ && parameterTypes[0].getCanonicalName().equals(PARCEL_CLASS)) {
+ mSavedStateConstructor = c;
+ mSavedStateConstructor.setAccessible(true);
+ break;
+ }
+ }
+ if (mSavedStateConstructor == null) {
+ throw new RuntimeException("RecyclerView$SavedState constructor not found!");
+ }
+ return mSavedStateConstructor;
+ }
+
+ /**
+ * Fades child by alpha. If child is a {@link ViewGroup} then it will recursively fade its
+ * children instead.
+ */
+ private void fadeChild(@NonNull View child, float alpha) {
+ if (child instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) child;
+ for (int i = 0; i < vg.getChildCount(); i++) {
+ fadeChild(vg.getChildAt(i), alpha);
+ }
+ } else {
+ child.setAlpha(alpha);
+ }
+ }
+}
diff --git a/android/support/car/widget/ColumnCardView.java b/android/support/car/widget/ColumnCardView.java
new file mode 100644
index 00000000..06f85536
--- /dev/null
+++ b/android/support/car/widget/ColumnCardView.java
@@ -0,0 +1,115 @@
+/*
+ * 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.support.car.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.car.R;
+import android.support.car.utils.ColumnCalculator;
+import android.support.v7.widget.CardView;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * A {@link CardView} whose width can be specified by the number of columns that it will span.
+ *
+ * <p>The {@code ColumnCardView} works similarly to a regular {@link CardView}, except that
+ * its {@code layout_width} attribute is always ignored. Instead, its width is automatically
+ * calculated based on a specified {@code columnSpan} attribute. Alternatively, a user can call
+ * {@link #setColumnSpan(int)}. If no column span is given, the {@code ColumnCardView} will have
+ * a default span value that it uses.
+ *
+ * <pre>
+ * &lt;android.support.car.widget.ColumnCardView
+ * android:layout_width="wrap_content"
+ * android:layout_height="wrap_content"
+ * app:columnSpan="4" /&gt;
+ * </pre>
+ *
+ * @see ColumnCalculator
+ */
+public final class ColumnCardView extends CardView {
+ private static final String TAG = "ColumnCardView";
+
+ private ColumnCalculator mColumnCalculator;
+ private int mColumnSpan;
+
+ public ColumnCardView(Context context) {
+ super(context);
+ init(context, null, 0 /* defStyleAttrs */);
+ }
+
+ public ColumnCardView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs, 0 /* defStyleAttrs */);
+ }
+
+ public ColumnCardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttrs) {
+ mColumnCalculator = ColumnCalculator.getInstance(context);
+
+ int defaultColumnSpan = getResources().getInteger(
+ R.integer.column_card_default_column_span);
+
+ TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColumnCardView,
+ defStyleAttrs, 0 /* defStyleRes */);
+ mColumnSpan = ta.getInteger(R.styleable.ColumnCardView_columnSpan, defaultColumnSpan);
+ ta.recycle();
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Column span: " + mColumnSpan);
+ }
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Override any specified width so that the width is one that is calculated based on
+ // column and gutter span.
+ int width = mColumnCalculator.getSizeForColumnSpan(mColumnSpan);
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ heightMeasureSpec);
+ }
+
+ /**
+ * Sets the number of columns that this {@code ColumnCardView} will span. The given span is
+ * ignored if it is less than 0 or greater than the number of columns that fit on screen.
+ *
+ * @param columnSpan The number of columns this {@code ColumnCardView} will span across.
+ */
+ public void setColumnSpan(int columnSpan) {
+ if (columnSpan <= 0 || columnSpan > mColumnCalculator.getNumOfColumns()) {
+ return;
+ }
+
+ mColumnSpan = columnSpan;
+ requestLayout();
+ }
+
+ /**
+ * Returns the currently number of columns that this {@code ColumnCardView} spans.
+ *
+ * @return The number of columns this {@code ColumnCardView} spans across.
+ */
+ public int getColumnSpan() {
+ return mColumnSpan;
+ }
+}
diff --git a/android/support/car/widget/DayNightStyle.java b/android/support/car/widget/DayNightStyle.java
new file mode 100644
index 00000000..ff5a1b33
--- /dev/null
+++ b/android/support/car/widget/DayNightStyle.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.support.car.widget;
+
+import android.support.annotation.IntDef;
+
+/**
+ * Specifies how the system UI should respond to day/night mode events.
+ *
+ * <p>By default, the Android Auto system UI assumes the app content background is light during the
+ * day and dark during the night. The system UI updates the foreground color (such as status bar
+ * icon colors) to be dark during day mode and light during night mode. By setting the
+ * DayNightStyle, the app can specify how the system should respond to a day/night mode event. For
+ * example, if the app has a dark content background for both day and night time, the app can tell
+ * the system to use {@link #FORCE_NIGHT} style so the foreground color is locked to light color for
+ * both cases.
+ *
+ * <p>Note: Not all system UI elements can be customized with a DayNightStyle.
+ */
+@IntDef({
+ DayNightStyle.AUTO,
+ DayNightStyle.AUTO_INVERSE,
+ DayNightStyle.FORCE_NIGHT,
+ DayNightStyle.FORCE_DAY,
+})
+public @interface DayNightStyle {
+ /**
+ * Sets the foreground color to be automatically changed based on day/night mode, assuming the
+ * app content background is light during the day and dark during the night.
+ *
+ * <p>This is the default behavior.
+ */
+ int AUTO = 0;
+
+ /**
+ * Sets the foreground color to be automatically changed based on day/night mode, assuming the
+ * app content background is dark during the day and light during the night.
+ */
+ int AUTO_INVERSE = 1;
+
+ /**
+ * Sets the foreground color to be locked to the night version, which assumes the app content
+ * background is always dark during both day and night.
+ */
+ int FORCE_NIGHT = 2;
+
+ /**
+ * Sets the foreground color to be locked to the day version, which assumes the app content
+ * background is always light during both day and night.
+ */
+ int FORCE_DAY = 3;
+}
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
new file mode 100644
index 00000000..8527c659
--- /dev/null
+++ b/android/support/car/widget/PagedListView.java
@@ -0,0 +1,854 @@
+/*
+ * 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.support.car.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Handler;
+import android.support.annotation.IdRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.Px;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.UiThread;
+import android.support.car.R;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+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.
+ */
+public class PagedListView extends FrameLayout {
+ /** Default maximum number of clicks allowed on a list */
+ public static final int DEFAULT_MAX_CLICKS = 6;
+
+ /**
+ * The amount of time after settling to wait before autoscrolling to the next page when the user
+ * holds down a pagination button.
+ */
+ protected static final int PAGINATION_HOLD_DELAY_MS = 400;
+
+ private static final String TAG = "PagedListView";
+ private static final int INVALID_RESOURCE_ID = -1;
+
+ protected final CarRecyclerView mRecyclerView;
+ protected final CarLayoutManager mLayoutManager;
+ protected final Handler mHandler = new Handler();
+ private final boolean mScrollBarEnabled;
+ private final boolean mRightGutterEnabled;
+ private final PagedScrollBarView mScrollBarView;
+
+ 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;
+
+ protected OnScrollListener mOnScrollListener;
+
+ /** Number of visible rows per page */
+ private int mDefaultMaxPages = DEFAULT_MAX_CLICKS;
+
+ /** Used to check if there are more items added to the list. */
+ private int mLastItemCount = 0;
+
+ private boolean mNeedsFocus;
+
+ /**
+ * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to cap the number of
+ * items.
+ *
+ * <p>NOTE: it is still up to the adapter to use maxItems in {@link
+ * android.support.v7.widget.RecyclerView.Adapter#getItemCount()}.
+ *
+ * <p>the recommended way would be with:
+ *
+ * <pre>{@code
+ * {@literal@}Override
+ * public int getItemCount() {
+ * return Math.min(super.getItemCount(), mMaxItems);
+ * }
+ * }</pre>
+ */
+ public interface ItemCap {
+ /**
+ * Sets the maximum number of items available in the adapter. A value less than '0' means
+ * the list should not be capped.
+ */
+ void setMaxItems(int maxItems);
+ }
+
+ /**
+ * Interface for a {@link android.support.v7.widget.RecyclerView.Adapter} to set the position
+ * offset for the adapter to load the data.
+ *
+ * <p>For example in the adapter, if the positionOffset is 20, then for position 0 it will show
+ * 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);
+ }
+
+ public PagedListView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
+ }
+
+ public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs) {
+ this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
+ }
+
+ public PagedListView(Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+ this(context, attrs, defStyleAttrs, defStyleRes, 0);
+ }
+
+ public PagedListView(
+ Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes, int layoutId) {
+ super(context, attrs, defStyleAttrs, defStyleRes);
+ if (layoutId == 0) {
+ layoutId = R.layout.car_paged_recycler_view;
+ }
+ LayoutInflater.from(context).inflate(layoutId, this /*root*/, true /*attachToRoot*/);
+
+ FrameLayout maxWidthLayout = (FrameLayout) findViewById(R.id.recycler_view_container);
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.PagedListView, defStyleAttrs, defStyleRes);
+ mRecyclerView = (CarRecyclerView) findViewById(R.id.recycler_view);
+ boolean fadeLastItem = a.getBoolean(R.styleable.PagedListView_fadeLastItem, false);
+ mRecyclerView.setFadeLastItem(fadeLastItem);
+ boolean offsetRows = a.getBoolean(R.styleable.PagedListView_offsetRows, false);
+
+ mMaxPages = getDefaultMaxPages();
+
+ mLayoutManager = new CarLayoutManager(context);
+ mLayoutManager.setOffsetRows(offsetRows);
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ mRecyclerView.setOnScrollListener(mRecyclerViewOnScrollListener);
+ mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
+ mRecyclerView.setItemAnimator(new CarItemAnimator(mLayoutManager));
+
+ if (a.getBoolean(R.styleable.PagedListView_showPagedListViewDivider, true)) {
+ int dividerStartMargin = a.getDimensionPixelSize(
+ R.styleable.PagedListView_dividerStartMargin, 0);
+ int dividerStartId = a.getResourceId(
+ R.styleable.PagedListView_alignDividerStartTo, INVALID_RESOURCE_ID);
+ int dividerEndId = a.getResourceId(
+ R.styleable.PagedListView_alignDividerEndTo, INVALID_RESOURCE_ID);
+
+ mRecyclerView.addItemDecoration(new DividerDecoration(context, dividerStartMargin,
+ dividerStartId, dividerEndId));
+ }
+
+ // 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
+ // the event.
+ setClickable(true);
+
+ // Set focusable false explicitly to handle the behavior change in Android O where
+ // clickable view becomes focusable by default.
+ setFocusable(false);
+
+ mScrollBarEnabled = a.getBoolean(R.styleable.PagedListView_scrollBarEnabled, true);
+ mScrollBarView = (PagedScrollBarView) findViewById(R.id.paged_scroll_view);
+ mScrollBarView.setPaginationListener(
+ new PagedScrollBarView.PaginationListener() {
+ @Override
+ public void onPaginate(int direction) {
+ if (direction == PagedScrollBarView.PaginationListener.PAGE_UP) {
+ mRecyclerView.pageUp();
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrollUpButtonClicked();
+ }
+ } else if (direction == PagedScrollBarView.PaginationListener.PAGE_DOWN) {
+ mRecyclerView.pageDown();
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrollDownButtonClicked();
+ }
+ } else {
+ Log.e(TAG, "Unknown pagination direction (" + direction + ")");
+ }
+ }
+ });
+ mScrollBarView.setVisibility(mScrollBarEnabled ? VISIBLE : GONE);
+
+ // Modify the layout if the Gutter or the Scroll Bar are not visible.
+ mRightGutterEnabled = a.getBoolean(R.styleable.PagedListView_rightGutterEnabled, false);
+ if (mRightGutterEnabled || !mScrollBarEnabled) {
+ FrameLayout.LayoutParams maxWidthLayoutLayoutParams =
+ (FrameLayout.LayoutParams) maxWidthLayout.getLayoutParams();
+ if (mRightGutterEnabled) {
+ maxWidthLayoutLayoutParams.rightMargin =
+ getResources().getDimensionPixelSize(R.dimen.car_card_margin);
+ }
+ if (!mScrollBarEnabled) {
+ maxWidthLayoutLayoutParams.setMarginStart(0);
+ }
+ maxWidthLayout.setLayoutParams(maxWidthLayoutLayoutParams);
+ }
+
+ setDayNightStyle(DayNightStyle.AUTO);
+ a.recycle();
+ }
+
+ /**
+ * Sets the starting and ending padding for each view in the list.
+ *
+ * @param start The start padding.
+ * @param end The end padding.
+ */
+ public void setListViewStartEndPadding(@Px int start, @Px int end) {
+ int carCardMargin = getResources().getDimensionPixelSize(R.dimen.car_card_margin);
+ int startGutter = mScrollBarEnabled ? carCardMargin : 0;
+ int startPadding = Math.max(start - startGutter, 0);
+ int endGutter = mRightGutterEnabled ? carCardMargin : 0;
+ int endPadding = Math.max(end - endGutter, 0);
+ mRecyclerView.setPaddingRelative(startPadding, mRecyclerView.getPaddingTop(),
+ endPadding, mRecyclerView.getPaddingBottom());
+
+ // Since we're setting padding we'll need to set the clip to padding to the same
+ // value as clip children to ensure that the cards fly off the screen.
+ mRecyclerView.setClipToPadding(mRecyclerView.getClipChildren());
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mHandler.removeCallbacks(mUpdatePaginationRunnable);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ 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);
+ }
+ return super.onInterceptTouchEvent(e);
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ 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);
+ }
+
+ /**
+ * Returns the position of the given View in the list.
+ *
+ * @param v The View to check for.
+ * @return The position or -1 if the given View is {@code null} or not in the list.
+ */
+ public int positionOf(@Nullable View v) {
+ if (v == null || v.getParent() != mRecyclerView) {
+ return -1;
+ }
+ return mLayoutManager.getPosition(v);
+ }
+
+ private void scroll(int direction) {
+ View focusedView = mRecyclerView.getFocusedChild();
+ if (focusedView != null) {
+ int position = mLayoutManager.getPosition(focusedView);
+ int newPosition =
+ Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0);
+ if (newPosition != position) {
+ // newPosition/position are adapter positions.
+ // Convert to layout position by subtracting adapter position of view at layout
+ // position 0.
+ View childAt = mRecyclerView.getChildAt(
+ newPosition - mLayoutManager.getPosition(mLayoutManager.getChildAt(0)));
+ if (childAt != null) {
+ childAt.requestFocus();
+ }
+ }
+ }
+ }
+
+ private boolean canScroll(int direction) {
+ View focusedView = mRecyclerView.getFocusedChild();
+ if (focusedView != null) {
+ int position = mLayoutManager.getPosition(focusedView);
+ int newPosition =
+ Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0);
+ if (newPosition != position) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @NonNull
+ public CarRecyclerView getRecyclerView() {
+ return mRecyclerView;
+ }
+
+ /**
+ * Scrolls to the given position in the PagedListView.
+ *
+ * @param position The position in the list to scroll to.
+ */
+ public void scrollToPosition(int position) {
+ mLayoutManager.scrollToPosition(position);
+
+ // Sometimes #scrollToPosition doesn't change the scroll state so we need to make sure
+ // the pagination arrows actually get updated. See b/http://b/15801119
+ mHandler.post(mUpdatePaginationRunnable);
+ }
+
+ /**
+ * Sets the adapter for the list.
+ *
+ * <p>It <em>must</em> implement {@link ItemCap}, otherwise, will throw an {@link
+ * IllegalArgumentException}.
+ */
+ 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();
+ }
+
+ /** @hide */
+ @RestrictTo(LIBRARY_GROUP)
+ @NonNull
+ public CarLayoutManager getLayoutManager() {
+ return mLayoutManager;
+ }
+
+ @Nullable
+ @SuppressWarnings("unchecked")
+ public RecyclerView.Adapter<? extends RecyclerView.ViewHolder> getAdapter() {
+ return mRecyclerView.getAdapter();
+ }
+
+ /**
+ * 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.
+ *
+ * @param maxPages The maximum number of pages that fit on the screen. Should be positive.
+ */
+ public void setMaxPages(int maxPages) {
+ if (maxPages < 0) {
+ return;
+ }
+ mMaxPages = maxPages;
+ updateMaxItems();
+ }
+
+ /**
+ * Returns the maximum number of pages allowed in the PagedListView. This number is set by
+ * {@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.
+ */
+ public int getMaxPages() {
+ return mMaxPages;
+ }
+
+ /**
+ * 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.
+ */
+ public int getRowsPerPage() {
+ return mRowsPerPage;
+ }
+
+ /** Resets the maximum number of pages to be shown to be the default. */
+ public void resetMaxPages() {
+ mMaxPages = getDefaultMaxPages();
+ updateMaxItems();
+ }
+
+ /**
+ * @return The position of first visible child in the list. -1 will be returned if there is no
+ * child.
+ */
+ public int getFirstFullyVisibleChildPosition() {
+ return mLayoutManager.getFirstFullyVisibleChildPosition();
+ }
+
+ /**
+ * @return The position of last visible child in the list. -1 will be returned if there is no
+ * child.
+ */
+ public int getLastFullyVisibleChildPosition() {
+ return mLayoutManager.getLastFullyVisibleChildPosition();
+ }
+
+ /**
+ * Adds an {@link android.support.v7.widget.RecyclerView.ItemDecoration} to this PagedListView.
+ *
+ * @param decor The decoration to add.
+ * @see RecyclerView#addItemDecoration(RecyclerView.ItemDecoration)
+ */
+ public void addItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+ mRecyclerView.addItemDecoration(decor);
+ }
+
+ /**
+ * Removes the given {@link android.support.v7.widget.RecyclerView.ItemDecoration} from this
+ * PagedListView.
+ *
+ * <p>The decoration will function the same as the item decoration for a {@link RecyclerView}.
+ *
+ * @param decor The decoration to remove.
+ * @see RecyclerView#removeItemDecoration(RecyclerView.ItemDecoration)
+ */
+ public void removeItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
+ mRecyclerView.removeItemDecoration(decor);
+ }
+
+ /**
+ * Adds an {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} to this
+ * PagedListView.
+ *
+ * <p>The listener will function the same as the listener for a regular {@link RecyclerView}.
+ *
+ * @param touchListener The touch listener to add.
+ * @see RecyclerView#addOnItemTouchListener(RecyclerView.OnItemTouchListener)
+ */
+ public void addOnItemTouchListener(@NonNull RecyclerView.OnItemTouchListener touchListener) {
+ mRecyclerView.addOnItemTouchListener(touchListener);
+ }
+
+ /**
+ * Removes the given {@link android.support.v7.widget.RecyclerView.OnItemTouchListener} from
+ * the PagedListView.
+ *
+ * @param touchListener The touch listener to remove.
+ * @see RecyclerView#removeOnItemTouchListener(RecyclerView.OnItemTouchListener)
+ */
+ public void removeOnItemTouchListener(@NonNull RecyclerView.OnItemTouchListener touchListener) {
+ mRecyclerView.removeOnItemTouchListener(touchListener);
+ }
+ /**
+ * Sets how this {@link PagedListView} responds to day/night configuration changes. By
+ * default, the PagedListView is darker in the day and lighter at night.
+ *
+ * @param dayNightStyle A value from {@link DayNightStyle}.
+ * @see DayNightStyle
+ */
+ public void setDayNightStyle(@DayNightStyle int dayNightStyle) {
+ // Update the scrollbar
+ mScrollBarView.setDayNightStyle(dayNightStyle);
+
+ int decorCount = mRecyclerView.getItemDecorationCount();
+ for (int i = 0; i < decorCount; i++) {
+ RecyclerView.ItemDecoration decor = mRecyclerView.getItemDecorationAt(i);
+ if (decor instanceof DividerDecoration) {
+ ((DividerDecoration) decor).updateDividerColor();
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link android.support.v7.widget.RecyclerView.ViewHolder} that corresponds to the
+ * last child in the PagedListView that is fully visible.
+ *
+ * @return The corresponding ViewHolder or {@code null} if none exists.
+ */
+ @Nullable
+ public RecyclerView.ViewHolder getLastViewHolder() {
+ View lastFullyVisible = mLayoutManager.getLastFullyVisibleChild();
+ if (lastFullyVisible == null) {
+ return null;
+ }
+ int lastFullyVisibleAdapterPosition = mLayoutManager.getPosition(lastFullyVisible);
+ RecyclerView.ViewHolder lastViewHolder = getRecyclerView()
+ .findViewHolderForAdapterPosition(lastFullyVisibleAdapterPosition + 1);
+ // We want to get the very last ViewHolder in the list, even if it's only fully visible
+ // If it doesn't exist, return the last fully visible ViewHolder.
+ if (lastViewHolder == null) {
+ lastViewHolder = getRecyclerView()
+ .findViewHolderForAdapterPosition(lastFullyVisibleAdapterPosition);
+ }
+ return lastViewHolder;
+ }
+
+ /**
+ * Sets the {@link OnScrollListener} that will be notified of scroll events within the
+ * PagedListView.
+ *
+ * @param listener The scroll listener to set.
+ */
+ public void setOnScrollListener(OnScrollListener listener) {
+ mOnScrollListener = listener;
+ mLayoutManager.setOnScrollListener(mOnScrollListener);
+ }
+
+ /** Returns the page the given position is on, starting with page 0. */
+ public int getPage(int position) {
+ if (mRowsPerPage == -1) {
+ return -1;
+ }
+ if (mRowsPerPage == 0) {
+ return 0;
+ }
+ return position / mRowsPerPage;
+ }
+
+ /**
+ * Sets the default number of pages that this PagedListView is limited to.
+ *
+ * @param newDefault The default number of pages. Should be positive.
+ */
+ public void setDefaultMaxPages(int newDefault) {
+ if (newDefault < 0) {
+ return;
+ }
+ mDefaultMaxPages = newDefault;
+ }
+
+ /** Returns the default number of pages the list should have */
+ protected int getDefaultMaxPages() {
+ // assume list shown in response to a click, so, reduce number of clicks by one
+ return mDefaultMaxPages - 1;
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ // if a late item is added to the top of the layout after the layout is stabilized, causing
+ // the former top item to be pushed to the 2nd page, the focus will still be on the former
+ // top item. Since our car layout manager tries to scroll the viewport so that the focused
+ // item is visible, the view port will be on the 2nd page. That means the newly added item
+ // will not be visible, on the first page.
+
+ // what we want to do is: if the formerly focused item is the first one in the list, any
+ // item added above it will make the focus to move to the new first item.
+ // if the focus is not on the formerly first item, then we don't need to do anything. Let
+ // the layout manager do the job and scroll the viewport so the currently focused item
+ // is visible.
+
+ // we need to calculate whether we want to request focus here, before the super call,
+ // because after the super call, the first born might be changed.
+ View focusedChild = mLayoutManager.getFocusedChild();
+ View firstBorn = mLayoutManager.getChildAt(0);
+
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mAdapter != null) {
+ int itemCount = mAdapter.getItemCount();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format(
+ "onLayout hasFocus: %s, mLastItemCount: %s, itemCount: %s, "
+ + "focusedChild: %s, firstBorn: %s, isInTouchMode: %s, "
+ + "mNeedsFocus: %s",
+ hasFocus(),
+ mLastItemCount,
+ itemCount,
+ focusedChild,
+ firstBorn,
+ isInTouchMode(),
+ mNeedsFocus));
+ }
+ updateMaxItems();
+ // This is a workaround for missing focus because isInTouchMode() is not always
+ // returning the right value.
+ // This is okay for the Engine release since focus is always showing.
+ // However, in Tala and Fender, we want to show focus only when the user uses
+ // hardware controllers, so we need to revisit this logic. b/22990605.
+ if (mNeedsFocus && itemCount > 0) {
+ if (focusedChild == null) {
+ requestFocus();
+ }
+ mNeedsFocus = false;
+ }
+ if (itemCount > mLastItemCount && focusedChild == firstBorn) {
+ requestFocus();
+ }
+ mLastItemCount = itemCount;
+ }
+ // We need to update the scroll buttons after layout has happened.
+ // Determining if a scrollbar is necessary requires looking at the layout of the child
+ // views. Therefore, this determination can only be done after layout has happened.
+ // Note: don't animate here to prevent b/26849677
+ updatePaginationButtons(false /*animate*/);
+ }
+
+ /**
+ * Returns the View at the given position within the list.
+ *
+ * @param position A position within the list.
+ * @return The View or {@code null} if no View exists at the given position.
+ */
+ @Nullable
+ public View findViewByPosition(int position) {
+ return mLayoutManager.findViewByPosition(position);
+ }
+
+ /**
+ * Determines if scrollbar should be visible or not and shows/hides it accordingly. If this is
+ * being called as a result of adapter changes, it should be called after the new layout has
+ * been calculated because the method of determining scrollbar visibility uses the current
+ * layout. If this is called after an adapter change but before the new layout, the visibility
+ * determination may not be correct.
+ *
+ * @param animate {@code true} if the scrollbar should animate to its new position.
+ * {@code false} if no animation is used
+ */
+ protected void updatePaginationButtons(boolean animate) {
+ if (!mScrollBarEnabled) {
+ // Don't change the visibility of the ScrollBar unless it's enabled.
+ return;
+ }
+
+ if ((mLayoutManager.isAtTop() && mLayoutManager.isAtBottom())
+ || mLayoutManager.getItemCount() == 0) {
+ mScrollBarView.setVisibility(View.INVISIBLE);
+ } else {
+ mScrollBarView.setVisibility(View.VISIBLE);
+ }
+ mScrollBarView.setUpEnabled(shouldEnablePageUpButton());
+ mScrollBarView.setDownEnabled(shouldEnablePageDownButton());
+
+ mScrollBarView.setParameters(
+ mRecyclerView.computeVerticalScrollRange(),
+ mRecyclerView.computeVerticalScrollOffset(),
+ mRecyclerView.computeVerticalScrollExtent(),
+ animate);
+ invalidate();
+ }
+
+ protected boolean shouldEnablePageUpButton() {
+ return !mLayoutManager.isAtTop();
+ }
+
+ protected boolean shouldEnablePageDownButton() {
+ return !mLayoutManager.isAtBottom();
+ }
+
+ @UiThread
+ protected void updateMaxItems() {
+ if (mAdapter == null) {
+ return;
+ }
+
+ final int originalCount = mAdapter.getItemCount();
+ updateRowsPerPage();
+ ((ItemCap) mAdapter).setMaxItems(calculateMaxItemCount());
+ final int newCount = mAdapter.getItemCount();
+ if (newCount == originalCount) {
+ return;
+ }
+
+ if (newCount < originalCount) {
+ mAdapter.notifyItemRangeRemoved(newCount, originalCount - newCount);
+ } else {
+ mAdapter.notifyItemRangeInserted(originalCount, newCount - originalCount);
+ }
+ }
+
+ protected int calculateMaxItemCount() {
+ final View firstChild = mLayoutManager.getChildAt(0);
+ if (firstChild == null || firstChild.getHeight() == 0) {
+ return -1;
+ } else {
+ return (mMaxPages < 0) ? -1 : mRowsPerPage * mMaxPages;
+ }
+ }
+
+ /**
+ * Updates the rows number per current page, which is used for calculating how many items we
+ * want to show.
+ */
+ protected void updateRowsPerPage() {
+ final View firstChild = mLayoutManager.getChildAt(0);
+ if (firstChild == null || firstChild.getHeight() == 0) {
+ mRowsPerPage = 1;
+ } else {
+ mRowsPerPage = Math.max(1, (getHeight() - getPaddingTop()) / firstChild.getHeight());
+ }
+ }
+
+ private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
+ new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrolled(recyclerView, dx, dy);
+ if (!mLayoutManager.isAtTop() && mLayoutManager.isAtBottom()) {
+ mOnScrollListener.onReachBottom();
+ } else if (mLayoutManager.isAtTop() || !mLayoutManager.isAtBottom()) {
+ mOnScrollListener.onLeaveBottom();
+ }
+ }
+ updatePaginationButtons(false);
+ }
+
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrollStateChanged(recyclerView, newState);
+ }
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS);
+ }
+ }
+ };
+
+ protected final Runnable mPaginationRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ boolean upPressed = mScrollBarView.isUpPressed();
+ boolean downPressed = mScrollBarView.isDownPressed();
+ if (upPressed && downPressed) {
+ return;
+ }
+ if (upPressed) {
+ mRecyclerView.pageUp();
+ } else if (downPressed) {
+ mRecyclerView.pageDown();
+ }
+ }
+ };
+
+ private final Runnable mUpdatePaginationRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ updatePaginationButtons(true /*animate*/);
+ }
+ };
+
+ /** Used to listen for {@code PagedListView} scroll events. */
+ public abstract static class OnScrollListener {
+ /** Called when menu reaches the bottom */
+ public void onReachBottom() {}
+ /** Called when menu leaves the bottom */
+ public void onLeaveBottom() {}
+ /** Called when scroll up button is clicked */
+ public void onScrollUpButtonClicked() {}
+ /** Called when scroll down button is clicked */
+ public void onScrollDownButtonClicked() {}
+ /** Called when scrolling to the previous page via up gesture */
+ public void onGestureUp() {}
+ /** Called when scrolling to the next page via down gesture */
+ public void onGestureDown() {}
+
+ /**
+ * Called when RecyclerView.OnScrollListener#onScrolled is called. See
+ * RecyclerView.OnScrollListener
+ */
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {}
+
+ /** See RecyclerView.OnScrollListener */
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {}
+
+ /** Called when the view scrolls up a page */
+ public void onPageUp() {}
+
+ /** Called when the view scrolls down a page */
+ public void onPageDown() {}
+ }
+
+ /**
+ * 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 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;
+
+ /**
+ * @param dividerStartMargin The start offset of the dividing line. This offset will be
+ * relative to {@code dividerStartId} if that value is given.
+ * @param dividerStartId A child view id whose starting edge will be used as the starting
+ * edge of the dividing line. If this value is {@link #INVALID_RESOURCE_ID}, the top
+ * container of each child view will be used.
+ * @param dividerEndId A child view id whose ending edge will be used as the starting edge
+ * of the dividing lin.e If this value is {@link #INVALID_RESOURCE_ID}, then the top
+ * container view of each child will be used.
+ */
+ private DividerDecoration(Context context, int dividerStartMargin,
+ @IdRes int dividerStartId, @IdRes int dividerEndId) {
+ mContext = context;
+ mDividerStartMargin = dividerStartMargin;
+ mDividerStartId = dividerStartId;
+ mDvidierEndId = dividerEndId;
+
+ Resources res = context.getResources();
+ mPaint = new Paint();
+ mPaint.setColor(res.getColor(R.color.car_list_divider));
+ mDividerHeight = res.getDimensionPixelSize(R.dimen.car_divider_height);
+ }
+
+ /** Updates the list divider color which may have changed due to a day night transition. */
+ public void updateDividerColor() {
+ mPaint.setColor(mContext.getResources().getColor(R.color.car_list_divider));
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++) {
+ View container = parent.getChildAt(i);
+ View startChild =
+ mDividerStartId != INVALID_RESOURCE_ID
+ ? container.findViewById(mDividerStartId)
+ : container;
+
+ View endChild =
+ mDvidierEndId != INVALID_RESOURCE_ID
+ ? container.findViewById(mDvidierEndId)
+ : container;
+
+ if (startChild == null || endChild == null) {
+ continue;
+ }
+
+ int left = mDividerStartMargin + startChild.getLeft();
+ int right = endChild.getRight();
+ int bottom = container.getBottom();
+ 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);
+ }
+ }
+ }
+ }
+}
diff --git a/android/support/car/widget/PagedScrollBarView.java b/android/support/car/widget/PagedScrollBarView.java
new file mode 100644
index 00000000..125b354c
--- /dev/null
+++ b/android/support/car/widget/PagedScrollBarView.java
@@ -0,0 +1,253 @@
+/*
+ * 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.support.car.widget;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.support.car.R;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/** A custom view to provide list scroll behaviour -- up/down buttons and scroll indicator. */
+public class PagedScrollBarView extends FrameLayout
+ implements View.OnClickListener, View.OnLongClickListener {
+ private static final float BUTTON_DISABLED_ALPHA = 0.2f;
+
+ @DayNightStyle private int mDayNightStyle;
+
+ /** Listener for when the list should paginate. */
+ public interface PaginationListener {
+ int PAGE_UP = 0;
+ int PAGE_DOWN = 1;
+
+ /** Called when the linked view should be paged in the given direction */
+ void onPaginate(int direction);
+ }
+
+ private final ImageView mUpButton;
+ private final ImageView mDownButton;
+ private final ImageView mScrollThumb;
+ /** The "filler" view between the up and down buttons */
+ private final View mFiller;
+
+ private final Interpolator mPaginationInterpolator = new AccelerateDecelerateInterpolator();
+ private final int mMinThumbLength;
+ private final int mMaxThumbLength;
+ private PaginationListener mPaginationListener;
+
+ public PagedScrollBarView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /*defStyleAttrs*/, 0 /*defStyleRes*/);
+ }
+
+ public PagedScrollBarView(Context context, AttributeSet attrs, int defStyleAttrs) {
+ this(context, attrs, defStyleAttrs, 0 /*defStyleRes*/);
+ }
+
+ public PagedScrollBarView(
+ Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+ super(context, attrs, defStyleAttrs, defStyleRes);
+
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.car_paged_scrollbar_buttons, this /* root */,
+ true /* attachToRoot */);
+
+ mUpButton = (ImageView) findViewById(R.id.page_up);
+ mUpButton.setOnClickListener(this);
+ mUpButton.setOnLongClickListener(this);
+ mDownButton = (ImageView) findViewById(R.id.page_down);
+ mDownButton.setOnClickListener(this);
+ mDownButton.setOnLongClickListener(this);
+
+ mScrollThumb = (ImageView) findViewById(R.id.scrollbar_thumb);
+ mFiller = findViewById(R.id.filler);
+
+ mMinThumbLength = getResources().getDimensionPixelSize(R.dimen.min_thumb_height);
+ mMaxThumbLength = getResources().getDimensionPixelSize(R.dimen.max_thumb_height);
+ }
+
+ @Override
+ public void onClick(View v) {
+ dispatchPageClick(v);
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ dispatchPageClick(v);
+ return true;
+ }
+
+ /**
+ * Sets the listener that will be notified when the up and down buttons have been pressed.
+ *
+ * @param listener The listener to set.
+ */
+ public void setPaginationListener(PaginationListener listener) {
+ mPaginationListener = listener;
+ }
+
+ /** Returns {@code true} if the "up" button is pressed */
+ public boolean isUpPressed() {
+ return mUpButton.isPressed();
+ }
+
+ /** Returns {@code true} if the "down" button is pressed */
+ public boolean isDownPressed() {
+ return mDownButton.isPressed();
+ }
+
+ /** 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
+ // render it within the specified constraints ({@link #mMaxThumbLength} and
+ // {@link #mMinThumbLength}).
+ final int size = mFiller.getHeight() - mFiller.getPaddingTop() - mFiller.getPaddingBottom();
+
+ int thumbLength = extent * size / range;
+ thumbLength = Math.max(Math.min(thumbLength, mMaxThumbLength), mMinThumbLength);
+
+ int thumbOffset = size - thumbLength;
+ if (isDownEnabled()) {
+ // We need to adjust the offset so that it fits into the possible space inside the
+ // filler with regarding to the constraints set by mMaxThumbLength and mMinThumbLength.
+ thumbOffset = (size - thumbLength) * offset / range;
+ }
+
+ // Sets the size of the thumb and request a redraw if needed.
+ final ViewGroup.LayoutParams lp = mScrollThumb.getLayoutParams();
+ if (lp.height != thumbLength) {
+ lp.height = thumbLength;
+ mScrollThumb.requestLayout();
+ }
+
+ moveY(mScrollThumb, thumbOffset, animate);
+ }
+
+ /**
+ * Sets how this {@link PagedScrollBarView} responds to day/night configuration changes. By
+ * default, the PagedScrollBarView is darker in the day and lighter at night.
+ *
+ * @param dayNightStyle A value from {@link DayNightStyle}.
+ * @see DayNightStyle
+ */
+ public void setDayNightStyle(@DayNightStyle int dayNightStyle) {
+ mDayNightStyle = dayNightStyle;
+ reloadColors();
+ }
+
+ /**
+ * Sets whether or not the up button on the scroll bar is clickable.
+ *
+ * @param enabled {@code true} if the up button is enabled.
+ */
+ public void setUpEnabled(boolean enabled) {
+ mUpButton.setEnabled(enabled);
+ mUpButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
+ }
+
+ /**
+ * Sets whether or not the down button on the scroll bar is clickable.
+ *
+ * @param enabled {@code true} if the down button is enabled.
+ */
+ public void setDownEnabled(boolean enabled) {
+ mDownButton.setEnabled(enabled);
+ mDownButton.setAlpha(enabled ? 1f : BUTTON_DISABLED_ALPHA);
+ }
+
+ /**
+ * Returns whether or not the down button on the scroll bar is clickable.
+ *
+ * @return {@code true} if the down button is enabled. {@code false} otherwise.
+ */
+ public boolean isDownEnabled() {
+ return mDownButton.isEnabled();
+ }
+
+ /** Reload the colors for the current {@link DayNightStyle}. */
+ private void reloadColors() {
+ int tint;
+ int thumbBackground;
+ int upDownBackgroundResId;
+
+ switch (mDayNightStyle) {
+ case DayNightStyle.AUTO:
+ tint = ContextCompat.getColor(getContext(), R.color.car_tint);
+ thumbBackground = ContextCompat.getColor(getContext(),
+ R.color.car_scrollbar_thumb);
+ upDownBackgroundResId = R.drawable.car_pagination_background;
+ break;
+ case DayNightStyle.AUTO_INVERSE:
+ tint = ContextCompat.getColor(getContext(), R.color.car_tint_inverse);
+ thumbBackground = ContextCompat.getColor(getContext(),
+ R.color.car_scrollbar_thumb_inverse);
+ upDownBackgroundResId = R.drawable.car_pagination_background_inverse;
+ break;
+ case DayNightStyle.FORCE_NIGHT:
+ tint = ContextCompat.getColor(getContext(), R.color.car_tint_light);
+ thumbBackground = ContextCompat.getColor(getContext(),
+ R.color.car_scrollbar_thumb_light);
+ upDownBackgroundResId = R.drawable.car_pagination_background_night;
+ break;
+ case DayNightStyle.FORCE_DAY:
+ tint = ContextCompat.getColor(getContext(), R.color.car_tint_dark);
+ thumbBackground = ContextCompat.getColor(getContext(),
+ R.color.car_scrollbar_thumb_dark);
+ upDownBackgroundResId = R.drawable.car_pagination_background_day;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown DayNightStyle: " + mDayNightStyle);
+ }
+
+ mScrollThumb.setBackgroundColor(thumbBackground);
+
+ mUpButton.setColorFilter(tint, PorterDuff.Mode.SRC_IN);
+ mUpButton.setBackgroundResource(upDownBackgroundResId);
+
+ mDownButton.setColorFilter(tint, PorterDuff.Mode.SRC_IN);
+ mDownButton.setBackgroundResource(upDownBackgroundResId);
+ }
+
+ private void dispatchPageClick(View v) {
+ final PaginationListener listener = mPaginationListener;
+ if (listener == null) {
+ return;
+ }
+
+ int direction = v.getId() == R.id.page_up
+ ? PaginationListener.PAGE_UP
+ : PaginationListener.PAGE_DOWN;
+ listener.onPaginate(direction);
+ }
+
+ /** Moves the given view to the specified 'y' position. */
+ private void moveY(final View view, float newPosition, boolean animate) {
+ final int duration = animate ? 200 : 0;
+ view.animate()
+ .y(newPosition)
+ .setDuration(duration)
+ .setInterpolator(mPaginationInterpolator)
+ .start();
+ }
+}
diff --git a/android/support/customtabs/CustomTabsCallback.java b/android/support/customtabs/CustomTabsCallback.java
index 818118a0..f8d349a8 100644
--- a/android/support/customtabs/CustomTabsCallback.java
+++ b/android/support/customtabs/CustomTabsCallback.java
@@ -16,7 +16,9 @@
package android.support.customtabs;
+import android.net.Uri;
import android.os.Bundle;
+import android.support.customtabs.CustomTabsService.Relation;
/**
* A callback class for custom tabs client to get messages regarding events in their custom tabs. In
@@ -98,4 +100,18 @@ public class CustomTabsCallback {
* @param extras Reserved for future use.
*/
public void onPostMessage(String message, Bundle extras) {}
+
+ /**
+ * Called when a relationship validation result is available.
+ *
+ * @param relation Relation for which the result is available. Value previously passed to
+ * {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. Must be one
+ * of the {@code CustomTabsService#RELATION_* } constants.
+ * @param requestedOrigin Origin requested. Value previously passed to
+ * {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}.
+ * @param result Whether the relation was validated.
+ * @param extras Reserved for future use.
+ */
+ public void onRelationshipValidationResult(@Relation int relation, Uri requestedOrigin,
+ boolean result, Bundle extras) {}
}
diff --git a/android/support/customtabs/CustomTabsClient.java b/android/support/customtabs/CustomTabsClient.java
index 09f31109..2e955cbe 100644
--- a/android/support/customtabs/CustomTabsClient.java
+++ b/android/support/customtabs/CustomTabsClient.java
@@ -31,6 +31,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.customtabs.CustomTabsService.Relation;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -234,6 +235,20 @@ public class CustomTabsClient {
}
});
}
+
+ @Override
+ public void onRelationshipValidationResult(
+ final @Relation int relation, final Uri requestedOrigin, final boolean result,
+ final @Nullable Bundle extras) throws RemoteException {
+ if (callback == null) return;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onRelationshipValidationResult(
+ relation, requestedOrigin, result, extras);
+ }
+ });
+ }
};
try {
diff --git a/android/support/customtabs/CustomTabsService.java b/android/support/customtabs/CustomTabsService.java
index 5a940cf4..aad174c0 100644
--- a/android/support/customtabs/CustomTabsService.java
+++ b/android/support/customtabs/CustomTabsService.java
@@ -78,6 +78,23 @@ public abstract class CustomTabsService extends Service {
*/
public static final int RESULT_FAILURE_MESSAGING_ERROR = -3;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({RELATION_USE_AS_ORIGIN, RELATION_HANDLE_ALL_URLS})
+ public @interface Relation {
+ }
+
+ /**
+ * Used for {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. For
+ * App -> Web transitions, requests the app to use the declared origin to be used as origin for
+ * the client app in the web APIs context.
+ */
+ public static final int RELATION_USE_AS_ORIGIN = 1;
+ /**
+ * Used for {@link CustomTabsSession#validateRelationship(int, Uri, Bundle)}. Requests the
+ * ability to handle all URLs from a given origin.
+ */
+ public static final int RELATION_HANDLE_ALL_URLS = 2;
+
private final Map<IBinder, DeathRecipient> mDeathRecipientMap = new ArrayMap<>();
private ICustomTabsService.Stub mBinder = new ICustomTabsService.Stub() {
@@ -137,6 +154,13 @@ public abstract class CustomTabsService extends Service {
return CustomTabsService.this.postMessage(
new CustomTabsSessionToken(callback), message, extras);
}
+
+ @Override
+ public boolean validateRelationship(
+ ICustomTabsCallback callback, @Relation int relation, Uri origin, Bundle extras) {
+ return CustomTabsService.this.validateRelationship(
+ new CustomTabsSessionToken(callback), relation, origin, extras);
+ }
};
@Override
@@ -268,4 +292,23 @@ public abstract class CustomTabsService extends Service {
@Result
protected abstract int postMessage(
CustomTabsSessionToken sessionToken, String message, Bundle extras);
+
+ /**
+ * Request to validate a relationship between the application and an origin.
+ *
+ * If this method returns true, the validation result will be provided through
+ * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}.
+ * Otherwise the request didn't succeed. The client must call
+ * {@link CustomTabsClient#warmup(long)} before this.
+ *
+ * @param sessionToken The unique identifier for the session. Can not be null.
+ * @param relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* }
+ * constants.
+ * @param origin Origin for the relation query.
+ * @param extras Reserved for future use.
+ * @return true if the request has been submitted successfully.
+ */
+ protected abstract boolean validateRelationship(
+ CustomTabsSessionToken sessionToken, @Relation int relation, Uri origin,
+ Bundle extras);
}
diff --git a/android/support/customtabs/CustomTabsSession.java b/android/support/customtabs/CustomTabsSession.java
index cad897c8..a84d63c7 100644
--- a/android/support/customtabs/CustomTabsSession.java
+++ b/android/support/customtabs/CustomTabsSession.java
@@ -25,6 +25,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.customtabs.CustomTabsService.Relation;
import android.support.customtabs.CustomTabsService.Result;
import android.view.View;
import android.widget.RemoteViews;
@@ -42,6 +44,21 @@ public final class CustomTabsSession {
private final ICustomTabsCallback mCallback;
private final ComponentName mComponentName;
+ /**
+ * Provides browsers a way to generate a mock {@link CustomTabsSession} for testing
+ * purposes.
+ *
+ * @param componentName The component the session should be created for.
+ * @return A mock session with no functionality.
+ */
+ @VisibleForTesting
+ @NonNull
+ public static CustomTabsSession createMockSessionForTesting(
+ @NonNull ComponentName componentName) {
+ return new CustomTabsSession(
+ null, new CustomTabsSessionToken.MockCallback(), componentName);
+ }
+
/* package */ CustomTabsSession(
ICustomTabsService service, ICustomTabsCallback callback, ComponentName componentName) {
mService = service;
@@ -185,6 +202,39 @@ public final class CustomTabsSession {
}
}
+ /**
+ * Requests to validate a relationship between the application and an origin.
+ *
+ * <p>
+ * See <a href="https://developers.google.com/digital-asset-links/v1/getting-started">here</a>
+ * for documentation about Digital Asset Links. This methods requests the browser to verify
+ * a relation with the calling application, to grant the associated rights.
+ *
+ * <p>
+ * If this method returns {@code true}, the validation result will be provided through
+ * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}.
+ * Otherwise the request didn't succeed. The client must call
+ * {@link CustomTabsClient#warmup(long)} before this.
+ *
+ * @param relation Relation to check, must be one of the {@code CustomTabsService#RELATION_* }
+ * constants.
+ * @param origin Origin.
+ * @param extras Reserved for future use.
+ * @return {@code true} if the request has been submitted successfully.
+ */
+ public boolean validateRelationship(@Relation int relation, @NonNull Uri origin,
+ @Nullable Bundle extras) {
+ if (relation < CustomTabsService.RELATION_USE_AS_ORIGIN
+ || relation > CustomTabsService.RELATION_HANDLE_ALL_URLS) {
+ return false;
+ }
+ try {
+ return mService.validateRelationship(mCallback, relation, origin, extras);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
/* package */ IBinder getBinder() {
return mCallback.asBinder();
}
diff --git a/android/support/customtabs/CustomTabsSessionToken.java b/android/support/customtabs/CustomTabsSessionToken.java
index adfadd92..5a9e1b66 100644
--- a/android/support/customtabs/CustomTabsSessionToken.java
+++ b/android/support/customtabs/CustomTabsSessionToken.java
@@ -17,9 +17,12 @@
package android.support.customtabs;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.customtabs.CustomTabsService.Relation;
import android.support.v4.app.BundleCompat;
import android.util.Log;
@@ -32,6 +35,29 @@ public class CustomTabsSessionToken {
private final ICustomTabsCallback mCallbackBinder;
private final CustomTabsCallback mCallback;
+ /* package */ static class MockCallback extends ICustomTabsCallback.Stub {
+ @Override
+ public void onNavigationEvent(int navigationEvent, Bundle extras) {}
+
+ @Override
+ public void extraCallback(String callbackName, Bundle args) {}
+
+ @Override
+ public void onMessageChannelReady(Bundle extras) {}
+
+ @Override
+ public void onPostMessage(String message, Bundle extras) {}
+
+ @Override
+ public void onRelationshipValidationResult(@Relation int relation, Uri requestedOrigin,
+ boolean result, Bundle extras) {}
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+ }
+
/**
* Obtain a {@link CustomTabsSessionToken} from an intent. See {@link CustomTabsIntent.Builder}
* for ways to generate an intent for custom tabs.
@@ -46,6 +72,17 @@ public class CustomTabsSessionToken {
return new CustomTabsSessionToken(ICustomTabsCallback.Stub.asInterface(binder));
}
+ /**
+ * Provides browsers a way to generate a mock {@link CustomTabsSessionToken} for testing
+ * purposes.
+ *
+ * @return A mock token with no functionality.
+ */
+ @NonNull
+ public static CustomTabsSessionToken createMockSessionTokenForTesting() {
+ return new CustomTabsSessionToken(new MockCallback());
+ }
+
CustomTabsSessionToken(ICustomTabsCallback callbackBinder) {
mCallbackBinder = callbackBinder;
mCallback = new CustomTabsCallback() {
@@ -85,6 +122,18 @@ public class CustomTabsSessionToken {
Log.e(TAG, "RemoteException during ICustomTabsCallback transaction");
}
}
+
+ @Override
+ public void onRelationshipValidationResult(@Relation int relation, Uri origin,
+ boolean result, Bundle extras) {
+ try {
+ mCallbackBinder.onRelationshipValidationResult(
+ relation, origin, result, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException during ICustomTabsCallback transaction");
+ }
+ }
+
};
}
diff --git a/android/support/customtabs/TrustedWebUtils.java b/android/support/customtabs/TrustedWebUtils.java
new file mode 100644
index 00000000..e9a22332
--- /dev/null
+++ b/android/support/customtabs/TrustedWebUtils.java
@@ -0,0 +1,82 @@
+/*
+ * 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.support.customtabs;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.BundleCompat;
+
+/**
+ * Class for utilities and convenience calls for opening a qualifying web page as a
+ * Trusted Web Activity.
+ *
+ * Trusted Web Activity is a fullscreen UI with no visible browser controls that hosts web pages
+ * meeting certain criteria. The full list of qualifications is at the implementing browser's
+ * discretion, but minimum recommended set is for the web page :
+ * <ul>
+ * <li>To have declared delegate_permission/common.handle_all_urls relationship with the
+ * launching client application ensuring 1:1 trust between the Android native and web
+ * components. See https://developers.google.com/digital-asset-links/ for details.</li>
+ * <li>To work as a reliable, fast and engaging standalone component within the launching app's
+ * flow.</li>
+ * <li>To be accessible and operable even when offline.</li>
+ * </ul>
+ *
+ * Fallback behaviors may also differ with implementation. Possibilities are launching the page in
+ * a custom tab, or showing it in browser UI. Browsers are encouraged to use
+ * {@link CustomTabsCallback#onRelationshipValidationResult(int, Uri, boolean, Bundle)}
+ * for sending details of the verification results.
+ */
+public class TrustedWebUtils {
+
+ /**
+ * Boolean extra that triggers a {@link CustomTabsIntent} launch to be in a fullscreen UI with
+ * no browser controls.
+ *
+ * @see TrustedWebUtils#launchAsTrustedWebActivity(Context, CustomTabsIntent, Uri).
+ */
+ public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY =
+ "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+
+ private TrustedWebUtils() {}
+
+ /**
+ * Launch the given {@link CustomTabsIntent} as a Trusted Web Activity. The given
+ * {@link CustomTabsIntent} should have a valid {@link CustomTabsSession} associated with it
+ * during construction. Once the Trusted Web Activity is launched, browser side implementations
+ * may have their own fallback behavior (e.g. Showing the page in a custom tab UI with toolbar)
+ * based on qualifications listed above or more.
+ *
+ * @param context {@link Context} to use while launching the {@link CustomTabsIntent}.
+ * @param customTabsIntent The {@link CustomTabsIntent} to use for launching the
+ * Trusted Web Activity. Note that all customizations in the given
+ * associated with browser toolbar controls will be ignored.
+ * @param uri The web page to launch as Trusted Web Activity.
+ */
+ public static void launchAsTrustedWebActivity(@NonNull Context context,
+ @NonNull CustomTabsIntent customTabsIntent, @NonNull Uri uri) {
+ if (BundleCompat.getBinder(
+ customTabsIntent.intent.getExtras(), CustomTabsIntent.EXTRA_SESSION) == null) {
+ throw new IllegalArgumentException(
+ "Given CustomTabsIntent should be associated with a valid CustomTabsSession");
+ }
+ customTabsIntent.intent.putExtra(EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY, true);
+ customTabsIntent.launchUrl(context, uri);
+ }
+}
diff --git a/android/support/media/ExifInterface.java b/android/support/media/ExifInterface.java
index b790cd27..72b61cb7 100644
--- a/android/support/media/ExifInterface.java
+++ b/android/support/media/ExifInterface.java
@@ -22,6 +22,7 @@ import android.graphics.BitmapFactory;
import android.location.Location;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
@@ -3699,7 +3700,7 @@ public class ExifInterface {
/**
* Reads Exif tags from the specified image file.
*/
- public ExifInterface(String filename) throws IOException {
+ public ExifInterface(@NonNull String filename) throws IOException {
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
@@ -3720,7 +3721,7 @@ public class ExifInterface {
* should close the input stream after use. This constructor is not intended to be used with
* an input stream that performs any networking operations.
*/
- public ExifInterface(InputStream inputStream) throws IOException {
+ public ExifInterface(@NonNull InputStream inputStream) throws IOException {
if (inputStream == null) {
throw new IllegalArgumentException("inputStream cannot be null");
}
@@ -3739,7 +3740,8 @@ public class ExifInterface {
*
* @param tag the name of the tag.
*/
- private ExifAttribute getExifAttribute(String tag) {
+ @Nullable
+ private ExifAttribute getExifAttribute(@NonNull String tag) {
if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
if (DEBUG) {
Log.d(TAG, "getExifAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
@@ -3764,7 +3766,8 @@ public class ExifInterface {
*
* @param tag the name of the tag.
*/
- public String getAttribute(String tag) {
+ @Nullable
+ public String getAttribute(@NonNull String tag) {
ExifAttribute attribute = getExifAttribute(tag);
if (attribute != null) {
if (!sTagSetForCompatibility.contains(tag)) {
@@ -3804,7 +3807,7 @@ public class ExifInterface {
* @param tag the name of the tag.
* @param defaultValue the value to return if the tag is not available.
*/
- public int getAttributeInt(String tag, int defaultValue) {
+ public int getAttributeInt(@NonNull String tag, int defaultValue) {
ExifAttribute exifAttribute = getExifAttribute(tag);
if (exifAttribute == null) {
return defaultValue;
@@ -3825,7 +3828,7 @@ public class ExifInterface {
* @param tag the name of the tag.
* @param defaultValue the value to return if the tag is not available.
*/
- public double getAttributeDouble(String tag, double defaultValue) {
+ public double getAttributeDouble(@NonNull String tag, double defaultValue) {
ExifAttribute exifAttribute = getExifAttribute(tag);
if (exifAttribute == null) {
return defaultValue;
@@ -3844,7 +3847,7 @@ public class ExifInterface {
* @param tag the name of the tag.
* @param value the value of the tag.
*/
- public void setAttribute(String tag, String value) {
+ public void setAttribute(@NonNull String tag, @Nullable String value) {
if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
if (DEBUG) {
Log.d(TAG, "setAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
@@ -4320,6 +4323,7 @@ public class ExifInterface {
* The returned data can be decoded using
* {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
*/
+ @Nullable
public byte[] getThumbnail() {
if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
return getThumbnailBytes();
@@ -4331,6 +4335,7 @@ public class ExifInterface {
* Returns the thumbnail bytes inside the image file, regardless of the compression type of the
* thumbnail image.
*/
+ @Nullable
public byte[] getThumbnailBytes() {
if (!mHasThumbnail) {
return null;
@@ -4379,6 +4384,7 @@ public class ExifInterface {
* Creates and returns a Bitmap object of the thumbnail image based on the byte array and the
* thumbnail compression value, or {@code null} if the compression type is unsupported.
*/
+ @Nullable
public Bitmap getThumbnailBitmap() {
if (!mHasThumbnail) {
return null;
@@ -4425,6 +4431,7 @@ public class ExifInterface {
* @return two-element array, the offset in the first value, and length in
* the second, or {@code null} if no thumbnail was found.
*/
+ @Nullable
public long[] getThumbnailRange() {
if (!mHasThumbnail) {
return null;
@@ -4462,6 +4469,7 @@ public class ExifInterface {
* array where the first element is the latitude and the second element is the longitude.
* Otherwise, it returns null.
*/
+ @Nullable
public double[] getLatLong() {
String latValue = getAttribute(TAG_GPS_LATITUDE);
String latRef = getAttribute(TAG_GPS_LATITUDE_REF);
diff --git a/android/support/percent/PercentFrameLayout.java b/android/support/percent/PercentFrameLayout.java
index b9abd39f..41908585 100644
--- a/android/support/percent/PercentFrameLayout.java
+++ b/android/support/percent/PercentFrameLayout.java
@@ -126,6 +126,7 @@ import android.widget.FrameLayout;
* app:layout_constraintBottom_toBottomOf="@+id/bottom_guideline" /&gt
*
* &lt;/android.support.constraint.ConstraintLayout&gt
+ * </pre>
*/
@Deprecated
public class PercentFrameLayout extends FrameLayout {
diff --git a/android/support/testutils/AppCompatActivityUtils.java b/android/support/testutils/AppCompatActivityUtils.java
new file mode 100644
index 00000000..49ccc1be
--- /dev/null
+++ b/android/support/testutils/AppCompatActivityUtils.java
@@ -0,0 +1,95 @@
+/*
+ * 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.support.testutils;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for testing AppCompat activities.
+ */
+public class AppCompatActivityUtils {
+ private static final Runnable DO_NOTHING = new Runnable() {
+ @Override
+ public void run() {
+ }
+ };
+
+ /**
+ * Waits for the execution of the provided activity test rule.
+ *
+ * @param rule Activity test rule to wait for.
+ */
+ public static void waitForExecution(
+ final ActivityTestRule<? extends RecreatedAppCompatActivity> rule) {
+ // Wait for two cycles. When starting a postponed transition, it will post to
+ // the UI thread and then the execution will be added onto the queue after that.
+ // The two-cycle wait makes sure fragments have the opportunity to complete both
+ // before returning.
+ try {
+ rule.runOnUiThread(DO_NOTHING);
+ rule.runOnUiThread(DO_NOTHING);
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+
+ private static void runOnUiThreadRethrow(
+ ActivityTestRule<? extends RecreatedAppCompatActivity> rule, Runnable r) {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ r.run();
+ } else {
+ try {
+ rule.runOnUiThread(r);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+
+ /**
+ * Restarts the RecreatedAppCompatActivity and waits for the new activity to be resumed.
+ *
+ * @return The newly-restarted RecreatedAppCompatActivity
+ */
+ public static <T extends RecreatedAppCompatActivity> T recreateActivity(
+ ActivityTestRule<? extends RecreatedAppCompatActivity> rule, final T activity)
+ throws InterruptedException {
+ // Now switch the orientation
+ RecreatedAppCompatActivity.sResumed = new CountDownLatch(1);
+ RecreatedAppCompatActivity.sDestroyed = new CountDownLatch(1);
+
+ runOnUiThreadRethrow(rule, new Runnable() {
+ @Override
+ public void run() {
+ activity.recreate();
+ }
+ });
+ assertTrue(RecreatedAppCompatActivity.sResumed.await(1, TimeUnit.SECONDS));
+ assertTrue(RecreatedAppCompatActivity.sDestroyed.await(1, TimeUnit.SECONDS));
+ T newActivity = (T) RecreatedAppCompatActivity.sActivity;
+
+ waitForExecution(rule);
+
+ RecreatedAppCompatActivity.clearState();
+ return newActivity;
+ }
+}
diff --git a/android/support/testutils/FragmentActivityUtils.java b/android/support/testutils/FragmentActivityUtils.java
new file mode 100644
index 00000000..7d12deb8
--- /dev/null
+++ b/android/support/testutils/FragmentActivityUtils.java
@@ -0,0 +1,91 @@
+/*
+ * 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.support.testutils;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.Looper;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for testing fragment activities.
+ */
+public class FragmentActivityUtils {
+ private static final Runnable DO_NOTHING = new Runnable() {
+ @Override
+ public void run() {
+ }
+ };
+
+ private static void waitForExecution(final ActivityTestRule<? extends FragmentActivity> rule) {
+ // Wait for two cycles. When starting a postponed transition, it will post to
+ // the UI thread and then the execution will be added onto the queue after that.
+ // The two-cycle wait makes sure fragments have the opportunity to complete both
+ // before returning.
+ try {
+ rule.runOnUiThread(DO_NOTHING);
+ rule.runOnUiThread(DO_NOTHING);
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+
+ private static void runOnUiThreadRethrow(ActivityTestRule<? extends Activity> rule,
+ Runnable r) {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ r.run();
+ } else {
+ try {
+ rule.runOnUiThread(r);
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+
+ /**
+ * Restarts the RecreatedActivity and waits for the new activity to be resumed.
+ *
+ * @return The newly-restarted Activity
+ */
+ public static <T extends RecreatedActivity> T recreateActivity(
+ ActivityTestRule<? extends RecreatedActivity> rule, final T activity)
+ throws InterruptedException {
+ // Now switch the orientation
+ RecreatedActivity.sResumed = new CountDownLatch(1);
+ RecreatedActivity.sDestroyed = new CountDownLatch(1);
+
+ runOnUiThreadRethrow(rule, new Runnable() {
+ @Override
+ public void run() {
+ activity.recreate();
+ }
+ });
+ assertTrue(RecreatedActivity.sResumed.await(1, TimeUnit.SECONDS));
+ assertTrue(RecreatedActivity.sDestroyed.await(1, TimeUnit.SECONDS));
+ T newActivity = (T) RecreatedActivity.sActivity;
+
+ waitForExecution(rule);
+
+ RecreatedActivity.clearState();
+ return newActivity;
+ }
+}
diff --git a/android/support/testutils/RecreatedActivity.java b/android/support/testutils/RecreatedActivity.java
new file mode 100644
index 00000000..aaea3a9f
--- /dev/null
+++ b/android/support/testutils/RecreatedActivity.java
@@ -0,0 +1,64 @@
+/*
+ * 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.support.testutils;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Extension of {@link FragmentActivity} that keeps track of when it is recreated.
+ * In order to use this class, have your activity extend it and call
+ * {@link FragmentActivityUtils#recreateActivity(ActivityTestRule, RecreatedActivity)} API.
+ */
+public class RecreatedActivity extends FragmentActivity {
+ // These must be cleared after each test using clearState()
+ public static RecreatedActivity sActivity;
+ public static CountDownLatch sResumed;
+ public static CountDownLatch sDestroyed;
+
+ static void clearState() {
+ sActivity = null;
+ sResumed = null;
+ sDestroyed = null;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sActivity = this;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (sResumed != null) {
+ sResumed.countDown();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (sDestroyed != null) {
+ sDestroyed.countDown();
+ }
+ }
+}
diff --git a/android/support/testutils/RecreatedAppCompatActivity.java b/android/support/testutils/RecreatedAppCompatActivity.java
new file mode 100644
index 00000000..d5645a30
--- /dev/null
+++ b/android/support/testutils/RecreatedAppCompatActivity.java
@@ -0,0 +1,65 @@
+/*
+ * 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.support.testutils;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v7.app.AppCompatActivity;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Extension of {@link AppCompatActivity} that keeps track of when it is recreated.
+ * In order to use this class, have your activity extend it and call
+ * {@link AppCompatActivityUtils#recreateActivity(ActivityTestRule, RecreatedAppCompatActivity)}
+ * API.
+ */
+public class RecreatedAppCompatActivity extends AppCompatActivity {
+ // These must be cleared after each test using clearState()
+ public static RecreatedAppCompatActivity sActivity;
+ public static CountDownLatch sResumed;
+ public static CountDownLatch sDestroyed;
+
+ static void clearState() {
+ sActivity = null;
+ sResumed = null;
+ sDestroyed = null;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sActivity = this;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (sResumed != null) {
+ sResumed.countDown();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (sDestroyed != null) {
+ sDestroyed.countDown();
+ }
+ }
+}
diff --git a/android/support/text/emoji/widget/EmojiEditTextHelper.java b/android/support/text/emoji/widget/EmojiEditTextHelper.java
index edc511f5..a999e342 100644
--- a/android/support/text/emoji/widget/EmojiEditTextHelper.java
+++ b/android/support/text/emoji/widget/EmojiEditTextHelper.java
@@ -20,6 +20,7 @@ import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.os.Build;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
import android.support.text.emoji.EmojiCompat;
@@ -130,7 +131,8 @@ public final class EmojiEditTextHelper {
/**
* Updates the InputConnection with emoji support. Should be called from {@link
* TextView#onCreateInputConnection(EditorInfo)}. When used on devices running API 18 or below,
- * this method returns {@code inputConnection} that is given as a parameter.
+ * this method returns {@code inputConnection} that is given as a parameter. If
+ * {@code inputConnection} is {@code null}, returns {@code null}.
*
* @param inputConnection InputConnection instance created by TextView
* @param outAttrs EditorInfo passed into
@@ -138,10 +140,10 @@ public final class EmojiEditTextHelper {
*
* @return a new InputConnection instance that wraps {@code inputConnection}
*/
- @NonNull
- public InputConnection onCreateInputConnection(@NonNull final InputConnection inputConnection,
+ @Nullable
+ public InputConnection onCreateInputConnection(@Nullable final InputConnection inputConnection,
@NonNull final EditorInfo outAttrs) {
- Preconditions.checkNotNull(inputConnection, "inputConnection cannot be null");
+ if (inputConnection == null) return null;
return mHelper.onCreateInputConnection(inputConnection, outAttrs);
}
diff --git a/android/support/v13/app/FragmentCompat.java b/android/support/v13/app/FragmentCompat.java
index 4f210525..31c2343e 100644
--- a/android/support/v13/app/FragmentCompat.java
+++ b/android/support/v13/app/FragmentCompat.java
@@ -24,6 +24,7 @@ import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
import java.util.Arrays;
@@ -37,6 +38,38 @@ public class FragmentCompat {
boolean shouldShowRequestPermissionRationale(Fragment fragment, String permission);
}
+ /**
+ * Customizable delegate that allows delegating permission related compatibility methods
+ * to a custom implementation.
+ *
+ * <p>
+ * To delegate fragment compatibility methods to a custom class, implement this interface,
+ * and call {@code FragmentCompat.setPermissionCompatDelegate(delegate);}. All future calls
+ * to the compatibility methods in this class will first check whether the delegate can
+ * handle the method call, and invoke the corresponding method if it can.
+ * </p>
+ */
+ public interface PermissionCompatDelegate {
+
+ /**
+ * Determines whether the delegate should handle
+ * {@link FragmentCompat#requestPermissions(Fragment, String[], int)}, and request
+ * permissions if applicable. If this method returns true, it means that permission
+ * request is successfully handled by the delegate, and platform should not perform any
+ * further requests for permission.
+ *
+ * @param fragment The target fragment.
+ * @param permissions The requested permissions.
+ * @param requestCode Application specific request code to match with a result
+ * reported to {@link OnRequestPermissionsResultCallback#onRequestPermissionsResult(
+ * int, String[], int[])}.
+ *
+ * @return Whether the delegate has handled the permission request.
+ * @see FragmentCompat#requestPermissions(Fragment, String[], int)
+ */
+ boolean requestPermissions(Fragment fragment, String[] permissions, int requestCode);
+ }
+
static class FragmentCompatBaseImpl implements FragmentCompatImpl {
@Override
public void setUserVisibleHint(Fragment f, boolean deferStart) {
@@ -117,6 +150,26 @@ public class FragmentCompat {
}
}
+ private static PermissionCompatDelegate sDelegate;
+
+ /**
+ * Sets the permission delegate for {@code FragmentCompat}. Replaces the previously set
+ * delegate.
+ *
+ * @param delegate The delegate to be set. {@code null} to clear the set delegate.
+ */
+ public static void setPermissionCompatDelegate(PermissionCompatDelegate delegate) {
+ sDelegate = delegate;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static PermissionCompatDelegate getPermissionCompatDelegate() {
+ return sDelegate;
+ }
+
/**
* This interface is the contract for receiving the results for permission requests.
*/
@@ -212,6 +265,11 @@ public class FragmentCompat {
*/
public static void requestPermissions(@NonNull Fragment fragment,
@NonNull String[] permissions, int requestCode) {
+ if (sDelegate != null && sDelegate.requestPermissions(fragment, permissions, requestCode)) {
+ // Delegate has handled the request.
+ return;
+ }
+
IMPL.requestPermissions(fragment, permissions, requestCode);
}
diff --git a/android/support/v13/app/FragmentPagerAdapter.java b/android/support/v13/app/FragmentPagerAdapter.java
index 082f883f..e0b788ab 100644
--- a/android/support/v13/app/FragmentPagerAdapter.java
+++ b/android/support/v13/app/FragmentPagerAdapter.java
@@ -48,18 +48,18 @@ import android.view.ViewGroup;
* <p>Here is an example implementation of a pager containing fragments of
* lists:
*
- * {@sample frameworks/support/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentPagerSupport.java
+ * {@sample frameworks/support/samples/Support13Demos/src/main/java/com/example/android/supportv13/app/FragmentPagerSupport.java
* complete}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
- * {@sample frameworks/support/samples/Support13Demos/res/layout/fragment_pager.xml
+ * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
* individual fragment's layout is:
*
- * {@sample frameworks/support/samples/Support13Demos/res/layout/fragment_pager_list.xml
+ * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager_list.xml
* complete}
*/
public abstract class FragmentPagerAdapter extends PagerAdapter {
diff --git a/android/support/v13/app/FragmentStatePagerAdapter.java b/android/support/v13/app/FragmentStatePagerAdapter.java
index 8907fec5..45a6bf53 100644
--- a/android/support/v13/app/FragmentStatePagerAdapter.java
+++ b/android/support/v13/app/FragmentStatePagerAdapter.java
@@ -51,18 +51,18 @@ import java.util.ArrayList;
* <p>Here is an example implementation of a pager containing fragments of
* lists:
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentStatePagerSupport.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentStatePagerSupport.java
* complete}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_pager.xml
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
* individual fragment's layout is:
*
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_pager_list.xml
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
* complete}
*/
public abstract class FragmentStatePagerAdapter extends PagerAdapter {
diff --git a/android/support/v14/preference/PreferenceFragment.java b/android/support/v14/preference/PreferenceFragment.java
index d1d9987a..24210505 100644
--- a/android/support/v14/preference/PreferenceFragment.java
+++ b/android/support/v14/preference/PreferenceFragment.java
@@ -103,13 +103,13 @@ import android.view.ViewGroup;
* <p>The following sample code shows a simple preference fragment that is
* populated from a resource. The resource it loads is:</p>
*
- * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
*
* <p>The fragment implementation itself simply populates the preferences
* when created. Note that the preferences framework takes care of loading
* the current values out of the app preferences and writing them when changed:</p>
*
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferences.java
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferences.java
* support_fragment}
*
* @see Preference
diff --git a/android/support/v17/leanback/app/BaseFragment.java b/android/support/v17/leanback/app/BaseFragment.java
index 7686c5c8..bdb213f2 100644
--- a/android/support/v17/leanback/app/BaseFragment.java
+++ b/android/support/v17/leanback/app/BaseFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BaseSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -15,6 +18,8 @@ package android.support.v17.leanback.app;
import android.annotation.SuppressLint;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.transition.TransitionListener;
import android.support.v17.leanback.util.StateMachine;
@@ -177,7 +182,7 @@ public class BaseFragment extends BrandedFragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStateMachine.fireEvent(EVT_ON_CREATEVIEW);
}
@@ -267,6 +272,10 @@ public class BaseFragment extends BrandedFragment {
void onExecuteEntranceTransition() {
// wait till views get their initial position before start transition
final View view = getView();
+ if (view == null) {
+ // fragment view destroyed, transition not needed
+ return;
+ }
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
diff --git a/android/support/v17/leanback/app/BaseRowFragment.java b/android/support/v17/leanback/app/BaseRowFragment.java
index 5a83b478..2d79f3e1 100644
--- a/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/android/support/v17/leanback/app/BaseRowFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BaseRowSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -13,8 +16,9 @@
*/
package android.support.v17.leanback.app;
-import android.app.Fragment;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ObjectAdapter;
@@ -22,6 +26,7 @@ import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -75,7 +80,7 @@ abstract class BaseRowFragment extends Fragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
}
diff --git a/android/support/v17/leanback/app/BaseRowSupportFragment.java b/android/support/v17/leanback/app/BaseRowSupportFragment.java
index bf49295c..dba78daf 100644
--- a/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ b/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BaseRowFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -16,8 +13,9 @@
*/
package android.support.v17.leanback.app;
-import android.support.v4.app.Fragment;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ObjectAdapter;
@@ -25,6 +23,7 @@ import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -78,7 +77,7 @@ abstract class BaseRowSupportFragment extends Fragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
}
diff --git a/android/support/v17/leanback/app/BaseSupportFragment.java b/android/support/v17/leanback/app/BaseSupportFragment.java
index 213ed834..d89cf39f 100644
--- a/android/support/v17/leanback/app/BaseSupportFragment.java
+++ b/android/support/v17/leanback/app/BaseSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BaseFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -18,6 +15,8 @@ package android.support.v17.leanback.app;
import android.annotation.SuppressLint;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.transition.TransitionListener;
import android.support.v17.leanback.util.StateMachine;
@@ -180,7 +179,7 @@ public class BaseSupportFragment extends BrandedSupportFragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStateMachine.fireEvent(EVT_ON_CREATEVIEW);
}
@@ -270,6 +269,10 @@ public class BaseSupportFragment extends BrandedSupportFragment {
void onExecuteEntranceTransition() {
// wait till views get their initial position before start transition
final View view = getView();
+ if (view == null) {
+ // fragment view destroyed, transition not needed
+ return;
+ }
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
diff --git a/android/support/v17/leanback/app/BrandedFragment.java b/android/support/v17/leanback/app/BrandedFragment.java
index 35350e41..1f6ad299 100644
--- a/android/support/v17/leanback/app/BrandedFragment.java
+++ b/android/support/v17/leanback/app/BrandedFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrandedSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -13,13 +16,15 @@
*/
package android.support.v17.leanback.app;
-import android.app.Fragment;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.SearchOrbView;
import android.support.v17.leanback.widget.TitleHelper;
import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.app.Fragment;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@@ -143,7 +148,7 @@ public class BrandedFragment extends Fragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState != null) {
mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
diff --git a/android/support/v17/leanback/app/BrandedSupportFragment.java b/android/support/v17/leanback/app/BrandedSupportFragment.java
index 9c42780a..306e1f11 100644
--- a/android/support/v17/leanback/app/BrandedSupportFragment.java
+++ b/android/support/v17/leanback/app/BrandedSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrandedFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -16,13 +13,15 @@
*/
package android.support.v17.leanback.app;
-import android.support.v4.app.Fragment;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.SearchOrbView;
import android.support.v17.leanback.widget.TitleHelper;
import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.support.v4.app.Fragment;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@@ -146,7 +145,7 @@ public class BrandedSupportFragment extends Fragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState != null) {
mShowingTitle = savedInstanceState.getBoolean(TITLE_SHOW);
diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java
index 8edaab67..f3773895 100644
--- a/android/support/v17/leanback/app/BrowseFragment.java
+++ b/android/support/v17/leanback/app/BrowseFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from BrowseSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -15,10 +18,6 @@ package android.support.v17.leanback.app;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
-import android.app.FragmentTransaction;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
@@ -45,6 +44,10 @@ import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.ScaleFrameLayout;
import android.support.v17.leanback.widget.TitleViewAdapter;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentManager.BackStackEntry;
+import android.app.FragmentTransaction;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
@@ -1110,7 +1113,7 @@ public class BrowseFragment extends BaseFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- final Context context = FragmentUtil.getContext(this);
+ final Context context = FragmentUtil.getContext(BrowseFragment.this);
TypedArray ta = context.obtainStyledAttributes(R.styleable.LeanbackTheme);
mContainerListMarginStart = (int) ta.getDimension(
R.styleable.LeanbackTheme_browseRowsMarginStart, context.getResources()
@@ -1278,7 +1281,7 @@ public class BrowseFragment extends BaseFragment {
}
void createHeadersTransition() {
- mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(this),
+ mHeadersTransition = TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
mShowingHeaders
? R.transition.lb_browse_headers_in : R.transition.lb_browse_headers_out);
@@ -1710,7 +1713,7 @@ public class BrowseFragment extends BaseFragment {
@Override
protected Object createEntranceTransition() {
- return TransitionHelper.loadTransition(FragmentUtil.getContext(this),
+ return TransitionHelper.loadTransition(FragmentUtil.getContext(BrowseFragment.this),
R.transition.lb_browse_entrance_transition);
}
diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java
index 2665b2a8..03b3c8a6 100644
--- a/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from BrowseFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -18,10 +15,6 @@ package android.support.v17.leanback.app;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentManager.BackStackEntry;
-import android.support.v4.app.FragmentTransaction;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
@@ -48,6 +41,10 @@ import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.ScaleFrameLayout;
import android.support.v17.leanback.widget.TitleViewAdapter;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
diff --git a/android/support/v17/leanback/app/DetailsFragment.java b/android/support/v17/leanback/app/DetailsFragment.java
index 2c4e24ac..36559637 100644
--- a/android/support/v17/leanback/app/DetailsFragment.java
+++ b/android/support/v17/leanback/app/DetailsFragment.java
@@ -1,3 +1,9 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from DetailsSupportFragment.java. DO NOT MODIFY. */
+
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from DetailsFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -771,7 +777,7 @@ public class DetailsFragment extends BaseFragment {
@Override
protected Object createEntranceTransition() {
- return TransitionHelper.loadTransition(FragmentUtil.getContext(this),
+ return TransitionHelper.loadTransition(FragmentUtil.getContext(DetailsFragment.this),
R.transition.lb_details_enter_transition);
}
diff --git a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
index 05dfb3a2..223b8ef2 100644
--- a/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
+++ b/android/support/v17/leanback/app/DetailsFragmentBackgroundController.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from {}DetailsSupportFragmentBackgroundController.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2017 The Android Open Source Project
*
@@ -16,7 +19,6 @@
package android.support.v17.leanback.app;
import android.animation.PropertyValuesHolder;
-import android.app.Fragment;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
@@ -30,6 +32,7 @@ import android.support.v17.leanback.media.PlaybackGlue;
import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.DetailsParallaxDrawable;
import android.support.v17.leanback.widget.ParallaxTarget;
+import android.app.Fragment;
/**
* Controller for DetailsFragment parallax background and embedded video play.
diff --git a/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java b/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
index 4a0be6ec..aac74ba0 100644
--- a/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
+++ b/android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoDetailsFragmentBackgroundController.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2017 The Android Open Source Project
*
@@ -19,7 +16,6 @@
package android.support.v17.leanback.app;
import android.animation.PropertyValuesHolder;
-import android.support.v4.app.Fragment;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
@@ -33,6 +29,7 @@ import android.support.v17.leanback.media.PlaybackGlue;
import android.support.v17.leanback.media.PlaybackGlueHost;
import android.support.v17.leanback.widget.DetailsParallaxDrawable;
import android.support.v17.leanback.widget.ParallaxTarget;
+import android.support.v4.app.Fragment;
/**
* Controller for DetailsSupportFragment parallax background and embedded video play.
diff --git a/android/support/v17/leanback/app/ErrorFragment.java b/android/support/v17/leanback/app/ErrorFragment.java
index c35fcdcc..2896d0f4 100644
--- a/android/support/v17/leanback/app/ErrorFragment.java
+++ b/android/support/v17/leanback/app/ErrorFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from ErrorSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
diff --git a/android/support/v17/leanback/app/ErrorSupportFragment.java b/android/support/v17/leanback/app/ErrorSupportFragment.java
index 179e2e97..55e7d929 100644
--- a/android/support/v17/leanback/app/ErrorSupportFragment.java
+++ b/android/support/v17/leanback/app/ErrorSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from ErrorFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
diff --git a/android/support/v17/leanback/app/GuidedStepFragment.java b/android/support/v17/leanback/app/GuidedStepFragment.java
index a01cf263..2b7f2d0d 100644
--- a/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from GuidedStepSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2015 The Android Open Source Project
*
@@ -17,11 +20,6 @@ import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.animation.Animator;
import android.animation.AnimatorSet;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentManager.BackStackEntry;
-import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
@@ -37,6 +35,11 @@ import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
import android.support.v17.leanback.widget.GuidedActionsStylist;
import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
import android.support.v4.app.ActivityCompat;
+import android.app.Fragment;
+import android.app.Activity;
+import android.app.FragmentManager;
+import android.app.FragmentManager.BackStackEntry;
+import android.app.FragmentTransaction;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.TypedValue;
@@ -1131,7 +1134,7 @@ public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.
} else {
// when there are two actions panel, we need adjust the weight of action to
// guidedActionContentWidthWeightTwoPanels.
- Context ctx = mThemeWrapper != null ? mThemeWrapper : FragmentUtil.getContext(this);
+ Context ctx = mThemeWrapper != null ? mThemeWrapper : FragmentUtil.getContext(GuidedStepFragment.this);
TypedValue typedValue = new TypedValue();
if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels,
typedValue, true)) {
@@ -1338,7 +1341,7 @@ public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.
private void resolveTheme() {
// Look up the guidedStepTheme in the currently specified theme. If it exists,
// replace the theme with its value.
- Context context = FragmentUtil.getContext(this);
+ Context context = FragmentUtil.getContext(GuidedStepFragment.this);
int theme = onProvideTheme();
if (theme == -1 && !isGuidedStepTheme(context)) {
// Look up the guidedStepTheme in the activity's currently specified theme. If it
diff --git a/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index ed345482..aeb2d334 100644
--- a/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from GuidedStepFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2015 The Android Open Source Project
*
@@ -20,11 +17,6 @@ import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.animation.Animator;
import android.animation.AnimatorSet;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentManager.BackStackEntry;
-import android.support.v4.app.FragmentTransaction;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
@@ -40,6 +32,11 @@ import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
import android.support.v17.leanback.widget.GuidedActionsStylist;
import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
+import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.TypedValue;
diff --git a/android/support/v17/leanback/app/HeadersFragment.java b/android/support/v17/leanback/app/HeadersFragment.java
index 724fa411..dd037d2f 100644
--- a/android/support/v17/leanback/app/HeadersFragment.java
+++ b/android/support/v17/leanback/app/HeadersFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from HeadersSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -20,6 +23,8 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.DividerPresenter;
@@ -160,7 +165,7 @@ public class HeadersFragment extends BaseRowFragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final VerticalGridView listView = getVerticalGridView();
if (listView == null) {
diff --git a/android/support/v17/leanback/app/HeadersSupportFragment.java b/android/support/v17/leanback/app/HeadersSupportFragment.java
index be867cb9..56c85af6 100644
--- a/android/support/v17/leanback/app/HeadersSupportFragment.java
+++ b/android/support/v17/leanback/app/HeadersSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from HeadersFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -23,6 +20,8 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.DividerPresenter;
@@ -163,7 +162,7 @@ public class HeadersSupportFragment extends BaseRowSupportFragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final VerticalGridView listView = getVerticalGridView();
if (listView == null) {
diff --git a/android/support/v17/leanback/app/MediaControllerGlue.java b/android/support/v17/leanback/app/MediaControllerGlue.java
deleted file mode 100644
index 7949bfb4..00000000
--- a/android/support/v17/leanback/app/MediaControllerGlue.java
+++ /dev/null
@@ -1,236 +0,0 @@
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaControllerCompat;
-import android.support.v4.media.session.PlaybackStateCompat;
-import android.util.Log;
-
-/**
- * A helper class for implementing a glue layer between a
- * {@link PlaybackOverlayFragment} and a
- * {@link android.support.v4.media.session.MediaControllerCompat}.
- * @deprecated Use {@link android.support.v17.leanback.media.MediaControllerGlue}.
- */
-@Deprecated
-public abstract class MediaControllerGlue extends PlaybackControlGlue {
- static final String TAG = "MediaControllerGlue";
- static final boolean DEBUG = false;
-
- MediaControllerCompat mMediaController;
-
- private final MediaControllerCompat.Callback mCallback = new MediaControllerCompat.Callback() {
- @Override
- public void onMetadataChanged(MediaMetadataCompat metadata) {
- if (DEBUG) Log.v(TAG, "onMetadataChanged");
- MediaControllerGlue.this.onMetadataChanged();
- }
- @Override
- public void onPlaybackStateChanged(PlaybackStateCompat state) {
- if (DEBUG) Log.v(TAG, "onPlaybackStateChanged");
- onStateChanged();
- }
- @Override
- public void onSessionDestroyed() {
- if (DEBUG) Log.v(TAG, "onSessionDestroyed");
- mMediaController = null;
- }
- @Override
- public void onSessionEvent(String event, Bundle extras) {
- if (DEBUG) Log.v(TAG, "onSessionEvent");
- }
- };
-
- /**
- * Constructor for the glue.
- *
- * <p>The {@link PlaybackOverlayFragment} must be passed in.
- * A {@link android.support.v17.leanback.widget.OnItemViewClickedListener} and
- * {@link android.support.v17.leanback.app.PlaybackControlGlue.InputEventHandler}
- * will be set on the fragment.
- * </p>
- *
- * @param context
- * @param fragment
- * @param seekSpeeds Array of seek speeds for fast forward and rewind.
- */
- public MediaControllerGlue(Context context,
- PlaybackOverlayFragment fragment,
- int[] seekSpeeds) {
- super(context, fragment, seekSpeeds);
- }
-
- /**
- * Constructor for the glue.
- *
- * <p>The {@link PlaybackOverlayFragment} must be passed in.
- * A {@link android.support.v17.leanback.widget.OnItemViewClickedListener} and
- * {@link android.support.v17.leanback.app.PlaybackControlGlue.InputEventHandler}
- * will be set on the fragment.
- * </p>
- *
- * @param context
- * @param fragment
- * @param fastForwardSpeeds Array of seek speeds for fast forward.
- * @param rewindSpeeds Array of seek speeds for rewind.
- */
- public MediaControllerGlue(Context context,
- PlaybackOverlayFragment fragment,
- int[] fastForwardSpeeds,
- int[] rewindSpeeds) {
- super(context, fragment, fastForwardSpeeds, rewindSpeeds);
- }
-
- /**
- * Attaches to the given media controller.
- */
- public void attachToMediaController(MediaControllerCompat mediaController) {
- if (mediaController != mMediaController) {
- if (DEBUG) Log.v(TAG, "New media controller " + mediaController);
- detach();
- mMediaController = mediaController;
- if (mMediaController != null) {
- mMediaController.registerCallback(mCallback);
- }
- onMetadataChanged();
- onStateChanged();
- }
- }
-
- /**
- * Detaches from the media controller. Must be called when the object is no longer
- * needed.
- */
- public void detach() {
- if (mMediaController != null) {
- mMediaController.unregisterCallback(mCallback);
- }
- mMediaController = null;
- }
-
- /**
- * Returns the media controller currently attached.
- */
- public final MediaControllerCompat getMediaController() {
- return mMediaController;
- }
-
- @Override
- public boolean hasValidMedia() {
- return mMediaController != null && mMediaController.getMetadata() != null;
- }
-
- @Override
- public boolean isMediaPlaying() {
- return mMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
- }
-
- @Override
- public int getCurrentSpeedId() {
- int speed = (int) mMediaController.getPlaybackState().getPlaybackSpeed();
- if (speed == 0) {
- return PLAYBACK_SPEED_PAUSED;
- } else if (speed == 1) {
- return PLAYBACK_SPEED_NORMAL;
- } else if (speed > 0) {
- int[] seekSpeeds = getFastForwardSpeeds();
- for (int index = 0; index < seekSpeeds.length; index++) {
- if (speed == seekSpeeds[index]) {
- return PLAYBACK_SPEED_FAST_L0 + index;
- }
- }
- } else {
- int[] seekSpeeds = getRewindSpeeds();
- for (int index = 0; index < seekSpeeds.length; index++) {
- if (-speed == seekSpeeds[index]) {
- return -PLAYBACK_SPEED_FAST_L0 - index;
- }
- }
- }
- Log.w(TAG, "Couldn't find index for speed " + speed);
- return PLAYBACK_SPEED_INVALID;
- }
-
- @Override
- public CharSequence getMediaTitle() {
- return mMediaController.getMetadata().getDescription().getTitle();
- }
-
- @Override
- public CharSequence getMediaSubtitle() {
- return mMediaController.getMetadata().getDescription().getSubtitle();
- }
-
- @Override
- public int getMediaDuration() {
- return (int) mMediaController.getMetadata().getLong(
- MediaMetadataCompat.METADATA_KEY_DURATION);
- }
-
- @Override
- public int getCurrentPosition() {
- return (int) mMediaController.getPlaybackState().getPosition();
- }
-
- @Override
- public Drawable getMediaArt() {
- Bitmap bitmap = mMediaController.getMetadata().getDescription().getIconBitmap();
- return bitmap == null ? null : new BitmapDrawable(getContext().getResources(), bitmap);
- }
-
- @Override
- public long getSupportedActions() {
- long result = 0;
- long actions = mMediaController.getPlaybackState().getActions();
- if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
- result |= ACTION_PLAY_PAUSE;
- }
- if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
- result |= ACTION_SKIP_TO_NEXT;
- }
- if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
- result |= ACTION_SKIP_TO_PREVIOUS;
- }
- if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
- result |= ACTION_FAST_FORWARD;
- }
- if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) {
- result |= ACTION_REWIND;
- }
- return result;
- }
-
- @Override
- protected void startPlayback(int speed) {
- if (DEBUG) Log.v(TAG, "startPlayback speed " + speed);
- if (speed == PLAYBACK_SPEED_NORMAL) {
- mMediaController.getTransportControls().play();
- } else if (speed > 0) {
- mMediaController.getTransportControls().fastForward();
- } else {
- mMediaController.getTransportControls().rewind();
- }
- }
-
- @Override
- protected void pausePlayback() {
- if (DEBUG) Log.v(TAG, "pausePlayback");
- mMediaController.getTransportControls().pause();
- }
-
- @Override
- protected void skipToNext() {
- if (DEBUG) Log.v(TAG, "skipToNext");
- mMediaController.getTransportControls().skipToNext();
- }
-
- @Override
- protected void skipToPrevious() {
- if (DEBUG) Log.v(TAG, "skipToPrevious");
- mMediaController.getTransportControls().skipToPrevious();
- }
-}
diff --git a/android/support/v17/leanback/app/OnboardingFragment.java b/android/support/v17/leanback/app/OnboardingFragment.java
index 22dd2111..b69d5a72 100644
--- a/android/support/v17/leanback/app/OnboardingFragment.java
+++ b/android/support/v17/leanback/app/OnboardingFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from OnboardingSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2015 The Android Open Source Project
*
@@ -22,14 +25,15 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.app.Fragment;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.PagingIndicator;
+import android.app.Fragment;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -340,7 +344,7 @@ abstract public class OnboardingFragment extends Fragment {
if (mStartButtonTextSet) {
((Button) mStartButton).setText(mStartButtonText);
}
- final Context context = FragmentUtil.getContext(this);
+ final Context context = FragmentUtil.getContext(OnboardingFragment.this);
if (sSlideDistance == 0) {
sSlideDistance = (int) (SLIDE_DISTANCE * context.getResources()
.getDisplayMetrics().scaledDensity);
@@ -350,7 +354,7 @@ abstract public class OnboardingFragment extends Fragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null) {
mCurrentPageIndex = 0;
@@ -538,7 +542,7 @@ abstract public class OnboardingFragment extends Fragment {
}
private void resolveTheme() {
- final Context context = FragmentUtil.getContext(this);
+ final Context context = FragmentUtil.getContext(OnboardingFragment.this);
int theme = onProvideTheme();
if (theme == -1) {
// Look up the onboardingTheme in the activity's currently specified theme. If it
@@ -592,7 +596,7 @@ abstract public class OnboardingFragment extends Fragment {
}
boolean startLogoAnimation() {
- final Context context = FragmentUtil.getContext(this);
+ final Context context = FragmentUtil.getContext(OnboardingFragment.this);
if (context == null) {
return false;
}
@@ -655,7 +659,7 @@ abstract public class OnboardingFragment extends Fragment {
View container = getView();
// Create custom views.
LayoutInflater inflater = getThemeInflater(LayoutInflater.from(
- FragmentUtil.getContext(this)));
+ FragmentUtil.getContext(OnboardingFragment.this)));
ViewGroup backgroundContainer = (ViewGroup) container.findViewById(
R.id.background_container);
View background = onCreateBackgroundView(inflater, backgroundContainer);
@@ -716,7 +720,7 @@ abstract public class OnboardingFragment extends Fragment {
* been done in the past, {@code false} otherwise
*/
protected final void startEnterAnimation(boolean force) {
- final Context context = FragmentUtil.getContext(this);
+ final Context context = FragmentUtil.getContext(OnboardingFragment.this);
if (context == null) {
return;
}
@@ -772,7 +776,7 @@ abstract public class OnboardingFragment extends Fragment {
* default fade and slide animation. Returning null will disable the animation.
*/
protected Animator onCreateDescriptionAnimator() {
- return AnimatorInflater.loadAnimator(FragmentUtil.getContext(this),
+ return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
R.animator.lb_onboarding_description_enter);
}
@@ -781,7 +785,7 @@ abstract public class OnboardingFragment extends Fragment {
* default fade and slide animation. Returning null will disable the animation.
*/
protected Animator onCreateTitleAnimator() {
- return AnimatorInflater.loadAnimator(FragmentUtil.getContext(this),
+ return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
R.animator.lb_onboarding_title_enter);
}
@@ -919,7 +923,7 @@ abstract public class OnboardingFragment extends Fragment {
}
});
- final Context context = FragmentUtil.getContext(this);
+ final Context context = FragmentUtil.getContext(OnboardingFragment.this);
// Animator for switching between page indicator and button.
if (getCurrentPageIndex() == getPageCount() - 1) {
mStartButton.setVisibility(View.VISIBLE);
diff --git a/android/support/v17/leanback/app/OnboardingSupportFragment.java b/android/support/v17/leanback/app/OnboardingSupportFragment.java
index a24ea4d9..51cb2dea 100644
--- a/android/support/v17/leanback/app/OnboardingSupportFragment.java
+++ b/android/support/v17/leanback/app/OnboardingSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from OnboardingFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2015 The Android Open Source Project
*
@@ -25,14 +22,15 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.support.v4.app.Fragment;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.PagingIndicator;
+import android.support.v4.app.Fragment;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -353,7 +351,7 @@ abstract public class OnboardingSupportFragment extends Fragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null) {
mCurrentPageIndex = 0;
diff --git a/android/support/v17/leanback/app/PlaybackControlGlue.java b/android/support/v17/leanback/app/PlaybackControlGlue.java
deleted file mode 100644
index d74fd114..00000000
--- a/android/support/v17/leanback/app/PlaybackControlGlue.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 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.app;
-
-import android.content.Context;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.View;
-
-/**
- * A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow}
- * and {@link PlaybackGlueHost} that implements a
- * recommended approach to handling standard playback control actions such as play/pause,
- * fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class
- * is a glue layer in that manages the configuration of and interaction between the
- * leanback UI components by defining a functional interface to the media player.
- *
- * <p>You can instantiate a concrete subclass such as MediaPlayerGlue or you must
- * subclass this abstract helper. To create a subclass you must implement all of the
- * abstract methods and the subclass must invoke {@link #onMetadataChanged()} and
- * {@link #onStateChanged()} appropriately.
- * </p>
- *
- * <p>To use an instance of the glue layer, first construct an instance. Constructor parameters
- * inform the glue what speed levels are supported for fast forward/rewind.
- * </p>
- *
- * <p>If you have your own controls row you must pass it to {@link #setControlsRow}.
- * The row will be updated by the glue layer based on the media metadata and playback state.
- * Alternatively, you may call {@link #createControlsRowAndPresenter()} which will set a controls
- * row and return a row presenter you can use to present the row.
- * </p>
- *
- * <p>The helper sets a {@link android.support.v17.leanback.widget.SparseArrayObjectAdapter}
- * on the controls row as the primary actions adapter, and adds actions to it. You can provide
- * additional actions by overriding {@link #createPrimaryActionsAdapter}. This helper does not
- * deal in secondary actions so those you may add separately.
- * </p>
- *
- * <p>Provide a click listener on your fragment and if an action is clicked, call
- * {@link #onActionClicked}. If you set a listener by calling {@link #setOnItemViewClickedListener},
- * your listener will be called for all unhandled actions.
- * </p>
- *
- * <p>This helper implements a key event handler. If you pass a
- * {@link PlaybackOverlayFragment}, it will configure its
- * fragment to intercept all key events. Otherwise, you should set the glue object as key event
- * handler to the ViewHolder when bound by your row presenter; see
- * {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
- * </p>
- *
- * <p>To update the controls row progress during playback, override {@link #enableProgressUpdating}
- * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
- * {@link #getUpdatePeriod()} provides a recommended update period.
- * </p>
- * @deprecated Use {@link android.support.v17.leanback.media.PlaybackControlGlue}
- */
-@Deprecated
-public abstract class PlaybackControlGlue extends
- android.support.v17.leanback.media.PlaybackControlGlue {
-
- OnItemViewClickedListener mExternalOnItemViewClickedListener;
-
- /**
- * Constructor for the glue.
- *
- * @param context
- * @param seekSpeeds Array of seek speeds for fast forward and rewind.
- */
- public PlaybackControlGlue(Context context, int[] seekSpeeds) {
- super(context, seekSpeeds, seekSpeeds);
- }
-
- /**
- * Constructor for the glue.
- *
- * @param context
- * @param fastForwardSpeeds Array of seek speeds for fast forward.
- * @param rewindSpeeds Array of seek speeds for rewind.
- */
- public PlaybackControlGlue(Context context,
- int[] fastForwardSpeeds,
- int[] rewindSpeeds) {
- super(context, fastForwardSpeeds, rewindSpeeds);
- }
-
- /**
- * Constructor for the glue.
- *
- * @param context
- * @param fragment Optional; if using a {@link PlaybackOverlayFragment}, pass it in.
- * @param seekSpeeds Array of seek speeds for fast forward and rewind.
- */
- public PlaybackControlGlue(Context context,
- PlaybackOverlayFragment fragment,
- int[] seekSpeeds) {
- this(context, fragment, seekSpeeds, seekSpeeds);
- }
-
- /**
- * Constructor for the glue.
- *
- * @param context
- * @param fragment Optional; if using a {@link PlaybackOverlayFragment}, pass it in.
- * @param fastForwardSpeeds Array of seek speeds for fast forward.
- * @param rewindSpeeds Array of seek speeds for rewind.
- */
- public PlaybackControlGlue(Context context,
- PlaybackOverlayFragment fragment,
- int[] fastForwardSpeeds,
- int[] rewindSpeeds) {
- super(context, fastForwardSpeeds, rewindSpeeds);
- setHost(fragment == null ? (PlaybackGlueHost) null : new PlaybackGlueHostOld(fragment));
- }
-
- @Override
- protected void onAttachedToHost(PlaybackGlueHost host) {
- super.onAttachedToHost(host);
- if (host instanceof PlaybackGlueHostOld) {
- ((PlaybackGlueHostOld) host).mGlue = this;
- }
- }
-
- /**
- * Returns the fragment.
- */
- public PlaybackOverlayFragment getFragment() {
- if (getHost() instanceof PlaybackGlueHostOld) {
- return ((PlaybackGlueHostOld)getHost()).mFragment;
- }
- return null;
- }
-
- /**
- * Start playback at the given speed.
- * @deprecated use {@link #play()} instead.
- *
- * @param speed The desired playback speed. For normal playback this will be
- * {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward,
- * and negative values for rewind.
- */
- @Deprecated
- protected void startPlayback(int speed) {}
-
- /**
- * Pause playback.
- * @deprecated use {@link #pause()} instead.
- */
- @Deprecated
- protected void pausePlayback() {}
-
- /**
- * Skip to the next track.
- * @deprecated use {@link #next()} instead.
- */
- @Deprecated
- protected void skipToNext() {}
-
- /**
- * Skip to the previous track.
- * @deprecated use {@link #previous()} instead.
- */
- @Deprecated
- protected void skipToPrevious() {}
-
- @Override
- public final void next() {
- skipToNext();
- }
-
- @Override
- public final void previous() {
- skipToPrevious();
- }
-
- @Override
- public final void play(int speed) {
- startPlayback(speed);
- }
-
- @Override
- public final void pause() {
- pausePlayback();
- }
-
- /**
- * This method invoked when the playback controls row has changed. The adapter
- * containing this row should be notified.
- */
- protected void onRowChanged(PlaybackControlsRow row) {
- }
-
- /**
- * Set the {@link OnItemViewClickedListener} to be called if the click event
- * is not handled internally.
- * @param listener
- * @deprecated Don't call this. Instead use the listener on the fragment yourself.
- */
- @Deprecated
- public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
- mExternalOnItemViewClickedListener = listener;
- }
-
- /**
- * Returns the {@link OnItemViewClickedListener}.
- * @deprecated Don't call this. Instead use the listener on the fragment yourself.
- */
- @Deprecated
- public OnItemViewClickedListener getOnItemViewClickedListener() {
- return mExternalOnItemViewClickedListener;
- }
-
- @Override
- protected void onCreateControlsRowAndPresenter() {
- // backward compatible, we dont create row / presenter by default.
- // User is expected to call createControlsRowAndPresenter() or setControlsRow()
- // explicitly.
- }
-
- /**
- * Helper method for instantiating a
- * {@link android.support.v17.leanback.widget.PlaybackControlsRow} and corresponding
- * {@link android.support.v17.leanback.widget.PlaybackControlsRowPresenter}.
- */
- public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
- super.onCreateControlsRowAndPresenter();
- return getControlsRowPresenter();
- }
-
- @Override
- protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
- PresenterSelector presenterSelector) {
- return super.createPrimaryActionsAdapter(presenterSelector);
- }
-
- /**
- * Interface allowing the application to handle input events.
- * @deprecated Use
- * {@link PlaybackGlueHost#setOnKeyInterceptListener(View.OnKeyListener)}.
- */
- @Deprecated
- public interface InputEventHandler {
- /**
- * Called when an {@link InputEvent} is received.
- *
- * @return If the event should be consumed, return true. To allow the event to
- * continue on to the next handler, return false.
- */
- boolean handleInputEvent(InputEvent event);
- }
-
- static final class PlaybackGlueHostOld extends PlaybackGlueHost {
- final PlaybackOverlayFragment mFragment;
- PlaybackControlGlue mGlue;
- OnActionClickedListener mActionClickedListener;
-
- public PlaybackGlueHostOld(PlaybackOverlayFragment fragment) {
- mFragment = fragment;
- mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- if (item instanceof Action
- && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder
- && mActionClickedListener != null) {
- mActionClickedListener.onActionClicked((Action) item);
- } else if (mGlue != null && mGlue.getOnItemViewClickedListener() != null) {
- mGlue.getOnItemViewClickedListener().onItemClicked(itemViewHolder,
- item, rowViewHolder, row);
- }
- }
- });
- }
-
- @Override
- public void setFadingEnabled(boolean enable) {
- mFragment.setFadingEnabled(enable);
- }
-
- @Override
- public void setOnKeyInterceptListener(final View.OnKeyListener onKeyListener) {
- mFragment.setEventHandler( new InputEventHandler() {
- @Override
- public boolean handleInputEvent(InputEvent event) {
- if (event instanceof KeyEvent) {
- KeyEvent keyEvent = (KeyEvent) event;
- return onKeyListener.onKey(null, keyEvent.getKeyCode(), keyEvent);
- }
- return false;
- }
- });
- }
-
- @Override
- public void setOnActionClickedListener(final OnActionClickedListener listener) {
- mActionClickedListener = listener;
- }
-
- @Override
- public void setHostCallback(HostCallback callback) {
- mFragment.setHostCallback(callback);
- }
-
- @Override
- public void fadeOut() {
- mFragment.fadeOut();
- }
-
- @Override
- public void notifyPlaybackRowChanged() {
- mGlue.onRowChanged(mGlue.getControlsRow());
- }
- }
-}
diff --git a/android/support/v17/leanback/app/PlaybackControlSupportGlue.java b/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
deleted file mode 100644
index b3d19aee..00000000
--- a/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/* This file is auto-generated from PlaybackControlGlue.java. DO NOT MODIFY. */
-
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.View;
-
-/**
- * @deprecated Use {@link android.support.v17.leanback.media.PlaybackControlGlue} and
- * {@link PlaybackSupportFragmentGlueHost} for {@link PlaybackSupportFragment}.
- */
-@Deprecated
-public abstract class PlaybackControlSupportGlue extends PlaybackControlGlue {
- /**
- * The adapter key for the first custom control on the left side
- * of the predefined primary controls.
- */
- public static final int ACTION_CUSTOM_LEFT_FIRST = PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST;
-
- /**
- * The adapter key for the skip to previous control.
- */
- public static final int ACTION_SKIP_TO_PREVIOUS = PlaybackControlGlue.ACTION_SKIP_TO_PREVIOUS;
-
- /**
- * The adapter key for the rewind control.
- */
- public static final int ACTION_REWIND = PlaybackControlGlue.ACTION_REWIND;
-
- /**
- * The adapter key for the play/pause control.
- */
- public static final int ACTION_PLAY_PAUSE = PlaybackControlGlue.ACTION_PLAY_PAUSE;
-
- /**
- * The adapter key for the fast forward control.
- */
- public static final int ACTION_FAST_FORWARD = PlaybackControlGlue.ACTION_FAST_FORWARD;
-
- /**
- * The adapter key for the skip to next control.
- */
- public static final int ACTION_SKIP_TO_NEXT = PlaybackControlGlue.ACTION_SKIP_TO_NEXT;
-
- /**
- * The adapter key for the first custom control on the right side
- * of the predefined primary controls.
- */
- public static final int ACTION_CUSTOM_RIGHT_FIRST =
- PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST;
-
- /**
- * Invalid playback speed.
- */
- public static final int PLAYBACK_SPEED_INVALID = PlaybackControlGlue.PLAYBACK_SPEED_INVALID;
-
- /**
- * Speed representing playback state that is paused.
- */
- public static final int PLAYBACK_SPEED_PAUSED = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
-
- /**
- * Speed representing playback state that is playing normally.
- */
- public static final int PLAYBACK_SPEED_NORMAL = PlaybackControlGlue.PLAYBACK_SPEED_NORMAL;
-
- /**
- * The initial (level 0) fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L0 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
-
- /**
- * The level 1 fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L1 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1;
-
- /**
- * The level 2 fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L2 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L2;
-
- /**
- * The level 3 fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L3 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L3;
-
- /**
- * The level 4 fast forward playback speed.
- * The negative of this value is for rewind at the same speed.
- */
- public static final int PLAYBACK_SPEED_FAST_L4 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L4;
-
- public PlaybackControlSupportGlue(Context context, int[] seekSpeeds) {
- this(context, null, seekSpeeds, seekSpeeds);
- }
-
- public PlaybackControlSupportGlue(
- Context context, int[] fastForwardSpeeds, int[] rewindSpeeds) {
- this(context, null, fastForwardSpeeds, rewindSpeeds);
- }
-
- public PlaybackControlSupportGlue(
- Context context,
- PlaybackOverlaySupportFragment fragment,
- int[] seekSpeeds) {
- this(context, fragment, seekSpeeds, seekSpeeds);
- }
-
- public PlaybackControlSupportGlue(
- Context context,
- PlaybackOverlaySupportFragment fragment,
- int[] fastForwardSpeeds,
- int[] rewindSpeeds) {
- super(context, fastForwardSpeeds, rewindSpeeds);
- setHost(fragment == null ? null : new PlaybackSupportGlueHostOld(fragment));
- }
-
- @Override
- protected void onAttachedToHost(PlaybackGlueHost host) {
- super.onAttachedToHost(host);
- if (host instanceof PlaybackSupportGlueHostOld) {
- ((PlaybackSupportGlueHostOld) host).mGlue = this;
- }
- }
-
- static final class PlaybackSupportGlueHostOld extends PlaybackGlueHost {
- final PlaybackOverlaySupportFragment mFragment;
- PlaybackControlSupportGlue mGlue;
- OnActionClickedListener mActionClickedListener;
-
- public PlaybackSupportGlueHostOld(PlaybackOverlaySupportFragment fragment) {
- mFragment = fragment;
- mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
- @Override
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
- if (item instanceof Action
- && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder
- && mActionClickedListener != null) {
- mActionClickedListener.onActionClicked((Action) item);
- } else if (mGlue != null && mGlue.getOnItemViewClickedListener() != null) {
- mGlue.getOnItemViewClickedListener().onItemClicked(itemViewHolder,
- item, rowViewHolder, row);
- }
- }
- });
- }
-
- @Override
- public void setFadingEnabled(boolean enable) {
- mFragment.setFadingEnabled(enable);
- }
-
- @Override
- public void setOnKeyInterceptListener(final View.OnKeyListener onKeyListenerr) {
- mFragment.setEventHandler( new InputEventHandler() {
- @Override
- public boolean handleInputEvent(InputEvent event) {
- if (event instanceof KeyEvent) {
- KeyEvent keyEvent = (KeyEvent) event;
- return onKeyListenerr.onKey(null, keyEvent.getKeyCode(), keyEvent);
- }
- return false;
- }
- });
- }
-
- @Override
- public void setOnActionClickedListener(final OnActionClickedListener listener) {
- mActionClickedListener = listener;
- }
-
- @Override
- public void setHostCallback(HostCallback callback) {
- mFragment.setHostCallback(callback);
- }
-
- @Override
- public void fadeOut() {
- mFragment.fadeOut();
- }
-
- @Override
- public void notifyPlaybackRowChanged() {
- mGlue.onRowChanged(mGlue.getControlsRow());
- }
- }
-}
diff --git a/android/support/v17/leanback/app/PlaybackFragment.java b/android/support/v17/leanback/app/PlaybackFragment.java
index 68a12151..33e787c3 100644
--- a/android/support/v17/leanback/app/PlaybackFragment.java
+++ b/android/support/v17/leanback/app/PlaybackFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2016 The Android Open Source Project
*
@@ -18,13 +21,14 @@ import android.animation.AnimatorInflater;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.Fragment;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.animation.LogAccelerateInterpolator;
import android.support.v17.leanback.animation.LogDecelerateInterpolator;
@@ -45,6 +49,7 @@ import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.InputEvent;
@@ -447,7 +452,7 @@ public class PlaybackFragment extends Fragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// controls view are initially visible, make it invisible
// if app has called hideControlsOverlay() before view created.
@@ -505,7 +510,7 @@ public class PlaybackFragment extends Fragment {
}
};
- Context context = FragmentUtil.getContext(this);
+ Context context = FragmentUtil.getContext(PlaybackFragment.this);
mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
mBgFadeInAnimator.addUpdateListener(listener);
mBgFadeInAnimator.addListener(mFadeListener);
@@ -540,7 +545,7 @@ public class PlaybackFragment extends Fragment {
}
};
- Context context = FragmentUtil.getContext(this);
+ Context context = FragmentUtil.getContext(PlaybackFragment.this);
mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
mControlRowFadeInAnimator.addUpdateListener(updateListener);
mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
@@ -570,7 +575,7 @@ public class PlaybackFragment extends Fragment {
}
};
- Context context = FragmentUtil.getContext(this);
+ Context context = FragmentUtil.getContext(PlaybackFragment.this);
mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
mOtherRowFadeInAnimator.addUpdateListener(updateListener);
mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
diff --git a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
index d537c3a0..4a9d10f8 100644
--- a/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from {}PlaybackSupportFragmentGlueHost.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2016 The Android Open Source Project
*
diff --git a/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/android/support/v17/leanback/app/PlaybackOverlayFragment.java
deleted file mode 100644
index d4b532b0..00000000
--- a/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ /dev/null
@@ -1,863 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.animation.LogAccelerateInterpolator;
-import android.support.v17.leanback.animation.LogDecelerateInterpolator;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.ItemAlignmentFacet;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-/**
- * A fragment for displaying playback controls and related content.
- * <p>
- * A PlaybackOverlayFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
- * of {@link RowPresenter}.
- * </p>
- * <p>
- * An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
- * at position 0 in the adapter.
- * </p>
- * <p>
- * This class is now deprecated, please us
- * </p>
- * @deprecated Use {@link PlaybackFragment}.
- */
-@Deprecated
-public class PlaybackOverlayFragment extends DetailsFragment {
-
- /**
- * No background.
- */
- public static final int BG_NONE = 0;
-
- /**
- * A dark translucent background.
- */
- public static final int BG_DARK = 1;
-
- /**
- * A light translucent background.
- */
- public static final int BG_LIGHT = 2;
-
- /**
- * Listener allowing the application to receive notification of fade in and/or fade out
- * completion events.
- */
- public static class OnFadeCompleteListener {
- public void onFadeInComplete() {
- }
- public void onFadeOutComplete() {
- }
- }
-
- static final String TAG = "PlaybackOF";
- static final boolean DEBUG = false;
- private static final int ANIMATION_MULTIPLIER = 1;
-
- static int START_FADE_OUT = 1;
-
- // Fading status
- static final int IDLE = 0;
- private static final int IN = 1;
- static final int OUT = 2;
-
- private int mOtherRowsCenterToBottom;
- private int mPaddingBottom;
- private View mRootView;
- private int mBackgroundType = BG_DARK;
- private int mBgDarkColor;
- private int mBgLightColor;
- private int mShowTimeMs;
- private int mMajorFadeTranslateY, mMinorFadeTranslateY;
- int mAnimationTranslateY;
- OnFadeCompleteListener mFadeCompleteListener;
- private PlaybackControlGlue.InputEventHandler mInputEventHandler;
- boolean mFadingEnabled = true;
- int mFadingStatus = IDLE;
- int mBgAlpha;
- private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
- private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
- private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
- private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
- boolean mResetControlsToPrimaryActionsPending;
- PlaybackGlueHost.HostCallback mHostCallback;
-
- private final Animator.AnimatorListener mFadeListener =
- new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- enableVerticalGridAnimations(false);
- }
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
- if (mBgAlpha > 0) {
- enableVerticalGridAnimations(true);
- startFadeTimer();
- if (mFadeCompleteListener != null) {
- mFadeCompleteListener.onFadeInComplete();
- }
- } else {
- VerticalGridView verticalView = getVerticalGridView();
- // reset focus to the primary actions only if the selected row was the controls row
- if (verticalView != null && verticalView.getSelectedPosition() == 0) {
- resetControlsToPrimaryActions(null);
- }
- if (mFadeCompleteListener != null) {
- mFadeCompleteListener.onFadeOutComplete();
- }
- }
- mFadingStatus = IDLE;
- }
- };
-
- static class FadeHandler extends Handler {
- @Override
- public void handleMessage(Message message) {
- PlaybackOverlayFragment fragment;
- if (message.what == START_FADE_OUT) {
- fragment = ((WeakReference<PlaybackOverlayFragment>) message.obj).get();
- if (fragment != null && fragment.mFadingEnabled) {
- fragment.fade(false);
- }
- }
- }
- }
-
- static final Handler sHandler = new FadeHandler();
-
- final WeakReference<PlaybackOverlayFragment> mFragmentReference = new WeakReference(this);
-
- private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
- new VerticalGridView.OnTouchInterceptListener() {
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- return onInterceptInputEvent(event);
- }
- };
-
- private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
- new VerticalGridView.OnKeyInterceptListener() {
- @Override
- public boolean onInterceptKeyEvent(KeyEvent event) {
- return onInterceptInputEvent(event);
- }
- };
-
- void setBgAlpha(int alpha) {
- mBgAlpha = alpha;
- if (mRootView != null) {
- mRootView.getBackground().setAlpha(alpha);
- }
- }
-
- void enableVerticalGridAnimations(boolean enable) {
- if (getVerticalGridView() != null) {
- getVerticalGridView().setAnimateChildLayout(enable);
- }
- }
-
- void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
- if (vh == null && getVerticalGridView() != null) {
- vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
- }
- if (vh == null) {
- mResetControlsToPrimaryActionsPending = true;
- } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
- mResetControlsToPrimaryActionsPending = false;
- ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
- (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
- }
- }
-
- /**
- * Enables or disables view fading. If enabled,
- * the view will be faded in when the fragment starts,
- * and will fade out after a time period. The timeout
- * period is reset each time {@link #tickle} is called.
- *
- */
- public void setFadingEnabled(boolean enabled) {
- if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
- if (enabled != mFadingEnabled) {
- mFadingEnabled = enabled;
- if (mFadingEnabled) {
- if (isResumed() && mFadingStatus == IDLE
- && !sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
- startFadeTimer();
- }
- } else {
- // Ensure fully opaque
- sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
- fade(true);
- }
- }
- }
-
- /**
- * Returns true if view fading is enabled.
- */
- public boolean isFadingEnabled() {
- return mFadingEnabled;
- }
-
- /**
- * Sets the listener to be called when fade in or out has completed.
- */
- public void setFadeCompleteListener(OnFadeCompleteListener listener) {
- mFadeCompleteListener = listener;
- }
-
- /**
- * Returns the listener to be called when fade in or out has completed.
- */
- public OnFadeCompleteListener getFadeCompleteListener() {
- return mFadeCompleteListener;
- }
-
- @Deprecated
- public interface InputEventHandler extends PlaybackControlGlue.InputEventHandler {
- }
-
- /**
- * Sets the input event handler.
- */
- @Deprecated
- public final void setInputEventHandler(InputEventHandler handler) {
- mInputEventHandler = handler;
- }
-
- /**
- * Returns the input event handler.
- */
- @Deprecated
- public final InputEventHandler getInputEventHandler() {
- return (InputEventHandler)mInputEventHandler;
- }
-
- /**
- * Sets the input event handler.
- */
- public final void setEventHandler(PlaybackControlGlue.InputEventHandler handler) {
- mInputEventHandler = handler;
- }
-
- /**
- * Returns the input event handler.
- */
- public final PlaybackControlGlue.InputEventHandler getEventHandler() {
- return mInputEventHandler;
- }
-
- /**
- * Tickles the playback controls. Fades in the view if it was faded out,
- * otherwise resets the fade out timer. Tickling on input events is handled
- * by the fragment.
- */
- public void tickle() {
- if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
- if (!mFadingEnabled || !isResumed()) {
- return;
- }
- if (sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
- // Restart the timer
- startFadeTimer();
- } else {
- fade(true);
- }
- }
-
- /**
- * Fades out the playback overlay immediately.
- */
- public void fadeOut() {
- sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
- fade(false);
- }
-
- /**
- * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
- * take appropriate actions to take action when the hosting fragment starts/stops processing.
- */
- void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
- this.mHostCallback = hostCallback;
- }
-
- @Override
- public void onStop() {
- if (mHostCallback != null) {
- mHostCallback.onHostStop();
- }
- super.onStop();
- }
-
- @Override
- public void onPause() {
- if (mHostCallback != null) {
- mHostCallback.onHostPause();
- }
- super.onPause();
- }
-
- private boolean areControlsHidden() {
- return mFadingStatus == IDLE && mBgAlpha == 0;
- }
-
- boolean onInterceptInputEvent(InputEvent event) {
- final boolean controlsHidden = areControlsHidden();
- if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
- boolean consumeEvent = false;
- int keyCode = KeyEvent.KEYCODE_UNKNOWN;
-
- if (mInputEventHandler != null) {
- consumeEvent = mInputEventHandler.handleInputEvent(event);
- }
- if (event instanceof KeyEvent) {
- keyCode = ((KeyEvent) event).getKeyCode();
- }
-
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- // Event may be consumed; regardless, if controls are hidden then these keys will
- // bring up the controls.
- if (controlsHidden) {
- consumeEvent = true;
- }
- tickle();
- break;
- case KeyEvent.KEYCODE_BACK:
- case KeyEvent.KEYCODE_ESCAPE:
- // If fading enabled and controls are not hidden, back will be consumed to fade
- // them out (even if the key was consumed by the handler).
- if (mFadingEnabled && !controlsHidden) {
- consumeEvent = true;
- sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
- fade(false);
- } else if (consumeEvent) {
- tickle();
- }
- break;
- default:
- if (consumeEvent) {
- tickle();
- }
- }
- return consumeEvent;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mFadingEnabled) {
- setBgAlpha(0);
- fade(true);
- }
- getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
- getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
- if (mHostCallback != null) {
- mHostCallback.onHostResume();
- }
- }
-
- void startFadeTimer() {
- sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
- sHandler.sendMessageDelayed(sHandler.obtainMessage(START_FADE_OUT, mFragmentReference),
- mShowTimeMs);
- }
-
- private static ValueAnimator loadAnimator(Context context, int resId) {
- ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
- animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
- return animator;
- }
-
- private void loadBgAnimator() {
- AnimatorUpdateListener listener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator arg0) {
- setBgAlpha((Integer) arg0.getAnimatedValue());
- }
- };
-
- Context context = FragmentUtil.getContext(this);
- mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
- mBgFadeInAnimator.addUpdateListener(listener);
- mBgFadeInAnimator.addListener(mFadeListener);
-
- mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
- mBgFadeOutAnimator.addUpdateListener(listener);
- mBgFadeOutAnimator.addListener(mFadeListener);
- }
-
- private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
- private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
-
- View getControlRowView() {
- if (getVerticalGridView() == null) {
- return null;
- }
- RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
- if (vh == null) {
- return null;
- }
- return vh.itemView;
- }
-
- private void loadControlRowAnimator() {
- final AnimatorListener listener = new AnimatorListener() {
- @Override
- void getViews(ArrayList<View> views) {
- View view = getControlRowView();
- if (view != null) {
- views.add(view);
- }
- }
- };
- final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator arg0) {
- View view = getControlRowView();
- if (view != null) {
- final float fraction = (Float) arg0.getAnimatedValue();
- if (DEBUG) Log.v(TAG, "fraction " + fraction);
- view.setAlpha(fraction);
- view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
- }
- }
- };
-
- Context context = FragmentUtil.getContext(this);
- mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
- mControlRowFadeInAnimator.addUpdateListener(updateListener);
- mControlRowFadeInAnimator.addListener(listener);
- mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
- mControlRowFadeOutAnimator = loadAnimator(context,
- R.animator.lb_playback_controls_fade_out);
- mControlRowFadeOutAnimator.addUpdateListener(updateListener);
- mControlRowFadeOutAnimator.addListener(listener);
- mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
- }
-
- private void loadOtherRowAnimator() {
- final AnimatorListener listener = new AnimatorListener() {
- @Override
- void getViews(ArrayList<View> views) {
- if (getVerticalGridView() == null) {
- return;
- }
- final int count = getVerticalGridView().getChildCount();
- for (int i = 0; i < count; i++) {
- View view = getVerticalGridView().getChildAt(i);
- if (view != null) {
- views.add(view);
- }
- }
- }
- };
- final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator arg0) {
- if (getVerticalGridView() == null) {
- return;
- }
- final float fraction = (Float) arg0.getAnimatedValue();
- for (View view : listener.mViews) {
- if (getVerticalGridView().getChildPosition(view) > 0) {
- view.setAlpha(fraction);
- view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
- }
- }
- }
- };
-
- Context context = FragmentUtil.getContext(this);
- mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
- mOtherRowFadeInAnimator.addListener(listener);
- mOtherRowFadeInAnimator.addUpdateListener(updateListener);
- mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
- mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
- mOtherRowFadeOutAnimator.addListener(listener);
- mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
- mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
- }
-
- private void loadDescriptionAnimator() {
- AnimatorUpdateListener listener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator arg0) {
- if (getVerticalGridView() == null) {
- return;
- }
- ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
- getVerticalGridView().findViewHolderForPosition(0);
- if (adapterVh != null && adapterVh.getViewHolder()
- instanceof PlaybackControlsRowPresenter.ViewHolder) {
- final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
- adapterVh.getViewHolder()).mDescriptionViewHolder;
- if (vh != null) {
- vh.view.setAlpha((Float) arg0.getAnimatedValue());
- }
- }
- }
- };
-
- Context context = FragmentUtil.getContext(this);
- mDescriptionFadeInAnimator = loadAnimator(context,
- R.animator.lb_playback_description_fade_in);
- mDescriptionFadeInAnimator.addUpdateListener(listener);
- mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
- mDescriptionFadeOutAnimator = loadAnimator(context,
- R.animator.lb_playback_description_fade_out);
- mDescriptionFadeOutAnimator.addUpdateListener(listener);
- }
-
- void fade(boolean fadeIn) {
- if (DEBUG) Log.v(TAG, "fade " + fadeIn);
- if (getView() == null) {
- return;
- }
- if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
- if (DEBUG) Log.v(TAG, "requested fade in progress");
- return;
- }
- if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
- if (DEBUG) Log.v(TAG, "fade is no-op");
- return;
- }
-
- mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0
- ? mMajorFadeTranslateY : mMinorFadeTranslateY;
-
- if (mFadingStatus == IDLE) {
- if (fadeIn) {
- mBgFadeInAnimator.start();
- mControlRowFadeInAnimator.start();
- mOtherRowFadeInAnimator.start();
- mDescriptionFadeInAnimator.start();
- } else {
- mBgFadeOutAnimator.start();
- mControlRowFadeOutAnimator.start();
- mOtherRowFadeOutAnimator.start();
- mDescriptionFadeOutAnimator.start();
- }
- } else {
- if (fadeIn) {
- mBgFadeOutAnimator.reverse();
- mControlRowFadeOutAnimator.reverse();
- mOtherRowFadeOutAnimator.reverse();
- mDescriptionFadeOutAnimator.reverse();
- } else {
- mBgFadeInAnimator.reverse();
- mControlRowFadeInAnimator.reverse();
- mOtherRowFadeInAnimator.reverse();
- mDescriptionFadeInAnimator.reverse();
- }
- }
- getView().announceForAccessibility(getString(fadeIn ? R.string.lb_playback_controls_shown
- : R.string.lb_playback_controls_hidden));
-
- // If fading in while control row is focused, set initial translationY so
- // views slide in from below.
- if (fadeIn && mFadingStatus == IDLE) {
- final int count = getVerticalGridView().getChildCount();
- for (int i = 0; i < count; i++) {
- getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
- }
- }
-
- mFadingStatus = fadeIn ? IN : OUT;
- }
-
- /**
- * Sets the list of rows for the fragment.
- */
- @Override
- public void setAdapter(ObjectAdapter adapter) {
- if (getAdapter() != null) {
- getAdapter().unregisterObserver(mObserver);
- }
- super.setAdapter(adapter);
- if (adapter != null) {
- adapter.registerObserver(mObserver);
- }
- }
-
- @Override
- protected void setupPresenter(Presenter rowPresenter) {
- if (rowPresenter instanceof PlaybackRowPresenter) {
- if (rowPresenter.getFacet(ItemAlignmentFacet.class) == null) {
- ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
- ItemAlignmentFacet.ItemAlignmentDef def =
- new ItemAlignmentFacet.ItemAlignmentDef();
- def.setItemAlignmentOffset(0);
- def.setItemAlignmentOffsetPercent(100);
- itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
- {def});
- rowPresenter.setFacet(ItemAlignmentFacet.class, itemAlignment);
- }
- } else {
- super.setupPresenter(rowPresenter);
- }
- }
-
- @Override
- void setVerticalGridViewLayout(VerticalGridView listview) {
- if (listview == null) {
- return;
- }
-
- // we set the base line of alignment to -paddingBottom
- listview.setWindowAlignmentOffset(-mPaddingBottom);
- listview.setWindowAlignmentOffsetPercent(
- VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-
- // align other rows that arent the last to center of screen, since our baseline is
- // -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
- listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
- listview.setItemAlignmentOffsetPercent(50);
-
- // Push last row to the bottom padding
- // Padding affects alignment when last row is focused
- listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
- listview.getPaddingRight(), mPaddingBottom);
- listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mOtherRowsCenterToBottom = getResources()
- .getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
- mPaddingBottom =
- getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
- mBgDarkColor =
- getResources().getColor(R.color.lb_playback_controls_background_dark);
- mBgLightColor =
- getResources().getColor(R.color.lb_playback_controls_background_light);
- mShowTimeMs =
- getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
- mMajorFadeTranslateY =
- getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
- mMinorFadeTranslateY =
- getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
-
- loadBgAnimator();
- loadControlRowAnimator();
- loadOtherRowAnimator();
- loadDescriptionAnimator();
- }
-
- /**
- * Sets the background type.
- *
- * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
- */
- public void setBackgroundType(int type) {
- switch (type) {
- case BG_LIGHT:
- case BG_DARK:
- case BG_NONE:
- if (type != mBackgroundType) {
- mBackgroundType = type;
- updateBackground();
- }
- break;
- default:
- throw new IllegalArgumentException("Invalid background type");
- }
- }
-
- /**
- * Returns the background type.
- */
- public int getBackgroundType() {
- return mBackgroundType;
- }
-
- private void updateBackground() {
- if (mRootView != null) {
- int color = mBgDarkColor;
- switch (mBackgroundType) {
- case BG_DARK: break;
- case BG_LIGHT: color = mBgLightColor; break;
- case BG_NONE: color = Color.TRANSPARENT; break;
- }
- mRootView.setBackground(new ColorDrawable(color));
- }
- }
-
- void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
- // Add extra space between rows 0 and 1
- if (vh == null && getVerticalGridView() != null) {
- vh = (ItemBridgeAdapter.ViewHolder)
- getVerticalGridView().findViewHolderForPosition(0);
- }
- if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
- final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
- ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
- (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
- adapterSize > 1);
- }
- }
-
- private final ItemBridgeAdapter.AdapterListener mAdapterListener =
- new ItemBridgeAdapter.AdapterListener() {
- @Override
- public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
- if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
- if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
- if (DEBUG) Log.v(TAG, "setting alpha to 0");
- vh.getViewHolder().view.setAlpha(0);
- }
- if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
- resetControlsToPrimaryActions(vh);
- }
- }
- @Override
- public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
- if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
- // Reset animation state
- vh.getViewHolder().view.setAlpha(1f);
- vh.getViewHolder().view.setTranslationY(0);
- if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
- Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
- vh.getViewHolder()).mDescriptionViewHolder;
- if (descriptionVh != null) {
- descriptionVh.view.setAlpha(1f);
- }
- }
- }
- @Override
- public void onBind(ItemBridgeAdapter.ViewHolder vh) {
- if (vh.getPosition() == 0) {
- updateControlsBottomSpace(vh);
- }
- }
- };
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mRootView = super.onCreateView(inflater, container, savedInstanceState);
- mBgAlpha = 255;
- updateBackground();
- getRowsFragment().setExternalAdapterListener(mAdapterListener);
- return mRootView;
- }
-
- @Override
- public void onDestroyView() {
- mRootView = null;
- if (mHostCallback != null) {
- mHostCallback.onHostDestroy();
- }
- super.onDestroyView();
- }
-
- @Override
- public void onStart() {
- super.onStart();
- // Workaround problem VideoView forcing itself to focused, let controls take focus.
- getRowsFragment().getView().requestFocus();
- if (mHostCallback != null) {
- mHostCallback.onHostStart();
- }
- }
-
- private final DataObserver mObserver = new DataObserver() {
- @Override
- public void onChanged() {
- updateControlsBottomSpace(null);
- }
- };
-
- static abstract class AnimatorListener implements Animator.AnimatorListener {
- ArrayList<View> mViews = new ArrayList<View>();
- ArrayList<Integer> mLayerType = new ArrayList<Integer>();
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- @Override
- public void onAnimationStart(Animator animation) {
- getViews(mViews);
- for (View view : mViews) {
- mLayerType.add(view.getLayerType());
- view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- for (int i = 0; i < mViews.size(); i++) {
- mViews.get(i).setLayerType(mLayerType.get(i), null);
- }
- mLayerType.clear();
- mViews.clear();
- }
- abstract void getViews(ArrayList<View> views);
-
- };
-}
diff --git a/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
deleted file mode 100644
index d7513204..00000000
--- a/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
+++ /dev/null
@@ -1,866 +0,0 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from PlaybackOverlayFragment.java. DO NOT MODIFY. */
-
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.animation.LogAccelerateInterpolator;
-import android.support.v17.leanback.animation.LogDecelerateInterpolator;
-import android.support.v17.leanback.media.PlaybackGlueHost;
-import android.support.v17.leanback.widget.ItemAlignmentFacet;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-/**
- * A fragment for displaying playback controls and related content.
- * <p>
- * A PlaybackOverlaySupportFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
- * of {@link RowPresenter}.
- * </p>
- * <p>
- * An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
- * at position 0 in the adapter.
- * </p>
- * <p>
- * This class is now deprecated, please us
- * </p>
- * @deprecated Use {@link PlaybackSupportFragment}.
- */
-@Deprecated
-public class PlaybackOverlaySupportFragment extends DetailsSupportFragment {
-
- /**
- * No background.
- */
- public static final int BG_NONE = 0;
-
- /**
- * A dark translucent background.
- */
- public static final int BG_DARK = 1;
-
- /**
- * A light translucent background.
- */
- public static final int BG_LIGHT = 2;
-
- /**
- * Listener allowing the application to receive notification of fade in and/or fade out
- * completion events.
- */
- public static class OnFadeCompleteListener {
- public void onFadeInComplete() {
- }
- public void onFadeOutComplete() {
- }
- }
-
- static final String TAG = "PlaybackOF";
- static final boolean DEBUG = false;
- private static final int ANIMATION_MULTIPLIER = 1;
-
- static int START_FADE_OUT = 1;
-
- // Fading status
- static final int IDLE = 0;
- private static final int IN = 1;
- static final int OUT = 2;
-
- private int mOtherRowsCenterToBottom;
- private int mPaddingBottom;
- private View mRootView;
- private int mBackgroundType = BG_DARK;
- private int mBgDarkColor;
- private int mBgLightColor;
- private int mShowTimeMs;
- private int mMajorFadeTranslateY, mMinorFadeTranslateY;
- int mAnimationTranslateY;
- OnFadeCompleteListener mFadeCompleteListener;
- private PlaybackControlGlue.InputEventHandler mInputEventHandler;
- boolean mFadingEnabled = true;
- int mFadingStatus = IDLE;
- int mBgAlpha;
- private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
- private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
- private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
- private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
- boolean mResetControlsToPrimaryActionsPending;
- PlaybackGlueHost.HostCallback mHostCallback;
-
- private final Animator.AnimatorListener mFadeListener =
- new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- enableVerticalGridAnimations(false);
- }
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
- if (mBgAlpha > 0) {
- enableVerticalGridAnimations(true);
- startFadeTimer();
- if (mFadeCompleteListener != null) {
- mFadeCompleteListener.onFadeInComplete();
- }
- } else {
- VerticalGridView verticalView = getVerticalGridView();
- // reset focus to the primary actions only if the selected row was the controls row
- if (verticalView != null && verticalView.getSelectedPosition() == 0) {
- resetControlsToPrimaryActions(null);
- }
- if (mFadeCompleteListener != null) {
- mFadeCompleteListener.onFadeOutComplete();
- }
- }
- mFadingStatus = IDLE;
- }
- };
-
- static class FadeHandler extends Handler {
- @Override
- public void handleMessage(Message message) {
- PlaybackOverlaySupportFragment fragment;
- if (message.what == START_FADE_OUT) {
- fragment = ((WeakReference<PlaybackOverlaySupportFragment>) message.obj).get();
- if (fragment != null && fragment.mFadingEnabled) {
- fragment.fade(false);
- }
- }
- }
- }
-
- static final Handler sHandler = new FadeHandler();
-
- final WeakReference<PlaybackOverlaySupportFragment> mFragmentReference = new WeakReference(this);
-
- private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
- new VerticalGridView.OnTouchInterceptListener() {
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- return onInterceptInputEvent(event);
- }
- };
-
- private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
- new VerticalGridView.OnKeyInterceptListener() {
- @Override
- public boolean onInterceptKeyEvent(KeyEvent event) {
- return onInterceptInputEvent(event);
- }
- };
-
- void setBgAlpha(int alpha) {
- mBgAlpha = alpha;
- if (mRootView != null) {
- mRootView.getBackground().setAlpha(alpha);
- }
- }
-
- void enableVerticalGridAnimations(boolean enable) {
- if (getVerticalGridView() != null) {
- getVerticalGridView().setAnimateChildLayout(enable);
- }
- }
-
- void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
- if (vh == null && getVerticalGridView() != null) {
- vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
- }
- if (vh == null) {
- mResetControlsToPrimaryActionsPending = true;
- } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
- mResetControlsToPrimaryActionsPending = false;
- ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
- (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
- }
- }
-
- /**
- * Enables or disables view fading. If enabled,
- * the view will be faded in when the fragment starts,
- * and will fade out after a time period. The timeout
- * period is reset each time {@link #tickle} is called.
- *
- */
- public void setFadingEnabled(boolean enabled) {
- if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
- if (enabled != mFadingEnabled) {
- mFadingEnabled = enabled;
- if (mFadingEnabled) {
- if (isResumed() && mFadingStatus == IDLE
- && !sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
- startFadeTimer();
- }
- } else {
- // Ensure fully opaque
- sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
- fade(true);
- }
- }
- }
-
- /**
- * Returns true if view fading is enabled.
- */
- public boolean isFadingEnabled() {
- return mFadingEnabled;
- }
-
- /**
- * Sets the listener to be called when fade in or out has completed.
- */
- public void setFadeCompleteListener(OnFadeCompleteListener listener) {
- mFadeCompleteListener = listener;
- }
-
- /**
- * Returns the listener to be called when fade in or out has completed.
- */
- public OnFadeCompleteListener getFadeCompleteListener() {
- return mFadeCompleteListener;
- }
-
- @Deprecated
- public interface InputEventHandler extends PlaybackControlGlue.InputEventHandler {
- }
-
- /**
- * Sets the input event handler.
- */
- @Deprecated
- public final void setInputEventHandler(InputEventHandler handler) {
- mInputEventHandler = handler;
- }
-
- /**
- * Returns the input event handler.
- */
- @Deprecated
- public final InputEventHandler getInputEventHandler() {
- return (InputEventHandler)mInputEventHandler;
- }
-
- /**
- * Sets the input event handler.
- */
- public final void setEventHandler(PlaybackControlGlue.InputEventHandler handler) {
- mInputEventHandler = handler;
- }
-
- /**
- * Returns the input event handler.
- */
- public final PlaybackControlGlue.InputEventHandler getEventHandler() {
- return mInputEventHandler;
- }
-
- /**
- * Tickles the playback controls. Fades in the view if it was faded out,
- * otherwise resets the fade out timer. Tickling on input events is handled
- * by the fragment.
- */
- public void tickle() {
- if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
- if (!mFadingEnabled || !isResumed()) {
- return;
- }
- if (sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
- // Restart the timer
- startFadeTimer();
- } else {
- fade(true);
- }
- }
-
- /**
- * Fades out the playback overlay immediately.
- */
- public void fadeOut() {
- sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
- fade(false);
- }
-
- /**
- * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
- * take appropriate actions to take action when the hosting fragment starts/stops processing.
- */
- void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
- this.mHostCallback = hostCallback;
- }
-
- @Override
- public void onStop() {
- if (mHostCallback != null) {
- mHostCallback.onHostStop();
- }
- super.onStop();
- }
-
- @Override
- public void onPause() {
- if (mHostCallback != null) {
- mHostCallback.onHostPause();
- }
- super.onPause();
- }
-
- private boolean areControlsHidden() {
- return mFadingStatus == IDLE && mBgAlpha == 0;
- }
-
- boolean onInterceptInputEvent(InputEvent event) {
- final boolean controlsHidden = areControlsHidden();
- if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
- boolean consumeEvent = false;
- int keyCode = KeyEvent.KEYCODE_UNKNOWN;
-
- if (mInputEventHandler != null) {
- consumeEvent = mInputEventHandler.handleInputEvent(event);
- }
- if (event instanceof KeyEvent) {
- keyCode = ((KeyEvent) event).getKeyCode();
- }
-
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- // Event may be consumed; regardless, if controls are hidden then these keys will
- // bring up the controls.
- if (controlsHidden) {
- consumeEvent = true;
- }
- tickle();
- break;
- case KeyEvent.KEYCODE_BACK:
- case KeyEvent.KEYCODE_ESCAPE:
- // If fading enabled and controls are not hidden, back will be consumed to fade
- // them out (even if the key was consumed by the handler).
- if (mFadingEnabled && !controlsHidden) {
- consumeEvent = true;
- sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
- fade(false);
- } else if (consumeEvent) {
- tickle();
- }
- break;
- default:
- if (consumeEvent) {
- tickle();
- }
- }
- return consumeEvent;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (mFadingEnabled) {
- setBgAlpha(0);
- fade(true);
- }
- getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
- getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
- if (mHostCallback != null) {
- mHostCallback.onHostResume();
- }
- }
-
- void startFadeTimer() {
- sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
- sHandler.sendMessageDelayed(sHandler.obtainMessage(START_FADE_OUT, mFragmentReference),
- mShowTimeMs);
- }
-
- private static ValueAnimator loadAnimator(Context context, int resId) {
- ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
- animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
- return animator;
- }
-
- private void loadBgAnimator() {
- AnimatorUpdateListener listener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator arg0) {
- setBgAlpha((Integer) arg0.getAnimatedValue());
- }
- };
-
- Context context = getContext();
- mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
- mBgFadeInAnimator.addUpdateListener(listener);
- mBgFadeInAnimator.addListener(mFadeListener);
-
- mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
- mBgFadeOutAnimator.addUpdateListener(listener);
- mBgFadeOutAnimator.addListener(mFadeListener);
- }
-
- private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
- private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
-
- View getControlRowView() {
- if (getVerticalGridView() == null) {
- return null;
- }
- RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
- if (vh == null) {
- return null;
- }
- return vh.itemView;
- }
-
- private void loadControlRowAnimator() {
- final AnimatorListener listener = new AnimatorListener() {
- @Override
- void getViews(ArrayList<View> views) {
- View view = getControlRowView();
- if (view != null) {
- views.add(view);
- }
- }
- };
- final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator arg0) {
- View view = getControlRowView();
- if (view != null) {
- final float fraction = (Float) arg0.getAnimatedValue();
- if (DEBUG) Log.v(TAG, "fraction " + fraction);
- view.setAlpha(fraction);
- view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
- }
- }
- };
-
- Context context = getContext();
- mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
- mControlRowFadeInAnimator.addUpdateListener(updateListener);
- mControlRowFadeInAnimator.addListener(listener);
- mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
- mControlRowFadeOutAnimator = loadAnimator(context,
- R.animator.lb_playback_controls_fade_out);
- mControlRowFadeOutAnimator.addUpdateListener(updateListener);
- mControlRowFadeOutAnimator.addListener(listener);
- mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
- }
-
- private void loadOtherRowAnimator() {
- final AnimatorListener listener = new AnimatorListener() {
- @Override
- void getViews(ArrayList<View> views) {
- if (getVerticalGridView() == null) {
- return;
- }
- final int count = getVerticalGridView().getChildCount();
- for (int i = 0; i < count; i++) {
- View view = getVerticalGridView().getChildAt(i);
- if (view != null) {
- views.add(view);
- }
- }
- }
- };
- final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator arg0) {
- if (getVerticalGridView() == null) {
- return;
- }
- final float fraction = (Float) arg0.getAnimatedValue();
- for (View view : listener.mViews) {
- if (getVerticalGridView().getChildPosition(view) > 0) {
- view.setAlpha(fraction);
- view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
- }
- }
- }
- };
-
- Context context = getContext();
- mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
- mOtherRowFadeInAnimator.addListener(listener);
- mOtherRowFadeInAnimator.addUpdateListener(updateListener);
- mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
- mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
- mOtherRowFadeOutAnimator.addListener(listener);
- mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
- mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
- }
-
- private void loadDescriptionAnimator() {
- AnimatorUpdateListener listener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator arg0) {
- if (getVerticalGridView() == null) {
- return;
- }
- ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
- getVerticalGridView().findViewHolderForPosition(0);
- if (adapterVh != null && adapterVh.getViewHolder()
- instanceof PlaybackControlsRowPresenter.ViewHolder) {
- final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
- adapterVh.getViewHolder()).mDescriptionViewHolder;
- if (vh != null) {
- vh.view.setAlpha((Float) arg0.getAnimatedValue());
- }
- }
- }
- };
-
- Context context = getContext();
- mDescriptionFadeInAnimator = loadAnimator(context,
- R.animator.lb_playback_description_fade_in);
- mDescriptionFadeInAnimator.addUpdateListener(listener);
- mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
-
- mDescriptionFadeOutAnimator = loadAnimator(context,
- R.animator.lb_playback_description_fade_out);
- mDescriptionFadeOutAnimator.addUpdateListener(listener);
- }
-
- void fade(boolean fadeIn) {
- if (DEBUG) Log.v(TAG, "fade " + fadeIn);
- if (getView() == null) {
- return;
- }
- if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
- if (DEBUG) Log.v(TAG, "requested fade in progress");
- return;
- }
- if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
- if (DEBUG) Log.v(TAG, "fade is no-op");
- return;
- }
-
- mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0
- ? mMajorFadeTranslateY : mMinorFadeTranslateY;
-
- if (mFadingStatus == IDLE) {
- if (fadeIn) {
- mBgFadeInAnimator.start();
- mControlRowFadeInAnimator.start();
- mOtherRowFadeInAnimator.start();
- mDescriptionFadeInAnimator.start();
- } else {
- mBgFadeOutAnimator.start();
- mControlRowFadeOutAnimator.start();
- mOtherRowFadeOutAnimator.start();
- mDescriptionFadeOutAnimator.start();
- }
- } else {
- if (fadeIn) {
- mBgFadeOutAnimator.reverse();
- mControlRowFadeOutAnimator.reverse();
- mOtherRowFadeOutAnimator.reverse();
- mDescriptionFadeOutAnimator.reverse();
- } else {
- mBgFadeInAnimator.reverse();
- mControlRowFadeInAnimator.reverse();
- mOtherRowFadeInAnimator.reverse();
- mDescriptionFadeInAnimator.reverse();
- }
- }
- getView().announceForAccessibility(getString(fadeIn ? R.string.lb_playback_controls_shown
- : R.string.lb_playback_controls_hidden));
-
- // If fading in while control row is focused, set initial translationY so
- // views slide in from below.
- if (fadeIn && mFadingStatus == IDLE) {
- final int count = getVerticalGridView().getChildCount();
- for (int i = 0; i < count; i++) {
- getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
- }
- }
-
- mFadingStatus = fadeIn ? IN : OUT;
- }
-
- /**
- * Sets the list of rows for the fragment.
- */
- @Override
- public void setAdapter(ObjectAdapter adapter) {
- if (getAdapter() != null) {
- getAdapter().unregisterObserver(mObserver);
- }
- super.setAdapter(adapter);
- if (adapter != null) {
- adapter.registerObserver(mObserver);
- }
- }
-
- @Override
- protected void setupPresenter(Presenter rowPresenter) {
- if (rowPresenter instanceof PlaybackRowPresenter) {
- if (rowPresenter.getFacet(ItemAlignmentFacet.class) == null) {
- ItemAlignmentFacet itemAlignment = new ItemAlignmentFacet();
- ItemAlignmentFacet.ItemAlignmentDef def =
- new ItemAlignmentFacet.ItemAlignmentDef();
- def.setItemAlignmentOffset(0);
- def.setItemAlignmentOffsetPercent(100);
- itemAlignment.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]
- {def});
- rowPresenter.setFacet(ItemAlignmentFacet.class, itemAlignment);
- }
- } else {
- super.setupPresenter(rowPresenter);
- }
- }
-
- @Override
- void setVerticalGridViewLayout(VerticalGridView listview) {
- if (listview == null) {
- return;
- }
-
- // we set the base line of alignment to -paddingBottom
- listview.setWindowAlignmentOffset(-mPaddingBottom);
- listview.setWindowAlignmentOffsetPercent(
- VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
-
- // align other rows that arent the last to center of screen, since our baseline is
- // -mPaddingBottom, we need subtract that from mOtherRowsCenterToBottom.
- listview.setItemAlignmentOffset(mOtherRowsCenterToBottom - mPaddingBottom);
- listview.setItemAlignmentOffsetPercent(50);
-
- // Push last row to the bottom padding
- // Padding affects alignment when last row is focused
- listview.setPadding(listview.getPaddingLeft(), listview.getPaddingTop(),
- listview.getPaddingRight(), mPaddingBottom);
- listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mOtherRowsCenterToBottom = getResources()
- .getDimensionPixelSize(R.dimen.lb_playback_other_rows_center_to_bottom);
- mPaddingBottom =
- getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
- mBgDarkColor =
- getResources().getColor(R.color.lb_playback_controls_background_dark);
- mBgLightColor =
- getResources().getColor(R.color.lb_playback_controls_background_light);
- mShowTimeMs =
- getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
- mMajorFadeTranslateY =
- getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
- mMinorFadeTranslateY =
- getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
-
- loadBgAnimator();
- loadControlRowAnimator();
- loadOtherRowAnimator();
- loadDescriptionAnimator();
- }
-
- /**
- * Sets the background type.
- *
- * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
- */
- public void setBackgroundType(int type) {
- switch (type) {
- case BG_LIGHT:
- case BG_DARK:
- case BG_NONE:
- if (type != mBackgroundType) {
- mBackgroundType = type;
- updateBackground();
- }
- break;
- default:
- throw new IllegalArgumentException("Invalid background type");
- }
- }
-
- /**
- * Returns the background type.
- */
- public int getBackgroundType() {
- return mBackgroundType;
- }
-
- private void updateBackground() {
- if (mRootView != null) {
- int color = mBgDarkColor;
- switch (mBackgroundType) {
- case BG_DARK: break;
- case BG_LIGHT: color = mBgLightColor; break;
- case BG_NONE: color = Color.TRANSPARENT; break;
- }
- mRootView.setBackground(new ColorDrawable(color));
- }
- }
-
- void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
- // Add extra space between rows 0 and 1
- if (vh == null && getVerticalGridView() != null) {
- vh = (ItemBridgeAdapter.ViewHolder)
- getVerticalGridView().findViewHolderForPosition(0);
- }
- if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
- final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
- ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
- (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
- adapterSize > 1);
- }
- }
-
- private final ItemBridgeAdapter.AdapterListener mAdapterListener =
- new ItemBridgeAdapter.AdapterListener() {
- @Override
- public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
- if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
- if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
- if (DEBUG) Log.v(TAG, "setting alpha to 0");
- vh.getViewHolder().view.setAlpha(0);
- }
- if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
- resetControlsToPrimaryActions(vh);
- }
- }
- @Override
- public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
- if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
- // Reset animation state
- vh.getViewHolder().view.setAlpha(1f);
- vh.getViewHolder().view.setTranslationY(0);
- if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
- Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
- vh.getViewHolder()).mDescriptionViewHolder;
- if (descriptionVh != null) {
- descriptionVh.view.setAlpha(1f);
- }
- }
- }
- @Override
- public void onBind(ItemBridgeAdapter.ViewHolder vh) {
- if (vh.getPosition() == 0) {
- updateControlsBottomSpace(vh);
- }
- }
- };
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mRootView = super.onCreateView(inflater, container, savedInstanceState);
- mBgAlpha = 255;
- updateBackground();
- getRowsSupportFragment().setExternalAdapterListener(mAdapterListener);
- return mRootView;
- }
-
- @Override
- public void onDestroyView() {
- mRootView = null;
- if (mHostCallback != null) {
- mHostCallback.onHostDestroy();
- }
- super.onDestroyView();
- }
-
- @Override
- public void onStart() {
- super.onStart();
- // Workaround problem VideoView forcing itself to focused, let controls take focus.
- getRowsSupportFragment().getView().requestFocus();
- if (mHostCallback != null) {
- mHostCallback.onHostStart();
- }
- }
-
- private final DataObserver mObserver = new DataObserver() {
- @Override
- public void onChanged() {
- updateControlsBottomSpace(null);
- }
- };
-
- static abstract class AnimatorListener implements Animator.AnimatorListener {
- ArrayList<View> mViews = new ArrayList<View>();
- ArrayList<Integer> mLayerType = new ArrayList<Integer>();
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- @Override
- public void onAnimationStart(Animator animation) {
- getViews(mViews);
- for (View view : mViews) {
- mLayerType.add(view.getLayerType());
- view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- for (int i = 0; i < mViews.size(); i++) {
- mViews.get(i).setLayerType(mLayerType.get(i), null);
- }
- mLayerType.clear();
- mViews.clear();
- }
- abstract void getViews(ArrayList<View> views);
-
- };
-}
diff --git a/android/support/v17/leanback/app/PlaybackSupportFragment.java b/android/support/v17/leanback/app/PlaybackSupportFragment.java
index d63e72c6..a8741aba 100644
--- a/android/support/v17/leanback/app/PlaybackSupportFragment.java
+++ b/android/support/v17/leanback/app/PlaybackSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from PlaybackFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2016 The Android Open Source Project
*
@@ -21,13 +18,14 @@ import android.animation.AnimatorInflater;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.support.v4.app.Fragment;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.animation.LogAccelerateInterpolator;
import android.support.v17.leanback.animation.LogDecelerateInterpolator;
@@ -48,6 +46,7 @@ import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.InputEvent;
@@ -450,7 +449,7 @@ public class PlaybackSupportFragment extends Fragment {
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// controls view are initially visible, make it invisible
// if app has called hideControlsOverlay() before view created.
diff --git a/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java b/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
index cdf3f97a..e7450947 100644
--- a/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoPlaybackFragmentGlueHost.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2016 The Android Open Source Project
*
diff --git a/android/support/v17/leanback/app/RowsFragment.java b/android/support/v17/leanback/app/RowsFragment.java
index dd0dbede..a008ad60 100644
--- a/android/support/v17/leanback/app/RowsFragment.java
+++ b/android/support/v17/leanback/app/RowsFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -16,6 +19,8 @@ package android.support.v17.leanback.app;
import android.animation.TimeAnimator;
import android.animation.TimeAnimator.TimeListener;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
@@ -285,7 +290,7 @@ public class RowsFragment extends BaseRowFragment implements
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (DEBUG) Log.v(TAG, "onViewCreated");
super.onViewCreated(view, savedInstanceState);
// Align the top edge of child with id row_content.
@@ -625,6 +630,11 @@ public class RowsFragment extends BaseRowFragment implements
}
+ /**
+ * The adapter that RowsFragment implements
+ * BrowseFragment.MainFragmentRowsAdapter.
+ * @see #getMainFragmentRowsAdapter().
+ */
public static class MainFragmentRowsAdapter
extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
diff --git a/android/support/v17/leanback/app/RowsSupportFragment.java b/android/support/v17/leanback/app/RowsSupportFragment.java
index c00f78b9..05e38130 100644
--- a/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from RowsFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -19,6 +16,8 @@ package android.support.v17.leanback.app;
import android.animation.TimeAnimator;
import android.animation.TimeAnimator.TimeListener;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
@@ -288,7 +287,7 @@ public class RowsSupportFragment extends BaseRowSupportFragment implements
}
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
if (DEBUG) Log.v(TAG, "onViewCreated");
super.onViewCreated(view, savedInstanceState);
// Align the top edge of child with id row_content.
@@ -628,6 +627,11 @@ public class RowsSupportFragment extends BaseRowSupportFragment implements
}
+ /**
+ * The adapter that RowsSupportFragment implements
+ * BrowseSupportFragment.MainFragmentRowsAdapter.
+ * @see #getMainFragmentRowsAdapter().
+ */
public static class MainFragmentRowsAdapter
extends BrowseSupportFragment.MainFragmentRowsAdapter<RowsSupportFragment> {
diff --git a/android/support/v17/leanback/app/SearchFragment.java b/android/support/v17/leanback/app/SearchFragment.java
index acf47454..2154ff28 100644
--- a/android/support/v17/leanback/app/SearchFragment.java
+++ b/android/support/v17/leanback/app/SearchFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from SearchSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -16,7 +19,6 @@ package android.support.v17.leanback.app;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest;
-import android.app.Fragment;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -35,6 +37,7 @@ import android.support.v17.leanback.widget.SearchBar;
import android.support.v17.leanback.widget.SearchOrbView;
import android.support.v17.leanback.widget.SpeechRecognitionCallback;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -52,12 +55,11 @@ import java.util.List;
* into a {@link RowsFragment}, in the same way that they are in a {@link
* BrowseFragment}.
*
- * <p>If you do not supply a callback via
- * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech
- * recognizer will be used for which your application will need to declare
+ * <p>A SpeechRecognizer object will be created for which your application will need to declare
* android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
* the device version is >= 23, a permission dialog will show first time using speech recognition.
* 0 will be used as requestCode in requestPermissions() call.
+ * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)} is deprecated.
* </p>
* <p>
* Speech recognition is automatically started when fragment is created, but
@@ -393,7 +395,7 @@ public class SearchFragment extends Fragment {
mIsPaused = false;
if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(
- FragmentUtil.getContext(this));
+ FragmentUtil.getContext(SearchFragment.this));
mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
}
if (mPendingStartRecognitionWhenPaused) {
@@ -576,8 +578,11 @@ public class SearchFragment extends Fragment {
/**
* Sets this callback to have the fragment pass speech recognition requests
- * to the activity rather than using an internal recognizer.
+ * to the activity rather than using a SpeechRecognizer object.
+ * @deprecated Launching voice recognition activity is no longer supported. App should declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file.
*/
+ @Deprecated
public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) {
mSpeechRecognitionCallback = callback;
if (mSearchBar != null) {
diff --git a/android/support/v17/leanback/app/SearchSupportFragment.java b/android/support/v17/leanback/app/SearchSupportFragment.java
index 36b560de..ed2a6792 100644
--- a/android/support/v17/leanback/app/SearchSupportFragment.java
+++ b/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from SearchFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -19,7 +16,6 @@ package android.support.v17.leanback.app;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.Manifest;
-import android.support.v4.app.Fragment;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -38,6 +34,7 @@ import android.support.v17.leanback.widget.SearchBar;
import android.support.v17.leanback.widget.SearchOrbView;
import android.support.v17.leanback.widget.SpeechRecognitionCallback;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -55,12 +52,11 @@ import java.util.List;
* into a {@link RowsSupportFragment}, in the same way that they are in a {@link
* BrowseSupportFragment}.
*
- * <p>If you do not supply a callback via
- * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech
- * recognizer will be used for which your application will need to declare
+ * <p>A SpeechRecognizer object will be created for which your application will need to declare
* android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
* the device version is >= 23, a permission dialog will show first time using speech recognition.
* 0 will be used as requestCode in requestPermissions() call.
+ * {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)} is deprecated.
* </p>
* <p>
* Speech recognition is automatically started when fragment is created, but
@@ -579,8 +575,11 @@ public class SearchSupportFragment extends Fragment {
/**
* Sets this callback to have the fragment pass speech recognition requests
- * to the activity rather than using an internal recognizer.
+ * to the activity rather than using a SpeechRecognizer object.
+ * @deprecated Launching voice recognition activity is no longer supported. App should declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file.
*/
+ @Deprecated
public void setSpeechRecognitionCallback(SpeechRecognitionCallback callback) {
mSpeechRecognitionCallback = callback;
if (mSearchBar != null) {
diff --git a/android/support/v17/leanback/app/VerticalGridFragment.java b/android/support/v17/leanback/app/VerticalGridFragment.java
index 5cf5799e..5bc52ff5 100644
--- a/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VerticalGridSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2014 The Android Open Source Project
*
@@ -240,7 +243,7 @@ public class VerticalGridFragment extends BaseFragment {
@Override
protected Object createEntranceTransition() {
- return TransitionHelper.loadTransition(FragmentUtil.getContext(this),
+ return TransitionHelper.loadTransition(FragmentUtil.getContext(VerticalGridFragment.this),
R.transition.lb_vertical_grid_entrance_transition);
}
diff --git a/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/android/support/v17/leanback/app/VerticalGridSupportFragment.java
index a38bac52..4cfe981a 100644
--- a/android/support/v17/leanback/app/VerticalGridSupportFragment.java
+++ b/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VerticalGridFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2014 The Android Open Source Project
*
diff --git a/android/support/v17/leanback/app/VideoFragment.java b/android/support/v17/leanback/app/VideoFragment.java
index 41241d0b..1b2b8d07 100644
--- a/android/support/v17/leanback/app/VideoFragment.java
+++ b/android/support/v17/leanback/app/VideoFragment.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VideoSupportFragment.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2016 The Android Open Source Project
*
@@ -38,7 +41,7 @@ public class VideoFragment extends PlaybackFragment {
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
- mVideoSurface = (SurfaceView) LayoutInflater.from(FragmentUtil.getContext(this)).inflate(
+ mVideoSurface = (SurfaceView) LayoutInflater.from(FragmentUtil.getContext(VideoFragment.this)).inflate(
R.layout.lb_video_surface, root, false);
root.addView(mVideoSurface, 0);
mVideoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
diff --git a/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
index a64b521b..d123676f 100644
--- a/android/support/v17/leanback/app/VideoFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/VideoFragmentGlueHost.java
@@ -1,3 +1,6 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from VideoSupportFragmentGlueHost.java. DO NOT MODIFY. */
+
/*
* Copyright (C) 2016 The Android Open Source Project
*
diff --git a/android/support/v17/leanback/app/VideoSupportFragment.java b/android/support/v17/leanback/app/VideoSupportFragment.java
index 321bdbed..51003d35 100644
--- a/android/support/v17/leanback/app/VideoSupportFragment.java
+++ b/android/support/v17/leanback/app/VideoSupportFragment.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoFragment.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2016 The Android Open Source Project
*
diff --git a/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java b/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
index 28f919b6..66aabc41 100644
--- a/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
+++ b/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
@@ -1,6 +1,3 @@
-// CHECKSTYLE:OFF Generated code
-/* This file is auto-generated from VideoVideoFragmentGlueHost.java. DO NOT MODIFY. */
-
/*
* Copyright (C) 2016 The Android Open Source Project
*
diff --git a/android/support/v17/leanback/app/package-info.java b/android/support/v17/leanback/app/package-info.java
index 852a0075..b7369090 100644
--- a/android/support/v17/leanback/app/package-info.java
+++ b/android/support/v17/leanback/app/package-info.java
@@ -13,47 +13,54 @@
*/
/**
- * <p>Support classes providing high level Leanback user interface building blocks:
- * fragments and helpers.</p>
+ * <p>Support classes providing high level Leanback user interface building blocks.</p>
* <p>
- * Leanback fragments are available both as platform fragments (subclassed from
- * {@link android.app.Fragment android.app.Fragment}) and as support fragments (subclassed from
- * {@link android.support.v4.app.Fragment android.support.v4.app.Fragment}). A few of the most
+ * Leanback fragments are available both as support fragments (subclassed from
+ * {@link android.support.v4.app.Fragment android.support.v4.app.Fragment}) and as platform
+ * fragments (subclassed from {@link android.app.Fragment android.app.Fragment}). A few of the most
* commonly used leanback fragments are described here.
* </p>
* <p>
- * A {@link android.support.v17.leanback.app.BrowseFragment} includes an optional “fastlane”
+ * A {@link android.support.v17.leanback.app.BrowseSupportFragment} by default operates in the "row"
+ * mode. It includes an optional “fastlane”
* navigation side panel and a list of rows, with one-to-one correspondance between each header
* in the fastlane and a row. The application supplies the
* {@link android.support.v17.leanback.widget.ObjectAdapter} containing the list of
* rows and a {@link android.support.v17.leanback.widget.PresenterSelector} of row presenters.
* </p>
* <p>
- * A {@link android.support.v17.leanback.app.DetailsFragment} will typically consist of a large
- * overview of an item at the top,
+ * A {@link android.support.v17.leanback.app.BrowseSupportFragment} also works in a "page" mode when
+ * each row of fastlane is mapped to a fragment that the app registers in
+ * {@link android.support.v17.leanback.app.BrowseSupportFragment#getMainFragmentRegistry()}.
+ * </p>
+ * <p>
+ * A {@link android.support.v17.leanback.app.DetailsSupportFragment} will typically consist of a
+ * large overview of an item at the top,
* some actions that a user can perform, and possibly rows of additional or related items.
- * The content for this fragment is specified in the same way as for the BrowseFragment, with the
- * convention that the first element in the ObjectAdapter corresponds to the overview row.
+ * The content for this fragment is specified in the same way as for the BrowseSupportFragment, with
+ * the convention that the first element in the ObjectAdapter corresponds to the overview row.
* The {@link android.support.v17.leanback.widget.DetailsOverviewRow} and
- * {@link android.support.v17.leanback.widget.DetailsOverviewRowPresenter} provide a default template
- * for this row.
+ * {@link android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter} provide a
+ * default template for this row.
* </p>
* <p>
- * A {@link android.support.v17.leanback.app.PlaybackOverlayFragment} implements standard playback
- * transport controls with a Leanback
- * look and feel. It is recommended to use an instance of the
- * {@link android.support.v17.leanback.app.PlaybackControlGlue} with the
- * PlaybackOverlayFragment. This helper implements a standard behavior for user interaction with
- * the most commonly used controls such as fast forward and rewind.
+ * A {@link android.support.v17.leanback.app.PlaybackSupportFragment} or its subclass
+ * {@link android.support.v17.leanback.app.VideoSupportFragment} hosts
+ * {@link android.support.v17.leanback.media.PlaybackTransportControlGlue}
+ * or {@link android.support.v17.leanback.media.PlaybackBannerControlGlue} with a Leanback
+ * look and feel. It is recommended to use an instance of
+ * {@link android.support.v17.leanback.media.PlaybackTransportControlGlue}.
+ * This helper implements a standard behavior for user interaction with
+ * the most commonly used controls as well as video scrubbing.
* </p>
* <p>
- * A {@link android.support.v17.leanback.app.SearchFragment} allows the developer to accept a query
- * from a user and display the results
+ * A {@link android.support.v17.leanback.app.SearchSupportFragment} allows the developer to accept a
+ * query from a user and display the results
* using the familiar list rows.
* </p>
* <p>
- * A {@link android.support.v17.leanback.app.GuidedStepFragment} is used to guide the user through a
- * decision or series of decisions.
+ * A {@link android.support.v17.leanback.app.GuidedStepSupportFragment} is used to guide the user
+ * through a decision or series of decisions.
* </p>
**/
diff --git a/android/support/v17/leanback/media/MediaControllerGlue.java b/android/support/v17/leanback/media/MediaControllerGlue.java
index 730bf3a4..b8e9b745 100644
--- a/android/support/v17/leanback/media/MediaControllerGlue.java
+++ b/android/support/v17/leanback/media/MediaControllerGlue.java
@@ -28,7 +28,10 @@ import android.util.Log;
/**
* A helper class for implementing a glue layer for {@link MediaControllerCompat}.
+ * @deprecated Use {@link MediaControllerAdapter} with {@link PlaybackTransportControlGlue} or
+ * {@link PlaybackBannerControlGlue}.
*/
+@Deprecated
public abstract class MediaControllerGlue extends PlaybackControlGlue {
static final String TAG = "MediaControllerGlue";
static final boolean DEBUG = false;
diff --git a/android/support/v17/leanback/media/MediaPlayerGlue.java b/android/support/v17/leanback/media/MediaPlayerGlue.java
index 3a274b1c..73bca979 100644
--- a/android/support/v17/leanback/media/MediaPlayerGlue.java
+++ b/android/support/v17/leanback/media/MediaPlayerGlue.java
@@ -22,6 +22,7 @@ import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
+import android.support.annotation.RestrictTo;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
@@ -50,7 +51,11 @@ import java.util.List;
* </ul>
*
* @hide
+ * @deprecated Use {@link MediaPlayerAdapter} with {@link PlaybackTransportControlGlue} or
+ * {@link PlaybackBannerControlGlue}.
*/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Deprecated
public class MediaPlayerGlue extends PlaybackControlGlue implements
OnItemViewSelectedListener {
@@ -485,11 +490,6 @@ public class MediaPlayerGlue extends PlaybackControlGlue implements
}
@Override
- public boolean isReadyForPlayback() {
- return mInitialized;
- }
-
- @Override
public boolean isPrepared() {
return mInitialized;
}
diff --git a/android/support/v17/leanback/media/PlaybackBannerControlGlue.java b/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
index ca424a8e..e6446323 100644
--- a/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
+++ b/android/support/v17/leanback/media/PlaybackBannerControlGlue.java
@@ -59,6 +59,24 @@ import java.lang.annotation.RetentionPolicy;
* {@link #onPlayCompleted()}.
* </p>
*
+ * Sample Code:
+ * <pre><code>
+ * public class MyVideoFragment extends VideoFragment {
+ * &#64;Override
+ * public void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * PlaybackBannerControlGlue<MediaPlayerAdapter> playerGlue =
+ * new PlaybackBannerControlGlue(getActivity(),
+ * new MediaPlayerAdapter(getActivity()));
+ * playerGlue.setHost(new VideoFragmentGlueHost(this));
+ * playerGlue.setSubtitle("Leanback artist");
+ * playerGlue.setTitle("Leanback team at work");
+ * String uriPath = "android.resource://com.example.android.leanback/raw/video";
+ * playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
+ * playerGlue.playWhenPrepared();
+ * }
+ * }
+ * </code></pre>
* @param <T> Type of {@link PlayerAdapter} passed in constructor.
*/
public class PlaybackBannerControlGlue<T extends PlayerAdapter>
diff --git a/android/support/v17/leanback/media/PlaybackGlue.java b/android/support/v17/leanback/media/PlaybackGlue.java
index 32d5545c..7c595739 100644
--- a/android/support/v17/leanback/media/PlaybackGlue.java
+++ b/android/support/v17/leanback/media/PlaybackGlue.java
@@ -49,21 +49,10 @@ public abstract class PlaybackGlue {
*/
public abstract static class PlayerCallback {
/**
- * This method is fired when media is ready for playback {@link #isPrepared()}.
- * @deprecated use {@link #onPreparedStateChanged(PlaybackGlue)}.
- */
- @Deprecated
- public void onReadyForPlayback() {
- }
-
- /**
* Event for {@link #isPrepared()} changed.
* @param glue The PlaybackGlue that has changed {@link #isPrepared()}.
*/
public void onPreparedStateChanged(PlaybackGlue glue) {
- if (glue.isPrepared()) {
- onReadyForPlayback();
- }
}
/**
@@ -98,41 +87,12 @@ public abstract class PlaybackGlue {
}
/**
- * Returns true when the media player is ready to start media playback. Subclasses must
- * implement this method correctly. When returning false, app may listen to
- * {@link PlayerCallback#onReadyForPlayback()} event.
- *
- * @see PlayerCallback#onReadyForPlayback()
- * @deprecated Use isPrepared() instead.
- */
- @Deprecated
- public boolean isReadyForPlayback() {
- return true;
- }
-
- /**
* Returns true when the media player is prepared to start media playback. When returning false,
* app may listen to {@link PlayerCallback#onPreparedStateChanged(PlaybackGlue)} event.
* @return True if prepared, false otherwise.
*/
public boolean isPrepared() {
- return isReadyForPlayback();
- }
-
- /**
- * Sets the {@link PlayerCallback} callback. It will reset the existing callbacks.
- * In most cases you would call {@link #addPlayerCallback(PlayerCallback)}.
- * @deprecated Use {@link #addPlayerCallback(PlayerCallback)}.
- */
- @Deprecated
- public void setPlayerCallback(PlayerCallback playerCallback) {
- if (playerCallback == null) {
- if (mPlayerCallbacks != null) {
- mPlayerCallbacks.clear();
- }
- } else {
- addPlayerCallback(playerCallback);
- }
+ return true;
}
/**
@@ -174,12 +134,32 @@ public abstract class PlaybackGlue {
}
/**
- * Starts the media player.
+ * Starts the media player. Does nothing if {@link #isPrepared()} is false. To wait
+ * {@link #isPrepared()} to be true before playing, use {@link #playWhenPrepared()}.
*/
public void play() {
}
/**
+ * Starts play when {@link #isPrepared()} becomes true.
+ */
+ public void playWhenPrepared() {
+ if (isPrepared()) {
+ play();
+ } else {
+ addPlayerCallback(new PlayerCallback() {
+ @Override
+ public void onPreparedStateChanged(PlaybackGlue glue) {
+ if (glue.isPrepared()) {
+ removePlayerCallback(this);
+ play();
+ }
+ }
+ });
+ }
+ }
+
+ /**
* Pauses the media player.
*/
public void pause() {
diff --git a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
index 61ea52ba..4aa9bf66 100644
--- a/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
+++ b/android/support/v17/leanback/media/PlaybackTransportControlGlue.java
@@ -68,23 +68,15 @@ import java.lang.ref.WeakReference;
* &#64;Override
* public void onCreate(Bundle savedInstanceState) {
* super.onCreate(savedInstanceState);
- * final PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
+ * PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
* new PlaybackTransportControlGlue(getActivity(),
* new MediaPlayerAdapter(getActivity()));
* playerGlue.setHost(new VideoFragmentGlueHost(this));
- * playerGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
- * &#64;Override
- * public void onPreparedStateChanged(PlaybackGlue glue) {
- * if (glue.isPrepared()) {
- * playerGlue.setSeekProvider(new MySeekProvider());
- * playerGlue.play();
- * }
- * }
- * });
* playerGlue.setSubtitle("Leanback artist");
* playerGlue.setTitle("Leanback team at work");
* String uriPath = "android.resource://com.example.android.leanback/raw/video";
* playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
+ * playerGlue.playWhenPrepared();
* }
* }
* </code></pre>
diff --git a/android/support/v17/leanback/package-info.java b/android/support/v17/leanback/package-info.java
index aa648277..80b26e92 100644
--- a/android/support/v17/leanback/package-info.java
+++ b/android/support/v17/leanback/package-info.java
@@ -41,19 +41,20 @@
* <p>
* Leanback contains a mixture of higher level building blocks such as Fragments in the
* {@link android.support.v17.leanback.app} package. Notable examples are the
- * {@link android.support.v17.leanback.app.BrowseFragment} and the
- * {@link android.support.v17.leanback.app.GuidedStepFragment}. Helper classes are also provided
- * that work with the leanback fragments, for example the
- * {@link android.support.v17.leanback.app.PlaybackControlGlue}.
+ * {@link android.support.v17.leanback.app.BrowseSupportFragment},
+ * {@link android.support.v17.leanback.app.DetailsSupportFragment},
+ * {@link android.support.v17.leanback.app.PlaybackSupportFragment} and the
+ * {@link android.support.v17.leanback.app.GuidedStepSupportFragment}. Helper classes are also
+ * provided that work with the leanback fragments, for example the
+ * {@link android.support.v17.leanback.media.PlaybackTransportControlGlue} and
+ * {@link android.support.v17.leanback.app.PlaybackSupportFragmentGlueHost}.
* </p>
* <p>
* Many lower level building blocks are also provided in the {@link android.support.v17.leanback.widget} package.
* These allow applications to easily incorporate Leanback look and feel while allowing for a
* high degree of customization. Primary examples include the UI widget
* {@link android.support.v17.leanback.widget.HorizontalGridView} and
- * {@link android.support.v17.leanback.widget.VerticalGridView}. Helper classes also exist at this level
- * which do not depend on the leanback fragments, for example the
- * {@link android.support.v17.leanback.widget.TitleHelper}.
+ * {@link android.support.v17.leanback.widget.VerticalGridView}.
*/
package android.support.v17.leanback; \ No newline at end of file
diff --git a/android/support/v17/leanback/widget/ActionPresenterSelector.java b/android/support/v17/leanback/widget/ActionPresenterSelector.java
index 1ced4d4b..a018c2e2 100644
--- a/android/support/v17/leanback/widget/ActionPresenterSelector.java
+++ b/android/support/v17/leanback/widget/ActionPresenterSelector.java
@@ -55,7 +55,41 @@ class ActionPresenterSelector extends PresenterSelector {
}
}
- static class OneLineActionPresenter extends Presenter {
+ abstract static class ActionPresenter extends Presenter {
+ @Override
+ public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+ Action action = (Action) item;
+ ActionViewHolder vh = (ActionViewHolder) viewHolder;
+ vh.mAction = action;
+ Drawable icon = action.getIcon();
+ if (icon != null) {
+ final int startPadding = vh.view.getResources()
+ .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start);
+ final int endPadding = vh.view.getResources()
+ .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end);
+ vh.view.setPaddingRelative(startPadding, 0, endPadding, 0);
+ } else {
+ final int padding = vh.view.getResources()
+ .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
+ vh.view.setPaddingRelative(padding, 0, padding, 0);
+ }
+ if (vh.mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
+ } else {
+ vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+ }
+ }
+
+ @Override
+ public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+ ActionViewHolder vh = (ActionViewHolder) viewHolder;
+ vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ vh.view.setPadding(0, 0, 0, 0);
+ vh.mAction = null;
+ }
+ }
+
+ static class OneLineActionPresenter extends ActionPresenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
@@ -65,19 +99,14 @@ class ActionPresenterSelector extends PresenterSelector {
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+ super.onBindViewHolder(viewHolder, item);
+ ActionViewHolder vh = ((ActionViewHolder) viewHolder);
Action action = (Action) item;
- ActionViewHolder vh = (ActionViewHolder) viewHolder;
- vh.mAction = action;
vh.mButton.setText(action.getLabel1());
}
-
- @Override
- public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
- ((ActionViewHolder) viewHolder).mAction = null;
- }
}
- static class TwoLineActionPresenter extends Presenter {
+ static class TwoLineActionPresenter extends ActionPresenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
@@ -87,27 +116,9 @@ class ActionPresenterSelector extends PresenterSelector {
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+ super.onBindViewHolder(viewHolder, item);
Action action = (Action) item;
ActionViewHolder vh = (ActionViewHolder) viewHolder;
- Drawable icon = action.getIcon();
- vh.mAction = action;
-
- if (icon != null) {
- final int startPadding = vh.view.getResources()
- .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_start);
- final int endPadding = vh.view.getResources()
- .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_end);
- vh.view.setPaddingRelative(startPadding, 0, endPadding, 0);
- } else {
- final int padding = vh.view.getResources()
- .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
- vh.view.setPaddingRelative(padding, 0, padding, 0);
- }
- if (vh.mLayoutDirection == View.LAYOUT_DIRECTION_RTL) {
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
- } else {
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
- }
CharSequence line1 = action.getLabel1();
CharSequence line2 = action.getLabel2();
@@ -119,13 +130,5 @@ class ActionPresenterSelector extends PresenterSelector {
vh.mButton.setText(line1 + "\n" + line2);
}
}
-
- @Override
- public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
- ActionViewHolder vh = (ActionViewHolder) viewHolder;
- vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
- vh.view.setPadding(0, 0, 0, 0);
- vh.mAction = null;
- }
}
}
diff --git a/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 88de24cb..00bc073d 100644
--- a/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -230,10 +230,17 @@ public class ArrayObjectAdapter extends ObjectAdapter {
* specified position.
*
* @param itemList List of new Items
- * @param callback DiffCallback Object to compute the difference between the old data set and
- * new data set.
+ * @param callback Optional DiffCallback Object to compute the difference between the old data
+ * set and new data set. When null, {@link #notifyChanged()} will be fired.
*/
public void setItems(final List itemList, final DiffCallback callback) {
+ if (callback == null) {
+ // shortcut when DiffCallback is not provided
+ mItems.clear();
+ mItems.addAll(itemList);
+ notifyChanged();
+ return;
+ }
mOldItems.clear();
mOldItems.addAll(mItems);
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index 8b0c4a73..81431972 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2348,21 +2348,22 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
// scroll in main direction may add/prune views
private int scrollDirectionPrimary(int da) {
if (TRACE) TraceCompat.beginSection("scrollPrimary");
- boolean isMaxUnknown = false, isMinUnknown = false;
- int minScroll = 0, maxScroll = 0;
- if (!mIsSlidingChildViews) {
+ // We apply the cap of maxScroll/minScroll to the delta, except for two cases:
+ // 1. when children are in sliding out mode
+ // 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 (da > 0) {
- isMaxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
- if (!isMaxUnknown) {
- maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
+ if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
+ int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
if (da > maxScroll) {
da = maxScroll;
}
}
} else if (da < 0) {
- isMinUnknown = mWindowAlignment.mainAxis().isMinUnknown();
- if (!isMinUnknown) {
- minScroll = mWindowAlignment.mainAxis().getMinScroll();
+ if (!mWindowAlignment.mainAxis().isMinUnknown()) {
+ int minScroll = mWindowAlignment.mainAxis().getMinScroll();
if (da < minScroll) {
da = minScroll;
}
@@ -2856,7 +2857,8 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
if (!mScrollEnabled && smooth) {
return;
}
- if (getScrollPosition(view, childView, sTwoInts)) {
+ if (getScrollPosition(view, childView, sTwoInts)
+ || extraDelta != 0 || extraDeltaSecondary != 0) {
scrollGrid(sTwoInts[0] + extraDelta, sTwoInts[1] + extraDeltaSecondary, smooth);
}
}
diff --git a/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java b/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
index 82cfa799..000db3c4 100644
--- a/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
+++ b/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
@@ -32,7 +32,7 @@ import android.widget.LinearLayout;
/**
* A PlaybackControlsRowPresenter renders a {@link PlaybackControlsRow} to display a
* series of playback control buttons. Typically this row will be the first row in a fragment
- * such as the {@link android.support.v17.leanback.app.PlaybackOverlayFragment}.
+ * such as the {@link android.support.v17.leanback.app.PlaybackFragment}.
*
* <p>The detailed description is rendered using a {@link Presenter} passed in
* {@link #PlaybackControlsRowPresenter(Presenter)}. Typically this will be an instance of
diff --git a/android/support/v17/leanback/widget/SearchBar.java b/android/support/v17/leanback/widget/SearchBar.java
index 18f608e2..1094343f 100644
--- a/android/support/v17/leanback/widget/SearchBar.java
+++ b/android/support/v17/leanback/widget/SearchBar.java
@@ -116,14 +116,6 @@ public class SearchBar extends RelativeLayout {
}
- private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener =
- new AudioManager.OnAudioFocusChangeListener() {
- @Override
- public void onAudioFocusChange(int focusChange) {
- stopRecognition();
- }
- };
-
SearchBarListener mSearchBarListener;
SearchEditText mSearchTextEditor;
SpeechOrbView mSpeechOrbView;
@@ -495,7 +487,12 @@ public class SearchBar extends RelativeLayout {
/**
* Sets the speech recognition callback.
+ *
+ * @deprecated Launching voice recognition activity is no longer supported. App should declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file. See details in
+ * {@link android.support.v17.leanback.app.SearchSupportFragment}.
*/
+ @Deprecated
public void setSpeechRecognitionCallback(SpeechRecognitionCallback request) {
mSpeechRecognitionCallback = request;
if (mSpeechRecognitionCallback != null && mSpeechRecognizer != null) {
@@ -582,7 +579,6 @@ public class SearchBar extends RelativeLayout {
if (mListening) {
mSpeechRecognizer.cancel();
mListening = false;
- mAudioManager.abandonAudioFocus(mAudioFocusChangeListener);
}
mSpeechRecognizer.setRecognitionListener(null);
@@ -624,17 +620,6 @@ public class SearchBar extends RelativeLayout {
}
mRecognizing = true;
- // Request audio focus
- int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener,
- // Use the music stream.
- AudioManager.STREAM_MUSIC,
- // Request exclusive transient focus.
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
-
-
- if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- Log.w(TAG, "Could not get audio focus");
- }
mSearchTextEditor.setText("");
diff --git a/android/support/v17/leanback/widget/SpeechRecognitionCallback.java b/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
index 02b0990e..173444d8 100644
--- a/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
+++ b/android/support/v17/leanback/widget/SpeechRecognitionCallback.java
@@ -15,7 +15,12 @@ package android.support.v17.leanback.widget;
/**
* Interface for receiving notification that speech recognition should be initiated.
+ *
+ * @deprecated Launching voice recognition activity is no longer supported. App should declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file. See details in
+ * {@link android.support.v17.leanback.app.SearchSupportFragment}.
*/
+@Deprecated
public interface SpeechRecognitionCallback {
/**
* Method invoked when speech recognition should be initiated.
diff --git a/android/support/v17/leanback/widget/WindowAlignment.java b/android/support/v17/leanback/widget/WindowAlignment.java
index 3ddb6f0d..55fa7589 100644
--- a/android/support/v17/leanback/widget/WindowAlignment.java
+++ b/android/support/v17/leanback/widget/WindowAlignment.java
@@ -261,20 +261,18 @@ class WindowAlignment {
// minScroll
mMinScroll = Math.min(mMinScroll,
calculateScrollToKeyLine(maxChildViewCenter, keyLine));
- } else {
- // don't over scroll max
- mMaxScroll = Math.max(mMinScroll, mMaxScroll);
}
+ // don't over scroll max
+ mMaxScroll = Math.max(mMinScroll, mMaxScroll);
} else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
if (isPreferKeylineOverHighEdge()) {
// if we prefer key line, might align min child to key line for
// maxScroll
mMaxScroll = Math.max(mMaxScroll,
calculateScrollToKeyLine(minChildViewCenter, keyLine));
- } else {
- // don't over scroll min
- mMinScroll = Math.min(mMinScroll, mMaxScroll);
}
+ // don't over scroll min
+ mMinScroll = Math.min(mMinScroll, mMaxScroll);
}
} else {
if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
@@ -283,20 +281,18 @@ class WindowAlignment {
// maxScroll
mMaxScroll = Math.max(mMaxScroll,
calculateScrollToKeyLine(minChildViewCenter, keyLine));
- } else {
- // don't over scroll min
- mMinScroll = Math.min(mMinScroll, mMaxScroll);
}
+ // don't over scroll min
+ mMinScroll = Math.min(mMinScroll, mMaxScroll);
} else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
if (isPreferKeylineOverHighEdge()) {
// if we prefer key line, might align max child to key line for
// minScroll
mMinScroll = Math.min(mMinScroll,
calculateScrollToKeyLine(maxChildViewCenter, keyLine));
- } else {
- // don't over scroll max
- mMaxScroll = Math.max(mMinScroll, mMaxScroll);
}
+ // don't over scroll max
+ mMaxScroll = Math.max(mMinScroll, mMaxScroll);
}
}
}
diff --git a/android/support/v17/preference/LeanbackPreferenceFragment.java b/android/support/v17/preference/LeanbackPreferenceFragment.java
index dbff1c85..48d14b83 100644
--- a/android/support/v17/preference/LeanbackPreferenceFragment.java
+++ b/android/support/v17/preference/LeanbackPreferenceFragment.java
@@ -30,12 +30,12 @@ import android.widget.TextView;
* <p>The following sample code shows a simple leanback preference fragment that is
* populated from a resource. The resource it loads is:</p>
*
- * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
*
* <p>The fragment needs only to implement {@link #onCreatePreferences(Bundle, String)} to populate
* the list of preference objects:</p>
*
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
* support_fragment_leanback}
*/
public abstract class LeanbackPreferenceFragment extends BaseLeanbackPreferenceFragment {
diff --git a/android/support/v17/preference/LeanbackSettingsFragment.java b/android/support/v17/preference/LeanbackSettingsFragment.java
index 08f19c47..d56a2a63 100644
--- a/android/support/v17/preference/LeanbackSettingsFragment.java
+++ b/android/support/v17/preference/LeanbackSettingsFragment.java
@@ -42,14 +42,14 @@ import android.widget.Space;
* <p>The following sample code shows a simple leanback preference fragment that is
* populated from a resource. The resource it loads is:</p>
*
- * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
*
* <p>The sample implements
* {@link PreferenceFragment.OnPreferenceStartFragmentCallback#onPreferenceStartFragment(PreferenceFragment, Preference)},
* {@link PreferenceFragment.OnPreferenceStartScreenCallback#onPreferenceStartScreen(PreferenceFragment, PreferenceScreen)},
* and {@link #onPreferenceStartInitialScreen()}:</p>
*
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
* support_fragment_leanback}
*/
public abstract class LeanbackSettingsFragment extends Fragment
diff --git a/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java b/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
index 3905ca55..0dcd9029 100644
--- a/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
+++ b/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
@@ -20,6 +20,8 @@ import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.pm.PackageManager;
import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.view.View;
/**
@@ -188,8 +190,9 @@ public final class AccessibilityServiceInfoCompat {
* @param packageManager The current package manager
* @return The localized description.
*/
+ @Nullable
public static String loadDescription(
- AccessibilityServiceInfo info, PackageManager packageManager) {
+ @NonNull AccessibilityServiceInfo info, @NonNull PackageManager packageManager) {
if (Build.VERSION.SDK_INT >= 16) {
return info.loadDescription(packageManager);
} else {
@@ -206,6 +209,7 @@ public final class AccessibilityServiceInfoCompat {
* @param feedbackType The feedback type.
* @return The string representation.
*/
+ @NonNull
public static String feedbackTypeToString(int feedbackType) {
StringBuilder builder = new StringBuilder();
builder.append("[");
@@ -245,6 +249,7 @@ public final class AccessibilityServiceInfoCompat {
* @param flag The flag.
* @return The string representation.
*/
+ @Nullable
public static String flagToString(int flag) {
switch (flag) {
case AccessibilityServiceInfo.DEFAULT:
@@ -276,7 +281,7 @@ public final class AccessibilityServiceInfoCompat {
* @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
* @see #CAPABILITY_CAN_FILTER_KEY_EVENTS
*/
- public static int getCapabilities(AccessibilityServiceInfo info) {
+ public static int getCapabilities(@NonNull AccessibilityServiceInfo info) {
if (Build.VERSION.SDK_INT >= 18) {
return info.getCapabilities();
} else {
@@ -296,6 +301,7 @@ public final class AccessibilityServiceInfoCompat {
* @param capability The capability.
* @return The string representation.
*/
+ @NonNull
public static String capabilityToString(int capability) {
switch (capability) {
case CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT:
diff --git a/android/support/v4/app/ActivityCompat.java b/android/support/v4/app/ActivityCompat.java
index f260508c..5833481a 100644
--- a/android/support/v4/app/ActivityCompat.java
+++ b/android/support/v4/app/ActivityCompat.java
@@ -74,6 +74,61 @@ public class ActivityCompat extends ContextCompat {
}
/**
+ * Customizable delegate that allows delegating permission compatibility methods to a custom
+ * implementation.
+ *
+ * <p>
+ * To delegate permission compatibility methods to a custom class, implement this interface,
+ * and call {@code ActivityCompat.setPermissionCompatDelegate(delegate);}. All future calls
+ * to the permission compatibility methods in this class will first check whether the
+ * delegate can handle the method call, and invoke the corresponding method if it can.
+ * </p>
+ */
+ public interface PermissionCompatDelegate {
+
+ /**
+ * Determines whether the delegate should handle
+ * {@link ActivityCompat#requestPermissions(Activity, String[], int)}, and request
+ * permissions if applicable. If this method returns true, it means that permission
+ * request is successfully handled by the delegate, and platform should not perform any
+ * further requests for permission.
+ *
+ * @param activity The target activity.
+ * @param permissions The requested permissions. Must me non-null and not empty.
+ * @param requestCode Application specific request code to match with a result reported to
+ * {@link
+ * OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}.
+ * Should be >= 0.
+ *
+ * @return Whether the delegate has handled the permission request.
+ * @see ActivityCompat#requestPermissions(Activity, String[], int)
+ */
+ boolean requestPermissions(@NonNull Activity activity,
+ @NonNull String[] permissions, @IntRange(from = 0) int requestCode);
+
+ /**
+ * Determines whether the delegate should handle the permission request as part of
+ * {@code FragmentActivity#onActivityResult(int, int, Intent)}. If this method returns true,
+ * it means that activity result is successfully handled by the delegate, and no further
+ * action is needed on this activity result.
+ *
+ * @param activity The target Activity.
+ * @param requestCode The integer request code originally supplied to
+ * {@code startActivityForResult()}, allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its {@code }setResult()}.
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ *
+ * @return Whether the delegate has handled the activity result.
+ * @see ActivityCompat#requestPermissions(Activity, String[], int)
+ */
+ boolean onActivityResult(@NonNull Activity activity,
+ @IntRange(from = 0) int requestCode, int resultCode, @Nullable Intent data);
+ }
+
+ /**
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -81,6 +136,8 @@ public class ActivityCompat extends ContextCompat {
void validateRequestPermissionsRequestCode(int requestCode);
}
+ private static PermissionCompatDelegate sDelegate;
+
/**
* This class should not be instantiated, but the constructor must be
* visible for the class to be extended (as in support-v13).
@@ -90,6 +147,25 @@ public class ActivityCompat extends ContextCompat {
}
/**
+ * Sets the permission delegate for {@code ActivityCompat}. Replaces the previously set
+ * delegate.
+ *
+ * @param delegate The delegate to be set. {@code null} to clear the set delegate.
+ */
+ public static void setPermissionCompatDelegate(
+ @Nullable PermissionCompatDelegate delegate) {
+ sDelegate = delegate;
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ public static PermissionCompatDelegate getPermissionCompatDelegate() {
+ return sDelegate;
+ }
+
+ /**
* Invalidate the activity's options menu, if able.
*
* <p>Before API level 11 (Android 3.0/Honeycomb) the lifecycle of the
@@ -120,7 +196,9 @@ public class ActivityCompat extends ContextCompat {
*
* @param activity Invalidate the options menu of this activity
* @return true if this operation was supported and it completed; false if it was not available.
+ * @deprecated Use {@link Activity#invalidateOptionsMenu()} directly.
*/
+ @Deprecated
public static boolean invalidateOptionsMenu(Activity activity) {
activity.invalidateOptionsMenu();
return true;
@@ -146,8 +224,8 @@ public class ActivityCompat extends ContextCompat {
* supplied here; there are no supported definitions for
* building it manually.
*/
- public static void startActivityForResult(Activity activity, Intent intent, int requestCode,
- @Nullable Bundle options) {
+ public static void startActivityForResult(@NonNull Activity activity, @NonNull Intent intent,
+ int requestCode, @Nullable Bundle options) {
if (Build.VERSION.SDK_INT >= 16) {
activity.startActivityForResult(intent, requestCode, options);
} else {
@@ -181,9 +259,10 @@ public class ActivityCompat extends ContextCompat {
* supplied here; there are no supported definitions for
* building it manually.
*/
- public static void startIntentSenderForResult(Activity activity, IntentSender intent,
- int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
- int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException {
+ public static void startIntentSenderForResult(@NonNull Activity activity,
+ @NonNull IntentSender intent, int requestCode, @Nullable Intent fillInIntent,
+ int flagsMask, int flagsValues, int extraFlags, @Nullable Bundle options)
+ throws IntentSender.SendIntentException {
if (Build.VERSION.SDK_INT >= 16) {
activity.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
flagsValues, extraFlags, options);
@@ -200,7 +279,7 @@ public class ActivityCompat extends ContextCompat {
* <p>On Android 4.1+ calling this method will call through to the native version of this
* method. For other platforms {@link Activity#finish()} will be called instead.</p>
*/
- public static void finishAffinity(Activity activity) {
+ public static void finishAffinity(@NonNull Activity activity) {
if (Build.VERSION.SDK_INT >= 16) {
activity.finishAffinity();
} else {
@@ -217,7 +296,7 @@ public class ActivityCompat extends ContextCompat {
* <p>On Android 4.4 or lower, this method only finishes the Activity with no
* special exit transition.</p>
*/
- public static void finishAfterTransition(Activity activity) {
+ public static void finishAfterTransition(@NonNull Activity activity) {
if (Build.VERSION.SDK_INT >= 21) {
activity.finishAfterTransition();
} else {
@@ -242,7 +321,7 @@ public class ActivityCompat extends ContextCompat {
* referrer information, applications can spoof it.</p>
*/
@Nullable
- public static Uri getReferrer(Activity activity) {
+ public static Uri getReferrer(@NonNull Activity activity) {
if (Build.VERSION.SDK_INT >= 22) {
return activity.getReferrer();
}
@@ -266,8 +345,8 @@ public class ActivityCompat extends ContextCompat {
*
* @param callback Used to manipulate shared element transitions on the launched Activity.
*/
- public static void setEnterSharedElementCallback(Activity activity,
- SharedElementCallback callback) {
+ public static void setEnterSharedElementCallback(@NonNull Activity activity,
+ @Nullable SharedElementCallback callback) {
if (Build.VERSION.SDK_INT >= 23) {
android.app.SharedElementCallback frameworkCallback = callback != null
? new SharedElementCallback23Impl(callback)
@@ -290,8 +369,8 @@ public class ActivityCompat extends ContextCompat {
*
* @param callback Used to manipulate shared element transitions on the launching Activity.
*/
- public static void setExitSharedElementCallback(Activity activity,
- SharedElementCallback callback) {
+ public static void setExitSharedElementCallback(@NonNull Activity activity,
+ @Nullable SharedElementCallback callback) {
if (Build.VERSION.SDK_INT >= 23) {
android.app.SharedElementCallback frameworkCallback = callback != null
? new SharedElementCallback23Impl(callback)
@@ -305,13 +384,13 @@ public class ActivityCompat extends ContextCompat {
}
}
- public static void postponeEnterTransition(Activity activity) {
+ public static void postponeEnterTransition(@NonNull Activity activity) {
if (Build.VERSION.SDK_INT >= 21) {
activity.postponeEnterTransition();
}
}
- public static void startPostponedEnterTransition(Activity activity) {
+ public static void startPostponedEnterTransition(@NonNull Activity activity) {
if (Build.VERSION.SDK_INT >= 21) {
activity.startPostponedEnterTransition();
}
@@ -386,6 +465,12 @@ public class ActivityCompat extends ContextCompat {
*/
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
+ if (sDelegate != null
+ && sDelegate.requestPermissions(activity, permissions, requestCode)) {
+ // Delegate has handled the permission request.
+ return;
+ }
+
if (Build.VERSION.SDK_INT >= 23) {
if (activity instanceof RequestPermissionsRequestCodeValidator) {
((RequestPermissionsRequestCodeValidator) activity)
diff --git a/android/support/v4/app/ActivityOptionsCompat.java b/android/support/v4/app/ActivityOptionsCompat.java
index 7b5916f5..66768058 100644
--- a/android/support/v4/app/ActivityOptionsCompat.java
+++ b/android/support/v4/app/ActivityOptionsCompat.java
@@ -24,6 +24,7 @@ import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.util.Pair;
@@ -60,7 +61,8 @@ public class ActivityOptionsCompat {
* @return Returns a new ActivityOptions object that you can use to supply
* these options as the options Bundle when starting an activity.
*/
- public static ActivityOptionsCompat makeCustomAnimation(Context context,
+ @NonNull
+ public static ActivityOptionsCompat makeCustomAnimation(@NonNull Context context,
int enterResId, int exitResId) {
if (Build.VERSION.SDK_INT >= 16) {
return createImpl(ActivityOptions.makeCustomAnimation(context, enterResId, exitResId));
@@ -88,7 +90,8 @@ public class ActivityOptionsCompat {
* @return Returns a new ActivityOptions object that you can use to supply
* these options as the options Bundle when starting an activity.
*/
- public static ActivityOptionsCompat makeScaleUpAnimation(View source,
+ @NonNull
+ public static ActivityOptionsCompat makeScaleUpAnimation(@NonNull View source,
int startX, int startY, int startWidth, int startHeight) {
if (Build.VERSION.SDK_INT >= 16) {
return createImpl(ActivityOptions.makeScaleUpAnimation(
@@ -111,7 +114,8 @@ public class ActivityOptionsCompat {
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
*/
- public static ActivityOptionsCompat makeClipRevealAnimation(View source,
+ @NonNull
+ public static ActivityOptionsCompat makeClipRevealAnimation(@NonNull View source,
int startX, int startY, int width, int height) {
if (Build.VERSION.SDK_INT >= 23) {
return createImpl(ActivityOptions.makeClipRevealAnimation(
@@ -139,8 +143,9 @@ public class ActivityOptionsCompat {
* @return Returns a new ActivityOptions object that you can use to supply
* these options as the options Bundle when starting an activity.
*/
- public static ActivityOptionsCompat makeThumbnailScaleUpAnimation(View source,
- Bitmap thumbnail, int startX, int startY) {
+ @NonNull
+ public static ActivityOptionsCompat makeThumbnailScaleUpAnimation(@NonNull View source,
+ @NonNull Bitmap thumbnail, int startX, int startY) {
if (Build.VERSION.SDK_INT >= 16) {
return createImpl(ActivityOptions.makeThumbnailScaleUpAnimation(
source, thumbnail, startX, startY));
@@ -166,8 +171,9 @@ public class ActivityOptionsCompat {
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
*/
- public static ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity,
- View sharedElement, String sharedElementName) {
+ @NonNull
+ public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity,
+ @NonNull View sharedElement, @NonNull String sharedElementName) {
if (Build.VERSION.SDK_INT >= 21) {
return createImpl(ActivityOptions.makeSceneTransitionAnimation(
activity, sharedElement, sharedElementName));
@@ -192,8 +198,9 @@ public class ActivityOptionsCompat {
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
*/
+ @NonNull
@SuppressWarnings("unchecked")
- public static ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity,
+ public static ActivityOptionsCompat makeSceneTransitionAnimation(@NonNull Activity activity,
Pair<View, String>... sharedElements) {
if (Build.VERSION.SDK_INT >= 21) {
android.util.Pair<View, String>[] pairs = null;
@@ -219,6 +226,7 @@ public class ActivityOptionsCompat {
* {@link android.R.attr#launchMode launchMode} values of
* <code>singleInstance</code> or <code>singleTask</code>.
*/
+ @NonNull
public static ActivityOptionsCompat makeTaskLaunchBehind() {
if (Build.VERSION.SDK_INT >= 21) {
return createImpl(ActivityOptions.makeTaskLaunchBehind());
@@ -230,6 +238,7 @@ public class ActivityOptionsCompat {
* Create a basic ActivityOptions that has no special animation associated with it.
* Other options can still be set.
*/
+ @NonNull
public static ActivityOptionsCompat makeBasic() {
if (Build.VERSION.SDK_INT >= 23) {
return createImpl(ActivityOptions.makeBasic());
@@ -314,8 +323,9 @@ public class ActivityOptionsCompat {
* {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
* @param screenSpacePixelRect Launch bounds to use for the activity or null for fullscreen.
*/
+ @NonNull
public ActivityOptionsCompat setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
- return null;
+ return this;
}
/**
@@ -335,6 +345,7 @@ public class ActivityOptionsCompat {
* object; you must not modify it, but can supply it to the startActivity
* methods that take an options Bundle.
*/
+ @Nullable
public Bundle toBundle() {
return null;
}
@@ -344,7 +355,7 @@ public class ActivityOptionsCompat {
* otherOptions. Any values defined in otherOptions replace those in the
* base options.
*/
- public void update(ActivityOptionsCompat otherOptions) {
+ public void update(@NonNull ActivityOptionsCompat otherOptions) {
// Do nothing.
}
@@ -372,7 +383,7 @@ public class ActivityOptionsCompat {
*
* @param receiver A broadcast receiver that will receive the report.
*/
- public void requestUsageTimeReport(PendingIntent receiver) {
+ public void requestUsageTimeReport(@NonNull PendingIntent receiver) {
// Do nothing.
}
}
diff --git a/android/support/v4/app/AlarmManagerCompat.java b/android/support/v4/app/AlarmManagerCompat.java
index 5a4582ba..a297cb5a 100644
--- a/android/support/v4/app/AlarmManagerCompat.java
+++ b/android/support/v4/app/AlarmManagerCompat.java
@@ -19,6 +19,7 @@ package android.support.v4.app;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.os.Build;
+import android.support.annotation.NonNull;
/**
* Compatibility library for {@link AlarmManager} with fallbacks for older platforms.
@@ -52,8 +53,8 @@ public final class AlarmManagerCompat {
* @see android.content.Context#registerReceiver
* @see android.content.Intent#filterEquals
*/
- public static void setAlarmClock(AlarmManager alarmManager, long triggerTime,
- PendingIntent showIntent, PendingIntent operation) {
+ public static void setAlarmClock(@NonNull AlarmManager alarmManager, long triggerTime,
+ @NonNull PendingIntent showIntent, @NonNull PendingIntent operation) {
if (Build.VERSION.SDK_INT >= 21) {
alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(triggerTime, showIntent),
operation);
@@ -110,8 +111,8 @@ public final class AlarmManagerCompat {
* @see AlarmManager#RTC
* @see AlarmManager#RTC_WAKEUP
*/
- public static void setAndAllowWhileIdle(AlarmManager alarmManager, int type,
- long triggerAtMillis, PendingIntent operation) {
+ public static void setAndAllowWhileIdle(@NonNull AlarmManager alarmManager, int type,
+ long triggerAtMillis, @NonNull PendingIntent operation) {
if (Build.VERSION.SDK_INT >= 23) {
alarmManager.setAndAllowWhileIdle(type, triggerAtMillis, operation);
} else {
@@ -155,8 +156,8 @@ public final class AlarmManagerCompat {
* @see AlarmManager#RTC
* @see AlarmManager#RTC_WAKEUP
*/
- public static void setExact(AlarmManager alarmManager, int type, long triggerAtMillis,
- PendingIntent operation) {
+ public static void setExact(@NonNull AlarmManager alarmManager, int type, long triggerAtMillis,
+ @NonNull PendingIntent operation) {
if (Build.VERSION.SDK_INT >= 19) {
alarmManager.setExact(type, triggerAtMillis, operation);
} else {
@@ -215,8 +216,8 @@ public final class AlarmManagerCompat {
* @see AlarmManager#RTC
* @see AlarmManager#RTC_WAKEUP
*/
- public static void setExactAndAllowWhileIdle(AlarmManager alarmManager, int type,
- long triggerAtMillis, PendingIntent operation) {
+ public static void setExactAndAllowWhileIdle(@NonNull AlarmManager alarmManager, int type,
+ long triggerAtMillis, @NonNull PendingIntent operation) {
if (Build.VERSION.SDK_INT >= 23) {
alarmManager.setExactAndAllowWhileIdle(type, triggerAtMillis, operation);
} else {
diff --git a/android/support/v4/app/AppLaunchChecker.java b/android/support/v4/app/AppLaunchChecker.java
index f8beb91c..af9512ad 100644
--- a/android/support/v4/app/AppLaunchChecker.java
+++ b/android/support/v4/app/AppLaunchChecker.java
@@ -22,8 +22,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v4.content.IntentCompat;
-import android.support.v4.content.SharedPreferencesCompat;
/**
* This class provides APIs for determining how an app has been launched.
@@ -46,7 +46,7 @@ public class AppLaunchChecker {
* @param context Context to check
* @return true if this app has been started by the user from the launcher at least once
*/
- public static boolean hasStartedFromLauncher(Context context) {
+ public static boolean hasStartedFromLauncher(@NonNull Context context) {
return context.getSharedPreferences(SHARED_PREFS_NAME, 0)
.getBoolean(KEY_STARTED_FROM_LAUNCHER, false);
}
@@ -62,7 +62,7 @@ public class AppLaunchChecker {
*
* @param activity the Activity currently running onCreate
*/
- public static void onActivityCreate(Activity activity) {
+ public static void onActivityCreate(@NonNull Activity activity) {
final SharedPreferences sp = activity.getSharedPreferences(SHARED_PREFS_NAME, 0);
if (sp.getBoolean(KEY_STARTED_FROM_LAUNCHER, false)) {
return;
@@ -76,8 +76,7 @@ public class AppLaunchChecker {
if (Intent.ACTION_MAIN.equals(launchIntent.getAction())
&& (launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
|| launchIntent.hasCategory(IntentCompat.CATEGORY_LEANBACK_LAUNCHER))) {
- SharedPreferencesCompat.EditorCompat.getInstance().apply(
- sp.edit().putBoolean(KEY_STARTED_FROM_LAUNCHER, true));
+ sp.edit().putBoolean(KEY_STARTED_FROM_LAUNCHER, true).apply();
}
}
}
diff --git a/android/support/v4/app/AppOpsManagerCompat.java b/android/support/v4/app/AppOpsManagerCompat.java
index ce2d2c6b..7e97199a 100644
--- a/android/support/v4/app/AppOpsManagerCompat.java
+++ b/android/support/v4/app/AppOpsManagerCompat.java
@@ -21,6 +21,7 @@ import static android.os.Build.VERSION.SDK_INT;
import android.app.AppOpsManager;
import android.content.Context;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
/**
* Helper for accessing features in {@link android.app.AppOpsManager}.
@@ -56,6 +57,7 @@ public final class AppOpsManagerCompat {
* @param permission The permission.
* @return The app op associated with the permission or null.
*/
+ @Nullable
public static String permissionToOp(@NonNull String permission) {
if (SDK_INT >= 23) {
return AppOpsManager.permissionToOp(permission);
diff --git a/android/support/v4/app/BundleCompat.java b/android/support/v4/app/BundleCompat.java
index e5fc3027..21d730d8 100644
--- a/android/support/v4/app/BundleCompat.java
+++ b/android/support/v4/app/BundleCompat.java
@@ -19,6 +19,8 @@ package android.support.v4.app;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
@@ -94,7 +96,8 @@ public final class BundleCompat {
* @param key The key to use while getting the {@link IBinder}.
* @return The {@link IBinder} that was obtained.
*/
- public static IBinder getBinder(Bundle bundle, String key) {
+ @Nullable
+ public static IBinder getBinder(@NonNull Bundle bundle, @Nullable String key) {
if (Build.VERSION.SDK_INT >= 18) {
return bundle.getBinder(key);
} else {
@@ -109,7 +112,8 @@ public final class BundleCompat {
* @param key The key to use while putting the {@link IBinder}.
* @param binder The {@link IBinder} to put.
*/
- public static void putBinder(Bundle bundle, String key, IBinder binder) {
+ public static void putBinder(@NonNull Bundle bundle, @Nullable String key,
+ @Nullable IBinder binder) {
if (Build.VERSION.SDK_INT >= 18) {
bundle.putBinder(key, binder);
} else {
diff --git a/android/support/v4/app/Fragment.java b/android/support/v4/app/Fragment.java
index ba74521d..e734a274 100644
--- a/android/support/v4/app/Fragment.java
+++ b/android/support/v4/app/Fragment.java
@@ -463,6 +463,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
/**
* Get the tag name of the fragment, if specified.
*/
+ @Nullable
final public String getTag() {
return mTag;
}
@@ -474,7 +475,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* <p>This method cannot be called if the fragment is added to a FragmentManager and
* if {@link #isStateSaved()} would return true.</p>
*/
- public void setArguments(Bundle args) {
+ public void setArguments(@Nullable Bundle args) {
if (mIndex >= 0 && isStateSaved()) {
throw new IllegalStateException("Fragment already active and state has been saved");
}
@@ -485,6 +486,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* Return the arguments supplied when the fragment was instantiated,
* if any.
*/
+ @Nullable
final public Bundle getArguments() {
return mArguments;
}
@@ -512,7 +514,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
*
* @param state The state the fragment should be restored from.
*/
- public void setInitialSavedState(SavedState state) {
+ public void setInitialSavedState(@Nullable SavedState state) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
@@ -532,7 +534,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* are going to call back with {@link #onActivityResult(int, int, Intent)}.
*/
@SuppressWarnings("ReferenceEquality")
- public void setTargetFragment(Fragment fragment, int requestCode) {
+ public void setTargetFragment(@Nullable Fragment fragment, int requestCode) {
// Don't allow a caller to set a target fragment in another FragmentManager,
// but there's a snag: people do set target fragments before fragments get added.
// We'll have the FragmentManager check that for validity when we move
@@ -558,6 +560,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
/**
* Return the target fragment set by {@link #setTargetFragment}.
*/
+ @Nullable
final public Fragment getTargetFragment() {
return mTarget;
}
@@ -572,6 +575,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
/**
* Return the {@link Context} this fragment is currently associated with.
*/
+ @Nullable
public Context getContext() {
return mHost == null ? null : mHost.getContext();
}
@@ -581,6 +585,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* May return {@code null} if the fragment is associated with a {@link Context}
* instead.
*/
+ @Nullable
final public FragmentActivity getActivity() {
return mHost == null ? null : (FragmentActivity) mHost.getActivity();
}
@@ -589,6 +594,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* Return the host object of this fragment. May return {@code null} if the fragment
* isn't currently being hosted.
*/
+ @Nullable
final public Object getHost() {
return mHost == null ? null : mHost.onGetHost();
}
@@ -596,6 +602,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
/**
* Return <code>getActivity().getResources()</code>.
*/
+ @NonNull
final public Resources getResources() {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
@@ -609,6 +616,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
*
* @param resId Resource id for the CharSequence text
*/
+ @NonNull
public final CharSequence getText(@StringRes int resId) {
return getResources().getText(resId);
}
@@ -619,6 +627,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
*
* @param resId Resource id for the string
*/
+ @NonNull
public final String getString(@StringRes int resId) {
return getResources().getString(resId);
}
@@ -631,7 +640,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @param resId Resource id for the format string
* @param formatArgs The format arguments that will be used for substitution.
*/
-
+ @NonNull
public final String getString(@StringRes int resId, Object... formatArgs) {
return getResources().getString(resId, formatArgs);
}
@@ -646,6 +655,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* <p>If this Fragment is a child of another Fragment, the FragmentManager
* returned here will be the parent's {@link #getChildFragmentManager()}.
*/
+ @Nullable
final public FragmentManager getFragmentManager() {
return mFragmentManager;
}
@@ -654,6 +664,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* Return a private FragmentManager for placing and managing Fragments
* inside of this Fragment.
*/
+ @NonNull
final public FragmentManager getChildFragmentManager() {
if (mChildFragmentManager == null) {
instantiateChildFragmentManager();
@@ -674,6 +685,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* Return this fragment's child FragmentManager one has been previously created,
* otherwise null.
*/
+ @Nullable
FragmentManager peekChildFragmentManager() {
return mChildFragmentManager;
}
@@ -682,6 +694,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* Returns the parent Fragment containing this Fragment. If this Fragment
* is attached directly to an Activity, returns null.
*/
+ @Nullable
final public Fragment getParentFragment() {
return mParentFragment;
}
@@ -1082,7 +1095,8 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* a previous saved state, this is the state.
* @return The LayoutInflater used to inflate Views of this Fragment.
*/
- public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
+ @NonNull
+ public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
// TODO: move the implementation in getLayoutInflater to here
return getLayoutInflater(savedInstanceState);
}
@@ -1113,7 +1127,8 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* a previous saved state, this is the state.
* @return The LayoutInflater used to inflate Views of this Fragment.
*/
- LayoutInflater performGetLayoutInflater(Bundle savedInstanceState) {
+ @NonNull
+ LayoutInflater performGetLayoutInflater(@Nullable Bundle savedInstanceState) {
LayoutInflater layoutInflater = onGetLayoutInflater(savedInstanceState);
mLayoutInflater = layoutInflater;
return mLayoutInflater;
@@ -1129,8 +1144,9 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* {@link #getLayoutInflater()} instead of this method.
*/
@Deprecated
+ @NonNull
@RestrictTo(LIBRARY_GROUP)
- public LayoutInflater getLayoutInflater(Bundle savedFragmentState) {
+ public LayoutInflater getLayoutInflater(@Nullable Bundle savedFragmentState) {
if (mHost == null) {
throw new IllegalStateException("onGetLayoutInflater() cannot be executed until the "
+ "Fragment is attached to the FragmentManager.");
@@ -1157,24 +1173,24 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* <p>Here is a typical implementation of a fragment that can take parameters
* both through attributes supplied here as well from {@link #getArguments()}:</p>
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentArgumentsSupport.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentArgumentsSupport.java
* fragment}
*
* <p>Note that parsing the XML attributes uses a "styleable" resource. The
* declaration for the styleable used here is:</p>
*
- * {@sample frameworks/support/samples/Support4Demos/res/values/attrs.xml fragment_arguments}
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/values/attrs.xml fragment_arguments}
*
* <p>The fragment can then be declared within its activity's content layout
* through a tag like this:</p>
*
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_arguments_support.xml from_attributes}
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_arguments_support.xml from_attributes}
*
* <p>This fragment can also be created dynamically from arguments given
* at runtime in the arguments Bundle; here is an example of doing so at
* creation of the containing activity:</p>
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentArgumentsSupport.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentArgumentsSupport.java
* create}
*
* @param context The Activity that is inflating this fragment.
@@ -1356,7 +1372,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @return Return the View for the fragment's UI, or null.
*/
@Nullable
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return null;
}
@@ -1371,7 +1387,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @param savedInstanceState If non-null, this fragment is being re-constructed
* from a previous saved state as given here.
*/
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
}
/**
@@ -1469,7 +1485,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
*
* @param outState Bundle in which to place your saved state.
*/
- public void onSaveInstanceState(Bundle outState) {
+ public void onSaveInstanceState(@NonNull Bundle outState) {
}
/**
@@ -1768,7 +1784,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
*
* @param transition The Transition to use to move Views into the initial Scene.
*/
- public void setEnterTransition(Object transition) {
+ public void setEnterTransition(@Nullable Object transition) {
ensureAnimationInfo().mEnterTransition = transition;
}
@@ -1781,6 +1797,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
*
* @return the Transition to use to move Views into the initial Scene.
*/
+ @Nullable
public Object getEnterTransition() {
if (mAnimationInfo == null) {
return null;
@@ -1802,7 +1819,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* is preparing to close. <code>transition</code> must be an
* android.transition.Transition.
*/
- public void setReturnTransition(Object transition) {
+ public void setReturnTransition(@Nullable Object transition) {
ensureAnimationInfo().mReturnTransition = transition;
}
@@ -1818,6 +1835,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @return the Transition to use to move Views out of the Scene when the Fragment
* is preparing to close.
*/
+ @Nullable
public Object getReturnTransition() {
if (mAnimationInfo == null) {
return null;
@@ -1839,7 +1857,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* is being closed not due to popping the back stack. <code>transition</code>
* must be an android.transition.Transition.
*/
- public void setExitTransition(Object transition) {
+ public void setExitTransition(@Nullable Object transition) {
ensureAnimationInfo().mExitTransition = transition;
}
@@ -1855,6 +1873,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @return the Transition to use to move Views out of the Scene when the Fragment
* is being closed not due to popping the back stack.
*/
+ @Nullable
public Object getExitTransition() {
if (mAnimationInfo == null) {
return null;
@@ -1875,7 +1894,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* previously-started Activity. <code>transition</code>
* must be an android.transition.Transition.
*/
- public void setReenterTransition(Object transition) {
+ public void setReenterTransition(@Nullable Object transition) {
ensureAnimationInfo().mReenterTransition = transition;
}
@@ -1908,7 +1927,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @param transition The Transition to use for shared elements transferred into the content
* Scene. <code>transition</code> must be an android.transition.Transition.
*/
- public void setSharedElementEnterTransition(Object transition) {
+ public void setSharedElementEnterTransition(@Nullable Object transition) {
ensureAnimationInfo().mSharedElementEnterTransition = transition;
}
@@ -1921,6 +1940,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @return The Transition to use for shared elements transferred into the content
* Scene.
*/
+ @Nullable
public Object getSharedElementEnterTransition() {
if (mAnimationInfo == null) {
return null;
@@ -1940,7 +1960,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @param transition The Transition to use for shared elements transferred out of the content
* Scene. <code>transition</code> must be an android.transition.Transition.
*/
- public void setSharedElementReturnTransition(Object transition) {
+ public void setSharedElementReturnTransition(@Nullable Object transition) {
ensureAnimationInfo().mSharedElementReturnTransition = transition;
}
@@ -1956,6 +1976,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
* @return The Transition to use for shared elements transferred out of the content
* Scene.
*/
+ @Nullable
public Object getSharedElementReturnTransition() {
if (mAnimationInfo == null) {
return null;
@@ -2231,8 +2252,8 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener
mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}
- View performCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ View performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java
index 481f50d3..cb3c59a6 100644
--- a/android/support/v4/app/FragmentActivity.java
+++ b/android/support/v4/app/FragmentActivity.java
@@ -153,6 +153,13 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
return;
}
+ ActivityCompat.PermissionCompatDelegate delegate =
+ ActivityCompat.getPermissionCompatDelegate();
+ if (delegate != null && delegate.onActivityResult(this, requestCode, resultCode, data)) {
+ // Delegate has handled the activity result
+ return;
+ }
+
super.onActivityResult(requestCode, resultCode, data);
}
@@ -270,6 +277,16 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
}
/**
+ * Returns the Lifecycle of the provider.
+ *
+ * @return The lifecycle of the provider.
+ */
+ @Override
+ public Lifecycle getLifecycle() {
+ return super.getLifecycle();
+ }
+
+ /**
* Perform initialization of all fragments and loaders.
*/
@SuppressWarnings("deprecation")
@@ -750,6 +767,7 @@ public class FragmentActivity extends BaseFragmentActivityApi16 implements
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
+ mFragments.noteStateNotSaved();
int index = (requestCode >> 16) & 0xffff;
if (index != 0) {
index--;
diff --git a/android/support/v4/app/FragmentHostCallback.java b/android/support/v4/app/FragmentHostCallback.java
index 7dc9f595..eeae62a5 100644
--- a/android/support/v4/app/FragmentHostCallback.java
+++ b/android/support/v4/app/FragmentHostCallback.java
@@ -94,8 +94,9 @@ public abstract class FragmentHostCallback<E> extends FragmentContainer {
* Return a {@link LayoutInflater}.
* See {@link Activity#getLayoutInflater()}.
*/
+ @NonNull
public LayoutInflater onGetLayoutInflater() {
- return (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ return LayoutInflater.from(mContext);
}
/**
diff --git a/android/support/v4/app/FragmentManager.java b/android/support/v4/app/FragmentManager.java
index 6e6caa04..16103f8c 100644
--- a/android/support/v4/app/FragmentManager.java
+++ b/android/support/v4/app/FragmentManager.java
@@ -1605,12 +1605,21 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
@Override
public void onAnimationEnd(Animation animation) {
super.onAnimationEnd(animation);
- container.endViewTransition(viewToAnimate);
-
- if (fragment.getAnimatingAway() != null) {
- fragment.setAnimatingAway(null);
- moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false);
- }
+ // onAnimationEnd() comes during draw(), so there can still be some
+ // draw events happening after this call. We don't want to detach
+ // the view until after the onAnimationEnd()
+ container.post(new Runnable() {
+ @Override
+ public void run() {
+ container.endViewTransition(viewToAnimate);
+
+ if (fragment.getAnimatingAway() != null) {
+ fragment.setAnimatingAway(null);
+ moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0,
+ false);
+ }
+ }
+ });
}
});
setHWLayerAnimListenerIfAlpha(viewToAnimate, anim);
diff --git a/android/support/v4/app/FragmentPagerAdapter.java b/android/support/v4/app/FragmentPagerAdapter.java
index 61b181df..6b25d2f7 100644
--- a/android/support/v4/app/FragmentPagerAdapter.java
+++ b/android/support/v4/app/FragmentPagerAdapter.java
@@ -44,18 +44,18 @@ import android.view.ViewGroup;
* <p>Here is an example implementation of a pager containing fragments of
* lists:
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentPagerSupport.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentPagerSupport.java
* complete}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_pager.xml
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
* individual fragment's layout is:
*
- * {@sample frameworks/support/samples/Support4Demos/res/layout/fragment_pager_list.xml
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
* complete}
*/
public abstract class FragmentPagerAdapter extends PagerAdapter {
diff --git a/android/support/v4/app/FragmentStatePagerAdapter.java b/android/support/v4/app/FragmentStatePagerAdapter.java
index fc27c4fc..040f2db2 100644
--- a/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -47,18 +47,18 @@ import java.util.ArrayList;
* <p>Here is an example implementation of a pager containing fragments of
* lists:
*
- * {@sample frameworks/support/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java
+ * {@sample frameworks/support/samples/Support13Demos/src/main/java/com/example/android/supportv13/app/FragmentStatePagerSupport.java
* complete}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
- * {@sample frameworks/support/samples/Support13Demos/res/layout/fragment_pager.xml
+ * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> resource containing each
* individual fragment's layout is:
*
- * {@sample frameworks/support/samples/Support13Demos/res/layout/fragment_pager_list.xml
+ * {@sample frameworks/support/samples/Support13Demos/src/main/res/layout/fragment_pager_list.xml
* complete}
*/
public abstract class FragmentStatePagerAdapter extends PagerAdapter {
diff --git a/android/support/v4/app/FragmentTabHost.java b/android/support/v4/app/FragmentTabHost.java
index 09b89b7f..6b914fef 100644
--- a/android/support/v4/app/FragmentTabHost.java
+++ b/android/support/v4/app/FragmentTabHost.java
@@ -41,12 +41,12 @@ import java.util.ArrayList;
*
* <p>Here is a simple example of using a FragmentTabHost in an Activity:
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
* complete}
*
* <p>This can also be used inside of a fragment through fragment nesting:
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
* complete}
*/
public class FragmentTabHost extends TabHost
diff --git a/android/support/v4/app/JobIntentService.java b/android/support/v4/app/JobIntentService.java
index c0d7f13e..87b7441e 100644
--- a/android/support/v4/app/JobIntentService.java
+++ b/android/support/v4/app/JobIntentService.java
@@ -84,7 +84,7 @@ import java.util.HashMap;
*
* <p>Here is an example implementation of this class:</p>
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/app/SimpleJobIntentService.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/SimpleJobIntentService.java
* complete}
*/
public abstract class JobIntentService extends Service {
diff --git a/android/support/v4/app/ListFragment.java b/android/support/v4/app/ListFragment.java
index 21617ad0..496bd8e1 100644
--- a/android/support/v4/app/ListFragment.java
+++ b/android/support/v4/app/ListFragment.java
@@ -19,6 +19,8 @@ package android.support.v4.app;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -142,7 +144,7 @@ public class ListFragment extends Fragment {
* Attach to list view once the view hierarchy has been created.
*/
@Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ensureList();
}
diff --git a/android/support/v4/app/NavUtils.java b/android/support/v4/app/NavUtils.java
index 99d44939..d2594178 100644
--- a/android/support/v4/app/NavUtils.java
+++ b/android/support/v4/app/NavUtils.java
@@ -24,6 +24,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -53,7 +54,8 @@ public final class NavUtils {
* @return true if navigating up should recreate a new task stack, false if the same task
* should be used for the destination
*/
- public static boolean shouldUpRecreateTask(Activity sourceActivity, Intent targetIntent) {
+ public static boolean shouldUpRecreateTask(@NonNull Activity sourceActivity,
+ @NonNull Intent targetIntent) {
if (Build.VERSION.SDK_INT >= 16) {
return sourceActivity.shouldUpRecreateTask(targetIntent);
} else {
@@ -74,7 +76,7 @@ public final class NavUtils {
*
* @param sourceActivity The current activity from which the user is attempting to navigate up
*/
- public static void navigateUpFromSameTask(Activity sourceActivity) {
+ public static void navigateUpFromSameTask(@NonNull Activity sourceActivity) {
Intent upIntent = getParentActivityIntent(sourceActivity);
if (upIntent == null) {
@@ -101,7 +103,7 @@ public final class NavUtils {
* @param sourceActivity The current activity from which the user is attempting to navigate up
* @param upIntent An intent representing the target destination for up navigation
*/
- public static void navigateUpTo(Activity sourceActivity, Intent upIntent) {
+ public static void navigateUpTo(@NonNull Activity sourceActivity, @NonNull Intent upIntent) {
if (Build.VERSION.SDK_INT >= 16) {
sourceActivity.navigateUpTo(upIntent);
} else {
@@ -121,7 +123,8 @@ public final class NavUtils {
* @param sourceActivity Activity to fetch a parent intent for
* @return a new Intent targeting the defined parent activity of sourceActivity
*/
- public static Intent getParentActivityIntent(Activity sourceActivity) {
+ @Nullable
+ public static Intent getParentActivityIntent(@NonNull Activity sourceActivity) {
if (Build.VERSION.SDK_INT >= 16) {
// Prefer the "real" JB definition if available,
// else fall back to the meta-data element.
@@ -157,7 +160,9 @@ public final class NavUtils {
* @return a new Intent targeting the defined parent activity of sourceActivity
* @throws NameNotFoundException if the ComponentName for sourceActivityClass is invalid
*/
- public static Intent getParentActivityIntent(Context context, Class<?> sourceActivityClass)
+ @Nullable
+ public static Intent getParentActivityIntent(@NonNull Context context,
+ @NonNull Class<?> sourceActivityClass)
throws NameNotFoundException {
String parentActivity = getParentActivityName(context,
new ComponentName(context, sourceActivityClass));
@@ -182,7 +187,9 @@ public final class NavUtils {
* @return a new Intent targeting the defined parent activity of sourceActivity
* @throws NameNotFoundException if the ComponentName for sourceActivityClass is invalid
*/
- public static Intent getParentActivityIntent(Context context, ComponentName componentName)
+ @Nullable
+ public static Intent getParentActivityIntent(@NonNull Context context,
+ @NonNull ComponentName componentName)
throws NameNotFoundException {
String parentActivity = getParentActivityName(context, componentName);
if (parentActivity == null) return null;
@@ -207,7 +214,7 @@ public final class NavUtils {
* it was not specified
*/
@Nullable
- public static String getParentActivityName(Activity sourceActivity) {
+ public static String getParentActivityName(@NonNull Activity sourceActivity) {
try {
return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
} catch (NameNotFoundException e) {
@@ -226,7 +233,8 @@ public final class NavUtils {
* it was not specified
*/
@Nullable
- public static String getParentActivityName(Context context, ComponentName componentName)
+ public static String getParentActivityName(@NonNull Context context,
+ @NonNull ComponentName componentName)
throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
diff --git a/android/support/v4/app/NotificationManagerCompat.java b/android/support/v4/app/NotificationManagerCompat.java
index 93775eca..1a0f1bca 100644
--- a/android/support/v4/app/NotificationManagerCompat.java
+++ b/android/support/v4/app/NotificationManagerCompat.java
@@ -36,6 +36,8 @@ import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.Log;
import java.lang.reflect.Field;
@@ -144,7 +146,8 @@ public final class NotificationManagerCompat {
public static final int IMPORTANCE_MAX = 5;
/** Get a {@link NotificationManagerCompat} instance for a provided context. */
- public static NotificationManagerCompat from(Context context) {
+ @NonNull
+ public static NotificationManagerCompat from(@NonNull Context context) {
return new NotificationManagerCompat(context);
}
@@ -167,7 +170,7 @@ public final class NotificationManagerCompat {
* @param tag the string identifier of the notification.
* @param id the ID of the notification
*/
- public void cancel(String tag, int id) {
+ public void cancel(@Nullable String tag, int id) {
mNotificationManager.cancel(tag, id);
if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) {
pushSideChannelQueue(new CancelTask(mContext.getPackageName(), id, tag));
@@ -197,7 +200,7 @@ public final class NotificationManagerCompat {
* @param id the ID of the notification. The pair (tag, id) must be unique within your app.
* @param notification the notification to post to the system
*/
- public void notify(String tag, int id, Notification notification) {
+ public void notify(@Nullable String tag, int id, @NonNull Notification notification) {
if (useSideChannelForNotification(notification)) {
pushSideChannelQueue(new NotifyTask(mContext.getPackageName(), id, tag, notification));
// Cancel this notification in notification manager if it just transitioned to being
@@ -253,7 +256,8 @@ public final class NotificationManagerCompat {
/**
* Get the set of packages that have an enabled notification listener component within them.
*/
- public static Set<String> getEnabledListenerPackages(Context context) {
+ @NonNull
+ public static Set<String> getEnabledListenerPackages(@NonNull Context context) {
final String enabledNotificationListeners = Settings.Secure.getString(
context.getContentResolver(),
SETTING_ENABLED_NOTIFICATION_LISTENERS);
diff --git a/android/support/v4/app/ServiceCompat.java b/android/support/v4/app/ServiceCompat.java
index 1676ee8a..2e63b237 100644
--- a/android/support/v4/app/ServiceCompat.java
+++ b/android/support/v4/app/ServiceCompat.java
@@ -22,6 +22,7 @@ import android.app.Notification;
import android.app.Service;
import android.os.Build;
import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
import java.lang.annotation.Retention;
@@ -92,7 +93,7 @@ public final class ServiceCompat {
* {@link #STOP_FOREGROUND_DETACH}.
* @see Service#startForeground(int, Notification)
*/
- public static void stopForeground(Service service, @StopForegroundFlags int flags) {
+ public static void stopForeground(@NonNull Service service, @StopForegroundFlags int flags) {
if (Build.VERSION.SDK_INT >= 24) {
service.stopForeground(flags);
} else {
diff --git a/android/support/v4/app/TaskStackBuilder.java b/android/support/v4/app/TaskStackBuilder.java
index dc9a2dc0..14aadce2 100644
--- a/android/support/v4/app/TaskStackBuilder.java
+++ b/android/support/v4/app/TaskStackBuilder.java
@@ -16,7 +16,6 @@
package android.support.v4.app;
-import android.support.annotation.RequiresApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -25,6 +24,9 @@ import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
import android.support.v4.content.ContextCompat;
import android.util.Log;
@@ -70,6 +72,7 @@ public final class TaskStackBuilder implements Iterable<Intent> {
private static final String TAG = "TaskStackBuilder";
public interface SupportParentable {
+ @Nullable
Intent getSupportParentActivityIntent();
}
@@ -117,7 +120,8 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* @param context The context that will launch the new task stack or generate a PendingIntent
* @return A new TaskStackBuilder
*/
- public static TaskStackBuilder create(Context context) {
+ @NonNull
+ public static TaskStackBuilder create(@NonNull Context context) {
return new TaskStackBuilder(context);
}
@@ -142,7 +146,8 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* @param nextIntent Intent for the next Activity in the synthesized task stack
* @return This TaskStackBuilder for method chaining
*/
- public TaskStackBuilder addNextIntent(Intent nextIntent) {
+ @NonNull
+ public TaskStackBuilder addNextIntent(@NonNull Intent nextIntent) {
mIntents.add(nextIntent);
return this;
}
@@ -159,7 +164,8 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* Its chain of parents as specified in the manifest will be added.
* @return This TaskStackBuilder for method chaining.
*/
- public TaskStackBuilder addNextIntentWithParentStack(Intent nextIntent) {
+ @NonNull
+ public TaskStackBuilder addNextIntentWithParentStack(@NonNull Intent nextIntent) {
ComponentName target = nextIntent.getComponent();
if (target == null) {
target = nextIntent.resolveActivity(mSourceContext.getPackageManager());
@@ -178,7 +184,8 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* @param sourceActivity All parents of this activity will be added
* @return This TaskStackBuilder for method chaining
*/
- public TaskStackBuilder addParentStack(Activity sourceActivity) {
+ @NonNull
+ public TaskStackBuilder addParentStack(@NonNull Activity sourceActivity) {
Intent parent = null;
if (sourceActivity instanceof SupportParentable) {
parent = ((SupportParentable) sourceActivity).getSupportParentActivityIntent();
@@ -207,7 +214,8 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* @param sourceActivityClass All parents of this activity will be added
* @return This TaskStackBuilder for method chaining
*/
- public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
+ @NonNull
+ public TaskStackBuilder addParentStack(@NonNull Class<?> sourceActivityClass) {
return addParentStack(new ComponentName(mSourceContext, sourceActivityClass));
}
@@ -264,6 +272,7 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* @param index Index from 0-getIntentCount()
* @return the intent at position index
*/
+ @Nullable
public Intent editIntentAt(int index) {
return mIntents.get(index);
}
@@ -300,7 +309,7 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)}
*/
- public void startActivities(Bundle options) {
+ public void startActivities(@Nullable Bundle options) {
if (mIntents.isEmpty()) {
throw new IllegalStateException(
"No intents added to TaskStackBuilder; cannot startActivities");
@@ -325,8 +334,10 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by
* {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the
* intent that can be supplied when the actual send happens.
- * @return The obtained PendingIntent
+ * @return The obtained PendingIntent. May return null only if
+ * {@link PendingIntent#FLAG_NO_CREATE} has been supplied.
*/
+ @Nullable
public PendingIntent getPendingIntent(int requestCode, int flags) {
return getPendingIntent(requestCode, flags, null);
}
@@ -342,9 +353,11 @@ public final class TaskStackBuilder implements Iterable<Intent> {
* intent that can be supplied when the actual send happens.
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)}
- * @return The obtained PendingIntent
+ * @return The obtained PendingIntent. May return null only if
+ * {@link PendingIntent#FLAG_NO_CREATE} has been supplied.
*/
- public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
+ @Nullable
+ public PendingIntent getPendingIntent(int requestCode, int flags, @Nullable Bundle options) {
if (mIntents.isEmpty()) {
throw new IllegalStateException(
"No intents added to TaskStackBuilder; cannot getPendingIntent");
@@ -364,6 +377,7 @@ public final class TaskStackBuilder implements Iterable<Intent> {
*
* @return An array containing the intents added to this builder.
*/
+ @NonNull
public Intent[] getIntents() {
Intent[] intents = new Intent[mIntents.size()];
if (intents.length == 0) return intents;
diff --git a/android/support/v4/content/AsyncTaskLoader.java b/android/support/v4/content/AsyncTaskLoader.java
index faa13ad7..5882f691 100644
--- a/android/support/v4/content/AsyncTaskLoader.java
+++ b/android/support/v4/content/AsyncTaskLoader.java
@@ -21,6 +21,8 @@ import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.v4.os.OperationCanceledException;
import android.support.v4.util.TimeUtils;
@@ -121,11 +123,11 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
long mLastLoadCompleteTime = -10000;
Handler mHandler;
- public AsyncTaskLoader(Context context) {
+ public AsyncTaskLoader(@NonNull Context context) {
this(context, ModernAsyncTask.THREAD_POOL_EXECUTOR);
}
- private AsyncTaskLoader(Context context, Executor executor) {
+ private AsyncTaskLoader(@NonNull Context context, @NonNull Executor executor) {
super(context);
mExecutor = executor;
}
@@ -200,7 +202,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
* @param data The value that was returned by {@link #loadInBackground}, or null
* if the task threw {@link OperationCanceledException}.
*/
- public void onCanceled(D data) {
+ public void onCanceled(@Nullable D data) {
}
void executePendingTask() {
@@ -284,6 +286,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
* @see #cancelLoadInBackground
* @see #onCanceled
*/
+ @Nullable
public abstract D loadInBackground();
/**
@@ -298,6 +301,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
*
* @see #loadInBackground
*/
+ @Nullable
protected D onLoadInBackground() {
return loadInBackground();
}
diff --git a/android/support/v4/content/ContextCompat.java b/android/support/v4/content/ContextCompat.java
index fdbe32f1..8a4fd7aa 100644
--- a/android/support/v4/content/ContextCompat.java
+++ b/android/support/v4/content/ContextCompat.java
@@ -79,7 +79,7 @@ public class ContextCompat {
* length-1 will correspond to the top activity on the resulting task stack.
* @return true if the underlying API was available and the call was successful, false otherwise
*/
- public static boolean startActivities(Context context, Intent[] intents) {
+ public static boolean startActivities(@NonNull Context context, @NonNull Intent[] intents) {
return startActivities(context, intents, null);
}
@@ -110,7 +110,8 @@ public class ContextCompat {
* See {@link android.content.Context#startActivity(Intent, android.os.Bundle)}
* @return true if the underlying API was available and the call was successful, false otherwise
*/
- public static boolean startActivities(Context context, Intent[] intents, Bundle options) {
+ public static boolean startActivities(@NonNull Context context, @NonNull Intent[] intents,
+ @Nullable Bundle options) {
if (Build.VERSION.SDK_INT >= 16) {
context.startActivities(intents, options);
} else {
@@ -136,7 +137,8 @@ public class ContextCompat {
* supplied here; there are no supported definitions for
* building it manually.
*/
- public static void startActivity(Context context, Intent intent, @Nullable Bundle options) {
+ public static void startActivity(@NonNull Context context, @NonNull Intent intent,
+ @Nullable Bundle options) {
if (Build.VERSION.SDK_INT >= 16) {
context.startActivity(intent, options);
} else {
@@ -159,7 +161,8 @@ public class ContextCompat {
*
* @see ApplicationInfo#dataDir
*/
- public static File getDataDir(Context context) {
+ @Nullable
+ public static File getDataDir(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 24) {
return context.getDataDir();
} else {
@@ -211,7 +214,8 @@ public class ContextCompat {
* @see Context#getObbDir()
* @see EnvironmentCompat#getStorageState(File)
*/
- public static File[] getObbDirs(Context context) {
+ @NonNull
+ public static File[] getObbDirs(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 19) {
return context.getObbDirs();
} else {
@@ -263,7 +267,8 @@ public class ContextCompat {
* @see Context#getExternalFilesDir(String)
* @see EnvironmentCompat#getStorageState(File)
*/
- public static File[] getExternalFilesDirs(Context context, String type) {
+ @NonNull
+ public static File[] getExternalFilesDirs(@NonNull Context context, @Nullable String type) {
if (Build.VERSION.SDK_INT >= 19) {
return context.getExternalFilesDirs(type);
} else {
@@ -315,7 +320,8 @@ public class ContextCompat {
* @see Context#getExternalCacheDir()
* @see EnvironmentCompat#getStorageState(File)
*/
- public static File[] getExternalCacheDirs(Context context) {
+ @NonNull
+ public static File[] getExternalCacheDirs(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 19) {
return context.getExternalCacheDirs();
} else {
@@ -346,7 +352,8 @@ public class ContextCompat {
* The value 0 is an invalid identifier.
* @return Drawable An object that can be used to draw this resource.
*/
- public static final Drawable getDrawable(Context context, @DrawableRes int id) {
+ @Nullable
+ public static final Drawable getDrawable(@NonNull Context context, @DrawableRes int id) {
if (Build.VERSION.SDK_INT >= 21) {
return context.getDrawable(id);
} else if (Build.VERSION.SDK_INT >= 16) {
@@ -382,7 +389,9 @@ public class ContextCompat {
* @throws android.content.res.Resources.NotFoundException if the given ID
* does not exist.
*/
- public static final ColorStateList getColorStateList(Context context, @ColorRes int id) {
+ @Nullable
+ public static final ColorStateList getColorStateList(@NonNull Context context,
+ @ColorRes int id) {
if (Build.VERSION.SDK_INT >= 23) {
return context.getColorStateList(id);
} else {
@@ -404,7 +413,7 @@ public class ContextCompat {
* does not exist.
*/
@ColorInt
- public static final int getColor(Context context, @ColorRes int id) {
+ public static final int getColor(@NonNull Context context, @ColorRes int id) {
if (Build.VERSION.SDK_INT >= 23) {
return context.getColor(id);
} else {
@@ -444,7 +453,8 @@ public class ContextCompat {
*
* @see android.content.Context#getFilesDir()
*/
- public static final File getNoBackupFilesDir(Context context) {
+ @Nullable
+ public static final File getNoBackupFilesDir(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 21) {
return context.getNoBackupFilesDir();
} else {
@@ -468,7 +478,7 @@ public class ContextCompat {
*
* @return The path of the directory holding application code cache files.
*/
- public static File getCodeCacheDir(Context context) {
+ public static File getCodeCacheDir(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 21) {
return context.getCodeCacheDir();
} else {
@@ -522,7 +532,8 @@ public class ContextCompat {
*
* @see ContextCompat#isDeviceProtectedStorage(Context)
*/
- public static Context createDeviceProtectedStorageContext(Context context) {
+ @Nullable
+ public static Context createDeviceProtectedStorageContext(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 24) {
return context.createDeviceProtectedStorageContext();
} else {
@@ -536,7 +547,7 @@ public class ContextCompat {
*
* @see ContextCompat#createDeviceProtectedStorageContext(Context)
*/
- public static boolean isDeviceProtectedStorage(Context context) {
+ public static boolean isDeviceProtectedStorage(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 24) {
return context.isDeviceProtectedStorage();
} else {
@@ -554,7 +565,7 @@ public class ContextCompat {
* @see Context#startForegeroundService()
* @see Context#startService()
*/
- public static void startForegroundService(Context context, Intent intent) {
+ public static void startForegroundService(@NonNull Context context, @NonNull Intent intent) {
if (Build.VERSION.SDK_INT >= 26) {
context.startForegroundService(intent);
} else {
diff --git a/android/support/v4/content/CursorLoader.java b/android/support/v4/content/CursorLoader.java
index 503bb9c7..5c6925d9 100644
--- a/android/support/v4/content/CursorLoader.java
+++ b/android/support/v4/content/CursorLoader.java
@@ -20,6 +20,8 @@ import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.os.CancellationSignal;
import android.support.v4.os.OperationCanceledException;
@@ -115,7 +117,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
* calls to {@link #setUri(Uri)}, {@link #setSelection(String)}, etc
* to specify the query to perform.
*/
- public CursorLoader(Context context) {
+ public CursorLoader(@NonNull Context context) {
super(context);
mObserver = new ForceLoadContentObserver();
}
@@ -126,8 +128,9 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
* ContentResolver.query()} for documentation on the meaning of the
* parameters. These will be passed as-is to that call.
*/
- public CursorLoader(Context context, Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
+ public CursorLoader(@NonNull Context context, @NonNull Uri uri, @Nullable String[] projection,
+ @Nullable String selection, @Nullable String[] selectionArgs,
+ @Nullable String sortOrder) {
super(context);
mObserver = new ForceLoadContentObserver();
mUri = uri;
@@ -183,43 +186,48 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
mCursor = null;
}
+ @NonNull
public Uri getUri() {
return mUri;
}
- public void setUri(Uri uri) {
+ public void setUri(@NonNull Uri uri) {
mUri = uri;
}
+ @Nullable
public String[] getProjection() {
return mProjection;
}
- public void setProjection(String[] projection) {
+ public void setProjection(@Nullable String[] projection) {
mProjection = projection;
}
+ @Nullable
public String getSelection() {
return mSelection;
}
- public void setSelection(String selection) {
+ public void setSelection(@Nullable String selection) {
mSelection = selection;
}
+ @Nullable
public String[] getSelectionArgs() {
return mSelectionArgs;
}
- public void setSelectionArgs(String[] selectionArgs) {
+ public void setSelectionArgs(@Nullable String[] selectionArgs) {
mSelectionArgs = selectionArgs;
}
+ @Nullable
public String getSortOrder() {
return mSortOrder;
}
- public void setSortOrder(String sortOrder) {
+ public void setSortOrder(@Nullable String sortOrder) {
mSortOrder = sortOrder;
}
diff --git a/android/support/v4/content/FileProvider.java b/android/support/v4/content/FileProvider.java
index c49fc123..8599911a 100644
--- a/android/support/v4/content/FileProvider.java
+++ b/android/support/v4/content/FileProvider.java
@@ -33,6 +33,8 @@ import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
@@ -362,7 +364,7 @@ public class FileProvider extends ContentProvider {
* @param info A {@link ProviderInfo} for the new provider.
*/
@Override
- public void attachInfo(Context context, ProviderInfo info) {
+ public void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
super.attachInfo(context, info);
// Sanity check our security
@@ -396,7 +398,8 @@ public class FileProvider extends ContentProvider {
* @throws IllegalArgumentException When the given {@link File} is outside
* the paths supported by the provider.
*/
- public static Uri getUriForFile(Context context, String authority, File file) {
+ public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
+ @NonNull File file) {
final PathStrategy strategy = getPathStrategy(context, authority);
return strategy.getUriForFile(file);
}
@@ -430,8 +433,9 @@ public class FileProvider extends ContentProvider {
*
*/
@Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
+ public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+ @Nullable String[] selectionArgs,
+ @Nullable String sortOrder) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
@@ -470,7 +474,7 @@ public class FileProvider extends ContentProvider {
* extension; otherwise <code>application/octet-stream</code>.
*/
@Override
- public String getType(Uri uri) {
+ public String getType(@NonNull Uri uri) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
@@ -491,7 +495,7 @@ public class FileProvider extends ContentProvider {
* subclass FileProvider if you want to provide different functionality.
*/
@Override
- public Uri insert(Uri uri, ContentValues values) {
+ public Uri insert(@NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException("No external inserts");
}
@@ -500,7 +504,8 @@ public class FileProvider extends ContentProvider {
* subclass FileProvider if you want to provide different functionality.
*/
@Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ public int update(@NonNull Uri uri, ContentValues values, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
throw new UnsupportedOperationException("No external updates");
}
@@ -516,7 +521,8 @@ public class FileProvider extends ContentProvider {
* @return 1 if the delete succeeds; otherwise, 0.
*/
@Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
+ public int delete(@NonNull Uri uri, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
return file.delete() ? 1 : 0;
@@ -538,7 +544,8 @@ public class FileProvider extends ContentProvider {
* @return A new {@link ParcelFileDescriptor} with which you can access the file.
*/
@Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
+ throws FileNotFoundException {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
final int fileMode = modeToMode(mode);
diff --git a/android/support/v4/content/IntentCompat.java b/android/support/v4/content/IntentCompat.java
index 1a7295b3..f5b3a0b1 100644
--- a/android/support/v4/content/IntentCompat.java
+++ b/android/support/v4/content/IntentCompat.java
@@ -18,6 +18,7 @@ package android.support.v4.content;
import android.content.Intent;
import android.os.Build;
+import android.support.annotation.NonNull;
/**
* Helper for accessing features in {@link android.content.Intent}.
@@ -69,8 +70,9 @@ public final class IntentCompat {
* @return Returns a newly created Intent that can be used to launch the
* activity as a main application entry.
*/
- public static Intent makeMainSelectorActivity(String selectorAction,
- String selectorCategory) {
+ @NonNull
+ public static Intent makeMainSelectorActivity(@NonNull String selectorAction,
+ @NonNull String selectorCategory) {
if (Build.VERSION.SDK_INT >= 15) {
return Intent.makeMainSelectorActivity(selectorAction, selectorCategory);
} else {
diff --git a/android/support/v4/content/Loader.java b/android/support/v4/content/Loader.java
index 40b459fa..2ac10d73 100644
--- a/android/support/v4/content/Loader.java
+++ b/android/support/v4/content/Loader.java
@@ -19,6 +19,8 @@ package android.support.v4.content;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.util.DebugUtils;
import java.io.FileDescriptor;
@@ -80,7 +82,7 @@ public class Loader<D> {
* @param loader the loader that completed the load
* @param data the result of the load
*/
- public void onLoadComplete(Loader<D> loader, D data);
+ void onLoadComplete(@NonNull Loader<D> loader, @Nullable D data);
}
/**
@@ -97,7 +99,7 @@ public class Loader<D> {
*
* @param loader the loader that canceled the load
*/
- public void onLoadCanceled(Loader<D> loader);
+ void onLoadCanceled(@NonNull Loader<D> loader);
}
/**
@@ -110,7 +112,7 @@ public class Loader<D> {
*
* @param context used to retrieve the application context.
*/
- public Loader(Context context) {
+ public Loader(@NonNull Context context) {
mContext = context.getApplicationContext();
}
@@ -121,7 +123,7 @@ public class Loader<D> {
*
* @param data the result of the load
*/
- public void deliverResult(D data) {
+ public void deliverResult(@Nullable D data) {
if (mListener != null) {
mListener.onLoadComplete(this, data);
}
@@ -142,6 +144,7 @@ public class Loader<D> {
/**
* @return an application context retrieved from the Context passed to the constructor.
*/
+ @NonNull
public Context getContext() {
return mContext;
}
@@ -160,7 +163,7 @@ public class Loader<D> {
*
* <p>Must be called from the process's main thread.
*/
- public void registerListener(int id, OnLoadCompleteListener<D> listener) {
+ public void registerListener(int id, @NonNull OnLoadCompleteListener<D> listener) {
if (mListener != null) {
throw new IllegalStateException("There is already a listener registered");
}
@@ -173,7 +176,7 @@ public class Loader<D> {
*
* Must be called from the process's main thread.
*/
- public void unregisterListener(OnLoadCompleteListener<D> listener) {
+ public void unregisterListener(@NonNull OnLoadCompleteListener<D> listener) {
if (mListener == null) {
throw new IllegalStateException("No listener register");
}
@@ -192,7 +195,7 @@ public class Loader<D> {
*
* @param listener The listener to register.
*/
- public void registerOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
+ public void registerOnLoadCanceledListener(@NonNull OnLoadCanceledListener<D> listener) {
if (mOnLoadCanceledListener != null) {
throw new IllegalStateException("There is already a listener registered");
}
@@ -207,7 +210,7 @@ public class Loader<D> {
*
* @param listener The listener to unregister.
*/
- public void unregisterOnLoadCanceledListener(OnLoadCanceledListener<D> listener) {
+ public void unregisterOnLoadCanceledListener(@NonNull OnLoadCanceledListener<D> listener) {
if (mOnLoadCanceledListener == null) {
throw new IllegalStateException("No listener register");
}
@@ -493,7 +496,8 @@ public class Loader<D> {
* For debugging, converts an instance of the Loader's data class to
* a string that can be printed. Must handle a null data.
*/
- public String dataToString(D data) {
+ @NonNull
+ public String dataToString(@Nullable D data) {
StringBuilder sb = new StringBuilder(64);
DebugUtils.buildShortClassTag(data, sb);
sb.append("}");
diff --git a/android/support/v4/content/LocalBroadcastManager.java b/android/support/v4/content/LocalBroadcastManager.java
index 324bb304..aaaf8be3 100644
--- a/android/support/v4/content/LocalBroadcastManager.java
+++ b/android/support/v4/content/LocalBroadcastManager.java
@@ -16,10 +16,6 @@
package android.support.v4.content;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Set;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -27,8 +23,13 @@ import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
+import android.support.annotation.NonNull;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+
/**
* Helper to register for and send broadcasts of Intents to local objects
* within your process. This has a number of advantages over sending
@@ -98,7 +99,8 @@ public final class LocalBroadcastManager {
private static final Object mLock = new Object();
private static LocalBroadcastManager mInstance;
- public static LocalBroadcastManager getInstance(Context context) {
+ @NonNull
+ public static LocalBroadcastManager getInstance(@NonNull Context context) {
synchronized (mLock) {
if (mInstance == null) {
mInstance = new LocalBroadcastManager(context.getApplicationContext());
@@ -132,7 +134,8 @@ public final class LocalBroadcastManager {
*
* @see #unregisterReceiver
*/
- public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ public void registerReceiver(@NonNull BroadcastReceiver receiver,
+ @NonNull IntentFilter filter) {
synchronized (mReceivers) {
ReceiverRecord entry = new ReceiverRecord(filter, receiver);
ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
@@ -162,7 +165,7 @@ public final class LocalBroadcastManager {
*
* @see #registerReceiver
*/
- public void unregisterReceiver(BroadcastReceiver receiver) {
+ public void unregisterReceiver(@NonNull BroadcastReceiver receiver) {
synchronized (mReceivers) {
final ArrayList<ReceiverRecord> filters = mReceivers.remove(receiver);
if (filters == null) {
@@ -205,7 +208,7 @@ public final class LocalBroadcastManager {
* broadcast receivers. (Note tha delivery may not ultimately take place if one of those
* receivers is unregistered before it is dispatched.)
*/
- public boolean sendBroadcast(Intent intent) {
+ public boolean sendBroadcast(@NonNull Intent intent) {
synchronized (mReceivers) {
final String action = intent.getAction();
final String type = intent.resolveTypeIfNeeded(
@@ -281,7 +284,7 @@ public final class LocalBroadcastManager {
* the Intent this function will block and immediately dispatch them before
* returning.
*/
- public void sendBroadcastSync(Intent intent) {
+ public void sendBroadcastSync(@NonNull Intent intent) {
if (sendBroadcast(intent)) {
executePendingBroadcasts();
}
diff --git a/android/support/v4/content/MimeTypeFilter.java b/android/support/v4/content/MimeTypeFilter.java
index 8734c4d2..8a90c62a 100644
--- a/android/support/v4/content/MimeTypeFilter.java
+++ b/android/support/v4/content/MimeTypeFilter.java
@@ -87,6 +87,7 @@ public final class MimeTypeFilter {
* Matches one nullable MIME type against an array of MIME type filters.
* @return The first matching filter, or null if nothing matches.
*/
+ @Nullable
public static String matches(
@Nullable String mimeType, @NonNull String[] filters) {
if (mimeType == null) {
@@ -108,6 +109,7 @@ public final class MimeTypeFilter {
* Matches multiple MIME types against an array of MIME type filters.
* @return The first matching MIME type, or null if nothing matches.
*/
+ @Nullable
public static String matches(
@Nullable String[] mimeTypes, @NonNull String filter) {
if (mimeTypes == null) {
@@ -129,6 +131,7 @@ public final class MimeTypeFilter {
* Matches multiple MIME types against an array of MIME type filters.
* @return The list of matching MIME types, or empty array if nothing matches.
*/
+ @NonNull
public static String[] matchesMany(
@Nullable String[] mimeTypes, @NonNull String filter) {
if (mimeTypes == null) {
diff --git a/android/support/v4/content/PermissionChecker.java b/android/support/v4/content/PermissionChecker.java
index 08662734..c9f18a9f 100644
--- a/android/support/v4/content/PermissionChecker.java
+++ b/android/support/v4/content/PermissionChecker.java
@@ -24,6 +24,7 @@ import android.os.Binder;
import android.os.Process;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.v4.app.AppOpsManagerCompat;
@@ -91,7 +92,7 @@ public final class PermissionChecker {
*/
@PermissionResult
public static int checkPermission(@NonNull Context context, @NonNull String permission,
- int pid, int uid, String packageName) {
+ int pid, int uid, @Nullable String packageName) {
if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
return PERMISSION_DENIED;
}
@@ -146,7 +147,7 @@ public final class PermissionChecker {
*/
@PermissionResult
public static int checkCallingPermission(@NonNull Context context,
- @NonNull String permission, String packageName) {
+ @NonNull String permission, @Nullable String packageName) {
if (Binder.getCallingPid() == Process.myPid()) {
return PERMISSION_DENIED;
}
diff --git a/android/support/v4/content/SharedPreferencesCompat.java b/android/support/v4/content/SharedPreferencesCompat.java
index 7d51fed5..1d43c2eb 100644
--- a/android/support/v4/content/SharedPreferencesCompat.java
+++ b/android/support/v4/content/SharedPreferencesCompat.java
@@ -19,8 +19,18 @@ package android.support.v4.content;
import android.content.SharedPreferences;
import android.support.annotation.NonNull;
+/**
+ * @deprecated This compatibility class is no longer required. Use {@link SharedPreferences}
+ * directly.
+ */
+@Deprecated
public final class SharedPreferencesCompat {
+ /**
+ * @deprecated This compatibility class is no longer required. Use
+ * {@link SharedPreferences.Editor} directly.
+ */
+ @Deprecated
public final static class EditorCompat {
private static EditorCompat sInstance;
@@ -46,14 +56,22 @@ public final class SharedPreferencesCompat {
private EditorCompat() {
mHelper = new Helper();
}
-
+ /**
+ * @deprecated This compatibility class is no longer required. Use
+ * {@link SharedPreferences.Editor} directly.
+ */
+ @Deprecated
public static EditorCompat getInstance() {
if (sInstance == null) {
sInstance = new EditorCompat();
}
return sInstance;
}
-
+ /**
+ * @deprecated This compatibility method is no longer required. Use
+ * {@link SharedPreferences.Editor#apply()} directly.
+ */
+ @Deprecated
public void apply(@NonNull SharedPreferences.Editor editor) {
// Note that this redirection is needed to not break the public API chain
// of getInstance().apply() calls. Otherwise this method could (and should)
diff --git a/android/support/v4/content/WakefulBroadcastReceiver.java b/android/support/v4/content/WakefulBroadcastReceiver.java
index b0cd653f..8ec3eee6 100644
--- a/android/support/v4/content/WakefulBroadcastReceiver.java
+++ b/android/support/v4/content/WakefulBroadcastReceiver.java
@@ -45,7 +45,7 @@ import android.util.SparseArray;
* {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
* holds an extra identifying the wake lock.</p>
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulReceiver.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/content/SimpleWakefulReceiver.java
* complete}
*
* <p>The service (in this example, an {@link android.app.IntentService}) does
@@ -55,7 +55,7 @@ import android.util.SparseArray;
* is the same intent that the {@link WakefulBroadcastReceiver} originally
* passed in.</p>
*
- * {@sample frameworks/support/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulService.java
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/content/SimpleWakefulService.java
* complete}
*
* @deprecated As of {@link android.os.Build.VERSION_CODES#O Android O}, background check
diff --git a/android/support/v4/content/pm/ShortcutInfoCompat.java b/android/support/v4/content/pm/ShortcutInfoCompat.java
index 3ae74701..63585e17 100644
--- a/android/support/v4/content/pm/ShortcutInfoCompat.java
+++ b/android/support/v4/content/pm/ShortcutInfoCompat.java
@@ -18,17 +18,20 @@ package android.support.v4.content.pm;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.graphics.drawable.IconCompat;
import android.text.TextUtils;
import java.util.Arrays;
/**
- * Helper for accessing features in {@link android.content.pm.ShortcutInfo}.
+ * Helper for accessing features in {@link ShortcutInfo}.
*/
public class ShortcutInfoCompat {
@@ -43,6 +46,7 @@ public class ShortcutInfoCompat {
private CharSequence mDisabledMessage;
private IconCompat mIcon;
+ private boolean mIsAlwaysBadged;
private ShortcutInfoCompat() { }
@@ -69,11 +73,26 @@ public class ShortcutInfoCompat {
return builder.build();
}
+ @VisibleForTesting
Intent addToIntent(Intent outIntent) {
outIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, mIntents[mIntents.length - 1])
.putExtra(Intent.EXTRA_SHORTCUT_NAME, mLabel.toString());
if (mIcon != null) {
- mIcon.addToShortcutIntent(outIntent);
+ Drawable badge = null;
+ if (mIsAlwaysBadged) {
+ PackageManager pm = mContext.getPackageManager();
+ if (mActivity != null) {
+ try {
+ badge = pm.getActivityIcon(mActivity);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore
+ }
+ }
+ if (badge == null) {
+ badge = mContext.getApplicationInfo().loadIcon(pm);
+ }
+ }
+ mIcon.addToShortcutIntent(outIntent, badge);
}
return outIntent;
}
@@ -250,7 +269,7 @@ public class ShortcutInfoCompat {
* on the launcher.
*
* @see ShortcutInfo#getActivity()
- * @see android.content.pm.ShortcutInfo.Builder#setActivity(ComponentName)
+ * @see ShortcutInfo.Builder#setActivity(ComponentName)
*/
@NonNull
public Builder setActivity(@NonNull ComponentName activity) {
@@ -259,6 +278,23 @@ public class ShortcutInfoCompat {
}
/**
+ * Badges the icon before passing it over to the Launcher.
+ * <p>
+ * Launcher automatically badges {@link ShortcutInfo}, so only the legacy shortcut icon,
+ * {@link Intent.ShortcutIconResource} is badged. This field is ignored when using
+ * {@link ShortcutInfo} on API 25 and above.
+ * <p>
+ * If the shortcut is associated with an activity, the activity icon is used as the badge,
+ * otherwise application icon is used.
+ *
+ * @see #setActivity(ComponentName)
+ */
+ public Builder setAlwaysBadged() {
+ mInfo.mIsAlwaysBadged = true;
+ return this;
+ }
+
+ /**
* Creates a {@link ShortcutInfoCompat} instance.
*/
@NonNull
diff --git a/android/support/v4/content/res/FontResourcesParserCompat.java b/android/support/v4/content/res/FontResourcesParserCompat.java
index 7fe86ada..8ad07d31 100644
--- a/android/support/v4/content/res/FontResourcesParserCompat.java
+++ b/android/support/v4/content/res/FontResourcesParserCompat.java
@@ -252,10 +252,19 @@ public class FontResourcesParserCompat {
throws XmlPullParserException, IOException {
AttributeSet attrs = Xml.asAttributeSet(parser);
TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont);
- int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, NORMAL_WEIGHT);
- boolean isItalic = ITALIC == array.getInt(R.styleable.FontFamilyFont_fontStyle, 0);
- int resourceId = array.getResourceId(R.styleable.FontFamilyFont_font, 0);
- String filename = array.getString(R.styleable.FontFamilyFont_font);
+ final int weightAttr = array.hasValue(R.styleable.FontFamilyFont_fontWeight)
+ ? R.styleable.FontFamilyFont_fontWeight
+ : R.styleable.FontFamilyFont_android_fontWeight;
+ int weight = array.getInt(weightAttr, NORMAL_WEIGHT);
+ final int styleAttr = array.hasValue(R.styleable.FontFamilyFont_fontStyle)
+ ? R.styleable.FontFamilyFont_fontStyle
+ : R.styleable.FontFamilyFont_android_fontStyle;
+ boolean isItalic = ITALIC == array.getInt(styleAttr, 0);
+ final int resourceAttr = array.hasValue(R.styleable.FontFamilyFont_font)
+ ? R.styleable.FontFamilyFont_font
+ : R.styleable.FontFamilyFont_android_font;
+ int resourceId = array.getResourceId(resourceAttr, 0);
+ String filename = array.getString(resourceAttr);
array.recycle();
while (parser.next() != XmlPullParser.END_TAG) {
skip(parser);
diff --git a/android/support/v4/content/res/ResourcesCompat.java b/android/support/v4/content/res/ResourcesCompat.java
index 43d78d0e..4c70ce93 100644
--- a/android/support/v4/content/res/ResourcesCompat.java
+++ b/android/support/v4/content/res/ResourcesCompat.java
@@ -27,6 +27,8 @@ import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
@@ -36,9 +38,11 @@ import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.v4.content.res.FontResourcesParserCompat.FamilyResourceEntry;
import android.support.v4.graphics.TypefaceCompat;
+import android.support.v4.provider.FontsContractCompat.FontRequestCallback;
+import android.support.v4.provider.FontsContractCompat.FontRequestCallback.FontRequestFailReason;
+import android.support.v4.util.Preconditions;
import android.util.Log;
import android.util.TypedValue;
-import android.widget.TextView;
import org.xmlpull.v1.XmlPullParserException;
@@ -175,8 +179,12 @@ public final class ResourcesCompat {
/**
* Returns a font Typeface associated with a particular resource ID.
* <p>
+ * This method will block the calling thread to retrieve the requested font, including if it
+ * is from a font provider. If you wish to not have this behavior, use
+ * {@link #getFont(Context, int, FontCallback, Handler)} instead.
+ * <p>
* Prior to API level 23, font resources with more than one font in a family will only load the
- * first font in that family.
+ * font closest to a regular weight typeface.
*
* @param context A context to retrieve the Resources from.
* @param id The desired resource identifier of a {@link Typeface},
@@ -184,8 +192,9 @@ public final class ResourcesCompat {
* package, type, and resource entry. The value 0 is an invalid
* identifier.
* @return A font Typeface object.
- * @throws NotFoundException Throws NotFoundException if the given ID does
- * not exist.
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @see #getFont(Context, int, FontCallback, Handler)
*/
@Nullable
public static Typeface getFont(@NonNull Context context, @FontRes int id)
@@ -193,34 +202,155 @@ public final class ResourcesCompat {
if (context.isRestricted()) {
return null;
}
- return loadFont(context, id, new TypedValue(), Typeface.NORMAL, null);
+ return loadFont(context, id, new TypedValue(), Typeface.NORMAL, null /* callback */,
+ null /* handler */, false /* isXmlRequest */);
+ }
+
+ /**
+ * Interface used to receive asynchronous font fetching events.
+ */
+ public abstract static class FontCallback {
+
+ /**
+ * Called when an asynchronous font was finished loading.
+ *
+ * @param typeface The font that was loaded.
+ */
+ public abstract void onFontRetrieved(@NonNull Typeface typeface);
+
+ /**
+ * Called when an asynchronous font failed to load.
+ *
+ * @param reason The reason the font failed to load. One of
+ * {@link FontRequestFailReason#FAIL_REASON_PROVIDER_NOT_FOUND},
+ * {@link FontRequestFailReason#FAIL_REASON_WRONG_CERTIFICATES},
+ * {@link FontRequestFailReason#FAIL_REASON_FONT_LOAD_ERROR},
+ * {@link FontRequestFailReason#FAIL_REASON_SECURITY_VIOLATION},
+ * {@link FontRequestFailReason#FAIL_REASON_FONT_NOT_FOUND},
+ * {@link FontRequestFailReason#FAIL_REASON_FONT_UNAVAILABLE} or
+ * {@link FontRequestFailReason#FAIL_REASON_MALFORMED_QUERY}.
+ */
+ public abstract void onFontRetrievalFailed(@FontRequestFailReason int reason);
+
+ /**
+ * Call {@link #onFontRetrieved(Typeface)} on the handler given, or the Ui Thread if it is
+ * null.
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public final void callbackSuccessAsync(final Typeface typeface, @Nullable Handler handler) {
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ onFontRetrieved(typeface);
+ }
+ });
+ }
+
+ /**
+ * Call {@link #onFontRetrievalFailed(int)} on the handler given, or the Ui Thread if it is
+ * null.
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public final void callbackFailAsync(
+ @FontRequestFailReason final int reason, @Nullable Handler handler) {
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ onFontRetrievalFailed(reason);
+ }
+ });
+ }
}
- /** @hide */
+ /**
+ * Returns a font Typeface associated with a particular resource ID asynchronously.
+ * <p>
+ * Prior to API level 23, font resources with more than one font in a family will only load the
+ * font closest to a regular weight typeface.
+ * </p>
+ *
+ * @param context A context to retrieve the Resources from.
+ * @param id The desired resource identifier of a {@link Typeface}, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource entry. The value 0 is an
+ * invalid identifier.
+ * @param fontCallback A callback to receive async fetching of this font. The callback will be
+ * triggered on the UI thread.
+ * @param handler A handler for the thread the callback should be called on. If null, the
+ * callback will be called on the UI thread.
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ */
+ public static void getFont(@NonNull Context context, @FontRes int id,
+ @NonNull FontCallback fontCallback, @Nullable Handler handler)
+ throws NotFoundException {
+ Preconditions.checkNotNull(fontCallback);
+ if (context.isRestricted()) {
+ fontCallback.callbackFailAsync(
+ FontRequestCallback.FAIL_REASON_SECURITY_VIOLATION, handler);
+ return;
+ }
+ loadFont(context, id, new TypedValue(), Typeface.NORMAL, fontCallback, handler,
+ false /* isXmlRequest */);
+ }
+
+ /**
+ * Used by TintTypedArray.
+ *
+ * @hide
+ */
@RestrictTo(LIBRARY_GROUP)
public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value,
- int style, @Nullable TextView targetView) throws NotFoundException {
+ int style) throws NotFoundException {
if (context.isRestricted()) {
return null;
}
- return loadFont(context, id, value, style, targetView);
+ return loadFont(context, id, value, style, null /* callback */, null /* handler */,
+ true /* isXmlRequest */);
}
+ /**
+ *
+ * @param context The Context to get Resources from
+ * @param id The Resource id to load
+ * @param value A TypedValue to use in the fetching
+ * @param style The font style to load
+ * @param fontCallback A callback to trigger when the font is fetched or an error occurs
+ * @param handler A handler to the thread the callback should be called on
+ * @param isRequestFromLayoutInflator Whether this request originated from XML. This is used to
+ * determine if we use or ignore the fontProviderFetchStrategy attribute in
+ * font provider XML fonts.
+ * @return
+ */
private static Typeface loadFont(@NonNull Context context, int id, TypedValue value,
- int style, @Nullable TextView targetView) {
+ int style, @Nullable FontCallback fontCallback, @Nullable Handler handler,
+ boolean isRequestFromLayoutInflator) {
final Resources resources = context.getResources();
resources.getValue(id, value, true);
- Typeface typeface = loadFont(context, resources, value, id, style, targetView);
- if (typeface != null) {
- return typeface;
+ Typeface typeface = loadFont(context, resources, value, id, style, fontCallback, handler,
+ isRequestFromLayoutInflator);
+ if (typeface == null && fontCallback == null) {
+ throw new NotFoundException("Font resource ID #0x"
+ + Integer.toHexString(id) + " could not be retrieved.");
}
- throw new NotFoundException("Font resource ID #0x"
- + Integer.toHexString(id));
+ return typeface;
}
+ /**
+ * Load the given font. This method will always return null for asynchronous requests, which
+ * provide a fontCallback, as there is no immediate result. When the callback is not provided,
+ * the request is treated as synchronous and fails if async loading is required.
+ */
private static Typeface loadFont(
@NonNull Context context, Resources wrapper, TypedValue value, int id, int style,
- @Nullable TextView targetView) {
+ @Nullable FontCallback fontCallback, @Nullable Handler handler,
+ boolean isRequestFromLayoutInflator) {
if (value.string == null) {
throw new NotFoundException("Resource \"" + wrapper.getResourceName(id) + "\" ("
+ Integer.toHexString(id) + ") is not a Font: " + value);
@@ -228,13 +358,20 @@ public final class ResourcesCompat {
final String file = value.string.toString();
if (!file.startsWith("res/")) {
- // Early exit if the specified string is unlikely to the resource path.
+ // Early exit if the specified string is unlikely to be a resource path.
+ if (fontCallback != null) {
+ fontCallback.callbackFailAsync(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
+ }
return null;
}
+ Typeface typeface = TypefaceCompat.findFromCache(wrapper, id, style);
- Typeface cached = TypefaceCompat.findFromCache(wrapper, id, style);
- if (cached != null) {
- return cached;
+ if (typeface != null) {
+ if (fontCallback != null) {
+ fontCallback.callbackSuccessAsync(typeface, handler);
+ }
+ return typeface;
}
try {
@@ -244,17 +381,35 @@ public final class ResourcesCompat {
FontResourcesParserCompat.parse(rp, wrapper);
if (familyEntry == null) {
Log.e(TAG, "Failed to find font-family tag");
+ if (fontCallback != null) {
+ fontCallback.callbackFailAsync(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
+ }
return null;
}
- return TypefaceCompat.createFromResourcesFamilyXml(
- context, familyEntry, wrapper, id, style, targetView);
+ return TypefaceCompat.createFromResourcesFamilyXml(context, familyEntry, wrapper,
+ id, style, fontCallback, handler, isRequestFromLayoutInflator);
+ }
+ typeface = TypefaceCompat.createFromResourcesFontFile(
+ context, wrapper, id, file, style);
+ if (fontCallback != null) {
+ if (typeface != null) {
+ fontCallback.callbackSuccessAsync(typeface, handler);
+ } else {
+ fontCallback.callbackFailAsync(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
+ }
}
- return TypefaceCompat.createFromResourcesFontFile(context, wrapper, id, file, style);
+ return typeface;
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse xml resource " + file, e);
} catch (IOException e) {
Log.e(TAG, "Failed to read xml resource " + file, e);
}
+ if (fontCallback != null) {
+ fontCallback.callbackFailAsync(
+ FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
+ }
return null;
}
diff --git a/android/support/v4/content/res/TypedArrayUtils.java b/android/support/v4/content/res/TypedArrayUtils.java
index e4d6b292..64cb981b 100644
--- a/android/support/v4/content/res/TypedArrayUtils.java
+++ b/android/support/v4/content/res/TypedArrayUtils.java
@@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable;
import android.support.annotation.AnyRes;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.annotation.StyleableRes;
import android.util.AttributeSet;
@@ -80,7 +81,7 @@ public class TypedArrayUtils {
* {@code defaultValue} if it does not exist.
*/
public static boolean getNamedBoolean(@NonNull TypedArray a, @NonNull XmlPullParser parser,
- String attrName, @StyleableRes int resId, boolean defaultValue) {
+ @NonNull String attrName, @StyleableRes int resId, boolean defaultValue) {
final boolean hasAttr = hasAttribute(parser, attrName);
if (!hasAttr) {
return defaultValue;
@@ -97,7 +98,7 @@ public class TypedArrayUtils {
* {@code defaultValue} if it does not exist.
*/
public static int getNamedInt(@NonNull TypedArray a, @NonNull XmlPullParser parser,
- String attrName, @StyleableRes int resId, int defaultValue) {
+ @NonNull String attrName, @StyleableRes int resId, int defaultValue) {
final boolean hasAttr = hasAttribute(parser, attrName);
if (!hasAttr) {
return defaultValue;
@@ -115,7 +116,7 @@ public class TypedArrayUtils {
*/
@ColorInt
public static int getNamedColor(@NonNull TypedArray a, @NonNull XmlPullParser parser,
- String attrName, @StyleableRes int resId, @ColorInt int defaultValue) {
+ @NonNull String attrName, @StyleableRes int resId, @ColorInt int defaultValue) {
final boolean hasAttr = hasAttribute(parser, attrName);
if (!hasAttr) {
return defaultValue;
@@ -133,7 +134,7 @@ public class TypedArrayUtils {
*/
@AnyRes
public static int getNamedResourceId(@NonNull TypedArray a, @NonNull XmlPullParser parser,
- String attrName, @StyleableRes int resId, @AnyRes int defaultValue) {
+ @NonNull String attrName, @StyleableRes int resId, @AnyRes int defaultValue) {
final boolean hasAttr = hasAttribute(parser, attrName);
if (!hasAttr) {
return defaultValue;
@@ -149,8 +150,9 @@ public class TypedArrayUtils {
* @return a string value in the {@link TypedArray} with the specified {@code resId}, or
* null if it does not exist.
*/
+ @Nullable
public static String getNamedString(@NonNull TypedArray a, @NonNull XmlPullParser parser,
- String attrName, @StyleableRes int resId) {
+ @NonNull String attrName, @StyleableRes int resId) {
final boolean hasAttr = hasAttribute(parser, attrName);
if (!hasAttr) {
return null;
@@ -164,8 +166,9 @@ public class TypedArrayUtils {
* and return a temporary object holding its data. This object is only
* valid until the next call on to {@link TypedArray}.
*/
- public static TypedValue peekNamedValue(TypedArray a, XmlPullParser parser, String attrName,
- int resId) {
+ @Nullable
+ public static TypedValue peekNamedValue(@NonNull TypedArray a, @NonNull XmlPullParser parser,
+ @NonNull String attrName, int resId) {
final boolean hasAttr = hasAttribute(parser, attrName);
if (!hasAttr) {
return null;
@@ -178,8 +181,9 @@ public class TypedArrayUtils {
* Obtains styled attributes from the theme, if available, or unstyled
* resources if the theme is null.
*/
- public static TypedArray obtainAttributes(
- Resources res, Resources.Theme theme, AttributeSet set, int[] attrs) {
+ @NonNull
+ public static TypedArray obtainAttributes(@NonNull Resources res,
+ @Nullable Resources.Theme theme, @NonNull AttributeSet set, @NonNull int[] attrs) {
if (theme == null) {
return res.obtainAttributes(set, attrs);
}
@@ -190,7 +194,7 @@ public class TypedArrayUtils {
* @return a boolean value of {@code index}. If it does not exist, a boolean value of
* {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
*/
- public static boolean getBoolean(TypedArray a, @StyleableRes int index,
+ public static boolean getBoolean(@NonNull TypedArray a, @StyleableRes int index,
@StyleableRes int fallbackIndex, boolean defaultValue) {
boolean val = a.getBoolean(fallbackIndex, defaultValue);
return a.getBoolean(index, val);
@@ -200,7 +204,8 @@ public class TypedArrayUtils {
* @return a drawable value of {@code index}. If it does not exist, a drawable value of
* {@code fallbackIndex}. If it still does not exist, {@code null}.
*/
- public static Drawable getDrawable(TypedArray a, @StyleableRes int index,
+ @Nullable
+ public static Drawable getDrawable(@NonNull TypedArray a, @StyleableRes int index,
@StyleableRes int fallbackIndex) {
Drawable val = a.getDrawable(index);
if (val == null) {
@@ -213,7 +218,7 @@ public class TypedArrayUtils {
* @return an int value of {@code index}. If it does not exist, an int value of
* {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
*/
- public static int getInt(TypedArray a, @StyleableRes int index,
+ public static int getInt(@NonNull TypedArray a, @StyleableRes int index,
@StyleableRes int fallbackIndex, int defaultValue) {
int val = a.getInt(fallbackIndex, defaultValue);
return a.getInt(index, val);
@@ -224,7 +229,7 @@ public class TypedArrayUtils {
* {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
*/
@AnyRes
- public static int getResourceId(TypedArray a, @StyleableRes int index,
+ public static int getResourceId(@NonNull TypedArray a, @StyleableRes int index,
@StyleableRes int fallbackIndex, @AnyRes int defaultValue) {
int val = a.getResourceId(fallbackIndex, defaultValue);
return a.getResourceId(index, val);
@@ -234,7 +239,8 @@ public class TypedArrayUtils {
* @return a string value of {@code index}. If it does not exist, a string value of
* {@code fallbackIndex}. If it still does not exist, {@code null}.
*/
- public static String getString(TypedArray a, @StyleableRes int index,
+ @Nullable
+ public static String getString(@NonNull TypedArray a, @StyleableRes int index,
@StyleableRes int fallbackIndex) {
String val = a.getString(index);
if (val == null) {
@@ -249,7 +255,8 @@ public class TypedArrayUtils {
* @return a text value of {@code index}. If it does not exist, a text value of
* {@code fallbackIndex}. If it still does not exist, {@code null}.
*/
- public static CharSequence getText(TypedArray a, @StyleableRes int index,
+ @Nullable
+ public static CharSequence getText(@NonNull TypedArray a, @StyleableRes int index,
@StyleableRes int fallbackIndex) {
CharSequence val = a.getText(index);
if (val == null) {
@@ -264,7 +271,8 @@ public class TypedArrayUtils {
* @return a string array value of {@code index}. If it does not exist, a string array value
* of {@code fallbackIndex}. If it still does not exist, {@code null}.
*/
- public static CharSequence[] getTextArray(TypedArray a, @StyleableRes int index,
+ @Nullable
+ public static CharSequence[] getTextArray(@NonNull TypedArray a, @StyleableRes int index,
@StyleableRes int fallbackIndex) {
CharSequence[] val = a.getTextArray(index);
if (val == null) {
@@ -277,7 +285,7 @@ public class TypedArrayUtils {
* @return The resource ID value in the {@code context} specified by {@code attr}. If it does
* not exist, {@code fallbackAttr}.
*/
- public static int getAttr(Context context, int attr, int fallbackAttr) {
+ public static int getAttr(@NonNull Context context, int attr, int fallbackAttr) {
TypedValue value = new TypedValue();
context.getTheme().resolveAttribute(attr, value, true);
if (value.resourceId != 0) {
diff --git a/android/support/v4/graphics/BitmapCompat.java b/android/support/v4/graphics/BitmapCompat.java
index 20caf80a..acc37b1f 100644
--- a/android/support/v4/graphics/BitmapCompat.java
+++ b/android/support/v4/graphics/BitmapCompat.java
@@ -17,6 +17,7 @@ package android.support.v4.graphics;
import android.graphics.Bitmap;
import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
/**
@@ -71,11 +72,11 @@ public final class BitmapCompat {
}
}
- public static boolean hasMipMap(Bitmap bitmap) {
+ public static boolean hasMipMap(@NonNull Bitmap bitmap) {
return IMPL.hasMipMap(bitmap);
}
- public static void setHasMipMap(Bitmap bitmap, boolean hasMipMap) {
+ public static void setHasMipMap(@NonNull Bitmap bitmap, boolean hasMipMap) {
IMPL.setHasMipMap(bitmap, hasMipMap);
}
@@ -86,7 +87,7 @@ public final class BitmapCompat {
* @param bitmap the bitmap in which to return its allocation size
* @return the allocation size in bytes
*/
- public static int getAllocationByteCount(Bitmap bitmap) {
+ public static int getAllocationByteCount(@NonNull Bitmap bitmap) {
return IMPL.getAllocationByteCount(bitmap);
}
diff --git a/android/support/v4/graphics/TypefaceCompat.java b/android/support/v4/graphics/TypefaceCompat.java
index 6d114b69..3c55df62 100644
--- a/android/support/v4/graphics/TypefaceCompat.java
+++ b/android/support/v4/graphics/TypefaceCompat.java
@@ -23,16 +23,18 @@ import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Build;
import android.os.CancellationSignal;
+import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
+import android.support.v4.content.res.FontResourcesParserCompat;
import android.support.v4.content.res.FontResourcesParserCompat.FamilyResourceEntry;
import android.support.v4.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry;
import android.support.v4.content.res.FontResourcesParserCompat.ProviderResourceEntry;
+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;
-import android.widget.TextView;
/**
* Helper for accessing features in {@link Typeface}.
@@ -82,7 +84,8 @@ public class TypefaceCompat {
*
* @return null if not found.
*/
- public static Typeface findFromCache(Resources resources, int id, int style) {
+ @Nullable
+ public static Typeface findFromCache(@NonNull Resources resources, int id, int style) {
return sTypefaceCache.get(createResourceUid(resources, id, style));
}
@@ -103,18 +106,35 @@ public class TypefaceCompat {
*
* @return null if failed to create.
*/
+ @Nullable
public static Typeface createFromResourcesFamilyXml(
- Context context, FamilyResourceEntry entry, Resources resources, int id, int style,
- @Nullable TextView targetView) {
+ @NonNull Context context, @NonNull FamilyResourceEntry entry,
+ @NonNull Resources resources, int id, int style,
+ @Nullable ResourcesCompat.FontCallback fontCallback, @Nullable Handler handler,
+ boolean isRequestFromLayoutInflator) {
Typeface typeface;
if (entry instanceof ProviderResourceEntry) {
ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
- typeface = FontsContractCompat.getFontSync(context,
- providerEntry.getRequest(), targetView, providerEntry.getFetchStrategy(),
- providerEntry.getTimeout(), style);
+ final boolean isBlocking = isRequestFromLayoutInflator
+ ? providerEntry.getFetchStrategy()
+ == FontResourcesParserCompat.FETCH_STRATEGY_BLOCKING
+ : fontCallback == null;
+ final int timeout = isRequestFromLayoutInflator ? providerEntry.getTimeout()
+ : FontResourcesParserCompat.INFINITE_TIMEOUT_VALUE;
+ typeface = FontsContractCompat.getFontSync(context, providerEntry.getRequest(),
+ fontCallback, handler, isBlocking, timeout, style);
} else {
typeface = sTypefaceCompatImpl.createFromFontFamilyFilesResourceEntry(
context, (FontFamilyFilesResourceEntry) entry, resources, style);
+ if (fontCallback != null) {
+ if (typeface != null) {
+ fontCallback.callbackSuccessAsync(typeface, handler);
+ } else {
+ fontCallback.callbackFailAsync(
+ FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR,
+ handler);
+ }
+ }
}
if (typeface != null) {
sTypefaceCache.put(createResourceUid(resources, id, style), typeface);
@@ -127,11 +147,13 @@ public class TypefaceCompat {
*/
@Nullable
public static Typeface createFromResourcesFontFile(
- Context context, Resources resources, int id, String path, int style) {
+ @NonNull Context context, @NonNull Resources resources, int id, String path,
+ int style) {
Typeface typeface = sTypefaceCompatImpl.createFromResourcesFontFile(
context, resources, id, path, style);
if (typeface != null) {
- sTypefaceCache.put(createResourceUid(resources, id, style), typeface);
+ final String resourceUid = createResourceUid(resources, id, style);
+ sTypefaceCache.put(resourceUid, typeface);
}
return typeface;
}
@@ -139,7 +161,8 @@ public class TypefaceCompat {
/**
* Create a Typeface from a given FontInfo list and a map that matches them to ByteBuffers.
*/
- public static Typeface createFromFontInfo(Context context,
+ @Nullable
+ public static Typeface createFromFontInfo(@NonNull Context context,
@Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts, int style) {
return sTypefaceCompatImpl.createFromFontInfo(context, cancellationSignal, fonts, style);
}
diff --git a/android/support/v4/graphics/TypefaceCompatApi24Impl.java b/android/support/v4/graphics/TypefaceCompatApi24Impl.java
index a107859e..89a6ec40 100644
--- a/android/support/v4/graphics/TypefaceCompatApi24Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi24Impl.java
@@ -145,7 +145,8 @@ class TypefaceCompatApi24Impl extends TypefaceCompatBaseImpl {
return null;
}
}
- return createFromFamiliesWithDefault(family);
+ final Typeface typeface = createFromFamiliesWithDefault(family);
+ return Typeface.create(typeface, style);
}
@Override
diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index c7066c8d..1b55a2e0 100644
--- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -118,7 +118,7 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
*/
private static boolean isFontFamilyPrivateAPIAvailable() {
if (sAddFontFromAssetManager == null) {
- Log.w(TAG, "Unable to collect necessary private methods."
+ Log.w(TAG, "Unable to collect necessary private methods. "
+ "Fallback to legacy implementation.");
}
return sAddFontFromAssetManager != null;
@@ -273,7 +273,8 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
if (!freeze(fontFamily)) {
return null;
}
- return createFromFamiliesWithDefault(fontFamily);
+ final Typeface typeface = createFromFamiliesWithDefault(fontFamily);
+ return Typeface.create(typeface, style);
}
/**
diff --git a/android/support/v4/graphics/drawable/IconCompat.java b/android/support/v4/graphics/drawable/IconCompat.java
index 0a99fede..359c96b3 100644
--- a/android/support/v4/graphics/drawable/IconCompat.java
+++ b/android/support/v4/graphics/drawable/IconCompat.java
@@ -18,6 +18,7 @@ package android.support.v4.graphics.drawable;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -27,13 +28,17 @@ import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
+import android.support.v4.content.ContextCompat;
/**
* Helper for accessing features in {@link android.graphics.drawable.Icon}.
@@ -202,25 +207,63 @@ public class IconCompat {
}
/**
+ * Use {@link #addToShortcutIntent(Intent, Drawable)} instead
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
- public void addToShortcutIntent(Intent outIntent) {
+ @Deprecated
+ public void addToShortcutIntent(@NonNull Intent outIntent) {
+ addToShortcutIntent(outIntent, null);
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(LIBRARY_GROUP)
+ public void addToShortcutIntent(@NonNull Intent outIntent, @Nullable Drawable badge) {
+ Bitmap icon;
switch (mType) {
case TYPE_BITMAP:
- outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, (Bitmap) mObj1);
+ icon = (Bitmap) mObj1;
+ if (badge != null) {
+ // Do not modify the original icon when applying a badge
+ icon = icon.copy(icon.getConfig(), true);
+ }
break;
case TYPE_ADAPTIVE_BITMAP:
- outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
- createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true));
+ icon = createLegacyIconFromAdaptiveIcon((Bitmap) mObj1, true);
break;
case TYPE_RESOURCE:
- outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
- Intent.ShortcutIconResource.fromContext((Context) mObj1, mInt1));
+ if (badge == null) {
+ outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+ Intent.ShortcutIconResource.fromContext((Context) mObj1, mInt1));
+ return;
+ } else {
+ Context context = (Context) mObj1;
+ Drawable dr = ContextCompat.getDrawable(context, mInt1);
+ if (dr.getIntrinsicWidth() <= 0 || dr.getIntrinsicHeight() <= 0) {
+ int size = ((ActivityManager) context.getSystemService(
+ Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize();
+ icon = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ } else {
+ icon = Bitmap.createBitmap(dr.getIntrinsicWidth(), dr.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ }
+ dr.setBounds(0, 0, icon.getWidth(), icon.getHeight());
+ dr.draw(new Canvas(icon));
+ }
break;
default:
throw new IllegalArgumentException("Icon type not supported for intent shortcuts");
}
+ if (badge != null) {
+ // Badge the icon
+ int w = icon.getWidth();
+ int h = icon.getHeight();
+ badge.setBounds(w / 2, h / 2, w, h);
+ badge.draw(new Canvas(icon));
+ }
+ outIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
}
/**
diff --git a/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java b/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
index d5155613..795126d2 100644
--- a/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
+++ b/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
@@ -27,6 +27,8 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.DisplayMetrics;
import android.view.Gravity;
@@ -66,6 +68,7 @@ public abstract class RoundedBitmapDrawable extends Drawable {
/**
* Returns the paint used to render this drawable.
*/
+ @NonNull
public final Paint getPaint() {
return mPaint;
}
@@ -73,6 +76,7 @@ public abstract class RoundedBitmapDrawable extends Drawable {
/**
* Returns the bitmap used by this drawable to render. May be null.
*/
+ @Nullable
public final Bitmap getBitmap() {
return mBitmap;
}
@@ -92,7 +96,7 @@ public abstract class RoundedBitmapDrawable extends Drawable {
* @see android.graphics.Bitmap#setDensity(int)
* @see android.graphics.Bitmap#getDensity()
*/
- public void setTargetDensity(Canvas canvas) {
+ public void setTargetDensity(@NonNull Canvas canvas) {
setTargetDensity(canvas.getDensity());
}
@@ -104,7 +108,7 @@ public abstract class RoundedBitmapDrawable extends Drawable {
* @see android.graphics.Bitmap#setDensity(int)
* @see android.graphics.Bitmap#getDensity()
*/
- public void setTargetDensity(DisplayMetrics metrics) {
+ public void setTargetDensity(@NonNull DisplayMetrics metrics) {
setTargetDensity(metrics.densityDpi);
}
@@ -253,7 +257,7 @@ public abstract class RoundedBitmapDrawable extends Drawable {
}
@Override
- public void draw(Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
final Bitmap bitmap = mBitmap;
if (bitmap == null) {
return;
diff --git a/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java b/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
index 5e144c76..7790055c 100644
--- a/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
+++ b/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
@@ -21,11 +21,15 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.graphics.BitmapCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.util.Log;
+import java.io.InputStream;
+
/**
* Constructs {@link RoundedBitmapDrawable RoundedBitmapDrawable} objects,
* either from Bitmaps directly, or from streams and files.
@@ -63,7 +67,8 @@ public final class RoundedBitmapDrawableFactory {
* Returns a new drawable by creating it from a bitmap, setting initial target density based on
* the display metrics of the resources.
*/
- public static RoundedBitmapDrawable create(Resources res, Bitmap bitmap) {
+ @NonNull
+ public static RoundedBitmapDrawable create(@NonNull Resources res, @Nullable Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= 21) {
return new RoundedBitmapDrawable21(res, bitmap);
}
@@ -73,8 +78,8 @@ public final class RoundedBitmapDrawableFactory {
/**
* Returns a new drawable, creating it by opening a given file path and decoding the bitmap.
*/
- public static RoundedBitmapDrawable create(Resources res,
- String filepath) {
+ @NonNull
+ public static RoundedBitmapDrawable create(@NonNull Resources res, @NonNull String filepath) {
final RoundedBitmapDrawable drawable = create(res, BitmapFactory.decodeFile(filepath));
if (drawable.getBitmap() == null) {
Log.w(TAG, "RoundedBitmapDrawable cannot decode " + filepath);
@@ -86,8 +91,8 @@ public final class RoundedBitmapDrawableFactory {
/**
* Returns a new drawable, creating it by decoding a bitmap from the given input stream.
*/
- public static RoundedBitmapDrawable create(Resources res,
- java.io.InputStream is) {
+ @NonNull
+ public static RoundedBitmapDrawable create(@NonNull Resources res, @NonNull InputStream is) {
final RoundedBitmapDrawable drawable = create(res, BitmapFactory.decodeStream(is));
if (drawable.getBitmap() == null) {
Log.w(TAG, "RoundedBitmapDrawable cannot decode " + is);
diff --git a/android/support/v4/hardware/display/DisplayManagerCompat.java b/android/support/v4/hardware/display/DisplayManagerCompat.java
index 50d246be..22d39acb 100644
--- a/android/support/v4/hardware/display/DisplayManagerCompat.java
+++ b/android/support/v4/hardware/display/DisplayManagerCompat.java
@@ -19,6 +19,8 @@ package android.support.v4.hardware.display;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.view.Display;
import android.view.WindowManager;
@@ -52,7 +54,8 @@ public abstract class DisplayManagerCompat {
/**
* Gets an instance of the display manager given the context.
*/
- public static DisplayManagerCompat getInstance(Context context) {
+ @NonNull
+ public static DisplayManagerCompat getInstance(@NonNull Context context) {
synchronized (sInstances) {
DisplayManagerCompat instance = sInstances.get(context);
if (instance == null) {
@@ -76,6 +79,7 @@ public abstract class DisplayManagerCompat {
* @param displayId The logical display id.
* @return The display object, or null if there is no valid display with the given id.
*/
+ @Nullable
public abstract Display getDisplay(int displayId);
/**
@@ -83,6 +87,7 @@ public abstract class DisplayManagerCompat {
*
* @return An array containing all displays.
*/
+ @NonNull
public abstract Display[] getDisplays();
/**
@@ -101,6 +106,7 @@ public abstract class DisplayManagerCompat {
*
* @see #DISPLAY_CATEGORY_PRESENTATION
*/
+ @NonNull
public abstract Display[] getDisplays(String category);
private static class DisplayManagerCompatApi14Impl extends DisplayManagerCompat {
diff --git a/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java b/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
index 5e23c689..68f94768 100644
--- a/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
+++ b/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
@@ -44,7 +44,8 @@ public final class FingerprintManagerCompat {
private final Context mContext;
/** Get a {@link FingerprintManagerCompat} instance for a provided context. */
- public static FingerprintManagerCompat from(Context context) {
+ @NonNull
+ public static FingerprintManagerCompat from(@NonNull Context context) {
return new FingerprintManagerCompat(context);
}
@@ -119,8 +120,9 @@ public final class FingerprintManagerCompat {
}
}
+ @Nullable
@RequiresApi(23)
- private static FingerprintManager getFingerprintManagerOrNull(Context context) {
+ private static FingerprintManager getFingerprintManagerOrNull(@NonNull Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
return context.getSystemService(FingerprintManager.class);
} else {
@@ -195,20 +197,20 @@ public final class FingerprintManagerCompat {
private final Cipher mCipher;
private final Mac mMac;
- public CryptoObject(Signature signature) {
+ public CryptoObject(@NonNull Signature signature) {
mSignature = signature;
mCipher = null;
mMac = null;
}
- public CryptoObject(Cipher cipher) {
+ public CryptoObject(@NonNull Cipher cipher) {
mCipher = cipher;
mSignature = null;
mMac = null;
}
- public CryptoObject(Mac mac) {
+ public CryptoObject(@NonNull Mac mac) {
mMac = mac;
mCipher = null;
mSignature = null;
@@ -218,18 +220,21 @@ public final class FingerprintManagerCompat {
* Get {@link Signature} object.
* @return {@link Signature} object or null if this doesn't contain one.
*/
+ @Nullable
public Signature getSignature() { return mSignature; }
/**
* Get {@link Cipher} object.
* @return {@link Cipher} object or null if this doesn't contain one.
*/
+ @Nullable
public Cipher getCipher() { return mCipher; }
/**
* Get {@link Mac} object.
* @return {@link Mac} object or null if this doesn't contain one.
*/
+ @Nullable
public Mac getMac() { return mMac; }
}
diff --git a/android/support/v4/media/session/MediaControllerCompat.java b/android/support/v4/media/session/MediaControllerCompat.java
index cea4771a..2509cd49 100644
--- a/android/support/v4/media/session/MediaControllerCompat.java
+++ b/android/support/v4/media/session/MediaControllerCompat.java
@@ -70,7 +70,7 @@ import java.util.List;
* <li>{@link #getPlaybackState()}.{@link PlaybackStateCompat#getExtras() getExtras()}</li>
* <li>{@link #isCaptioningEnabled()}</li>
* <li>{@link #getRepeatMode()}</li>
- * <li>{@link #isShuffleModeEnabled()}</li>
+ * <li>{@link #getShuffleMode()}</li>
* </ul></p>
*
* <div class="special reference">
@@ -439,18 +439,6 @@ public final class MediaControllerCompat {
}
/**
- * Returns whether the shuffle mode is enabled for this session.
- *
- * @return {@code true} if the shuffle mode is enabled, {@code false} if it is disabled, not
- * set, or the session is not ready.
- * @deprecated Use {@link #getShuffleMode} instead.
- */
- @Deprecated
- public boolean isShuffleModeEnabled() {
- return mImpl.isShuffleModeEnabled();
- }
-
- /**
* Gets the shuffle mode for this session.
*
* @return The latest shuffle mode set to the session, or
@@ -760,16 +748,6 @@ public final class MediaControllerCompat {
/**
* Override to handle changes to the shuffle mode.
*
- * @param enabled {@code true} if the shuffle mode is enabled, {@code false} otherwise.
- * @deprecated Use {@link #onShuffleModeChanged(int)} instead.
- */
- @Deprecated
- public void onShuffleModeChanged(boolean enabled) {
- }
-
- /**
- * Override to handle changes to the shuffle mode.
- *
* @param shuffleMode The shuffle mode. Must be one of the followings:
* {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
* {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
@@ -963,12 +941,8 @@ public final class MediaControllerCompat {
}
@Override
- public void onShuffleModeChangedDeprecated(boolean enabled) throws RemoteException {
- MediaControllerCompat.Callback callback = mCallback.get();
- if (callback != null) {
- callback.postToHandler(
- MessageHandler.MSG_UPDATE_SHUFFLE_MODE_DEPRECATED, enabled, null);
- }
+ public void onShuffleModeChangedRemoved(boolean enabled) throws RemoteException {
+ // Do nothing.
}
@Override
@@ -1020,7 +994,6 @@ public final class MediaControllerCompat {
private static final int MSG_UPDATE_EXTRAS = 7;
private static final int MSG_DESTROYED = 8;
private static final int MSG_UPDATE_REPEAT_MODE = 9;
- private static final int MSG_UPDATE_SHUFFLE_MODE_DEPRECATED = 10;
private static final int MSG_UPDATE_CAPTIONING_ENABLED = 11;
private static final int MSG_UPDATE_SHUFFLE_MODE = 12;
private static final int MSG_SESSION_READY = 13;
@@ -1058,9 +1031,6 @@ public final class MediaControllerCompat {
case MSG_UPDATE_REPEAT_MODE:
onRepeatModeChanged((int) msg.obj);
break;
- case MSG_UPDATE_SHUFFLE_MODE_DEPRECATED:
- onShuffleModeChanged((boolean) msg.obj);
- break;
case MSG_UPDATE_SHUFFLE_MODE:
onShuffleModeChanged((int) msg.obj);
break;
@@ -1264,15 +1234,6 @@ public final class MediaControllerCompat {
/**
* Sets the shuffle mode for this session.
*
- * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
- * @deprecated Use {@link #setShuffleMode} instead.
- */
- @Deprecated
- public abstract void setShuffleModeEnabled(boolean enabled);
-
- /**
- * Sets the shuffle mode for this session.
- *
* @param shuffleMode The shuffle mode. Must be one of the followings:
* {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
* {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
@@ -1414,7 +1375,6 @@ public final class MediaControllerCompat {
int getRatingType();
boolean isCaptioningEnabled();
int getRepeatMode();
- boolean isShuffleModeEnabled();
int getShuffleMode();
long getFlags();
PlaybackInfo getPlaybackInfo();
@@ -1610,16 +1570,6 @@ public final class MediaControllerCompat {
}
@Override
- public boolean isShuffleModeEnabled() {
- try {
- return mBinder.isShuffleModeEnabledDeprecated();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in isShuffleModeEnabled.", e);
- }
- return false;
- }
-
- @Override
public int getShuffleMode() {
try {
return mBinder.getShuffleMode();
@@ -1899,15 +1849,6 @@ public final class MediaControllerCompat {
}
@Override
- public void setShuffleModeEnabled(boolean enabled) {
- try {
- mBinder.setShuffleModeEnabledDeprecated(enabled);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in setShuffleModeEnabled.", e);
- }
- }
-
- @Override
public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
try {
mBinder.setShuffleMode(shuffleMode);
@@ -2122,18 +2063,6 @@ public final class MediaControllerCompat {
}
@Override
- public boolean isShuffleModeEnabled() {
- if (mExtraBinder != null) {
- try {
- return mExtraBinder.isShuffleModeEnabledDeprecated();
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in isShuffleModeEnabled.", e);
- }
- }
- return false;
- }
-
- @Override
public int getShuffleMode() {
if (mExtraBinder != null) {
try {
@@ -2391,13 +2320,6 @@ public final class MediaControllerCompat {
}
@Override
- public void setShuffleModeEnabled(boolean enabled) {
- Bundle bundle = new Bundle();
- bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED, enabled);
- sendCustomAction(MediaSessionCompat.ACTION_SET_SHUFFLE_MODE_ENABLED, bundle);
- }
-
- @Override
public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
Bundle bundle = new Bundle();
bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE, shuffleMode);
diff --git a/android/support/v4/media/session/MediaSessionCompat.java b/android/support/v4/media/session/MediaSessionCompat.java
index ea5bedae..8b91413d 100644
--- a/android/support/v4/media/session/MediaSessionCompat.java
+++ b/android/support/v4/media/session/MediaSessionCompat.java
@@ -259,12 +259,6 @@ public class MediaSessionCompat {
"android.support.v4.media.session.action.SET_REPEAT_MODE";
/**
- * Custom action to invoke setShuffleModeEnabled() for the forward compatibility.
- */
- static final String ACTION_SET_SHUFFLE_MODE_ENABLED =
- "android.support.v4.media.session.action.SET_SHUFFLE_MODE_ENABLED";
-
- /**
* Custom action to invoke setShuffleMode() for the forward compatibility.
*/
static final String ACTION_SET_SHUFFLE_MODE =
@@ -321,13 +315,6 @@ public class MediaSessionCompat {
"android.support.v4.media.session.action.ARGUMENT_REPEAT_MODE";
/**
- * Argument for use with {@link #ACTION_SET_SHUFFLE_MODE_ENABLED} indicating that shuffle mode
- * is enabled.
- */
- static final String ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED =
- "android.support.v4.media.session.action.ARGUMENT_SHUFFLE_MODE_ENABLED";
-
- /**
* Argument for use with {@link #ACTION_SET_SHUFFLE_MODE} indicating shuffle mode.
*/
static final String ACTION_ARGUMENT_SHUFFLE_MODE =
@@ -704,20 +691,6 @@ public class MediaSessionCompat {
/**
* Sets the shuffle mode for this session.
* <p>
- * Note that if this method is not called before,
- * {@link MediaControllerCompat#isShuffleModeEnabled} will return {@code false}.
- *
- * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
- * @deprecated Use {@link #setShuffleMode} instead.
- */
- @Deprecated
- public void setShuffleModeEnabled(boolean enabled) {
- mImpl.setShuffleModeEnabled(enabled);
- }
-
- /**
- * Sets the shuffle mode for this session.
- * <p>
* Note that if this method is not called before, {@link MediaControllerCompat#getShuffleMode}
* will return {@link PlaybackStateCompat#SHUFFLE_MODE_NONE}.
*
@@ -1134,20 +1107,6 @@ public class MediaSessionCompat {
/**
* Override to handle the setting of the shuffle mode.
* <p>
- * You should call {@link #setShuffleModeEnabled} before the end of this method in order to
- * notify the change to the {@link MediaControllerCompat}, or
- * {@link MediaControllerCompat#isShuffleModeEnabled} could return an invalid value.
- *
- * @param enabled true when the shuffle mode is enabled, false otherwise.
- * @deprecated Use {@link #onSetShuffleMode} instead.
- */
- @Deprecated
- public void onSetShuffleModeEnabled(boolean enabled) {
- }
-
- /**
- * Override to handle the setting of the shuffle mode.
- * <p>
* You should call {@link #setShuffleMode} before the end of this method in order to
* notify the change to the {@link MediaControllerCompat}, or
* {@link MediaControllerCompat#getShuffleMode} could return an invalid value.
@@ -1386,9 +1345,6 @@ public class MediaSessionCompat {
} else if (action.equals(ACTION_SET_REPEAT_MODE)) {
int repeatMode = extras.getInt(ACTION_ARGUMENT_REPEAT_MODE);
Callback.this.onSetRepeatMode(repeatMode);
- } else if (action.equals(ACTION_SET_SHUFFLE_MODE_ENABLED)) {
- boolean enabled = extras.getBoolean(ACTION_ARGUMENT_SHUFFLE_MODE_ENABLED);
- Callback.this.onSetShuffleModeEnabled(enabled);
} else if (action.equals(ACTION_SET_SHUFFLE_MODE)) {
int shuffleMode = extras.getInt(ACTION_ARGUMENT_SHUFFLE_MODE);
Callback.this.onSetShuffleMode(shuffleMode);
@@ -1797,7 +1753,6 @@ public class MediaSessionCompat {
void setRatingType(@RatingCompat.Style int type);
void setCaptioningEnabled(boolean enabled);
void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
- void setShuffleModeEnabled(boolean enabled);
void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
void setExtras(Bundle extras);
@@ -1844,7 +1799,6 @@ public class MediaSessionCompat {
boolean mCaptioningEnabled;
@PlaybackStateCompat.RepeatMode int mRepeatMode;
@PlaybackStateCompat.ShuffleMode int mShuffleMode;
- boolean mShuffleModeEnabled;
Bundle mExtras;
int mVolumeType;
@@ -2254,14 +2208,6 @@ public class MediaSessionCompat {
}
@Override
- public void setShuffleModeEnabled(boolean enabled) {
- if (mShuffleModeEnabled != enabled) {
- mShuffleModeEnabled = enabled;
- sendShuffleModeEnabled(enabled);
- }
- }
-
- @Override
public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
if (mShuffleMode != shuffleMode) {
mShuffleMode = shuffleMode;
@@ -2460,18 +2406,6 @@ public class MediaSessionCompat {
mControllerCallbacks.finishBroadcast();
}
- private void sendShuffleModeEnabled(boolean enabled) {
- int size = mControllerCallbacks.beginBroadcast();
- for (int i = size - 1; i >= 0; i--) {
- IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
- try {
- cb.onShuffleModeChangedDeprecated(enabled);
- } catch (RemoteException e) {
- }
- }
- mControllerCallbacks.finishBroadcast();
- }
-
private void sendShuffleMode(int shuffleMode) {
int size = mControllerCallbacks.beginBroadcast();
for (int i = size - 1; i >= 0; i--) {
@@ -2695,8 +2629,8 @@ public class MediaSessionCompat {
}
@Override
- public void setShuffleModeEnabledDeprecated(boolean enabled) throws RemoteException {
- postToHandler(MessageHandler.MSG_SET_SHUFFLE_MODE_ENABLED, enabled);
+ public void setShuffleModeEnabledRemoved(boolean enabled) throws RemoteException {
+ // Do nothing.
}
@Override
@@ -2783,8 +2717,8 @@ public class MediaSessionCompat {
}
@Override
- public boolean isShuffleModeEnabledDeprecated() {
- return mShuffleModeEnabled;
+ public boolean isShuffleModeEnabledRemoved() {
+ return false;
}
@Override
@@ -2837,7 +2771,6 @@ public class MediaSessionCompat {
private static final int MSG_MEDIA_BUTTON = 21;
private static final int MSG_SET_VOLUME = 22;
private static final int MSG_SET_REPEAT_MODE = 23;
- private static final int MSG_SET_SHUFFLE_MODE_ENABLED = 24;
private static final int MSG_ADD_QUEUE_ITEM = 25;
private static final int MSG_ADD_QUEUE_ITEM_AT = 26;
private static final int MSG_REMOVE_QUEUE_ITEM = 27;
@@ -2978,9 +2911,6 @@ public class MediaSessionCompat {
case MSG_SET_REPEAT_MODE:
cb.onSetRepeatMode(msg.arg1);
break;
- case MSG_SET_SHUFFLE_MODE_ENABLED:
- cb.onSetShuffleModeEnabled((boolean) msg.obj);
- break;
case MSG_SET_SHUFFLE_MODE:
cb.onSetShuffleMode(msg.arg1);
break;
@@ -3206,7 +3136,6 @@ public class MediaSessionCompat {
@RatingCompat.Style int mRatingType;
boolean mCaptioningEnabled;
@PlaybackStateCompat.RepeatMode int mRepeatMode;
- boolean mShuffleModeEnabled;
@PlaybackStateCompat.ShuffleMode int mShuffleMode;
public MediaSessionImplApi21(Context context, String tag) {
@@ -3381,22 +3310,6 @@ public class MediaSessionCompat {
}
@Override
- public void setShuffleModeEnabled(boolean enabled) {
- if (mShuffleModeEnabled != enabled) {
- mShuffleModeEnabled = enabled;
- int size = mExtraControllerCallbacks.beginBroadcast();
- for (int i = size - 1; i >= 0; i--) {
- IMediaControllerCallback cb = mExtraControllerCallbacks.getBroadcastItem(i);
- try {
- cb.onShuffleModeChangedDeprecated(enabled);
- } catch (RemoteException e) {
- }
- }
- mExtraControllerCallbacks.finishBroadcast();
- }
- }
-
- @Override
public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
if (mShuffleMode != shuffleMode) {
mShuffleMode = shuffleMode;
@@ -3625,9 +3538,8 @@ public class MediaSessionCompat {
}
@Override
- public void setShuffleModeEnabledDeprecated(boolean enabled) throws RemoteException {
- // Will not be called.
- throw new AssertionError();
+ public void setShuffleModeEnabledRemoved(boolean enabled) throws RemoteException {
+ // Do nothing.
}
@Override
@@ -3713,8 +3625,8 @@ public class MediaSessionCompat {
}
@Override
- public boolean isShuffleModeEnabledDeprecated() {
- return mShuffleModeEnabled;
+ public boolean isShuffleModeEnabledRemoved() {
+ return false;
}
@Override
diff --git a/android/support/v4/media/session/PlaybackStateCompat.java b/android/support/v4/media/session/PlaybackStateCompat.java
index eee09c5c..d7634b00 100644
--- a/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/android/support/v4/media/session/PlaybackStateCompat.java
@@ -202,6 +202,7 @@ public final class PlaybackStateCompat implements Parcelable {
* @see Builder#setActions(long)
* @deprecated Use {@link #ACTION_SET_SHUFFLE_MODE} instead.
*/
+ @Deprecated
public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 1 << 19;
/**
diff --git a/android/support/v4/net/ConnectivityManagerCompat.java b/android/support/v4/net/ConnectivityManagerCompat.java
index cdddb680..c08cac83 100644
--- a/android/support/v4/net/ConnectivityManagerCompat.java
+++ b/android/support/v4/net/ConnectivityManagerCompat.java
@@ -32,6 +32,8 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresPermission;
import android.support.annotation.RestrictTo;
@@ -91,7 +93,7 @@ public final class ConnectivityManagerCompat {
*/
@SuppressWarnings("deprecation")
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public static boolean isActiveNetworkMetered(ConnectivityManager cm) {
+ public static boolean isActiveNetworkMetered(@NonNull ConnectivityManager cm) {
if (Build.VERSION.SDK_INT >= 16) {
return cm.isActiveNetworkMetered();
} else {
@@ -128,8 +130,10 @@ public final class ConnectivityManagerCompat {
* potentially-stale value from
* {@link ConnectivityManager#EXTRA_NETWORK_INFO}. May be {@code null}.
*/
+ @Nullable
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- public static NetworkInfo getNetworkInfoFromBroadcast(ConnectivityManager cm, Intent intent) {
+ public static NetworkInfo getNetworkInfoFromBroadcast(@NonNull ConnectivityManager cm,
+ @NonNull Intent intent) {
final NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
if (info != null) {
return cm.getNetworkInfo(info.getType());
@@ -147,7 +151,7 @@ public final class ConnectivityManagerCompat {
* or {@link #RESTRICT_BACKGROUND_STATUS_WHITELISTED}
*/
@RestrictBackgroundStatus
- public static int getRestrictBackgroundStatus(ConnectivityManager cm) {
+ public static int getRestrictBackgroundStatus(@NonNull ConnectivityManager cm) {
if (Build.VERSION.SDK_INT >= 24) {
return cm.getRestrictBackgroundStatus();
} else {
diff --git a/android/support/v4/net/TrafficStatsCompat.java b/android/support/v4/net/TrafficStatsCompat.java
index 1049fa53..b74b8d02 100644
--- a/android/support/v4/net/TrafficStatsCompat.java
+++ b/android/support/v4/net/TrafficStatsCompat.java
@@ -19,6 +19,7 @@ package android.support.v4.net;
import android.net.TrafficStats;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.support.annotation.NonNull;
import java.net.DatagramSocket;
import java.net.Socket;
@@ -131,7 +132,7 @@ public final class TrafficStatsCompat {
*
* @see #setThreadStatsTag(int)
*/
- public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ public static void tagDatagramSocket(@NonNull DatagramSocket socket) throws SocketException {
if (Build.VERSION.SDK_INT >= 24) {
TrafficStats.tagDatagramSocket(socket);
} else {
@@ -148,7 +149,7 @@ public final class TrafficStatsCompat {
/**
* Remove any statistics parameters from the given {@link DatagramSocket}.
*/
- public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ public static void untagDatagramSocket(@NonNull DatagramSocket socket) throws SocketException {
if (Build.VERSION.SDK_INT >= 24) {
TrafficStats.untagDatagramSocket(socket);
} else {
diff --git a/android/support/v4/provider/FontRequest.java b/android/support/v4/provider/FontRequest.java
index cb32f067..b14f85e4 100644
--- a/android/support/v4/provider/FontRequest.java
+++ b/android/support/v4/provider/FontRequest.java
@@ -89,6 +89,7 @@ public final class FontRequest {
* Returns the selected font provider's authority. This tells the system what font provider
* it should request the font from.
*/
+ @NonNull
public String getProviderAuthority() {
return mProviderAuthority;
}
@@ -97,6 +98,7 @@ public final class FontRequest {
* Returns the selected font provider's package. This helps the system verify that the provider
* identified by the given authority is the one requested.
*/
+ @NonNull
public String getProviderPackage() {
return mProviderPackage;
}
@@ -105,6 +107,7 @@ public final class FontRequest {
* Returns the query string. Refer to your font provider's documentation on the format of this
* string.
*/
+ @NonNull
public String getQuery() {
return mQuery;
}
diff --git a/android/support/v4/provider/FontsContractCompat.java b/android/support/v4/provider/FontsContractCompat.java
index 6ad46a17..9ef1b0b0 100644
--- a/android/support/v4/provider/FontsContractCompat.java
+++ b/android/support/v4/provider/FontsContractCompat.java
@@ -17,7 +17,6 @@
package android.support.v4.provider;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.v4.content.res.FontResourcesParserCompat.FetchStrategy;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
@@ -46,17 +45,16 @@ import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
import android.support.annotation.VisibleForTesting;
import android.support.v4.content.res.FontResourcesParserCompat;
+import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.graphics.TypefaceCompat;
import android.support.v4.graphics.TypefaceCompatUtil;
import android.support.v4.provider.SelfDestructiveThread.ReplyCallback;
import android.support.v4.util.LruCache;
import android.support.v4.util.Preconditions;
import android.support.v4.util.SimpleArrayMap;
-import android.widget.TextView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -166,11 +164,11 @@ public class FontsContractCompat {
// space open for new provider codes, these should all be negative numbers.
/** @hide */
@RestrictTo(LIBRARY_GROUP)
- public static final int RESULT_CODE_PROVIDER_NOT_FOUND = -1;
+ /* package */ static final int RESULT_CODE_PROVIDER_NOT_FOUND = -1;
/** @hide */
@RestrictTo(LIBRARY_GROUP)
- public static final int RESULT_CODE_WRONG_CERTIFICATES = -2;
- // Note -3 is used by Typeface to indicate the font failed to load.
+ /* package */ static final int RESULT_CODE_WRONG_CERTIFICATES = -2;
+ // Note -3 is used by FontRequestCallback to indicate the font failed to load.
private static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
@@ -179,51 +177,87 @@ public class FontsContractCompat {
new SelfDestructiveThread("fonts", Process.THREAD_PRIORITY_BACKGROUND,
BACKGROUND_THREAD_KEEP_ALIVE_DURATION_MS);
- private static Typeface getFontInternal(final Context context, final FontRequest request,
+ @NonNull
+ private static TypefaceResult getFontInternal(final Context context, final FontRequest request,
int style) {
FontFamilyResult result;
try {
result = fetchFonts(context, null /* CancellationSignal */, request);
} catch (PackageManager.NameNotFoundException e) {
- return null;
+ return new TypefaceResult(null, FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND);
}
if (result.getStatusCode() == FontFamilyResult.STATUS_OK) {
- return TypefaceCompat.createFromFontInfo(context, null /* CancellationSignal */,
- result.getFonts(), style);
+ final Typeface typeface = TypefaceCompat.createFromFontInfo(
+ context, null /* CancellationSignal */, result.getFonts(), style);
+ return new TypefaceResult(typeface, typeface != null
+ ? FontRequestCallback.RESULT_OK
+ : FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
}
- return null;
+ int resultCode = result.getStatusCode() == FontFamilyResult.STATUS_WRONG_CERTIFICATES
+ ? FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES
+ : FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR;
+ return new TypefaceResult(null, resultCode);
}
private static final Object sLock = new Object();
@GuardedBy("sLock")
- private static final SimpleArrayMap<String, ArrayList<ReplyCallback<Typeface>>>
+ private static final SimpleArrayMap<String, ArrayList<ReplyCallback<TypefaceResult>>>
sPendingReplies = new SimpleArrayMap<>();
+ private static final class TypefaceResult {
+ final Typeface mTypeface;
+ @FontRequestCallback.FontRequestFailReason final int mResult;
+
+ TypefaceResult(@Nullable Typeface typeface,
+ @FontRequestCallback.FontRequestFailReason int result) {
+ mTypeface = typeface;
+ mResult = result;
+ }
+ }
+
+ /**
+ * Used for tests, should not be used otherwise.
+ * @hide
+ **/
+ @RestrictTo(LIBRARY_GROUP)
+ public static final void resetCache() {
+ sTypefaceCache.evictAll();
+ }
+
/** @hide */
@RestrictTo(LIBRARY_GROUP)
public static Typeface getFontSync(final Context context, final FontRequest request,
- final @Nullable TextView targetView, @FetchStrategy int strategy, int timeout,
+ final @Nullable ResourcesCompat.FontCallback fontCallback,
+ final @Nullable Handler handler, boolean isBlockingFetch, int timeout,
final int style) {
final String id = request.getIdentifier() + "-" + style;
Typeface cached = sTypefaceCache.get(id);
if (cached != null) {
+ if (fontCallback != null) {
+ fontCallback.onFontRetrieved(cached);
+ }
return cached;
}
- final boolean isBlockingFetch =
- strategy == FontResourcesParserCompat.FETCH_STRATEGY_BLOCKING;
-
if (isBlockingFetch && timeout == FontResourcesParserCompat.INFINITE_TIMEOUT_VALUE) {
// Wait forever. No need to post to the thread.
- return getFontInternal(context, request, style);
+ TypefaceResult typefaceResult = getFontInternal(context, request, style);
+ if (fontCallback != null) {
+ if (typefaceResult.mResult == FontFamilyResult.STATUS_OK) {
+ fontCallback.callbackSuccessAsync(typefaceResult.mTypeface, handler);
+ } else {
+ fontCallback.callbackFailAsync(typefaceResult.mResult, handler);
+ }
+ }
+ return typefaceResult.mTypeface;
}
- final Callable<Typeface> fetcher = new Callable<Typeface>() {
+ final Callable<TypefaceResult> fetcher = new Callable<TypefaceResult>() {
@Override
- public Typeface call() throws Exception {
- Typeface typeface = getFontInternal(context, request, style);
- if (typeface != null) {
- sTypefaceCache.put(id, typeface);
+ public TypefaceResult call() throws Exception {
+ TypefaceResult typeface = getFontInternal(context, request, style);
+ if (typeface.mTypeface != null) {
+ sTypefaceCache.put(id, typeface.mTypeface);
}
return typeface;
}
@@ -231,37 +265,42 @@ public class FontsContractCompat {
if (isBlockingFetch) {
try {
- return sBackgroundThread.postAndWait(fetcher, timeout);
+ return sBackgroundThread.postAndWait(fetcher, timeout).mTypeface;
} catch (InterruptedException e) {
return null;
}
} else {
- final WeakReference<TextView> textViewWeak = new WeakReference<TextView>(targetView);
- final ReplyCallback<Typeface> reply = new ReplyCallback<Typeface>() {
- @Override
- public void onReply(final Typeface typeface) {
- final TextView textView = textViewWeak.get();
- if (textView != null) {
- targetView.setTypeface(typeface, style);
- }
- }
- };
+ final ReplyCallback<TypefaceResult> reply = fontCallback == null ? null
+ : new ReplyCallback<TypefaceResult>() {
+ @Override
+ public void onReply(final TypefaceResult typeface) {
+ if (typeface.mResult == FontFamilyResult.STATUS_OK) {
+ fontCallback.callbackSuccessAsync(typeface.mTypeface, handler);
+ } else {
+ fontCallback.callbackFailAsync(typeface.mResult, handler);
+ }
+ }
+ };
synchronized (sLock) {
if (sPendingReplies.containsKey(id)) {
// Already requested. Do not request the same provider again and insert the
// reply to the queue instead.
- sPendingReplies.get(id).add(reply);
+ if (reply != null) {
+ sPendingReplies.get(id).add(reply);
+ }
return null;
}
- ArrayList<ReplyCallback<Typeface>> pendingReplies = new ArrayList<>();
- pendingReplies.add(reply);
- sPendingReplies.put(id, pendingReplies);
+ if (reply != null) {
+ ArrayList<ReplyCallback<TypefaceResult>> pendingReplies = new ArrayList<>();
+ pendingReplies.add(reply);
+ sPendingReplies.put(id, pendingReplies);
+ }
}
- sBackgroundThread.postAndReply(fetcher, new ReplyCallback<Typeface>() {
+ sBackgroundThread.postAndReply(fetcher, new ReplyCallback<TypefaceResult>() {
@Override
- public void onReply(final Typeface typeface) {
- final ArrayList<ReplyCallback<Typeface>> replies;
+ public void onReply(final TypefaceResult typeface) {
+ final ArrayList<ReplyCallback<TypefaceResult>> replies;
synchronized (sLock) {
replies = sPendingReplies.get(id);
sPendingReplies.remove(id);
@@ -269,7 +308,7 @@ public class FontsContractCompat {
for (int i = 0; i < replies.size(); ++i) {
replies.get(i).onReply(typeface);
}
- };
+ }
});
return null;
}
@@ -292,8 +331,9 @@ public class FontsContractCompat {
* @param weight An integer that indicates the font weight.
* @param italic A boolean that indicates the font is italic style or not.
* @param resultCode A boolean that indicates the font contents is ready.
+ *
+ * @hide
*/
- /** @hide */
@RestrictTo(LIBRARY_GROUP)
public FontInfo(@NonNull Uri uri, @IntRange(from = 0) int ttcIndex,
@IntRange(from = 1, to = 1000) int weight,
@@ -396,6 +436,9 @@ public class FontsContractCompat {
* Interface used to receive asynchronously fetched typefaces.
*/
public static class FontRequestCallback {
+ /** @hide */
+ @RestrictTo(LIBRARY_GROUP)
+ public static final int RESULT_OK = Columns.RESULT_CODE_OK;
/**
* Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
* provider was not found on the device.
@@ -412,6 +455,11 @@ public class FontsContractCompat {
*/
public static final int FAIL_REASON_FONT_LOAD_ERROR = -3;
/**
+ * Constant that signals that the font was not loaded due to security issues. This usually
+ * means the font was attempted to load on a restricted context.
+ */
+ public static final int FAIL_REASON_SECURITY_VIOLATION = -4;
+ /**
* Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
* provider did not return any results for the given query.
*/
@@ -431,9 +479,10 @@ public class FontsContractCompat {
@RestrictTo(LIBRARY_GROUP)
@IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE,
- FAIL_REASON_MALFORMED_QUERY, FAIL_REASON_WRONG_CERTIFICATES })
+ FAIL_REASON_MALFORMED_QUERY, FAIL_REASON_WRONG_CERTIFICATES,
+ FAIL_REASON_SECURITY_VIOLATION, RESULT_OK })
@Retention(RetentionPolicy.SOURCE)
- @interface FontRequestFailReason {}
+ public @interface FontRequestFailReason {}
public FontRequestCallback() {}
@@ -600,6 +649,7 @@ public class FontsContractCompat {
* @param fonts An array of {@link FontInfo} to be used to create a Typeface.
* @return A Typeface object. Returns null if typeface creation fails.
*/
+ @Nullable
public static Typeface buildTypeface(@NonNull Context context,
@Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) {
return TypefaceCompat.createFromFontInfo(context, cancellationSignal, fonts,
diff --git a/android/support/v4/provider/SelfDestructiveThread.java b/android/support/v4/provider/SelfDestructiveThread.java
index 885799be..7cfe1f8c 100644
--- a/android/support/v4/provider/SelfDestructiveThread.java
+++ b/android/support/v4/provider/SelfDestructiveThread.java
@@ -129,7 +129,7 @@ public class SelfDestructiveThread {
/**
* Execute the specific callable object on this thread and call the reply callback on the
- * calling thread once it finishs.
+ * calling thread once it finishes.
*/
public <T> void postAndReply(final Callable<T> callable, final ReplyCallback<T> reply) {
final Handler callingHandler = new Handler();
diff --git a/android/support/v4/util/AtomicFile.java b/android/support/v4/util/AtomicFile.java
index 275f4e20..aefe7050 100644
--- a/android/support/v4/util/AtomicFile.java
+++ b/android/support/v4/util/AtomicFile.java
@@ -16,6 +16,8 @@
package android.support.v4.util;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.Log;
import java.io.File;
@@ -48,7 +50,7 @@ public class AtomicFile {
* Create a new AtomicFile for a file located at the given File path.
* The secondary backup file will be the same file path with ".bak" appended.
*/
- public AtomicFile(File baseName) {
+ public AtomicFile(@NonNull File baseName) {
mBaseName = baseName;
mBackupName = new File(baseName.getPath() + ".bak");
}
@@ -57,6 +59,7 @@ public class AtomicFile {
* Return the path to the base file. You should not generally use this,
* as the data at that path may not be valid.
*/
+ @NonNull
public File getBaseFile() {
return mBaseName;
}
@@ -83,6 +86,7 @@ public class AtomicFile {
* safe (or will be lost). You must do your own threading protection for
* access to AtomicFile.
*/
+ @NonNull
public FileOutputStream startWrite() throws IOException {
// Rename the current file so it may be used as a backup during the next read
if (mBaseName.exists()) {
@@ -95,7 +99,7 @@ public class AtomicFile {
mBaseName.delete();
}
}
- FileOutputStream str = null;
+ FileOutputStream str;
try {
str = new FileOutputStream(mBaseName);
} catch (FileNotFoundException e) {
@@ -118,7 +122,7 @@ public class AtomicFile {
* commit the new data. The next attempt to read the atomic file
* will return the new file stream.
*/
- public void finishWrite(FileOutputStream str) {
+ public void finishWrite(@Nullable FileOutputStream str) {
if (str != null) {
sync(str);
try {
@@ -135,7 +139,7 @@ public class AtomicFile {
* returned by {@link #startWrite()}. This will close the current
* write stream, and roll back to the previous state of the file.
*/
- public void failWrite(FileOutputStream str) {
+ public void failWrite(@Nullable FileOutputStream str) {
if (str != null) {
sync(str);
try {
@@ -160,6 +164,7 @@ public class AtomicFile {
* be dropped. You must do your own threading protection for access to
* AtomicFile.
*/
+ @NonNull
public FileInputStream openRead() throws FileNotFoundException {
if (mBackupName.exists()) {
mBaseName.delete();
@@ -172,6 +177,7 @@ public class AtomicFile {
* A convenience for {@link #openRead()} that also reads all of the
* file contents into a byte array which is returned.
*/
+ @NonNull
public byte[] readFully() throws IOException {
FileInputStream stream = openRead();
try {
@@ -200,11 +206,9 @@ public class AtomicFile {
}
}
- static boolean sync(FileOutputStream stream) {
+ private static boolean sync(@NonNull FileOutputStream stream) {
try {
- if (stream != null) {
- stream.getFD().sync();
- }
+ stream.getFD().sync();
return true;
} catch (IOException e) {
}
diff --git a/android/support/v4/util/ObjectsCompat.java b/android/support/v4/util/ObjectsCompat.java
index 46500605..b6c740e5 100644
--- a/android/support/v4/util/ObjectsCompat.java
+++ b/android/support/v4/util/ObjectsCompat.java
@@ -16,6 +16,7 @@
package android.support.v4.util;
import android.os.Build;
+import android.support.annotation.Nullable;
import java.util.Objects;
@@ -43,7 +44,7 @@ public class ObjectsCompat {
* and {@code false} otherwise
* @see Object#equals(Object)
*/
- public static boolean equals(Object a, Object b) {
+ public static boolean equals(@Nullable Object a, @Nullable Object b) {
if (Build.VERSION.SDK_INT >= 19) {
return Objects.equals(a, b);
} else {
diff --git a/android/support/v4/util/Pair.java b/android/support/v4/util/Pair.java
index 46ea5cd6..9047aec1 100644
--- a/android/support/v4/util/Pair.java
+++ b/android/support/v4/util/Pair.java
@@ -16,14 +16,17 @@
package android.support.v4.util;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
/**
* Container to ease passing around a tuple of two objects. This object provides a sensible
* implementation of equals(), returning true if equals() is true on each of the contained
* objects.
*/
public class Pair<F, S> {
- public final F first;
- public final S second;
+ public final @Nullable F first;
+ public final @Nullable S second;
/**
* Constructor for a Pair.
@@ -31,7 +34,7 @@ public class Pair<F, S> {
* @param first the first object in the Pair
* @param second the second object in the pair
*/
- public Pair(F first, S second) {
+ public Pair(@Nullable F first, @Nullable S second) {
this.first = first;
this.second = second;
}
@@ -78,7 +81,8 @@ public class Pair<F, S> {
* @param b the second object in the pair
* @return a Pair that is templatized with the types of a and b
*/
- public static <A, B> Pair <A, B> create(A a, B b) {
+ @NonNull
+ public static <A, B> Pair <A, B> create(@Nullable A a, @Nullable B b) {
return new Pair<A, B>(a, b);
}
}
diff --git a/android/support/v4/util/Pools.java b/android/support/v4/util/Pools.java
index 68826601..99fd888a 100644
--- a/android/support/v4/util/Pools.java
+++ b/android/support/v4/util/Pools.java
@@ -18,6 +18,9 @@
package android.support.v4.util;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
/**
* Helper class for creating pools of objects. An example use looks like this:
* <pre>
@@ -53,6 +56,7 @@ public final class Pools {
/**
* @return An instance from the pool if such, null otherwise.
*/
+ @Nullable
public T acquire();
/**
@@ -63,7 +67,7 @@ public final class Pools {
*
* @throws IllegalStateException If the instance is already in the pool.
*/
- public boolean release(T instance);
+ public boolean release(@NonNull T instance);
}
private Pools() {
@@ -108,7 +112,7 @@ public final class Pools {
}
@Override
- public boolean release(T instance) {
+ public boolean release(@NonNull T instance) {
if (isInPool(instance)) {
throw new IllegalStateException("Already in the pool!");
}
@@ -120,7 +124,7 @@ public final class Pools {
return false;
}
- private boolean isInPool(T instance) {
+ private boolean isInPool(@NonNull T instance) {
for (int i = 0; i < mPoolSize; i++) {
if (mPool[i] == instance) {
return true;
@@ -157,7 +161,7 @@ public final class Pools {
}
@Override
- public boolean release(T element) {
+ public boolean release(@NonNull T element) {
synchronized (mLock) {
return super.release(element);
}
diff --git a/android/support/v4/view/AbsSavedState.java b/android/support/v4/view/AbsSavedState.java
index 4cf38ac8..86f491f6 100644
--- a/android/support/v4/view/AbsSavedState.java
+++ b/android/support/v4/view/AbsSavedState.java
@@ -18,6 +18,8 @@ package android.support.v4.view;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
/**
* A {@link Parcelable} implementation that should be used by inheritance
@@ -40,7 +42,7 @@ public abstract class AbsSavedState implements Parcelable {
*
* @param superState The state of the superclass of this view
*/
- protected AbsSavedState(Parcelable superState) {
+ protected AbsSavedState(@NonNull Parcelable superState) {
if (superState == null) {
throw new IllegalArgumentException("superState must not be null");
}
@@ -52,7 +54,7 @@ public abstract class AbsSavedState implements Parcelable {
*
* @param source parcel to read from
*/
- protected AbsSavedState(Parcel source) {
+ protected AbsSavedState(@NonNull Parcel source) {
this(source, null);
}
@@ -62,11 +64,12 @@ public abstract class AbsSavedState implements Parcelable {
* @param source parcel to read from
* @param loader ClassLoader to use for reading
*/
- protected AbsSavedState(Parcel source, ClassLoader loader) {
+ protected AbsSavedState(@NonNull Parcel source, @Nullable ClassLoader loader) {
Parcelable superState = source.readParcelable(loader);
mSuperState = superState != null ? superState : EMPTY_STATE;
}
+ @Nullable
public final Parcelable getSuperState() {
return mSuperState;
}
diff --git a/android/support/v4/view/AsyncLayoutInflater.java b/android/support/v4/view/AsyncLayoutInflater.java
index e194a508..d26e5b82 100644
--- a/android/support/v4/view/AsyncLayoutInflater.java
+++ b/android/support/v4/view/AsyncLayoutInflater.java
@@ -107,7 +107,8 @@ public final class AsyncLayoutInflater {
};
public interface OnInflateFinishedListener {
- void onInflateFinished(View view, int resid, ViewGroup parent);
+ void onInflateFinished(@NonNull View view, @LayoutRes int resid,
+ @Nullable ViewGroup parent);
}
private static class InflateRequest {
diff --git a/android/support/v4/view/PagerAdapter.java b/android/support/v4/view/PagerAdapter.java
index 1b1dc3de..a8fb099c 100644
--- a/android/support/v4/view/PagerAdapter.java
+++ b/android/support/v4/view/PagerAdapter.java
@@ -19,6 +19,8 @@ package android.support.v4.view;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
@@ -92,7 +94,7 @@ public abstract class PagerAdapter {
* @param container The containing View which is displaying this adapter's
* page views.
*/
- public void startUpdate(ViewGroup container) {
+ public void startUpdate(@NonNull ViewGroup container) {
startUpdate((View) container);
}
@@ -107,7 +109,8 @@ public abstract class PagerAdapter {
* @return Returns an Object representing the new page. This does not
* need to be a View, but can be some other container of the page.
*/
- public Object instantiateItem(ViewGroup container, int position) {
+ @NonNull
+ public Object instantiateItem(@NonNull ViewGroup container, int position) {
return instantiateItem((View) container, position);
}
@@ -121,7 +124,7 @@ public abstract class PagerAdapter {
* @param object The same object that was returned by
* {@link #instantiateItem(View, int)}.
*/
- public void destroyItem(ViewGroup container, int position, Object object) {
+ public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
destroyItem((View) container, position, object);
}
@@ -134,7 +137,7 @@ public abstract class PagerAdapter {
* @param object The same object that was returned by
* {@link #instantiateItem(View, int)}.
*/
- public void setPrimaryItem(ViewGroup container, int position, Object object) {
+ public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
setPrimaryItem((View) container, position, object);
}
@@ -145,7 +148,7 @@ public abstract class PagerAdapter {
* @param container The containing View which is displaying this adapter's
* page views.
*/
- public void finishUpdate(ViewGroup container) {
+ public void finishUpdate(@NonNull ViewGroup container) {
finishUpdate((View) container);
}
@@ -157,7 +160,7 @@ public abstract class PagerAdapter {
* @deprecated Use {@link #startUpdate(ViewGroup)}
*/
@Deprecated
- public void startUpdate(View container) {
+ public void startUpdate(@NonNull View container) {
}
/**
@@ -174,7 +177,8 @@ public abstract class PagerAdapter {
* @deprecated Use {@link #instantiateItem(ViewGroup, int)}
*/
@Deprecated
- public Object instantiateItem(View container, int position) {
+ @NonNull
+ public Object instantiateItem(@NonNull View container, int position) {
throw new UnsupportedOperationException(
"Required method instantiateItem was not overridden");
}
@@ -192,7 +196,7 @@ public abstract class PagerAdapter {
* @deprecated Use {@link #destroyItem(ViewGroup, int, Object)}
*/
@Deprecated
- public void destroyItem(View container, int position, Object object) {
+ public void destroyItem(@NonNull View container, int position, @NonNull Object object) {
throw new UnsupportedOperationException("Required method destroyItem was not overridden");
}
@@ -208,7 +212,7 @@ public abstract class PagerAdapter {
* @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)}
*/
@Deprecated
- public void setPrimaryItem(View container, int position, Object object) {
+ public void setPrimaryItem(@NonNull View container, int position, @NonNull Object object) {
}
/**
@@ -221,7 +225,7 @@ public abstract class PagerAdapter {
* @deprecated Use {@link #finishUpdate(ViewGroup)}
*/
@Deprecated
- public void finishUpdate(View container) {
+ public void finishUpdate(@NonNull View container) {
}
/**
@@ -233,7 +237,7 @@ public abstract class PagerAdapter {
* @param object Object to check for association with <code>view</code>
* @return true if <code>view</code> is associated with the key object <code>object</code>
*/
- public abstract boolean isViewFromObject(View view, Object object);
+ public abstract boolean isViewFromObject(@NonNull View view, @NonNull Object object);
/**
* Save any instance state associated with this adapter and its pages that should be
@@ -241,6 +245,7 @@ public abstract class PagerAdapter {
*
* @return Saved state for this adapter
*/
+ @Nullable
public Parcelable saveState() {
return null;
}
@@ -252,7 +257,7 @@ public abstract class PagerAdapter {
* @param state State previously saved by a call to {@link #saveState()}
* @param loader A ClassLoader that should be used to instantiate any restored objects
*/
- public void restoreState(Parcelable state, ClassLoader loader) {
+ public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
}
/**
@@ -270,7 +275,7 @@ public abstract class PagerAdapter {
* {@link #POSITION_UNCHANGED} if the object's position has not changed,
* or {@link #POSITION_NONE} if the item is no longer present.
*/
- public int getItemPosition(Object object) {
+ public int getItemPosition(@NonNull Object object) {
return POSITION_UNCHANGED;
}
@@ -292,7 +297,7 @@ public abstract class PagerAdapter {
*
* @param observer The {@link android.database.DataSetObserver} which will receive callbacks.
*/
- public void registerDataSetObserver(DataSetObserver observer) {
+ public void registerDataSetObserver(@NonNull DataSetObserver observer) {
mObservable.registerObserver(observer);
}
@@ -301,7 +306,7 @@ public abstract class PagerAdapter {
*
* @param observer The {@link android.database.DataSetObserver} which will be unregistered.
*/
- public void unregisterDataSetObserver(DataSetObserver observer) {
+ public void unregisterDataSetObserver(@NonNull DataSetObserver observer) {
mObservable.unregisterObserver(observer);
}
@@ -320,6 +325,7 @@ public abstract class PagerAdapter {
* @param position The position of the title requested
* @return A title for the requested page
*/
+ @Nullable
public CharSequence getPageTitle(int position) {
return null;
}
diff --git a/android/support/v4/view/PagerTabStrip.java b/android/support/v4/view/PagerTabStrip.java
index f4c0b212..6c885722 100644
--- a/android/support/v4/view/PagerTabStrip.java
+++ b/android/support/v4/view/PagerTabStrip.java
@@ -24,6 +24,8 @@ import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
@@ -76,11 +78,11 @@ public class PagerTabStrip extends PagerTitleStrip {
private float mInitialMotionY;
private int mTouchSlop;
- public PagerTabStrip(Context context) {
+ public PagerTabStrip(@NonNull Context context) {
this(context, null);
}
- public PagerTabStrip(Context context, AttributeSet attrs) {
+ public PagerTabStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mIndicatorColor = mTextColor;
diff --git a/android/support/v4/view/PagerTitleStrip.java b/android/support/v4/view/PagerTitleStrip.java
index b63e4f42..79a6240c 100644
--- a/android/support/v4/view/PagerTitleStrip.java
+++ b/android/support/v4/view/PagerTitleStrip.java
@@ -22,6 +22,8 @@ import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.FloatRange;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.widget.TextViewCompat;
import android.text.TextUtils.TruncateAt;
import android.text.method.SingleLineTransformationMethod;
@@ -102,11 +104,11 @@ public class PagerTitleStrip extends ViewGroup {
text.setTransformationMethod(new SingleLineAllCapsTransform(text.getContext()));
}
- public PagerTitleStrip(Context context) {
+ public PagerTitleStrip(@NonNull Context context) {
this(context, null);
}
- public PagerTitleStrip(Context context, AttributeSet attrs) {
+ public PagerTitleStrip(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
addView(mPrevText = new TextView(context));
diff --git a/android/support/v4/view/ViewCompat.java b/android/support/v4/view/ViewCompat.java
index e7443d7d..34a198a1 100644
--- a/android/support/v4/view/ViewCompat.java
+++ b/android/support/v4/view/ViewCompat.java
@@ -3029,7 +3029,10 @@ public class ViewCompat {
* {@link ViewGroup#getChildDrawingOrder(int, int)}, false otherwise
*
* <p>Prior to API 7 this will have no effect.</p>
+ *
+ * @deprecated Use {@link ViewGroup#setChildrenDrawingOrderEnabled(boolean)} directly.
*/
+ @Deprecated
public static void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
IMPL.setChildrenDrawingOrderEnabled(viewGroup, enabled);
}
diff --git a/android/support/v4/view/ViewPager.java b/android/support/v4/view/ViewPager.java
index 8cd973c8..36d8696c 100644
--- a/android/support/v4/view/ViewPager.java
+++ b/android/support/v4/view/ViewPager.java
@@ -347,7 +347,7 @@ public class ViewPager extends ViewGroup {
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
- void transformPage(View page, float position);
+ void transformPage(@NonNull View page, float position);
}
/**
@@ -381,12 +381,12 @@ public class ViewPager extends ViewGroup {
public @interface DecorView {
}
- public ViewPager(Context context) {
+ public ViewPager(@NonNull Context context) {
super(context);
initViewPager();
}
- public ViewPager(Context context, AttributeSet attrs) {
+ public ViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initViewPager();
}
@@ -496,7 +496,7 @@ public class ViewPager extends ViewGroup {
*
* @param adapter Adapter to use
*/
- public void setAdapter(PagerAdapter adapter) {
+ public void setAdapter(@Nullable PagerAdapter adapter) {
if (mAdapter != null) {
mAdapter.setViewPagerObserver(null);
mAdapter.startUpdate(this);
@@ -561,6 +561,7 @@ public class ViewPager extends ViewGroup {
*
* @return The currently registered PagerAdapter
*/
+ @Nullable
public PagerAdapter getAdapter() {
return mAdapter;
}
@@ -712,7 +713,7 @@ public class ViewPager extends ViewGroup {
*
* @param listener listener to add
*/
- public void addOnPageChangeListener(OnPageChangeListener listener) {
+ public void addOnPageChangeListener(@NonNull OnPageChangeListener listener) {
if (mOnPageChangeListeners == null) {
mOnPageChangeListeners = new ArrayList<>();
}
@@ -725,7 +726,7 @@ public class ViewPager extends ViewGroup {
*
* @param listener listener to remove
*/
- public void removeOnPageChangeListener(OnPageChangeListener listener) {
+ public void removeOnPageChangeListener(@NonNull OnPageChangeListener listener) {
if (mOnPageChangeListeners != null) {
mOnPageChangeListeners.remove(listener);
}
@@ -757,7 +758,8 @@ public class ViewPager extends ViewGroup {
* to be drawn from last to first instead of first to last.
* @param transformer PageTransformer that will modify each page's animation properties
*/
- public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
+ public void setPageTransformer(boolean reverseDrawingOrder,
+ @Nullable PageTransformer transformer) {
setPageTransformer(reverseDrawingOrder, transformer, View.LAYER_TYPE_HARDWARE);
}
@@ -774,8 +776,8 @@ public class ViewPager extends ViewGroup {
* {@link View#LAYER_TYPE_SOFTWARE}, or
* {@link View#LAYER_TYPE_NONE}.
*/
- public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer,
- int pageLayerType) {
+ public void setPageTransformer(boolean reverseDrawingOrder,
+ @Nullable PageTransformer transformer, int pageLayerType) {
final boolean hasTransformer = transformer != null;
final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
mPageTransformer = transformer;
@@ -881,7 +883,7 @@ public class ViewPager extends ViewGroup {
*
* @param d Drawable to display between pages
*/
- public void setPageMarginDrawable(Drawable d) {
+ public void setPageMarginDrawable(@Nullable Drawable d) {
mMarginDrawable = d;
if (d != null) refreshDrawableState();
setWillNotDraw(d == null);
@@ -1383,7 +1385,7 @@ public class ViewPager extends ViewGroup {
Parcelable adapterState;
ClassLoader loader;
- public SavedState(Parcelable superState) {
+ public SavedState(@NonNull Parcelable superState) {
super(superState);
}
@@ -2744,7 +2746,7 @@ public class ViewPager extends ViewGroup {
* @param event The key event to execute.
* @return Return true if the event was handled, else false.
*/
- public boolean executeKeyEvent(KeyEvent event) {
+ public boolean executeKeyEvent(@NonNull KeyEvent event) {
boolean handled = false;
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
diff --git a/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index f9329faa..327be017 100644
--- a/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -1863,7 +1863,7 @@ public class AccessibilityNodeInfoCompat {
}
/**
- * Sets whether this node is visible to the user.
+ * Gets whether this node is visible to the user.
*
* @return Whether the node is visible to the user.
*/
diff --git a/android/support/v4/widget/AutoScrollHelper.java b/android/support/v4/widget/AutoScrollHelper.java
index d0407be1..60d208d8 100644
--- a/android/support/v4/widget/AutoScrollHelper.java
+++ b/android/support/v4/widget/AutoScrollHelper.java
@@ -18,6 +18,7 @@ package android.support.v4.widget;
import android.content.res.Resources;
import android.os.SystemClock;
+import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -205,7 +206,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
*
* @param target The view to automatically scroll.
*/
- public AutoScrollHelper(View target) {
+ public AutoScrollHelper(@NonNull View target) {
mTarget = target;
final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
@@ -289,6 +290,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* {@link #NO_MAX} to leave the relative value unconstrained.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setMaximumVelocity(float horizontalMax, float verticalMax) {
mMaximumVelocity[HORIZONTAL] = horizontalMax / 1000f;
mMaximumVelocity[VERTICAL] = verticalMax / 1000f;
@@ -307,6 +309,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* {@link #NO_MIN} to leave the relative value unconstrained.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setMinimumVelocity(float horizontalMin, float verticalMin) {
mMinimumVelocity[HORIZONTAL] = horizontalMin / 1000f;
mMinimumVelocity[VERTICAL] = verticalMin / 1000f;
@@ -328,6 +331,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* ignore.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setRelativeVelocity(float horizontal, float vertical) {
mRelativeVelocity[HORIZONTAL] = horizontal / 1000f;
mRelativeVelocity[VERTICAL] = vertical / 1000f;
@@ -349,6 +353,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* @param type The type of edge to use.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setEdgeType(int type) {
mEdgeType = type;
return this;
@@ -368,6 +373,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* maximum value.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setRelativeEdges(float horizontal, float vertical) {
mRelativeEdges[HORIZONTAL] = horizontal;
mRelativeEdges[VERTICAL] = vertical;
@@ -390,6 +396,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* value.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setMaximumEdges(float horizontalMax, float verticalMax) {
mMaximumEdges[HORIZONTAL] = horizontalMax;
mMaximumEdges[VERTICAL] = verticalMax;
@@ -407,6 +414,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* @param delayMillis The activation delay in milliseconds.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setActivationDelay(int delayMillis) {
mActivationDelay = delayMillis;
return this;
@@ -422,6 +430,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* @param durationMillis The ramp-up duration in milliseconds.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setRampUpDuration(int durationMillis) {
mScroller.setRampUpDuration(durationMillis);
return this;
@@ -437,6 +446,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* @param durationMillis The ramp-down duration in milliseconds.
* @return The scroll helper, which may used to chain setter calls.
*/
+ @NonNull
public AutoScrollHelper setRampDownDuration(int durationMillis) {
mScroller.setRampDownDuration(durationMillis);
return this;
diff --git a/android/support/v4/widget/CircularProgressDrawable.java b/android/support/v4/widget/CircularProgressDrawable.java
index ac295414..20556690 100644
--- a/android/support/v4/widget/CircularProgressDrawable.java
+++ b/android/support/v4/widget/CircularProgressDrawable.java
@@ -132,7 +132,7 @@ public class CircularProgressDrawable extends Drawable implements Animatable {
/**
* @param context application context
*/
- public CircularProgressDrawable(Context context) {
+ public CircularProgressDrawable(@NonNull Context context) {
mResources = Preconditions.checkNotNull(context).getResources();
mRing = new Ring();
@@ -215,7 +215,7 @@ public class CircularProgressDrawable extends Drawable implements Animatable {
*
* @param strokeCap stroke cap
*/
- public void setStrokeCap(Paint.Cap strokeCap) {
+ public void setStrokeCap(@NonNull Paint.Cap strokeCap) {
mRing.setStrokeCap(strokeCap);
invalidateSelf();
}
@@ -225,6 +225,7 @@ public class CircularProgressDrawable extends Drawable implements Animatable {
*
* @return stroke cap
*/
+ @NonNull
public Paint.Cap getStrokeCap() {
return mRing.getStrokeCap();
}
@@ -373,6 +374,7 @@ public class CircularProgressDrawable extends Drawable implements Animatable {
*
* @return list of ARGB colors
*/
+ @NonNull
public int[] getColorSchemeColors() {
return mRing.getColors();
}
@@ -383,7 +385,7 @@ public class CircularProgressDrawable extends Drawable implements Animatable {
*
* @param colors list of ARGB colors to be used in the spinner
*/
- public void setColorSchemeColors(int... colors) {
+ public void setColorSchemeColors(@NonNull int... colors) {
mRing.setColors(colors);
mRing.setColorIndex(0);
invalidateSelf();
diff --git a/android/support/v4/widget/ContentLoadingProgressBar.java b/android/support/v4/widget/ContentLoadingProgressBar.java
index 98b63a47..356c7b9e 100644
--- a/android/support/v4/widget/ContentLoadingProgressBar.java
+++ b/android/support/v4/widget/ContentLoadingProgressBar.java
@@ -17,6 +17,8 @@
package android.support.v4.widget;
import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ProgressBar;
@@ -61,11 +63,11 @@ public class ContentLoadingProgressBar extends ProgressBar {
}
};
- public ContentLoadingProgressBar(Context context) {
+ public ContentLoadingProgressBar(@NonNull Context context) {
this(context, null);
}
- public ContentLoadingProgressBar(Context context, AttributeSet attrs) {
+ public ContentLoadingProgressBar(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs, 0);
}
diff --git a/android/support/v4/widget/DrawerLayout.java b/android/support/v4/widget/DrawerLayout.java
index c7b40e96..a73e1f10 100644
--- a/android/support/v4/widget/DrawerLayout.java
+++ b/android/support/v4/widget/DrawerLayout.java
@@ -248,7 +248,7 @@ public class DrawerLayout extends ViewGroup {
* @param drawerView The child view that was moved
* @param slideOffset The new offset of this drawer within its range, from 0-1
*/
- void onDrawerSlide(View drawerView, float slideOffset);
+ void onDrawerSlide(@NonNull View drawerView, float slideOffset);
/**
* Called when a drawer has settled in a completely open state.
@@ -256,14 +256,14 @@ public class DrawerLayout extends ViewGroup {
*
* @param drawerView Drawer view that is now open
*/
- void onDrawerOpened(View drawerView);
+ void onDrawerOpened(@NonNull View drawerView);
/**
* Called when a drawer has settled in a completely closed state.
*
* @param drawerView Drawer view that is now closed
*/
- void onDrawerClosed(View drawerView);
+ void onDrawerClosed(@NonNull View drawerView);
/**
* Called when the drawer motion state changes. The new state will
@@ -296,15 +296,15 @@ public class DrawerLayout extends ViewGroup {
}
}
- public DrawerLayout(Context context) {
+ public DrawerLayout(@NonNull Context context) {
this(context, null);
}
- public DrawerLayout(Context context, AttributeSet attrs) {
+ public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
+ public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
final float density = getResources().getDisplayMetrics().density;
@@ -626,7 +626,7 @@ public class DrawerLayout extends ViewGroup {
* @see #LOCK_MODE_LOCKED_CLOSED
* @see #LOCK_MODE_LOCKED_OPEN
*/
- public void setDrawerLockMode(@LockMode int lockMode, View drawerView) {
+ public void setDrawerLockMode(@LockMode int lockMode, @NonNull View drawerView) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a "
+ "drawer with appropriate layout_gravity");
@@ -700,7 +700,7 @@ public class DrawerLayout extends ViewGroup {
* {@link #LOCK_MODE_LOCKED_OPEN}.
*/
@LockMode
- public int getDrawerLockMode(View drawerView) {
+ public int getDrawerLockMode(@NonNull View drawerView) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a drawer");
}
@@ -718,7 +718,7 @@ public class DrawerLayout extends ViewGroup {
* drawer to set the title for.
* @param title The title for the drawer.
*/
- public void setDrawerTitle(@EdgeGravity int edgeGravity, CharSequence title) {
+ public void setDrawerTitle(@EdgeGravity int edgeGravity, @Nullable CharSequence title) {
final int absGravity = GravityCompat.getAbsoluteGravity(
edgeGravity, ViewCompat.getLayoutDirection(this));
if (absGravity == Gravity.LEFT) {
@@ -1276,7 +1276,7 @@ public class DrawerLayout extends ViewGroup {
*
* @param bg Background drawable to draw behind the status bar
*/
- public void setStatusBarBackground(Drawable bg) {
+ public void setStatusBarBackground(@Nullable Drawable bg) {
mStatusBarBackground = bg;
invalidate();
}
@@ -1286,6 +1286,7 @@ public class DrawerLayout extends ViewGroup {
*
* @return The status bar background drawable, or null if none set
*/
+ @Nullable
public Drawable getStatusBarBackgroundDrawable() {
return mStatusBarBackground;
}
@@ -1577,7 +1578,7 @@ public class DrawerLayout extends ViewGroup {
*
* @param drawerView Drawer view to open
*/
- public void openDrawer(View drawerView) {
+ public void openDrawer(@NonNull View drawerView) {
openDrawer(drawerView, true);
}
@@ -1587,7 +1588,7 @@ public class DrawerLayout extends ViewGroup {
* @param drawerView Drawer view to open
* @param animate Whether opening of the drawer should be animated.
*/
- public void openDrawer(View drawerView, boolean animate) {
+ public void openDrawer(@NonNull View drawerView, boolean animate) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
@@ -1646,7 +1647,7 @@ public class DrawerLayout extends ViewGroup {
*
* @param drawerView Drawer view to close
*/
- public void closeDrawer(View drawerView) {
+ public void closeDrawer(@NonNull View drawerView) {
closeDrawer(drawerView, true);
}
@@ -1656,7 +1657,7 @@ public class DrawerLayout extends ViewGroup {
* @param drawerView Drawer view to close
* @param animate Whether closing of the drawer should be animated.
*/
- public void closeDrawer(View drawerView, boolean animate) {
+ public void closeDrawer(@NonNull View drawerView, boolean animate) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
@@ -1718,7 +1719,7 @@ public class DrawerLayout extends ViewGroup {
* @return true if the given drawer view is in an open state
* @see #isDrawerVisible(android.view.View)
*/
- public boolean isDrawerOpen(View drawer) {
+ public boolean isDrawerOpen(@NonNull View drawer) {
if (!isDrawerView(drawer)) {
throw new IllegalArgumentException("View " + drawer + " is not a drawer");
}
@@ -1751,7 +1752,7 @@ public class DrawerLayout extends ViewGroup {
* @return true if the given drawer is visible on-screen
* @see #isDrawerOpen(android.view.View)
*/
- public boolean isDrawerVisible(View drawer) {
+ public boolean isDrawerVisible(@NonNull View drawer) {
if (!isDrawerView(drawer)) {
throw new IllegalArgumentException("View " + drawer + " is not a drawer");
}
@@ -2001,7 +2002,7 @@ public class DrawerLayout extends ViewGroup {
@LockMode int lockModeStart;
@LockMode int lockModeEnd;
- public SavedState(Parcel in, ClassLoader loader) {
+ public SavedState(@NonNull Parcel in, @Nullable ClassLoader loader) {
super(in, loader);
openDrawerGravity = in.readInt();
lockModeLeft = in.readInt();
@@ -2010,7 +2011,7 @@ public class DrawerLayout extends ViewGroup {
lockModeEnd = in.readInt();
}
- public SavedState(Parcelable superState) {
+ public SavedState(@NonNull Parcelable superState) {
super(superState);
}
@@ -2218,7 +2219,7 @@ public class DrawerLayout extends ViewGroup {
boolean isPeeking;
int openState;
- public LayoutParams(Context c, AttributeSet attrs) {
+ public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
super(c, attrs);
final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
@@ -2235,16 +2236,16 @@ public class DrawerLayout extends ViewGroup {
this.gravity = gravity;
}
- public LayoutParams(LayoutParams source) {
+ public LayoutParams(@NonNull LayoutParams source) {
super(source);
this.gravity = source.gravity;
}
- public LayoutParams(ViewGroup.LayoutParams source) {
+ public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
super(source);
}
- public LayoutParams(ViewGroup.MarginLayoutParams source) {
+ public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
super(source);
}
}
diff --git a/android/support/v4/widget/EdgeEffectCompat.java b/android/support/v4/widget/EdgeEffectCompat.java
index 9293e60f..0d370a80 100644
--- a/android/support/v4/widget/EdgeEffectCompat.java
+++ b/android/support/v4/widget/EdgeEffectCompat.java
@@ -18,6 +18,7 @@ package android.support.v4.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.widget.EdgeEffect;
@@ -170,7 +171,8 @@ public final class EdgeEffectCompat {
*
* @see {@link EdgeEffect#onPull(float, float)}
*/
- public static void onPull(EdgeEffect edgeEffect, float deltaDistance, float displacement) {
+ public static void onPull(@NonNull EdgeEffect edgeEffect, float deltaDistance,
+ float displacement) {
IMPL.onPull(edgeEffect, deltaDistance, displacement);
}
diff --git a/android/support/v4/widget/ExploreByTouchHelper.java b/android/support/v4/widget/ExploreByTouchHelper.java
index 8a29eff6..2b5ed0a1 100644
--- a/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/android/support/v4/widget/ExploreByTouchHelper.java
@@ -129,7 +129,7 @@ public abstract class ExploreByTouchHelper extends AccessibilityDelegateCompat {
*
* @param host view whose virtual view hierarchy is exposed by this helper
*/
- public ExploreByTouchHelper(View host) {
+ public ExploreByTouchHelper(@NonNull View host) {
if (host == null) {
throw new IllegalArgumentException("View may not be null");
}
@@ -1107,7 +1107,8 @@ public abstract class ExploreByTouchHelper extends AccessibilityDelegateCompat {
* populate the event
* @param event The event to populate
*/
- protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ protected void onPopulateEventForVirtualView(int virtualViewId,
+ @NonNull AccessibilityEvent event) {
// Default implementation is no-op.
}
@@ -1119,7 +1120,7 @@ public abstract class ExploreByTouchHelper extends AccessibilityDelegateCompat {
*
* @param event the event to populate with information about the host view
*/
- protected void onPopulateEventForHost(AccessibilityEvent event) {
+ protected void onPopulateEventForHost(@NonNull AccessibilityEvent event) {
// Default implementation is no-op.
}
@@ -1187,7 +1188,7 @@ public abstract class ExploreByTouchHelper extends AccessibilityDelegateCompat {
* @param node The node to populate
*/
protected abstract void onPopulateNodeForVirtualView(
- int virtualViewId, AccessibilityNodeInfoCompat node);
+ int virtualViewId, @NonNull AccessibilityNodeInfoCompat node);
/**
* Populates an {@link AccessibilityNodeInfoCompat} with information
@@ -1197,7 +1198,7 @@ public abstract class ExploreByTouchHelper extends AccessibilityDelegateCompat {
*
* @param node the node to populate with information about the host view
*/
- protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
+ protected void onPopulateNodeForHost(@NonNull AccessibilityNodeInfoCompat node) {
// Default implementation is no-op.
}
@@ -1225,7 +1226,7 @@ public abstract class ExploreByTouchHelper extends AccessibilityDelegateCompat {
* @return true if the action was performed
*/
protected abstract boolean onPerformActionForVirtualView(
- int virtualViewId, int action, Bundle arguments);
+ int virtualViewId, int action, @Nullable Bundle arguments);
/**
* Exposes a virtual view hierarchy to the accessibility framework.
diff --git a/android/support/v4/widget/ImageViewCompat.java b/android/support/v4/widget/ImageViewCompat.java
index acaaf636..b517de5f 100644
--- a/android/support/v4/widget/ImageViewCompat.java
+++ b/android/support/v4/widget/ImageViewCompat.java
@@ -20,6 +20,8 @@ import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.widget.ImageView;
@@ -130,21 +132,24 @@ public class ImageViewCompat {
/**
* Return the tint applied to the image drawable, if specified.
*/
- public static ColorStateList getImageTintList(ImageView view) {
+ @Nullable
+ public static ColorStateList getImageTintList(@NonNull ImageView view) {
return IMPL.getImageTintList(view);
}
/**
* Applies a tint to the image drawable.
*/
- public static void setImageTintList(ImageView view, ColorStateList tintList) {
+ public static void setImageTintList(@NonNull ImageView view,
+ @Nullable ColorStateList tintList) {
IMPL.setImageTintList(view, tintList);
}
/**
* Return the blending mode used to apply the tint to the image drawable, if specified.
*/
- public static PorterDuff.Mode getImageTintMode(ImageView view) {
+ @Nullable
+ public static PorterDuff.Mode getImageTintMode(@NonNull ImageView view) {
return IMPL.getImageTintMode(view);
}
@@ -153,7 +158,7 @@ public class ImageViewCompat {
* {@link #setImageTintList(android.widget.ImageView, android.content.res.ColorStateList)}
* to the image drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}.
*/
- public static void setImageTintMode(ImageView view, PorterDuff.Mode mode) {
+ public static void setImageTintMode(@NonNull ImageView view, @Nullable PorterDuff.Mode mode) {
IMPL.setImageTintMode(view, mode);
}
diff --git a/android/support/v4/widget/ListPopupWindowCompat.java b/android/support/v4/widget/ListPopupWindowCompat.java
index ab86e58b..45327331 100644
--- a/android/support/v4/widget/ListPopupWindowCompat.java
+++ b/android/support/v4/widget/ListPopupWindowCompat.java
@@ -17,6 +17,8 @@
package android.support.v4.widget;
import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ListPopupWindow;
@@ -88,8 +90,9 @@ public final class ListPopupWindowCompat {
* @return a touch listener that controls drag-to-open behavior, or {@code null} on
* unsupported APIs
*/
+ @Nullable
public static OnTouchListener createDragToOpenListener(
- ListPopupWindow listPopupWindow, View src) {
+ @NonNull ListPopupWindow listPopupWindow, @NonNull View src) {
if (Build.VERSION.SDK_INT >= 19) {
return listPopupWindow.createDragToOpenListener(src);
} else {
diff --git a/android/support/v4/widget/ListViewAutoScrollHelper.java b/android/support/v4/widget/ListViewAutoScrollHelper.java
index 73d18cec..c373f27d 100644
--- a/android/support/v4/widget/ListViewAutoScrollHelper.java
+++ b/android/support/v4/widget/ListViewAutoScrollHelper.java
@@ -16,6 +16,7 @@
package android.support.v4.widget;
+import android.support.annotation.NonNull;
import android.view.View;
import android.widget.ListView;
@@ -26,7 +27,7 @@ import android.widget.ListView;
public class ListViewAutoScrollHelper extends AutoScrollHelper {
private final ListView mTarget;
- public ListViewAutoScrollHelper(ListView target) {
+ public ListViewAutoScrollHelper(@NonNull ListView target) {
super(target);
mTarget = target;
diff --git a/android/support/v4/widget/NestedScrollView.java b/android/support/v4/widget/NestedScrollView.java
index 517686f3..73ff0848 100644
--- a/android/support/v4/widget/NestedScrollView.java
+++ b/android/support/v4/widget/NestedScrollView.java
@@ -26,6 +26,8 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.InputDeviceCompat;
@@ -181,15 +183,16 @@ public class NestedScrollView extends FrameLayout implements NestedScrollingPare
private OnScrollChangeListener mOnScrollChangeListener;
- public NestedScrollView(Context context) {
+ public NestedScrollView(@NonNull Context context) {
this(context, null);
}
- public NestedScrollView(Context context, AttributeSet attrs) {
+ public NestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public NestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
super(context, attrs, defStyleAttr);
initScrollView();
@@ -441,7 +444,7 @@ public class NestedScrollView extends FrameLayout implements NestedScrollingPare
* @see android.view.View#getScrollX()
* @see android.view.View#getScrollY()
*/
- public void setOnScrollChangeListener(OnScrollChangeListener l) {
+ public void setOnScrollChangeListener(@Nullable OnScrollChangeListener l) {
mOnScrollChangeListener = l;
}
@@ -552,7 +555,7 @@ public class NestedScrollView extends FrameLayout implements NestedScrollingPare
* @param event The key event to execute.
* @return Return true if the event was handled, else false.
*/
- public boolean executeKeyEvent(KeyEvent event) {
+ public boolean executeKeyEvent(@NonNull KeyEvent event) {
mTempRect.setEmpty();
if (!canScroll()) {
diff --git a/android/support/v4/widget/PopupMenuCompat.java b/android/support/v4/widget/PopupMenuCompat.java
index 639a84ba..10c5ff3c 100644
--- a/android/support/v4/widget/PopupMenuCompat.java
+++ b/android/support/v4/widget/PopupMenuCompat.java
@@ -17,6 +17,8 @@
package android.support.v4.widget;
import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.view.View.OnTouchListener;
import android.widget.PopupMenu;
@@ -47,7 +49,8 @@ public final class PopupMenuCompat {
* @return a touch listener that controls drag-to-open behavior, or {@code null} on
* unsupported APIs
*/
- public static OnTouchListener getDragToOpenListener(Object popupMenu) {
+ @Nullable
+ public static OnTouchListener getDragToOpenListener(@NonNull Object popupMenu) {
if (Build.VERSION.SDK_INT >= 19) {
return ((PopupMenu) popupMenu).getDragToOpenListener();
} else {
diff --git a/android/support/v4/widget/PopupWindowCompat.java b/android/support/v4/widget/PopupWindowCompat.java
index d846b409..d9de3db6 100644
--- a/android/support/v4/widget/PopupWindowCompat.java
+++ b/android/support/v4/widget/PopupWindowCompat.java
@@ -17,6 +17,7 @@
package android.support.v4.widget;
import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
@@ -213,8 +214,8 @@ public final class PopupWindowCompat {
* @param yoff A vertical offset from the anchor in pixels
* @param gravity Alignment of the popup relative to the anchor
*/
- public static void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
- int gravity) {
+ public static void showAsDropDown(@NonNull PopupWindow popup, @NonNull View anchor,
+ int xoff, int yoff, int gravity) {
IMPL.showAsDropDown(popup, anchor, xoff, yoff, gravity);
}
@@ -224,7 +225,7 @@ public final class PopupWindowCompat {
*
* @param overlapAnchor Whether the popup should overlap its anchor.
*/
- public static void setOverlapAnchor(PopupWindow popupWindow, boolean overlapAnchor) {
+ public static void setOverlapAnchor(@NonNull PopupWindow popupWindow, boolean overlapAnchor) {
IMPL.setOverlapAnchor(popupWindow, overlapAnchor);
}
@@ -234,7 +235,7 @@ public final class PopupWindowCompat {
*
* @return Whether the popup should overlap its anchor.
*/
- public static boolean getOverlapAnchor(PopupWindow popupWindow) {
+ public static boolean getOverlapAnchor(@NonNull PopupWindow popupWindow) {
return IMPL.getOverlapAnchor(popupWindow);
}
@@ -247,7 +248,7 @@ public final class PopupWindowCompat {
*
* @see android.view.WindowManager.LayoutParams#type
*/
- public static void setWindowLayoutType(PopupWindow popupWindow, int layoutType) {
+ public static void setWindowLayoutType(@NonNull PopupWindow popupWindow, int layoutType) {
IMPL.setWindowLayoutType(popupWindow, layoutType);
}
@@ -256,7 +257,7 @@ public final class PopupWindowCompat {
*
* @see #setWindowLayoutType(PopupWindow popupWindow, int)
*/
- public static int getWindowLayoutType(PopupWindow popupWindow) {
+ public static int getWindowLayoutType(@NonNull PopupWindow popupWindow) {
return IMPL.getWindowLayoutType(popupWindow);
}
}
diff --git a/android/support/v4/widget/SlidingPaneLayout.java b/android/support/v4/widget/SlidingPaneLayout.java
index 602df705..5676ccf8 100644
--- a/android/support/v4/widget/SlidingPaneLayout.java
+++ b/android/support/v4/widget/SlidingPaneLayout.java
@@ -30,6 +30,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.AbsSavedState;
@@ -213,20 +215,20 @@ public class SlidingPaneLayout extends ViewGroup {
* @param panel The child view that was moved
* @param slideOffset The new offset of this sliding pane within its range, from 0-1
*/
- void onPanelSlide(View panel, float slideOffset);
+ void onPanelSlide(@NonNull View panel, float slideOffset);
/**
* Called when a sliding pane becomes slid completely open. The pane may or may not
* be interactive at this point depending on how much of the pane is visible.
* @param panel The child view that was slid to an open position, revealing other panes
*/
- void onPanelOpened(View panel);
+ void onPanelOpened(@NonNull View panel);
/**
* Called when a sliding pane becomes slid completely closed. The pane is now guaranteed
* to be interactive. It may now obscure other views in the layout.
* @param panel The child view that was slid to a closed position
*/
- void onPanelClosed(View panel);
+ void onPanelClosed(@NonNull View panel);
}
/**
@@ -245,15 +247,15 @@ public class SlidingPaneLayout extends ViewGroup {
}
}
- public SlidingPaneLayout(Context context) {
+ public SlidingPaneLayout(@NonNull Context context) {
this(context, null);
}
- public SlidingPaneLayout(Context context, AttributeSet attrs) {
+ public SlidingPaneLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public SlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
+ public SlidingPaneLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final float density = context.getResources().getDisplayMetrics().density;
@@ -324,7 +326,7 @@ public class SlidingPaneLayout extends ViewGroup {
return mCoveredFadeColor;
}
- public void setPanelSlideListener(PanelSlideListener listener) {
+ public void setPanelSlideListener(@Nullable PanelSlideListener listener) {
mPanelSlideListener = listener;
}
@@ -1081,7 +1083,7 @@ public class SlidingPaneLayout extends ViewGroup {
*
* @param d drawable to use as a shadow
*/
- public void setShadowDrawableLeft(Drawable d) {
+ public void setShadowDrawableLeft(@Nullable Drawable d) {
mShadowDrawableLeft = d;
}
@@ -1091,7 +1093,7 @@ public class SlidingPaneLayout extends ViewGroup {
*
* @param d drawable to use as a shadow
*/
- public void setShadowDrawableRight(Drawable d) {
+ public void setShadowDrawableRight(@Nullable Drawable d) {
mShadowDrawableRight = d;
}
@@ -1410,20 +1412,20 @@ public class SlidingPaneLayout extends ViewGroup {
super(width, height);
}
- public LayoutParams(android.view.ViewGroup.LayoutParams source) {
+ public LayoutParams(@NonNull android.view.ViewGroup.LayoutParams source) {
super(source);
}
- public LayoutParams(MarginLayoutParams source) {
+ public LayoutParams(@NonNull MarginLayoutParams source) {
super(source);
}
- public LayoutParams(LayoutParams source) {
+ public LayoutParams(@NonNull LayoutParams source) {
super(source);
this.weight = source.weight;
}
- public LayoutParams(Context c, AttributeSet attrs) {
+ public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
super(c, attrs);
final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
diff --git a/android/support/v4/widget/Space.java b/android/support/v4/widget/Space.java
index 77a2d2ee..7d37a72b 100644
--- a/android/support/v4/widget/Space.java
+++ b/android/support/v4/widget/Space.java
@@ -19,6 +19,8 @@ package android.support.v4.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
@@ -28,18 +30,18 @@ import android.view.View;
*/
public class Space extends View {
- public Space(Context context, AttributeSet attrs, int defStyle) {
+ public Space(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (getVisibility() == VISIBLE) {
setVisibility(INVISIBLE);
}
}
- public Space(Context context, AttributeSet attrs) {
+ public Space(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public Space(Context context) {
+ public Space(@NonNull Context context) {
this(context, null);
}
diff --git a/android/support/v4/widget/SwipeRefreshLayout.java b/android/support/v4/widget/SwipeRefreshLayout.java
index d36ae225..ca04e46a 100644
--- a/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/android/support/v4/widget/SwipeRefreshLayout.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v4.content.ContextCompat;
@@ -316,7 +317,7 @@ public class SwipeRefreshLayout extends ViewGroup implements NestedScrollingPare
*
* @param context
*/
- public SwipeRefreshLayout(Context context) {
+ public SwipeRefreshLayout(@NonNull Context context) {
this(context, null);
}
@@ -326,7 +327,7 @@ public class SwipeRefreshLayout extends ViewGroup implements NestedScrollingPare
* @param context
* @param attrs
*/
- public SwipeRefreshLayout(Context context, AttributeSet attrs) {
+ public SwipeRefreshLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
@@ -341,7 +342,7 @@ public class SwipeRefreshLayout extends ViewGroup implements NestedScrollingPare
mCircleDiameter = (int) (CIRCLE_DIAMETER * metrics.density);
createProgressView();
- ViewCompat.setChildrenDrawingOrderEnabled(this, true);
+ setChildrenDrawingOrderEnabled(true);
// the absolute offset has to take into account that the circle starts at an offset
mSpinnerOffsetEnd = (int) (DEFAULT_CIRCLE_TARGET * metrics.density);
mTotalDragDistance = mSpinnerOffsetEnd;
@@ -387,7 +388,7 @@ public class SwipeRefreshLayout extends ViewGroup implements NestedScrollingPare
* Set the listener to be notified when a refresh is triggered via the swipe
* gesture.
*/
- public void setOnRefreshListener(OnRefreshListener listener) {
+ public void setOnRefreshListener(@Nullable OnRefreshListener listener) {
mListener = listener;
}
@@ -1189,6 +1190,6 @@ public class SwipeRefreshLayout extends ViewGroup implements NestedScrollingPare
*
* @return Whether it is possible for the child view of parent layout to scroll up.
*/
- boolean canChildScrollUp(SwipeRefreshLayout parent, @Nullable View child);
+ boolean canChildScrollUp(@NonNull SwipeRefreshLayout parent, @Nullable View child);
}
}
diff --git a/android/support/v4/widget/TextViewCompat.java b/android/support/v4/widget/TextViewCompat.java
index 8d9e4ab0..dc87a38b 100644
--- a/android/support/v4/widget/TextViewCompat.java
+++ b/android/support/v4/widget/TextViewCompat.java
@@ -479,6 +479,7 @@ public final class TextViewCompat {
/**
* Returns drawables for the start, top, end, and bottom borders from the given text view.
*/
+ @NonNull
public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) {
return IMPL.getCompoundDrawablesRelative(textView);
}
@@ -493,7 +494,8 @@ public final class TextViewCompat {
*
* @attr name android:autoSizeTextType
*/
- public static void setAutoSizeTextTypeWithDefaults(TextView textView, int autoSizeTextType) {
+ public static void setAutoSizeTextTypeWithDefaults(@NonNull TextView textView,
+ int autoSizeTextType) {
IMPL.setAutoSizeTextTypeWithDefaults(textView, autoSizeTextType);
}
@@ -519,7 +521,7 @@ public final class TextViewCompat {
* @attr name android:autoSizeStepGranularity
*/
public static void setAutoSizeTextTypeUniformWithConfiguration(
- TextView textView,
+ @NonNull TextView textView,
int autoSizeMinTextSize,
int autoSizeMaxTextSize,
int autoSizeStepGranularity,
@@ -542,7 +544,7 @@ public final class TextViewCompat {
* @attr name android:autoSizeTextType
* @attr name android:autoSizePresetSizes
*/
- public static void setAutoSizeTextTypeUniformWithPresetSizes(TextView textView,
+ public static void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull TextView textView,
@NonNull int[] presetSizes, int unit) throws IllegalArgumentException {
IMPL.setAutoSizeTextTypeUniformWithPresetSizes(textView, presetSizes, unit);
}
@@ -556,7 +558,7 @@ public final class TextViewCompat {
*
* @attr name android:autoSizeTextType
*/
- public static int getAutoSizeTextType(TextView textView) {
+ public static int getAutoSizeTextType(@NonNull TextView textView) {
return IMPL.getAutoSizeTextType(textView);
}
@@ -565,7 +567,7 @@ public final class TextViewCompat {
*
* @attr name android:autoSizeStepGranularity
*/
- public static int getAutoSizeStepGranularity(TextView textView) {
+ public static int getAutoSizeStepGranularity(@NonNull TextView textView) {
return IMPL.getAutoSizeStepGranularity(textView);
}
@@ -575,7 +577,7 @@ public final class TextViewCompat {
*
* @attr name android:autoSizeMinTextSize
*/
- public static int getAutoSizeMinTextSize(TextView textView) {
+ public static int getAutoSizeMinTextSize(@NonNull TextView textView) {
return IMPL.getAutoSizeMinTextSize(textView);
}
@@ -585,7 +587,7 @@ public final class TextViewCompat {
*
* @attr name android:autoSizeMaxTextSize
*/
- public static int getAutoSizeMaxTextSize(TextView textView) {
+ public static int getAutoSizeMaxTextSize(@NonNull TextView textView) {
return IMPL.getAutoSizeMaxTextSize(textView);
}
@@ -594,7 +596,8 @@ public final class TextViewCompat {
*
* @attr name android:autoSizePresetSizes
*/
- public static int[] getAutoSizeTextAvailableSizes(TextView textView) {
+ @NonNull
+ public static int[] getAutoSizeTextAvailableSizes(@NonNull TextView textView) {
return IMPL.getAutoSizeTextAvailableSizes(textView);
}
}
diff --git a/android/support/v4/widget/ViewDragHelper.java b/android/support/v4/widget/ViewDragHelper.java
index c222c175..09c6f663 100644
--- a/android/support/v4/widget/ViewDragHelper.java
+++ b/android/support/v4/widget/ViewDragHelper.java
@@ -18,6 +18,8 @@
package android.support.v4.widget;
import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.util.Log;
import android.view.MotionEvent;
@@ -167,7 +169,9 @@ public class ViewDragHelper {
* @param dx Change in X position from the last call
* @param dy Change in Y position from the last call
*/
- 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) {
+ }
/**
* Called when a child view is captured for dragging or settling. The ID of the pointer
@@ -178,7 +182,7 @@ public class ViewDragHelper {
* @param capturedChild Child view that was captured
* @param activePointerId Pointer id tracking the child capture
*/
- public void onViewCaptured(View capturedChild, int activePointerId) {}
+ public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {}
/**
* Called when the child view is no longer being actively dragged.
@@ -198,7 +202,7 @@ public class ViewDragHelper {
* @param xvel X velocity of the pointer as it left the screen in pixels per second.
* @param yvel Y velocity of the pointer as it left the screen in pixels per second.
*/
- public void onViewReleased(View releasedChild, float xvel, float yvel) {}
+ public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {}
/**
* Called when one of the subscribed edges in the parent view has been touched
@@ -256,7 +260,7 @@ public class ViewDragHelper {
* @param child Child view to check
* @return range of horizontal motion in pixels
*/
- public int getViewHorizontalDragRange(View child) {
+ public int getViewHorizontalDragRange(@NonNull View child) {
return 0;
}
@@ -267,7 +271,7 @@ public class ViewDragHelper {
* @param child Child view to check
* @return range of vertical motion in pixels
*/
- public int getViewVerticalDragRange(View child) {
+ public int getViewVerticalDragRange(@NonNull View child) {
return 0;
}
@@ -287,7 +291,7 @@ public class ViewDragHelper {
* @param pointerId ID of the pointer attempting the capture
* @return true if capture should be allowed, false otherwise
*/
- public abstract boolean tryCaptureView(View child, int pointerId);
+ public abstract boolean tryCaptureView(@NonNull View child, int pointerId);
/**
* Restrict the motion of the dragged child view along the horizontal axis.
@@ -300,7 +304,7 @@ public class ViewDragHelper {
* @param dx Proposed change in position for left
* @return The new clamped position for left
*/
- public int clampViewPositionHorizontal(View child, int left, int dx) {
+ public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
return 0;
}
@@ -315,7 +319,7 @@ public class ViewDragHelper {
* @param dy Proposed change in position for top
* @return The new clamped position for top
*/
- public int clampViewPositionVertical(View child, int top, int dy) {
+ public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
return 0;
}
}
@@ -345,7 +349,7 @@ public class ViewDragHelper {
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
- public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
+ public static ViewDragHelper create(@NonNull ViewGroup forParent, @NonNull Callback cb) {
return new ViewDragHelper(forParent.getContext(), forParent, cb);
}
@@ -358,7 +362,8 @@ public class ViewDragHelper {
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
- public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
+ public static ViewDragHelper create(@NonNull ViewGroup forParent, float sensitivity,
+ @NonNull Callback cb) {
final ViewDragHelper helper = create(forParent, cb);
helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
return helper;
@@ -372,7 +377,8 @@ public class ViewDragHelper {
* @param context Context to initialize config-dependent params from
* @param forParent Parent view to monitor
*/
- private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
+ private ViewDragHelper(@NonNull Context context, @NonNull ViewGroup forParent,
+ @NonNull Callback cb) {
if (forParent == null) {
throw new IllegalArgumentException("Parent view may not be null");
}
@@ -458,7 +464,7 @@ public class ViewDragHelper {
* @param childView Child view to capture
* @param activePointerId ID of the pointer that is dragging the captured child view
*/
- public void captureChildView(View childView, int activePointerId) {
+ public void captureChildView(@NonNull View childView, int activePointerId) {
if (childView.getParent() != mParentView) {
throw new IllegalArgumentException("captureChildView: parameter must be a descendant "
+ "of the ViewDragHelper's tracked parent view (" + mParentView + ")");
@@ -473,6 +479,7 @@ public class ViewDragHelper {
/**
* @return The currently captured view, or null if no view has been captured.
*/
+ @Nullable
public View getCapturedView() {
return mCapturedView;
}
@@ -537,7 +544,7 @@ public class ViewDragHelper {
* @param finalTop Final top position of child
* @return true if animation should continue through {@link #continueSettling(boolean)} calls
*/
- public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
+ public boolean smoothSlideViewTo(@NonNull View child, int finalLeft, int finalTop) {
mCapturedView = child;
mActivePointerId = INVALID_POINTER;
@@ -918,7 +925,7 @@ public class ViewDragHelper {
* @param y Y coordinate of the active touch point
* @return true if child views of v can be scrolled by delta of dx.
*/
- protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) {
+ protected boolean canScroll(@NonNull View v, boolean checkV, int dx, int dy, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int scrollX = v.getScrollX();
@@ -948,7 +955,7 @@ public class ViewDragHelper {
* @param ev MotionEvent provided to onInterceptTouchEvent
* @return true if the parent view should return true from onInterceptTouchEvent
*/
- public boolean shouldInterceptTouchEvent(MotionEvent ev) {
+ public boolean shouldInterceptTouchEvent(@NonNull MotionEvent ev) {
final int action = ev.getActionMasked();
final int actionIndex = ev.getActionIndex();
@@ -1082,7 +1089,7 @@ public class ViewDragHelper {
*
* @param ev The touch event received by the parent view
*/
- public void processTouchEvent(MotionEvent ev) {
+ public void processTouchEvent(@NonNull MotionEvent ev) {
final int action = ev.getActionMasked();
final int actionIndex = ev.getActionIndex();
@@ -1453,7 +1460,7 @@ public class ViewDragHelper {
* @param y Y position to test in the parent's coordinate system
* @return true if the supplied view is under the given point, false otherwise
*/
- public boolean isViewUnder(View view, int x, int y) {
+ public boolean isViewUnder(@Nullable View view, int x, int y) {
if (view == null) {
return false;
}
@@ -1471,6 +1478,7 @@ public class ViewDragHelper {
* @param y Y position to test in the parent's coordinate system
* @return The topmost child view under (x, y) or null if none found.
*/
+ @Nullable
public View findTopChildUnder(int x, int y) {
final int childCount = mParentView.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
diff --git a/android/support/v7/app/NotificationCompat.java b/android/support/v7/app/NotificationCompat.java
deleted file mode 100644
index 6b2b8591..00000000
--- a/android/support/v7/app/NotificationCompat.java
+++ /dev/null
@@ -1,52 +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.support.v7.app;
-
-import android.content.Context;
-
-/**
- * An extension of {@link android.support.v4.app.NotificationCompat} which adds additional styles.
- * @deprecated Use {@link android.support.v4.app.NotificationCompat}.
- */
-@Deprecated
-public class NotificationCompat extends android.support.v4.app.NotificationCompat {
-
- /**
- * @deprecated Use the static classes in {@link android.support.v4.app.NotificationCompat}.
- */
- @Deprecated
- public NotificationCompat() {
- }
-
- /**
- * @deprecated All {@link android.support.v4.app.NotificationCompat.Style styles} can now be
- * used with {@link android.support.v4.app.NotificationCompat.Builder}.
- */
- @Deprecated
- public static class Builder extends android.support.v4.app.NotificationCompat.Builder {
-
- /**
- * @inheritDoc
- * @deprecated Use {@link android.support.v4.app.NotificationCompat.Builder
- * #NotificationCompat.Builder(Context, String)}
- */
- @Deprecated
- public Builder(Context context) {
- super(context);
- }
- }
-}
diff --git a/android/support/v7/graphics/Palette.java b/android/support/v7/graphics/Palette.java
index b7fb0545..e716fb50 100644
--- a/android/support/v7/graphics/Palette.java
+++ b/android/support/v7/graphics/Palette.java
@@ -80,7 +80,7 @@ public final class Palette {
/**
* Called when the {@link Palette} has been generated.
*/
- void onGenerated(Palette palette);
+ void onGenerated(@NonNull Palette palette);
}
static final int DEFAULT_RESIZE_BITMAP_AREA = 112 * 112;
@@ -95,7 +95,8 @@ public final class Palette {
/**
* Start generating a {@link Palette} with the returned {@link Builder} instance.
*/
- public static Builder from(Bitmap bitmap) {
+ @NonNull
+ public static Builder from(@NonNull Bitmap bitmap) {
return new Builder(bitmap);
}
@@ -104,7 +105,8 @@ public final class Palette {
* This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
* list of swatches. Will return null if the {@code swatches} is null.
*/
- public static Palette from(List<Swatch> swatches) {
+ @NonNull
+ public static Palette from(@NonNull List<Swatch> swatches) {
return new Builder(swatches).generate();
}
@@ -484,6 +486,7 @@ public final class Palette {
* hsv[1] is Saturation [0...1]
* hsv[2] is Lightness [0...1]
*/
+ @NonNull
public float[] getHsl() {
if (mHsl == null) {
mHsl = new float[3];
@@ -610,7 +613,7 @@ public final class Palette {
/**
* Construct a new {@link Builder} using a source {@link Bitmap}
*/
- public Builder(Bitmap bitmap) {
+ public Builder(@NonNull Bitmap bitmap) {
if (bitmap == null || bitmap.isRecycled()) {
throw new IllegalArgumentException("Bitmap is not valid");
}
@@ -631,7 +634,7 @@ public final class Palette {
* Construct a new {@link Builder} using a list of {@link Swatch} instances.
* Typically only used for testing.
*/
- public Builder(List<Swatch> swatches) {
+ public Builder(@NonNull List<Swatch> swatches) {
if (swatches == null || swatches.isEmpty()) {
throw new IllegalArgumentException("List of Swatches is not valid");
}
@@ -850,7 +853,8 @@ public final class Palette {
* generated.
*/
@NonNull
- public AsyncTask<Bitmap, Void, Palette> generate(final PaletteAsyncListener listener) {
+ public AsyncTask<Bitmap, Void, Palette> generate(
+ @NonNull final PaletteAsyncListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener can not be null");
}
@@ -943,7 +947,7 @@ public final class Palette {
*
* @see Builder#addFilter(Filter)
*/
- boolean isAllowed(int rgb, float[] hsl);
+ boolean isAllowed(@ColorInt int rgb, @NonNull float[] hsl);
}
/**
diff --git a/android/support/v7/graphics/Target.java b/android/support/v7/graphics/Target.java
index 640970b8..0eff90b4 100644
--- a/android/support/v7/graphics/Target.java
+++ b/android/support/v7/graphics/Target.java
@@ -17,6 +17,7 @@
package android.support.v7.graphics;
import android.support.annotation.FloatRange;
+import android.support.annotation.NonNull;
/**
* A class which allows custom selection of colors in a {@link Palette}'s generation. Instances
@@ -122,7 +123,7 @@ public final class Target {
setDefaultWeights();
}
- Target(Target from) {
+ Target(@NonNull Target from) {
System.arraycopy(from.mSaturationTargets, 0, mSaturationTargets, 0,
mSaturationTargets.length);
System.arraycopy(from.mLightnessTargets, 0, mLightnessTargets, 0,
@@ -295,13 +296,14 @@ public final class Target {
/**
* Create a new builder based on an existing {@link Target}.
*/
- public Builder(Target target) {
+ public Builder(@NonNull Target target) {
mTarget = new Target(target);
}
/**
* Set the minimum saturation value for this target.
*/
+ @NonNull
public Builder setMinimumSaturation(@FloatRange(from = 0, to = 1) float value) {
mTarget.mSaturationTargets[INDEX_MIN] = value;
return this;
@@ -310,6 +312,7 @@ public final class Target {
/**
* Set the target/ideal saturation value for this target.
*/
+ @NonNull
public Builder setTargetSaturation(@FloatRange(from = 0, to = 1) float value) {
mTarget.mSaturationTargets[INDEX_TARGET] = value;
return this;
@@ -318,6 +321,7 @@ public final class Target {
/**
* Set the maximum saturation value for this target.
*/
+ @NonNull
public Builder setMaximumSaturation(@FloatRange(from = 0, to = 1) float value) {
mTarget.mSaturationTargets[INDEX_MAX] = value;
return this;
@@ -326,6 +330,7 @@ public final class Target {
/**
* Set the minimum lightness value for this target.
*/
+ @NonNull
public Builder setMinimumLightness(@FloatRange(from = 0, to = 1) float value) {
mTarget.mLightnessTargets[INDEX_MIN] = value;
return this;
@@ -334,6 +339,7 @@ public final class Target {
/**
* Set the target/ideal lightness value for this target.
*/
+ @NonNull
public Builder setTargetLightness(@FloatRange(from = 0, to = 1) float value) {
mTarget.mLightnessTargets[INDEX_TARGET] = value;
return this;
@@ -342,6 +348,7 @@ public final class Target {
/**
* Set the maximum lightness value for this target.
*/
+ @NonNull
public Builder setMaximumLightness(@FloatRange(from = 0, to = 1) float value) {
mTarget.mLightnessTargets[INDEX_MAX] = value;
return this;
@@ -358,6 +365,7 @@ public final class Target {
*
* @see #setTargetSaturation(float)
*/
+ @NonNull
public Builder setSaturationWeight(@FloatRange(from = 0) float weight) {
mTarget.mWeights[INDEX_WEIGHT_SAT] = weight;
return this;
@@ -374,6 +382,7 @@ public final class Target {
*
* @see #setTargetLightness(float)
*/
+ @NonNull
public Builder setLightnessWeight(@FloatRange(from = 0) float weight) {
mTarget.mWeights[INDEX_WEIGHT_LUMA] = weight;
return this;
@@ -389,6 +398,7 @@ public final class Target {
* <p>A weight of 0 means that it has no weight, and thus has no
* bearing on the selection.</p>
*/
+ @NonNull
public Builder setPopulationWeight(@FloatRange(from = 0) float weight) {
mTarget.mWeights[INDEX_WEIGHT_POP] = weight;
return this;
@@ -401,6 +411,7 @@ public final class Target {
* @param exclusive true if any the color is exclusive to this target, or false is the
* color can be selected for other targets.
*/
+ @NonNull
public Builder setExclusive(boolean exclusive) {
mTarget.mIsExclusive = exclusive;
return this;
@@ -409,6 +420,7 @@ public final class Target {
/**
* Builds and returns the resulting {@link Target}.
*/
+ @NonNull
public Target build() {
return mTarget;
}
diff --git a/android/support/v7/preference/Preference.java b/android/support/v7/preference/Preference.java
index cfc4311f..fa8461db 100644
--- a/android/support/v7/preference/Preference.java
+++ b/android/support/v7/preference/Preference.java
@@ -31,7 +31,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.v4.content.ContextCompat;
-import android.support.v4.content.SharedPreferencesCompat;
import android.support.v4.content.res.TypedArrayUtils;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.text.TextUtils;
@@ -1543,7 +1542,7 @@ public class Preference implements Comparable<Preference> {
private void tryCommit(@NonNull SharedPreferences.Editor editor) {
if (mPreferenceManager.shouldCommit()) {
- SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
+ editor.apply();
}
}
diff --git a/android/support/v7/preference/PreferenceFragmentCompat.java b/android/support/v7/preference/PreferenceFragmentCompat.java
index 4fb9ff8d..6094217e 100644
--- a/android/support/v7/preference/PreferenceFragmentCompat.java
+++ b/android/support/v7/preference/PreferenceFragmentCompat.java
@@ -92,13 +92,13 @@ import android.view.ViewGroup;
* <p>The following sample code shows a simple preference fragment that is
* populated from a resource. The resource it loads is:</p>
*
- * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
*
* <p>The fragment implementation itself simply populates the preferences
* when created. Note that the preferences framework takes care of loading
* the current values out of the app preferences and writing them when changed:</p>
*
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferencesCompat.java
+ * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesCompat.java
* support_fragment_compat}
*
* @see Preference
@@ -321,7 +321,7 @@ public abstract class PreferenceFragmentCompat extends Fragment implements
}
@Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (mHavePrefs) {
diff --git a/android/support/v7/preference/PreferenceManager.java b/android/support/v7/preference/PreferenceManager.java
index 83af86ca..19b6908a 100644
--- a/android/support/v7/preference/PreferenceManager.java
+++ b/android/support/v7/preference/PreferenceManager.java
@@ -25,7 +25,6 @@ import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.v4.content.ContextCompat;
-import android.support.v4.content.SharedPreferencesCompat;
import android.text.TextUtils;
/**
@@ -464,10 +463,9 @@ public class PreferenceManager {
pm.setSharedPreferencesMode(sharedPreferencesMode);
pm.inflateFromResource(context, resId, null);
- SharedPreferences.Editor editor =
- defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
-
- SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
+ defaultValueSp.edit()
+ .putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true)
+ .apply();
}
}
@@ -511,7 +509,7 @@ public class PreferenceManager {
private void setNoCommit(boolean noCommit) {
if (!noCommit && mEditor != null) {
- SharedPreferencesCompat.EditorCompat.getInstance().apply(mEditor);
+ mEditor.apply();
}
mNoCommit = noCommit;
}
diff --git a/android/support/v7/recyclerview/extensions/ListAdapter.java b/android/support/v7/recyclerview/extensions/ListAdapter.java
index e08cb53c..8b28072e 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapter.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapter.java
@@ -66,27 +66,21 @@ import java.util.List;
* public void onBindViewHolder(UserViewHolder holder, int position) {
* holder.bindTo(getItem(position));
* }
- * }
- *
- * {@literal @}Entity
- * class User {
- * // ... simple POJO code omitted ...
- *
- * public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
- * {@literal @}Override
- * public boolean areItemsTheSame(
- * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- * // User properties may have changed if reloaded from the DB, but ID is fixed
- * return oldUser.getId() == newUser.getId();
- * }
- * {@literal @}Override
- * public boolean areContentsTheSame(
- * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- * // NOTE: if you use equals, your object must properly override Object#equals()
- * // Incorrectly returning false here will result in too many animations.
- * return oldUser.equals(newUser);
- * }
- * }
+ * public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
+ * {@literal @}Override
+ * public boolean areItemsTheSame(
+ * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ * // User properties may have changed if reloaded from the DB, but ID is fixed
+ * return oldUser.getId() == newUser.getId();
+ * }
+ * {@literal @}Override
+ * public boolean areContentsTheSame(
+ * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ * // NOTE: if you use equals, your object must properly override Object#equals()
+ * // Incorrectly returning false here will result in too many animations.
+ * return oldUser.equals(newUser);
+ * }
+ * }
* }</pre>
*
* Advanced users that wish for more control over adapter behavior, or to provide a specific base
diff --git a/android/support/v7/recyclerview/extensions/ListAdapterConfig.java b/android/support/v7/recyclerview/extensions/ListAdapterConfig.java
index f861242b..25697a11 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapterConfig.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapterConfig.java
@@ -16,7 +16,7 @@
package android.support.v7.recyclerview.extensions;
-import android.arch.core.executor.AppToolkitTaskExecutor;
+import android.arch.core.executor.ArchTaskExecutor;
import java.util.concurrent.Executor;
@@ -118,10 +118,10 @@ public final class ListAdapterConfig<T> {
throw new IllegalArgumentException("Must provide a diffCallback");
}
if (mBackgroundThreadExecutor == null) {
- mBackgroundThreadExecutor = AppToolkitTaskExecutor.getIOThreadExecutor();
+ mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
}
if (mMainThreadExecutor == null) {
- mMainThreadExecutor = AppToolkitTaskExecutor.getMainThreadExecutor();
+ mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor();
}
return new ListAdapterConfig<>(
mMainThreadExecutor,
diff --git a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
index b47b833a..d0c7bb3e 100644
--- a/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
+++ b/android/support/v7/recyclerview/extensions/ListAdapterHelper.java
@@ -84,27 +84,21 @@ import java.util.List;
* User user = mHelper.getItem(position);
* holder.bindTo(user);
* }
- * }
- *
- * {@literal @}Entity
- * class User {
- * // ... simple POJO code omitted ...
- *
- * public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;Customer>() {
- * {@literal @}Override
- * public boolean areItemsTheSame(
- * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- * // User properties may have changed if reloaded from the DB, but ID is fixed
- * return oldUser.getId() == newUser.getId();
- * }
- * {@literal @}Override
- * public boolean areContentsTheSame(
- * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
- * // NOTE: if you use equals, your object must properly override Object#equals()
- * // Incorrectly returning false here will result in too many animations.
- * return oldUser.equals(newUser);
- * }
- * }
+ * public static final DiffCallback&lt;User> DIFF_CALLBACK = new DiffCallback&lt;User>() {
+ * {@literal @}Override
+ * public boolean areItemsTheSame(
+ * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ * // User properties may have changed if reloaded from the DB, but ID is fixed
+ * return oldUser.getId() == newUser.getId();
+ * }
+ * {@literal @}Override
+ * public boolean areContentsTheSame(
+ * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
+ * // NOTE: if you use equals, your object must properly override Object#equals()
+ * // Incorrectly returning false here will result in too many animations.
+ * return oldUser.equals(newUser);
+ * }
+ * }
* }</pre>
*
* @param <T> Type of the lists this helper will receive.
diff --git a/android/support/v7/widget/AppCompatTextHelper.java b/android/support/v7/widget/AppCompatTextHelper.java
index 75fa38ff..51510aa2 100644
--- a/android/support/v7/widget/AppCompatTextHelper.java
+++ b/android/support/v7/widget/AppCompatTextHelper.java
@@ -214,7 +214,7 @@ class AppCompatTextHelper {
: R.styleable.TextAppearance_fontFamily;
if (!context.isRestricted()) {
try {
- mFontTypeface = a.getFont(fontFamilyId, mStyle, mView);
+ mFontTypeface = a.getFont(fontFamilyId, mStyle);
} catch (UnsupportedOperationException | Resources.NotFoundException e) {
// Expected if it is not a font resource.
}
diff --git a/android/support/v7/widget/CardView.java b/android/support/v7/widget/CardView.java
index 3df45d9f..58a04f0a 100644
--- a/android/support/v7/widget/CardView.java
+++ b/android/support/v7/widget/CardView.java
@@ -24,6 +24,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.cardview.R;
import android.util.AttributeSet;
@@ -106,17 +107,17 @@ public class CardView extends FrameLayout {
final Rect mShadowBounds = new Rect();
- public CardView(Context context) {
+ public CardView(@NonNull Context context) {
super(context);
initialize(context, null, 0);
}
- public CardView(Context context, AttributeSet attrs) {
+ public CardView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs, 0);
}
- public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public CardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs, defStyleAttr);
}
@@ -300,6 +301,7 @@ public class CardView extends FrameLayout {
*
* @return The background color state list of the CardView.
*/
+ @NonNull
public ColorStateList getCardBackgroundColor() {
return IMPL.getBackgroundColor(mCardViewDelegate);
}
diff --git a/android/support/v7/widget/TintTypedArray.java b/android/support/v7/widget/TintTypedArray.java
index 2213dd30..22709551 100644
--- a/android/support/v7/widget/TintTypedArray.java
+++ b/android/support/v7/widget/TintTypedArray.java
@@ -25,7 +25,6 @@ import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.annotation.RestrictTo;
@@ -34,7 +33,6 @@ import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
import android.util.TypedValue;
-import android.widget.TextView;
/**
* A class that wraps a {@link android.content.res.TypedArray} and provides the same public API
@@ -98,9 +96,9 @@ public class TintTypedArray {
*
* @param index Index of attribute to retrieve.
* @param style A style value used for selecting best match font from the list of family. Note
- * that this value will be ignored if the platform supports font family(API 24 or later).
- * @param targetView A text view to be applied this font. If async loading is specified in XML,
- * this view will be refreshed with result typeface.
+ * that this value will be ignored if the platform supports font family (API 24 or later).
+ * @param fontCallback A callback to receive async fetching of this font. If async loading is
+ * specified in XML, this callback will be triggered.
*
* @return Typeface for the attribute, or {@code null} if not defined.
* @throws RuntimeException if the TypedArray has already been recycled.
@@ -108,7 +106,7 @@ public class TintTypedArray {
* not a font resource.
*/
@Nullable
- public Typeface getFont(@StyleableRes int index, int style, @NonNull TextView targetView) {
+ public Typeface getFont(@StyleableRes int index, int style) {
final int resourceId = mWrapped.getResourceId(index, 0);
if (resourceId == 0) {
return null;
@@ -116,7 +114,7 @@ public class TintTypedArray {
if (mTypedValue == null) {
mTypedValue = new TypedValue();
}
- return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style, targetView);
+ return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style);
}
public int length() {
diff --git a/android/support/v7/widget/helper/ItemTouchHelper.java b/android/support/v7/widget/helper/ItemTouchHelper.java
index b0a2cb36..aee48dfa 100644
--- a/android/support/v7/widget/helper/ItemTouchHelper.java
+++ b/android/support/v7/widget/helper/ItemTouchHelper.java
@@ -292,6 +292,11 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
*/
GestureDetectorCompat mGestureDetector;
+ /**
+ * Callback for when long press occurs.
+ */
+ private ItemTouchHelperGestureListener mItemTouchHelperGestureListener;
+
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
@@ -468,7 +473,7 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
mRecyclerView.addItemDecoration(this);
mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
mRecyclerView.addOnChildAttachStateChangeListener(this);
- initGestureDetector();
+ startGestureDetection();
}
private void destroyCallbacks() {
@@ -485,14 +490,23 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
mOverdrawChild = null;
mOverdrawChildPosition = -1;
releaseVelocityTracker();
+ stopGestureDetection();
+ }
+
+ private void startGestureDetection() {
+ mItemTouchHelperGestureListener = new ItemTouchHelperGestureListener();
+ mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),
+ mItemTouchHelperGestureListener);
}
- private void initGestureDetector() {
+ private void stopGestureDetection() {
+ if (mItemTouchHelperGestureListener != null) {
+ mItemTouchHelperGestureListener.doNotReactToLongPress();
+ mItemTouchHelperGestureListener = null;
+ }
if (mGestureDetector != null) {
- return;
+ mGestureDetector = null;
}
- mGestureDetector = new GestureDetectorCompat(mRecyclerView.getContext(),
- new ItemTouchHelperGestureListener());
}
private void getSelectedDxDy(float[] outPosition) {
@@ -2242,9 +2256,33 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
+ /**
+ * Whether to execute code in response to the the invoking of
+ * {@link ItemTouchHelperGestureListener#onLongPress(MotionEvent)}.
+ *
+ * It is necessary to control this here because
+ * {@link GestureDetector.SimpleOnGestureListener} can only be set on a
+ * {@link GestureDetector} in a GestureDetector's constructor, a GestureDetector will call
+ * onLongPress if an {@link MotionEvent#ACTION_DOWN} event is not followed by another event
+ * that would cancel it (like {@link MotionEvent#ACTION_UP} or
+ * {@link MotionEvent#ACTION_CANCEL}), the long press responding to the long press event
+ * needs to be cancellable to prevent unexpected behavior.
+ *
+ * @see #doNotReactToLongPress()
+ */
+ private boolean mShouldReactToLongPress = true;
+
ItemTouchHelperGestureListener() {
}
+ /**
+ * Call to prevent executing code in response to
+ * {@link ItemTouchHelperGestureListener#onLongPress(MotionEvent)} being called.
+ */
+ void doNotReactToLongPress() {
+ mShouldReactToLongPress = false;
+ }
+
@Override
public boolean onDown(MotionEvent e) {
return true;
@@ -2252,6 +2290,9 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration
@Override
public void onLongPress(MotionEvent e) {
+ if (!mShouldReactToLongPress) {
+ return;
+ }
View child = findChildView(e);
if (child != null) {
ViewHolder vh = mRecyclerView.getChildViewHolder(child);
diff --git a/android/support/wear/ambient/AmbientDelegate.java b/android/support/wear/ambient/AmbientDelegate.java
new file mode 100644
index 00000000..49012908
--- /dev/null
+++ b/android/support/wear/ambient/AmbientDelegate.java
@@ -0,0 +1,207 @@
+/*
+ * 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.support.wear.ambient;
+
+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.
+ */
+final class AmbientDelegate {
+
+ private static final String TAG = "AmbientDelegate";
+
+ private WearableActivityController mWearableController;
+
+ private static boolean sInitAutoResumeEnabledMethod;
+ private static boolean sHasAutoResumeEnabledMethod;
+ private final WearableControllerProvider mWearableControllerProvider;
+ private final AmbientCallback mCallback;
+ private final WeakReference<Activity> mActivity;
+
+ /**
+ * AmbientCallback must be implemented by all users of the delegate.
+ */
+ interface AmbientCallback {
+ /**
+ * Called when an activity is entering ambient mode. This event is sent while an activity is
+ * running (after onResume, before onPause). All drawing should complete by the conclusion
+ * of this method. Note that {@code invalidate()} calls will be executed before resuming
+ * lower-power mode.
+ * <p>
+ * <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>
+ *
+ * @param ambientDetails bundle containing information about the display being used.
+ * It includes information about low-bit color and burn-in protection.
+ */
+ void onEnterAmbient(Bundle ambientDetails);
+
+ /**
+ * Called when the system is updating the display for ambient mode. Activities may use this
+ * opportunity to update or invalidate views.
+ */
+ void onUpdateAmbient();
+
+ /**
+ * Called when an activity should exit ambient mode. This event is sent while an activity is
+ * running (after onResume, before onPause).
+ * <p>
+ * <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>
+ */
+ void onExitAmbient();
+ }
+
+ AmbientDelegate(@Nullable Activity activity,
+ @NonNull WearableControllerProvider wearableControllerProvider,
+ @NonNull AmbientCallback callback) {
+ mActivity = new WeakReference<>(activity);
+ mCallback = callback;
+ mWearableControllerProvider = wearableControllerProvider;
+ }
+
+ /**
+ * Receives and handles the onCreate call from the associated {@link AmbientMode}
+ */
+ void onCreate() {
+ Activity activity = mActivity.get();
+ if (activity != null) {
+ mWearableController =
+ mWearableControllerProvider.getWearableController(activity, mCallback);
+ }
+ if (mWearableController != null) {
+ mWearableController.onCreate();
+ }
+ }
+
+ /**
+ * Receives and handles the onResume call from the associated {@link AmbientMode}
+ */
+ void onResume() {
+ if (mWearableController != null) {
+ mWearableController.onResume();
+ }
+ }
+
+ /**
+ * Receives and handles the onPause call from the associated {@link AmbientMode}
+ */
+ void onPause() {
+ if (mWearableController != null) {
+ mWearableController.onPause();
+ }
+ }
+
+ /**
+ * Receives and handles the onStop call from the associated {@link AmbientMode}
+ */
+ void onStop() {
+ if (mWearableController != null) {
+ mWearableController.onStop();
+ }
+ }
+
+ /**
+ * Receives and handles the onDestroy call from the associated {@link AmbientMode}
+ */
+ void onDestroy() {
+ if (mWearableController != null) {
+ mWearableController.onDestroy();
+ }
+ }
+
+ /**
+ * Sets that this activity should remain displayed when the system enters ambient mode. The
+ * default is false. In this case, the activity is stopped when the system enters ambient mode.
+ */
+ void setAmbientEnabled() {
+ if (mWearableController != null) {
+ mWearableController.setAmbientEnabled();
+ }
+ }
+
+ /**
+ * 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() {
+ if (mWearableController != null) {
+ return mWearableController.isAmbient();
+ }
+ return false;
+ }
+
+ /**
+ * Dump the current state of the wearableController responsible for implementing the Ambient
+ * mode.
+ */
+ void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (mWearableController != null) {
+ 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
new file mode 100644
index 00000000..7fbbbb3f
--- /dev/null
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -0,0 +1,286 @@
+/*
+ * 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.support.wear.ambient;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import com.google.android.wearable.compat.WearableActivityController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Use this as a headless Fragment to add ambient support to an Activity on Wearable devices.
+ * <p>
+ * The application that uses this should add the {@link android.Manifest.permission#WAKE_LOCK}
+ * permission to its manifest.
+ * <p>
+ * The primary entry point for this code is the {@link #attachAmbientSupport(Activity)} method.
+ * 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.
+ * 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);
+ * }</pre>
+ */
+public final class AmbientMode extends Fragment {
+
+ /**
+ * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
+ * whether burn-in protection is required. When this property is set to true, views must be
+ * shifted around periodically in ambient mode. To ensure that content isn't shifted off
+ * the screen, avoid placing content within 10 pixels of the edge of the screen. Activities
+ * should also avoid solid white areas to prevent pixel burn-in. Both of these requirements
+ * only apply in ambient mode, and only when this property is set to true.
+ */
+ public static final String EXTRA_BURN_IN_PROTECTION =
+ WearableActivityController.EXTRA_BURN_IN_PROTECTION;
+
+ /**
+ * Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
+ * whether the device has low-bit ambient mode. When this property is set to true, the screen
+ * supports fewer bits for each color in ambient mode. In this case, activities should disable
+ * anti-aliasing in ambient mode.
+ */
+ public static final String EXTRA_LOWBIT_AMBIENT =
+ WearableActivityController.EXTRA_LOWBIT_AMBIENT;
+
+ /**
+ * Fragment tag used by default when adding {@link AmbientMode} to add ambient support to an
+ * {@link Activity}.
+ */
+ public static final String FRAGMENT_TAG = "android.support.wearable.ambient.AmbientMode";
+
+ /**
+ * Interface for any {@link Activity} that wishes to implement Ambient Mode. Use the
+ * {@link #getAmbientCallback()} method to return and {@link AmbientCallback} which can be used
+ * to bind the {@link AmbientMode} to the instantiation of this interface.
+ * <p>
+ * <pre class="prettyprint">{@code
+ * return new AmbientMode.AmbientCallback() {
+ * public void onEnterAmbient(Bundle ambientDetails) {...}
+ * public void onExitAmbient(Bundle ambientDetails) {...}
+ * }
+ * }</pre>
+ */
+ public interface AmbientCallbackProvider {
+ /**
+ * @return the {@link AmbientCallback} to be used by this class to communicate with the
+ * entity interested in ambient events.
+ */
+ AmbientCallback getAmbientCallback();
+ }
+
+ /**
+ * Callback to receive ambient mode state changes. It must be used by all users of AmbientMode.
+ */
+ public abstract static class AmbientCallback {
+ /**
+ * Called when an activity is entering ambient mode. This event is sent while an activity is
+ * running (after onResume, before onPause). All drawing should complete by the conclusion
+ * of this method. Note that {@code invalidate()} calls will be executed before resuming
+ * lower-power mode.
+ * <p>
+ * <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>
+ *
+ * @param ambientDetails bundle containing information about the display being used.
+ * It includes information about low-bit color and burn-in protection.
+ */
+ public void onEnterAmbient(Bundle ambientDetails) {}
+
+ /**
+ * Called when the system is updating the display for ambient mode. Activities may use this
+ * opportunity to update or invalidate views.
+ */
+ public void onUpdateAmbient() {};
+
+ /**
+ * Called when an activity should exit ambient mode. This event is sent while an activity is
+ * running (after onResume, before onPause).
+ * <p>
+ * <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() {};
+ }
+
+ private final AmbientDelegate.AmbientCallback mCallback =
+ new AmbientDelegate.AmbientCallback() {
+ @Override
+ public void onEnterAmbient(Bundle ambientDetails) {
+ mSuppliedCallback.onEnterAmbient(ambientDetails);
+ }
+
+ @Override
+ public void onExitAmbient() {
+ mSuppliedCallback.onExitAmbient();
+ }
+
+ @Override
+ public void onUpdateAmbient() {
+ mSuppliedCallback.onUpdateAmbient();
+ }
+ };
+ private AmbientDelegate mDelegate;
+ private AmbientCallback mSuppliedCallback;
+ private AmbientController mController;
+
+ /**
+ * Constructor
+ */
+ public AmbientMode() {
+ mController = new AmbientController();
+ }
+
+ @Override
+ @CallSuper
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mDelegate = new AmbientDelegate(getActivity(), new WearableControllerProvider(), mCallback);
+
+ if (context instanceof AmbientCallbackProvider) {
+ mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback();
+ } else {
+ throw new IllegalArgumentException(
+ "fragment should attach to an activity that implements AmbientCallback");
+ }
+ }
+
+ @Override
+ @CallSuper
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mDelegate.onCreate();
+ mDelegate.setAmbientEnabled();
+ }
+
+ @Override
+ @CallSuper
+ public void onResume() {
+ super.onResume();
+ mDelegate.onResume();
+ }
+
+ @Override
+ @CallSuper
+ public void onPause() {
+ mDelegate.onPause();
+ super.onPause();
+ }
+
+ @Override
+ @CallSuper
+ public void onStop() {
+ mDelegate.onStop();
+ super.onStop();
+ }
+
+ @Override
+ @CallSuper
+ public void onDestroy() {
+ mDelegate.onDestroy();
+ super.onDestroy();
+ }
+
+ @Override
+ @CallSuper
+ public void onDetach() {
+ mDelegate = null;
+ super.onDetach();
+ }
+
+ /**
+ * Attach ambient support to the given activity.
+ *
+ * @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.
+ */
+ public static <T extends Activity & AmbientCallbackProvider> AmbientController
+ attachAmbientSupport(T activity) {
+ FragmentManager fragmentManager = activity.getFragmentManager();
+ AmbientMode ambientFragment = (AmbientMode) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
+ if (ambientFragment == null) {
+ AmbientMode fragment = new AmbientMode();
+ fragmentManager
+ .beginTransaction()
+ .add(fragment, FRAGMENT_TAG)
+ .commit();
+ ambientFragment = fragment;
+ }
+ return ambientFragment.mController;
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (mDelegate != null) {
+ mDelegate.dump(prefix, fd, writer, args);
+ }
+ }
+
+ @VisibleForTesting
+ void setAmbientDelegate(AmbientDelegate delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * 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}.
+ */
+ public final class AmbientController {
+ private static final String TAG = "AmbientController";
+
+ // Do not initialize outside of this class.
+ AmbientController() {}
+
+ /**
+ * 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.
+ */
+ public void setAutoResumeEnabled(boolean enabled) {
+ if (mDelegate != null) {
+ mDelegate.setAutoResumeEnabled(enabled);
+ } else {
+ Log.w(TAG, "The fragment is not yet fully initialized, this call is a no-op");
+ }
+ }
+
+ /**
+ * @return {@code true} if the activity is currently in ambient.
+ */
+ public boolean isAmbient() {
+ return mDelegate == null ? false : mDelegate.isAmbient();
+ }
+ }
+}
diff --git a/android/support/wear/ambient/SharedLibraryVersion.java b/android/support/wear/ambient/SharedLibraryVersion.java
new file mode 100644
index 00000000..cd90a3b7
--- /dev/null
+++ b/android/support/wear/ambient/SharedLibraryVersion.java
@@ -0,0 +1,97 @@
+/*
+ * 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.support.wear.ambient;
+
+import android.os.Build;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+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() {
+ }
+
+ /**
+ * Returns the version of the wearable shared library available on the current device.
+ * <p>
+ * <p>Version 1 was introduced on 2016-09-26, so any previous shared library will return 0. In
+ * those cases, it may be necessary to check {@code Build.VERSION.SDK_INT}.
+ *
+ * @throws IllegalStateException if the Wearable Shared Library is not present, which means that
+ * the {@code <uses-library>} tag is missing.
+ */
+ public static int version() {
+ verifySharedLibraryPresent();
+ return VersionHolder.VERSION;
+ }
+
+ /**
+ * Throws {@link IllegalStateException} if the Wearable Shared Library is not present and API
+ * level is at least LMP MR1.
+ * <p>
+ * <p>This validates that the developer hasn't forgotten to include a {@code <uses-library>} tag
+ * in their manifest. The method should be used in combination with API level checks for
+ * features added before {@link #version() version} 1.
+ */
+ public static void verifySharedLibraryPresent() {
+ if (!PresenceHolder.PRESENT) {
+ throw new IllegalStateException("Could not find wearable shared library classes. "
+ + "Please add <uses-library android:name=\"com.google.android.wearable\" "
+ + "android:required=\"false\" /> to the application manifest");
+ }
+ }
+
+ // Lazy initialization holder class (see Effective Java item 71)
+ @VisibleForTesting
+ static final class VersionHolder {
+ static final int VERSION = getSharedLibVersion(Build.VERSION.SDK_INT);
+
+ @VisibleForTesting
+ static int getSharedLibVersion(int sdkInt) {
+ if (sdkInt < Build.VERSION_CODES.N_MR1) {
+ // WearableSharedLib was introduced in N MR1 (Wear FDP 4)
+ return 0;
+ }
+ return WearableSharedLib.version();
+ }
+ }
+
+ // Lazy initialization holder class (see Effective Java item 71)
+ @VisibleForTesting
+ static final class PresenceHolder {
+ static final boolean PRESENT = isSharedLibPresent(Build.VERSION.SDK_INT);
+
+ @VisibleForTesting
+ static boolean isSharedLibPresent(int sdkInt) {
+ try {
+ // A class which has been available on the shared library from the first version.
+ Class.forName("com.google.android.wearable.compat.WearableActivityController");
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/android/support/wear/ambient/WearableControllerProvider.java b/android/support/wear/ambient/WearableControllerProvider.java
new file mode 100644
index 00000000..1682dc0e
--- /dev/null
+++ b/android/support/wear/ambient/WearableControllerProvider.java
@@ -0,0 +1,96 @@
+/*
+ * 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.support.wear.ambient;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.RestrictTo;
+
+import com.google.android.wearable.compat.WearableActivityController;
+
+import java.lang.reflect.Method;
+
+/**
+ * Provides a {@link WearableActivityController} for ambient mode control.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class WearableControllerProvider {
+
+ private static final String TAG = "WearableControllerProvider";
+
+ private static volatile boolean sAmbientCallbacksVerifiedPresent;
+
+ /**
+ * Retrieves a {@link WearableActivityController} to use for ambient mode.
+ *
+ * @param activity The {@link Activity} to be associated with the Controller.
+ * @param callback The {@link AmbientDelegate.AmbientCallback} for the Controller.
+ * @return the platform-appropriate version of the {@link WearableActivityController}.
+ */
+ public WearableActivityController getWearableController(Activity activity,
+ final AmbientDelegate.AmbientCallback callback) {
+ SharedLibraryVersion.verifySharedLibraryPresent();
+
+ // The AmbientCallback is an abstract class instead of an interface.
+ WearableActivityController.AmbientCallback callbackBridge =
+ new WearableActivityController.AmbientCallback() {
+ @Override
+ public void onEnterAmbient(Bundle ambientDetails) {
+ callback.onEnterAmbient(ambientDetails);
+ }
+
+ @Override
+ public void onUpdateAmbient() {
+ callback.onUpdateAmbient();
+ }
+
+ @Override
+ public void onExitAmbient() {
+ callback.onExitAmbient();
+ }
+ };
+
+ verifyAmbientCallbacksPresent();
+
+ return new WearableActivityController(TAG, activity, callbackBridge);
+ }
+
+ private static void verifyAmbientCallbacksPresent() {
+ if (sAmbientCallbacksVerifiedPresent) {
+ return;
+ }
+ try {
+ Method method =
+ WearableActivityController.AmbientCallback.class.getDeclaredMethod(
+ "onEnterAmbient", Bundle.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 (!(".onEnterAmbient".equals("." + method.getName()))) {
+ throw new NoSuchMethodException();
+ }
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(
+ "Could not find a required method for "
+ + "ambient support, likely due to proguard optimization. Please add "
+ + "com.google.android.wearable:wearable jar to the list of library jars"
+ + " for your project");
+ }
+ sAmbientCallbacksVerifiedPresent = true;
+ }
+}
diff --git a/android/system/Os.java b/android/system/Os.java
index 5b9ff476..8e312ddc 100644
--- a/android/system/Os.java
+++ b/android/system/Os.java
@@ -516,7 +516,6 @@ public final class Os {
/** @hide */ public static void setsockoptIpMreqn(FileDescriptor fd, int level, int option, int value) throws ErrnoException { Libcore.os.setsockoptIpMreqn(fd, level, option, value); }
/** @hide */ public static void setsockoptGroupReq(FileDescriptor fd, int level, int option, StructGroupReq value) throws ErrnoException { Libcore.os.setsockoptGroupReq(fd, level, option, value); }
- /** @hide */ public static void setsockoptGroupSourceReq(FileDescriptor fd, int level, int option, StructGroupSourceReq value) throws ErrnoException { Libcore.os.setsockoptGroupSourceReq(fd, level, option, value); }
/** @hide */ public static void setsockoptLinger(FileDescriptor fd, int level, int option, StructLinger value) throws ErrnoException { Libcore.os.setsockoptLinger(fd, level, option, value); }
/** @hide */ public static void setsockoptTimeval(FileDescriptor fd, int level, int option, StructTimeval value) throws ErrnoException { Libcore.os.setsockoptTimeval(fd, level, option, value); }
diff --git a/android/system/StructGroupSourceReq.java b/android/system/StructGroupSourceReq.java
deleted file mode 100644
index c300338f..00000000
--- a/android/system/StructGroupSourceReq.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.system;
-
-import java.net.InetAddress;
-import libcore.util.Objects;
-
-/**
- * Corresponds to C's {@code struct group_source_req}.
- *
- * @hide
- */
-public final class StructGroupSourceReq {
- public final int gsr_interface;
- public final InetAddress gsr_group;
- public final InetAddress gsr_source;
-
- public StructGroupSourceReq(int gsr_interface, InetAddress gsr_group, InetAddress gsr_source) {
- this.gsr_interface = gsr_interface;
- this.gsr_group = gsr_group;
- this.gsr_source = gsr_source;
- }
-
- @Override public String toString() {
- return Objects.toString(this);
- }
-}
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
index ebac0419..764b7b22 100644
--- a/android/telephony/MbmsDownloadSession.java
+++ b/android/telephony/MbmsDownloadSession.java
@@ -522,8 +522,7 @@ public class MbmsDownloadSession implements AutoCloseable {
* @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
*/
public void registerStateCallback(@NonNull DownloadRequest request,
- @NonNull DownloadStateCallback callback,
- @NonNull Handler handler) {
+ @NonNull DownloadStateCallback callback, @NonNull Handler handler) {
IMbmsDownloadService downloadService = mService.get();
if (downloadService == null) {
throw new IllegalStateException("Middleware not yet bound");
@@ -533,7 +532,8 @@ public class MbmsDownloadSession implements AutoCloseable {
new InternalDownloadStateCallback(callback, handler);
try {
- int result = downloadService.registerStateCallback(request, internalCallback);
+ int result = downloadService.registerStateCallback(request, internalCallback,
+ callback.getCallbackFilterFlags());
if (result != MbmsErrors.SUCCESS) {
if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
throw new IllegalArgumentException("Unknown download request.");
diff --git a/android/telephony/PhoneNumberUtils.java b/android/telephony/PhoneNumberUtils.java
index d5ff1adb..ff671163 100644
--- a/android/telephony/PhoneNumberUtils.java
+++ b/android/telephony/PhoneNumberUtils.java
@@ -77,9 +77,28 @@ public class PhoneNumberUtils
public static final int TOA_International = 0x91;
public static final int TOA_Unknown = 0x81;
+ /*
+ * The BCD extended type used to determine the extended char for the digit which is greater than
+ * 9.
+ *
+ * see TS 51.011 section 10.5.1 EF_ADN(Abbreviated dialling numbers)
+ */
+ public static final int BCD_EXTENDED_TYPE_EF_ADN = 1;
+
+ /*
+ * The BCD extended type used to determine the extended char for the digit which is greater than
+ * 9.
+ *
+ * see TS 24.008 section 10.5.4.7 Called party BCD number
+ */
+ public static final int BCD_EXTENDED_TYPE_CALLED_PARTY = 2;
+
static final String LOG_TAG = "PhoneNumberUtils";
private static final boolean DBG = false;
+ private static final String BCD_EF_ADN_EXTENDED = "*#,N;";
+ private static final String BCD_CALLED_PARTY_EXTENDED = "*#abc";
+
/*
* global-phone-number = ["+"] 1*( DIGIT / written-sep )
* written-sep = ("-"/".")
@@ -799,11 +818,33 @@ public class PhoneNumberUtils
*
* @return partial string on invalid decode
*
- * FIXME(mkf) support alphanumeric address type
- * currently implemented in SMSMessage.getAddress()
+ * @deprecated use {@link #calledPartyBCDToString(byte[], int, int, int)} instead. Calling this
+ * method is equivalent to calling {@link #calledPartyBCDToString(byte[], int, int)} with
+ * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
*/
- public static String
- calledPartyBCDToString (byte[] bytes, int offset, int length) {
+ @Deprecated
+ public static String calledPartyBCDToString(byte[] bytes, int offset, int length) {
+ return calledPartyBCDToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN);
+ }
+
+ /**
+ * 3GPP TS 24.008 10.5.4.7
+ * Called Party BCD Number
+ *
+ * See Also TS 51.011 10.5.1 "dialing number/ssc string"
+ * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
+ *
+ * @param bytes the data buffer
+ * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
+ * @param length is the number of bytes including TOA byte
+ * and must be at least 2
+ * @param bcdExtType used to determine the extended bcd coding
+ * @see #BCD_EXTENDED_TYPE_EF_ADN
+ * @see #BCD_EXTENDED_TYPE_CALLED_PARTY
+ *
+ */
+ public static String calledPartyBCDToString(
+ byte[] bytes, int offset, int length, int bcdExtType) {
boolean prependPlus = false;
StringBuilder ret = new StringBuilder(1 + length * 2);
@@ -817,7 +858,7 @@ public class PhoneNumberUtils
}
internalCalledPartyBCDFragmentToString(
- ret, bytes, offset + 1, length - 1);
+ ret, bytes, offset + 1, length - 1, bcdExtType);
if (prependPlus && ret.length() == 0) {
// If the only thing there is a prepended plus, return ""
@@ -902,14 +943,13 @@ public class PhoneNumberUtils
return ret.toString();
}
- private static void
- internalCalledPartyBCDFragmentToString(
- StringBuilder sb, byte [] bytes, int offset, int length) {
+ private static void internalCalledPartyBCDFragmentToString(
+ StringBuilder sb, byte [] bytes, int offset, int length, int bcdExtType) {
for (int i = offset ; i < length + offset ; i++) {
byte b;
char c;
- c = bcdToChar((byte)(bytes[i] & 0xf));
+ c = bcdToChar((byte)(bytes[i] & 0xf), bcdExtType);
if (c == 0) {
return;
@@ -930,7 +970,7 @@ public class PhoneNumberUtils
break;
}
- c = bcdToChar(b);
+ c = bcdToChar(b, bcdExtType);
if (c == 0) {
return;
}
@@ -943,49 +983,65 @@ public class PhoneNumberUtils
/**
* Like calledPartyBCDToString, but field does not start with a
* TOA byte. For example: SIM ADN extension fields
+ *
+ * @deprecated use {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} instead.
+ * Calling this method is equivalent to calling
+ * {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} with
+ * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
*/
+ @Deprecated
+ public static String calledPartyBCDFragmentToString(byte[] bytes, int offset, int length) {
+ return calledPartyBCDFragmentToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN);
+ }
- public static String
- calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) {
+ /**
+ * Like calledPartyBCDToString, but field does not start with a
+ * TOA byte. For example: SIM ADN extension fields
+ */
+ public static String calledPartyBCDFragmentToString(
+ byte[] bytes, int offset, int length, int bcdExtType) {
StringBuilder ret = new StringBuilder(length * 2);
-
- internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
-
+ internalCalledPartyBCDFragmentToString(ret, bytes, offset, length, bcdExtType);
return ret.toString();
}
- /** returns 0 on invalid value */
- private static char
- bcdToChar(byte b) {
+ /**
+ * Returns the correspond character for given {@code b} based on {@code bcdExtType}, or 0 on
+ * invalid code.
+ */
+ private static char bcdToChar(byte b, int bcdExtType) {
if (b < 0xa) {
- return (char)('0' + b);
- } else switch (b) {
- case 0xa: return '*';
- case 0xb: return '#';
- case 0xc: return PAUSE;
- case 0xd: return WILD;
+ return (char) ('0' + b);
+ }
- default: return 0;
+ String extended = null;
+ if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) {
+ extended = BCD_EF_ADN_EXTENDED;
+ } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) {
+ extended = BCD_CALLED_PARTY_EXTENDED;
}
+ if (extended == null || b - 0xa >= extended.length()) {
+ return 0;
+ }
+
+ return extended.charAt(b - 0xa);
}
- private static int
- charToBCD(char c) {
- if (c >= '0' && c <= '9') {
+ private static int charToBCD(char c, int bcdExtType) {
+ if ('0' <= c && c <= '9') {
return c - '0';
- } else if (c == '*') {
- return 0xa;
- } else if (c == '#') {
- return 0xb;
- } else if (c == PAUSE) {
- return 0xc;
- } else if (c == WILD) {
- return 0xd;
- } else if (c == WAIT) {
- return 0xe;
- } else {
- throw new RuntimeException ("invalid char for BCD " + c);
}
+
+ String extended = null;
+ if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) {
+ extended = BCD_EF_ADN_EXTENDED;
+ } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) {
+ extended = BCD_CALLED_PARTY_EXTENDED;
+ }
+ if (extended == null || extended.indexOf(c) == -1) {
+ throw new RuntimeException("invalid char for BCD " + c);
+ }
+ return 0xa + extended.indexOf(c);
}
/**
@@ -1034,40 +1090,60 @@ public class PhoneNumberUtils
*
* Returns null if network portion is empty.
*/
- public static byte[]
- networkPortionToCalledPartyBCD(String s) {
+ public static byte[] networkPortionToCalledPartyBCD(String s) {
String networkPortion = extractNetworkPortion(s);
- return numberToCalledPartyBCDHelper(networkPortion, false);
+ return numberToCalledPartyBCDHelper(
+ networkPortion, false, BCD_EXTENDED_TYPE_EF_ADN);
}
/**
* Same as {@link #networkPortionToCalledPartyBCD}, but includes a
* one-byte length prefix.
*/
- public static byte[]
- networkPortionToCalledPartyBCDWithLength(String s) {
+ public static byte[] networkPortionToCalledPartyBCDWithLength(String s) {
String networkPortion = extractNetworkPortion(s);
- return numberToCalledPartyBCDHelper(networkPortion, true);
+ return numberToCalledPartyBCDHelper(
+ networkPortion, true, BCD_EXTENDED_TYPE_EF_ADN);
+ }
+
+ /**
+ * Convert a dialing number to BCD byte array
+ *
+ * @param number dialing number string. If the dialing number starts with '+', set to
+ * international TOA
+ *
+ * @return BCD byte array
+ *
+ * @deprecated use {@link #numberToCalledPartyBCD(String, int)} instead. Calling this method
+ * is equivalent to calling {@link #numberToCalledPartyBCD(String, int)} with
+ * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
+ */
+ @Deprecated
+ public static byte[] numberToCalledPartyBCD(String number) {
+ return numberToCalledPartyBCD(number, BCD_EXTENDED_TYPE_EF_ADN);
}
/**
* Convert a dialing number to BCD byte array
*
- * @param number dialing number string
- * if the dialing number starts with '+', set to international TOA
+ * @param number dialing number string. If the dialing number starts with '+', set to
+ * international TOA
+ * @param bcdExtType used to determine the extended bcd coding
+ * @see #BCD_EXTENDED_TYPE_EF_ADN
+ * @see #BCD_EXTENDED_TYPE_CALLED_PARTY
+ *
* @return BCD byte array
*/
- public static byte[]
- numberToCalledPartyBCD(String number) {
- return numberToCalledPartyBCDHelper(number, false);
+ public static byte[] numberToCalledPartyBCD(String number, int bcdExtType) {
+ return numberToCalledPartyBCDHelper(number, false, bcdExtType);
}
/**
* If includeLength is true, prepend a one-byte length value to
* the return array.
*/
- private static byte[]
- numberToCalledPartyBCDHelper(String number, boolean includeLength) {
+ private static byte[] numberToCalledPartyBCDHelper(
+ String number, boolean includeLength, int bcdExtType) {
int numberLenReal = number.length();
int numberLenEffective = numberLenReal;
boolean hasPlus = number.indexOf('+') != -1;
@@ -1087,7 +1163,8 @@ public class PhoneNumberUtils
char c = number.charAt(i);
if (c == '+') continue;
int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
- result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
+ result[extraBytes + (digitCount >> 1)] |=
+ (byte)((charToBCD(c, bcdExtType) & 0x0F) << shift);
digitCount++;
}
diff --git a/android/telephony/TelephonyManager.java b/android/telephony/TelephonyManager.java
index cde0bdfd..c0564c55 100644
--- a/android/telephony/TelephonyManager.java
+++ b/android/telephony/TelephonyManager.java
@@ -107,8 +107,6 @@ public class TelephonyManager {
public static final String MODEM_ACTIVITY_RESULT_KEY =
BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY;
- private static ITelephonyRegistry sRegistry;
-
/**
* The allowed states of Wi-Fi calling.
*
@@ -179,11 +177,6 @@ public class TelephonyManager {
mContext = context;
}
mSubscriptionManager = SubscriptionManager.from(mContext);
-
- if (sRegistry == null) {
- sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
- }
}
/** @hide */
@@ -3513,6 +3506,10 @@ public class TelephonyManager {
return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
}
+ private ITelephonyRegistry getTelephonyRegistry() {
+ return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
+ }
+
//
//
// PhoneStateListener
@@ -3552,12 +3549,16 @@ public class TelephonyManager {
if (listener.mSubId == null) {
listener.mSubId = mSubId;
}
- sRegistry.listenForSubscriber(listener.mSubId, getOpPackageName(),
- listener.callback, events, notifyNow);
+
+ ITelephonyRegistry registry = getTelephonyRegistry();
+ if (registry != null) {
+ registry.listenForSubscriber(listener.mSubId, getOpPackageName(),
+ listener.callback, events, notifyNow);
+ } else {
+ Rlog.w(TAG, "telephony registry not ready.");
+ }
} catch (RemoteException ex) {
// system process dead
- } catch (NullPointerException ex) {
- // system process dead
}
}
diff --git a/android/telephony/mbms/DownloadStateCallback.java b/android/telephony/mbms/DownloadStateCallback.java
index 86920bd3..892fbf07 100644
--- a/android/telephony/mbms/DownloadStateCallback.java
+++ b/android/telephony/mbms/DownloadStateCallback.java
@@ -16,8 +16,12 @@
package android.telephony.mbms;
+import android.annotation.IntDef;
import android.telephony.MbmsDownloadSession;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A optional listener class used by download clients to track progress. Apps should extend this
* class and pass an instance into
@@ -29,6 +33,71 @@ import android.telephony.MbmsDownloadSession;
public class DownloadStateCallback {
/**
+ * Bitmask flags used for filtering out callback methods. Used when constructing the
+ * DownloadStateCallback as an optional parameter.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
+ public @interface FilterFlag {}
+
+ /**
+ * Receive all callbacks.
+ * Default value.
+ */
+ public static final int ALL_UPDATES = 0x00;
+ /**
+ * Receive callbacks for {@link #onProgressUpdated}.
+ */
+ public static final int PROGRESS_UPDATES = 0x01;
+ /**
+ * Receive callbacks for {@link #onStateUpdated}.
+ */
+ public static final int STATE_UPDATES = 0x02;
+
+ private final int mCallbackFilterFlags;
+
+ /**
+ * Creates a DownloadStateCallback that will receive all callbacks.
+ */
+ public DownloadStateCallback() {
+ mCallbackFilterFlags = ALL_UPDATES;
+ }
+
+ /**
+ * Creates a DownloadStateCallback that will only receive callbacks for the methods specified
+ * via the filterFlags parameter.
+ * @param filterFlags A bitmask of filter flags that will specify which callback this instance
+ * is interested in.
+ */
+ public DownloadStateCallback(int filterFlags) {
+ mCallbackFilterFlags = filterFlags;
+ }
+
+ /**
+ * Return the currently set filter flags.
+ * @return An integer containing the bitmask of flags that this instance is interested in.
+ * @hide
+ */
+ public int getCallbackFilterFlags() {
+ return mCallbackFilterFlags;
+ }
+
+ /**
+ * Returns true if a filter flag is set for a particular callback method. If the flag is set,
+ * the callback will be delivered to the listening process.
+ * @param flag A filter flag specifying whether or not a callback method is registered to
+ * receive callbacks.
+ * @return true if registered to receive callbacks in the listening process, false if not.
+ */
+ public final boolean isFilterFlagSet(@FilterFlag int flag) {
+ if (mCallbackFilterFlags == ALL_UPDATES) {
+ return true;
+ }
+ return (mCallbackFilterFlags & flag) > 0;
+ }
+
+ /**
* Called when the middleware wants to report progress for a file in a {@link DownloadRequest}.
*
* @param request a {@link DownloadRequest}, indicating which download is being referenced.
diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java
index 61415b50..fe275372 100644
--- a/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -165,6 +165,12 @@ public class MbmsDownloadReceiver extends BroadcastReceiver {
Log.w(LOG_TAG, "Download result did not include a result code. 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;
diff --git a/android/telephony/mbms/ServiceInfo.java b/android/telephony/mbms/ServiceInfo.java
index 9a01ed00..8529f525 100644
--- a/android/telephony/mbms/ServiceInfo.java
+++ b/android/telephony/mbms/ServiceInfo.java
@@ -23,6 +23,7 @@ import android.os.Parcelable;
import android.text.TextUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -62,12 +63,6 @@ public class ServiceInfo {
throw new RuntimeException("bad locales length " + newLocales.size());
}
- for (Locale l : newLocales) {
- if (!newNames.containsKey(l)) {
- throw new IllegalArgumentException("A name must be provided for each locale");
- }
- }
-
names = new HashMap(newNames.size());
names.putAll(newNames);
className = newClassName;
@@ -127,7 +122,7 @@ public class ServiceInfo {
* Get the user-displayable name for this cell-broadcast service corresponding to the
* provided {@link Locale}.
* @param locale The {@link Locale} in which you want the name of the service. This must be a
- * value from the list returned by {@link #getLocales()} -- an
+ * value from the set returned by {@link #getNamedContentLocales()} -- an
* {@link java.util.NoSuchElementException} may be thrown otherwise.
* @return The {@link CharSequence} providing the name of the service in the given
* {@link Locale}
@@ -140,6 +135,17 @@ public class ServiceInfo {
}
/**
+ * Return an unmodifiable set of the current {@link Locale}s that have a user-displayable name
+ * associated with them. The user-displayable name associated with any {@link Locale} in this
+ * set can be retrieved with {@link #getNameForLocale(Locale)}.
+ * @return An unmodifiable set of {@link Locale} objects corresponding to a user-displayable
+ * content name in that locale.
+ */
+ public @NonNull Set<Locale> getNamedContentLocales() {
+ return Collections.unmodifiableSet(names.keySet());
+ }
+
+ /**
* The class name for this service - used to categorize and filter
*/
public String getServiceClassName() {
diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index d845a57b..2f85a1df 100644
--- a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -46,6 +46,47 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
+
+ // Filters the DownloadStateCallbacks by its configuration from the app.
+ private abstract static class FilteredDownloadStateCallback extends DownloadStateCallback {
+
+ private final IDownloadStateCallback mCallback;
+ public FilteredDownloadStateCallback(IDownloadStateCallback callback, int callbackFlags) {
+ super(callbackFlags);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
+ int currentDownloadSize, int fullDownloadSize, int currentDecodedSize,
+ int fullDecodedSize) {
+ if (!isFilterFlagSet(PROGRESS_UPDATES)) {
+ return;
+ }
+ try {
+ mCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+ fullDownloadSize, currentDecodedSize, fullDecodedSize);
+ } catch (RemoteException e) {
+ onRemoteException(e);
+ }
+ }
+
+ @Override
+ public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+ @MbmsDownloadSession.DownloadStatus int state) {
+ if (!isFilterFlagSet(STATE_UPDATES)) {
+ return;
+ }
+ try {
+ mCallback.onStateUpdated(request, fileInfo, state);
+ } catch (RemoteException e) {
+ onRemoteException(e);
+ }
+ }
+
+ protected abstract void onRemoteException(RemoteException e);
+ }
+
/**
* Initialize the download service for this app and subId, registering the listener.
*
@@ -196,9 +237,8 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
* @hide
*/
@Override
- public final int registerStateCallback(
- final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
- throws RemoteException {
+ public final int registerStateCallback(final DownloadRequest downloadRequest,
+ final IDownloadStateCallback callback, int flags) throws RemoteException {
final int uid = Binder.getCallingUid();
DeathRecipient deathRecipient = new DeathRecipient() {
@Override
@@ -211,28 +251,10 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
callback.asBinder().linkToDeath(deathRecipient, 0);
- DownloadStateCallback exposedCallback = new DownloadStateCallback() {
+ DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
@Override
- public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
- currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
- fullDecodedSize) {
- try {
- callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
- fullDownloadSize,
- currentDecodedSize, fullDecodedSize);
- } catch (RemoteException e) {
- onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
- }
- }
-
- @Override
- public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
- @MbmsDownloadSession.DownloadStatus int state) {
- try {
- callback.onStateUpdated(request, fileInfo, state);
- } catch (RemoteException e) {
- onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
- }
+ protected void onRemoteException(RemoteException e) {
+ onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
}
};
diff --git a/android/telephony/mbms/vendor/VendorUtils.java b/android/telephony/mbms/vendor/VendorUtils.java
index 8fb27b29..a43f1224 100644
--- a/android/telephony/mbms/vendor/VendorUtils.java
+++ b/android/telephony/mbms/vendor/VendorUtils.java
@@ -38,8 +38,9 @@ public class VendorUtils {
/**
* The MBMS middleware should send this when a download of single file has completed or
- * failed. Mandatory extras are
+ * failed. The only mandatory extra is
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+ * and the following are required when the download has completed:
* {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
* {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
* {@link #EXTRA_TEMP_LIST}
diff --git a/android/text/BoringLayoutCreateDrawPerfTest.java b/android/text/BoringLayoutCreateDrawPerfTest.java
new file mode 100644
index 00000000..47dd257b
--- /dev/null
+++ b/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for {@link BoringLayout} create and draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class BoringLayoutCreateDrawPerfTest {
+
+ private static final boolean[] BOOLEANS = new boolean[]{false, true};
+ private static final float SPACING_ADD = 10f;
+ private static final float SPACING_MULT = 1.5f;
+
+ @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ public static Collection cases() {
+ final List<Object[]> params = new ArrayList<>();
+ for (int length : new int[]{128}) {
+ for (boolean cached : BOOLEANS) {
+ for (TextType textType : new TextType[]{TextType.STRING,
+ TextType.SPANNABLE_BUILDER}) {
+ params.add(new Object[]{textType.name(), length, textType, cached});
+ }
+ }
+ }
+ return params;
+ }
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private final int mLength;
+ private final TextType mTextType;
+ private final boolean mCached;
+ private final TextPaint mTextPaint;
+
+ public BoringLayoutCreateDrawPerfTest(String label, int length, TextType textType,
+ boolean cached) {
+ mLength = length;
+ mCached = cached;
+ mTextType = textType;
+ mTextPaint = new TextPaint();
+ mTextPaint.setTextSize(10);
+ }
+
+ /**
+ * Measures the creation time for {@link BoringLayout}.
+ */
+ @Test
+ public void timeCreate() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final CharSequence text = createRandomText();
+ // isBoring result is calculated in another test, we want to measure only the
+ // create time for Boring without isBoring check. Therefore it is calculated here.
+ final BoringLayout.Metrics metrics = BoringLayout.isBoring(text, mTextPaint);
+ if (mCached) createLayout(text, metrics);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ createLayout(text, metrics);
+ }
+ }
+
+ /**
+ * Measures the draw time for {@link BoringLayout} or {@link StaticLayout}.
+ */
+ @Test
+ public void timeDraw() throws Throwable {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final RenderNode node = RenderNode.create("benchmark", null);
+ final CharSequence text = createRandomText();
+ final BoringLayout.Metrics metrics = BoringLayout.isBoring(text, mTextPaint);
+ final Layout layout = createLayout(text, metrics);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+
+ state.pauseTiming();
+ final DisplayListCanvas canvas = node.start(1200, 200);
+ final int save = canvas.save();
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ layout.draw(canvas);
+
+ state.pauseTiming();
+ canvas.restoreToCount(save);
+ node.end(canvas);
+ state.resumeTiming();
+ }
+ }
+
+ private CharSequence createRandomText() {
+ return new NonEditableTextGenerator(new Random(0))
+ .setSequenceLength(mLength)
+ .setCreateBoring(true)
+ .setTextType(mTextType)
+ .build();
+ }
+
+ private Layout createLayout(CharSequence text,
+ BoringLayout.Metrics metrics) {
+ return BoringLayout.make(text, mTextPaint, Integer.MAX_VALUE /*width*/,
+ ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, metrics, true /*includePad*/);
+ }
+}
diff --git a/android/text/BoringLayoutIsBoringPerfTest.java b/android/text/BoringLayoutIsBoringPerfTest.java
new file mode 100644
index 00000000..34de65de
--- /dev/null
+++ b/android/text/BoringLayoutIsBoringPerfTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.text;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for {@link BoringLayout#isBoring(CharSequence, TextPaint)}.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class BoringLayoutIsBoringPerfTest {
+
+ private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+ @Parameterized.Parameters(name = "cached={4},{1} chars,{0}")
+ public static Collection cases() {
+ final List<Object[]> params = new ArrayList<>();
+ for (int length : new int[]{128}) {
+ for (boolean boring : BOOLEANS) {
+ for (boolean cached : BOOLEANS) {
+ for (TextType textType : new TextType[]{TextType.STRING,
+ TextType.SPANNABLE_BUILDER}) {
+ params.add(new Object[]{
+ (boring ? "Boring" : "NotBoring") + "," + textType.name(),
+ length, boring, textType, cached});
+ }
+ }
+ }
+ }
+ return params;
+ }
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private final int mLength;
+ private final TextType mTextType;
+ private final boolean mCreateBoring;
+ private final boolean mCached;
+ private final TextPaint mTextPaint;
+
+ public BoringLayoutIsBoringPerfTest(String label, int length, boolean boring, TextType textType,
+ boolean cached) {
+ mLength = length;
+ mCreateBoring = boring;
+ mCached = cached;
+ mTextType = textType;
+ mTextPaint = new TextPaint();
+ mTextPaint.setTextSize(10);
+ }
+
+ /**
+ * Measure the time for the {@link BoringLayout#isBoring(CharSequence, TextPaint)}.
+ */
+ @Test
+ public void timeIsBoring() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final CharSequence text = createRandomText();
+ if (mCached) BoringLayout.isBoring(text, mTextPaint);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ BoringLayout.isBoring(text, mTextPaint);
+ }
+ }
+
+ private CharSequence createRandomText() {
+ return new NonEditableTextGenerator(new Random(0))
+ .setSequenceLength(mLength)
+ .setCreateBoring(mCreateBoring)
+ .setTextType(mTextType)
+ .build();
+ }
+}
diff --git a/android/text/DynamicLayout.java b/android/text/DynamicLayout.java
index 5e40935c..24260c4f 100644
--- a/android/text/DynamicLayout.java
+++ b/android/text/DynamicLayout.java
@@ -384,7 +384,7 @@ public class DynamicLayout extends Layout
private DynamicLayout(@NonNull Builder b) {
super(createEllipsizer(b.mEllipsize, b.mDisplay),
- b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
+ b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
mDisplay = b.mDisplay;
mIncludePad = b.mIncludePad;
diff --git a/android/text/DynamicLayoutPerfTest.java b/android/text/DynamicLayoutPerfTest.java
index e644a1f3..b4c7f543 100644
--- a/android/text/DynamicLayoutPerfTest.java
+++ b/android/text/DynamicLayoutPerfTest.java
@@ -16,35 +16,28 @@
package android.text;
-import android.app.Activity;
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
-import android.os.Bundle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
-
-import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
import android.text.style.ReplacementSpan;
import android.util.ArraySet;
-import static android.text.Layout.Alignment.ALIGN_NORMAL;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-import java.util.Random;
-
-import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/android/text/Hyphenator.java b/android/text/Hyphenator.java
index ea1100ea..ad26f23a 100644
--- a/android/text/Hyphenator.java
+++ b/android/text/Hyphenator.java
@@ -16,7 +16,12 @@
package android.text;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -24,9 +29,6 @@ import com.android.internal.annotations.GuardedBy;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Locale;
@@ -37,39 +39,19 @@ import java.util.Locale;
* @hide
*/
public class Hyphenator {
- // This class has deliberately simple lifetime management (no finalizer) because in
- // the common case a process will use a very small number of locales.
-
private static String TAG = "Hyphenator";
- // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but
- // that appears too small.
- private static final int INDIC_MIN_PREFIX = 2;
- private static final int INDIC_MIN_SUFFIX = 2;
-
private final static Object sLock = new Object();
@GuardedBy("sLock")
final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
- // Reasonable enough values for cases where we have no hyphenation patterns but may be able to
- // do some automatic hyphenation based on characters. These values would be used very rarely.
- private static final int DEFAULT_MIN_PREFIX = 2;
- private static final int DEFAULT_MIN_SUFFIX = 2;
- final static Hyphenator sEmptyHyphenator =
- new Hyphenator(StaticLayout.nLoadHyphenator(
- null, 0, DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX),
- null);
-
- final private long mNativePtr;
-
- // We retain a reference to the buffer to keep the memory mapping valid
- @SuppressWarnings("unused")
- final private ByteBuffer mBuffer;
+ private final long mNativePtr;
+ private final HyphenationData mData;
- private Hyphenator(long nativePtr, ByteBuffer b) {
+ private Hyphenator(long nativePtr, HyphenationData data) {
mNativePtr = nativePtr;
- mBuffer = b;
+ mData = data;
}
public long getNativePtr() {
@@ -90,8 +72,7 @@ public class Hyphenator {
new Locale(locale.getLanguage(), "", variant);
result = sMap.get(languageAndVariantOnlyLocale);
if (result != null) {
- sMap.put(locale, result);
- return result;
+ return putAlias(locale, result);
}
}
@@ -99,8 +80,7 @@ public class Hyphenator {
final Locale languageOnlyLocale = new Locale(locale.getLanguage());
result = sMap.get(languageOnlyLocale);
if (result != null) {
- sMap.put(locale, result);
- return result;
+ return putAlias(locale, result);
}
// Fall back to script-only, if available
@@ -112,135 +92,94 @@ public class Hyphenator {
.build();
result = sMap.get(scriptOnlyLocale);
if (result != null) {
- sMap.put(locale, result);
- return result;
+ return putAlias(locale, result);
}
}
- sMap.put(locale, sEmptyHyphenator); // To remember we found nothing.
+ return putEmptyAlias(locale);
}
- return sEmptyHyphenator;
}
private static class HyphenationData {
- final String mLanguageTag;
- final int mMinPrefix, mMinSuffix;
- HyphenationData(String languageTag, int minPrefix, int minSuffix) {
- this.mLanguageTag = languageTag;
- this.mMinPrefix = minPrefix;
- this.mMinSuffix = minSuffix;
- }
- }
+ private static final String SYSTEM_HYPHENATOR_LOCATION = "/system/usr/hyphen-data";
+
+ public final int mMinPrefix, mMinSuffix;
+ public final long mDataAddress;
+
+ // Reasonable enough values for cases where we have no hyphenation patterns but may be able
+ // to do some automatic hyphenation based on characters. These values would be used very
+ // rarely.
+ private static final int DEFAULT_MIN_PREFIX = 2;
+ private static final int DEFAULT_MIN_SUFFIX = 2;
+
+ public static final HyphenationData sEmptyData =
+ new HyphenationData(DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX);
- private static Hyphenator loadHyphenator(HyphenationData data) {
- String patternFilename = "hyph-" + data.mLanguageTag.toLowerCase(Locale.US) + ".hyb";
- File patternFile = new File(getSystemHyphenatorLocation(), patternFilename);
- if (!patternFile.canRead()) {
- Log.e(TAG, "hyphenation patterns for " + patternFile + " not found or unreadable");
- return null;
+ // Create empty HyphenationData.
+ private HyphenationData(int minPrefix, int minSuffix) {
+ mMinPrefix = minPrefix;
+ mMinSuffix = minSuffix;
+ mDataAddress = 0;
}
- try {
- RandomAccessFile f = new RandomAccessFile(patternFile, "r");
- try {
- FileChannel fc = f.getChannel();
- MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
- long nativePtr = StaticLayout.nLoadHyphenator(
- buf, 0, data.mMinPrefix, data.mMinSuffix);
- return new Hyphenator(nativePtr, buf);
- } finally {
- f.close();
+
+ HyphenationData(String languageTag, int minPrefix, int minSuffix) {
+ mMinPrefix = minPrefix;
+ mMinSuffix = minSuffix;
+
+ final String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb";
+ final File patternFile = new File(SYSTEM_HYPHENATOR_LOCATION, patternFilename);
+ if (!patternFile.canRead()) {
+ Log.e(TAG, "hyphenation patterns for " + patternFile + " not found or unreadable");
+ mDataAddress = 0;
+ } else {
+ long address;
+ try (RandomAccessFile f = new RandomAccessFile(patternFile, "r")) {
+ address = Os.mmap(0, f.length(), OsConstants.PROT_READ,
+ OsConstants.MAP_SHARED, f.getFD(), 0 /* offset */);
+ } catch (IOException | ErrnoException e) {
+ Log.e(TAG, "error loading hyphenation " + patternFile, e);
+ address = 0;
+ }
+ mDataAddress = address;
}
- } catch (IOException e) {
- Log.e(TAG, "error loading hyphenation " + patternFile, e);
- return null;
}
}
- private static File getSystemHyphenatorLocation() {
- return new File("/system/usr/hyphen-data");
+ // Do not call this method outside of init method.
+ private static Hyphenator putNewHyphenator(Locale loc, HyphenationData data) {
+ final Hyphenator hyphenator = new Hyphenator(nBuildHyphenator(
+ data.mDataAddress, loc.getLanguage(), data.mMinPrefix, data.mMinSuffix), data);
+ sMap.put(loc, hyphenator);
+ return hyphenator;
}
- // This array holds pairs of language tags that are used to prefill the map from locale to
- // hyphenation data: The hyphenation data for the first field will be prefilled from the
- // hyphenation data for the second field.
- //
- // The aliases that are computable by the get() method above are not included.
- private static final String[][] LOCALE_FALLBACK_DATA = {
- // English locales that fall back to en-US. The data is
- // from CLDR. It's all English locales, minus the locales whose
- // parent is en-001 (from supplementalData.xml, under <parentLocales>).
- // TODO: Figure out how to get this from ICU.
- {"en-AS", "en-US"}, // English (American Samoa)
- {"en-GU", "en-US"}, // English (Guam)
- {"en-MH", "en-US"}, // English (Marshall Islands)
- {"en-MP", "en-US"}, // English (Northern Mariana Islands)
- {"en-PR", "en-US"}, // English (Puerto Rico)
- {"en-UM", "en-US"}, // English (United States Minor Outlying Islands)
- {"en-VI", "en-US"}, // English (Virgin Islands)
-
- // All English locales other than those falling back to en-US are mapped to en-GB.
- {"en", "en-GB"},
-
- // For German, we're assuming the 1996 (and later) orthography by default.
- {"de", "de-1996"},
- // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
- {"de-LI-1901", "de-CH-1901"},
-
- // Norwegian is very probably Norwegian Bokmål.
- {"no", "nb"},
-
- // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
- {"mn", "mn-Cyrl"}, // Mongolian
-
- // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
- // Data is from CLDR's likelySubtags.xml.
- // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
- {"am", "und-Ethi"}, // Amharic
- {"byn", "und-Ethi"}, // Blin
- {"gez", "und-Ethi"}, // Geʻez
- {"ti", "und-Ethi"}, // Tigrinya
- {"wal", "und-Ethi"}, // Wolaytta
- };
+ // Do not call this method outside of init method.
+ private static void loadData(String langTag, int minPrefix, int maxPrefix) {
+ final HyphenationData data = new HyphenationData(langTag, minPrefix, maxPrefix);
+ putNewHyphenator(Locale.forLanguageTag(langTag), data);
+ }
- private static final HyphenationData[] AVAILABLE_LANGUAGES = {
- new HyphenationData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Assamese
- new HyphenationData("bg", 2, 2), // Bulgarian
- new HyphenationData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Bengali
- new HyphenationData("cu", 1, 2), // Church Slavonic
- new HyphenationData("cy", 2, 3), // Welsh
- new HyphenationData("da", 2, 2), // Danish
- new HyphenationData("de-1901", 2, 2), // German 1901 orthography
- new HyphenationData("de-1996", 2, 2), // German 1996 orthography
- new HyphenationData("de-CH-1901", 2, 2), // Swiss High German 1901 orthography
- new HyphenationData("en-GB", 2, 3), // British English
- new HyphenationData("en-US", 2, 3), // American English
- new HyphenationData("es", 2, 2), // Spanish
- new HyphenationData("et", 2, 3), // Estonian
- new HyphenationData("eu", 2, 2), // Basque
- new HyphenationData("fr", 2, 3), // French
- new HyphenationData("ga", 2, 3), // Irish
- new HyphenationData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Gujarati
- new HyphenationData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Hindi
- new HyphenationData("hr", 2, 2), // Croatian
- new HyphenationData("hu", 2, 2), // Hungarian
- // texhyphen sources say Armenian may be (1, 2), but that it needs confirmation.
- // Going with a more conservative value of (2, 2) for now.
- new HyphenationData("hy", 2, 2), // Armenian
- new HyphenationData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Kannada
- new HyphenationData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Malayalam
- new HyphenationData("mn-Cyrl", 2, 2), // Mongolian in Cyrillic script
- new HyphenationData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Marathi
- new HyphenationData("nb", 2, 2), // Norwegian Bokmål
- new HyphenationData("nn", 2, 2), // Norwegian Nynorsk
- new HyphenationData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Oriya
- new HyphenationData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Punjabi
- new HyphenationData("pt", 2, 3), // Portuguese
- new HyphenationData("sl", 2, 2), // Slovenian
- new HyphenationData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Tamil
- new HyphenationData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Telugu
- new HyphenationData("tk", 2, 2), // Turkmen
- new HyphenationData("und-Ethi", 1, 1), // Any language in Ethiopic script
- };
+ // Caller must acquire sLock before calling this method.
+ // The Hyphenator for the baseLangTag must exists.
+ private static Hyphenator addAliasByTag(String langTag, String baseLangTag) {
+ return putAlias(Locale.forLanguageTag(langTag),
+ sMap.get(Locale.forLanguageTag(baseLangTag)));
+ }
+
+ // Caller must acquire sLock before calling this method.
+ private static Hyphenator putAlias(Locale locale, Hyphenator base) {
+ return putNewHyphenator(locale, base.mData);
+ }
+
+ // Caller must acquire sLock before calling this method.
+ private static Hyphenator putEmptyAlias(Locale locale) {
+ return putNewHyphenator(locale, HyphenationData.sEmptyData);
+ }
+
+ // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but
+ // that appears too small.
+ private static final int INDIC_MIN_PREFIX = 2;
+ private static final int INDIC_MIN_SUFFIX = 2;
/**
* Load hyphenation patterns at initialization time. We want to have patterns
@@ -250,20 +189,85 @@ public class Hyphenator {
* @hide
*/
public static void init() {
- sMap.put(null, null);
-
- for (int i = 0; i < AVAILABLE_LANGUAGES.length; i++) {
- HyphenationData data = AVAILABLE_LANGUAGES[i];
- Hyphenator h = loadHyphenator(data);
- if (h != null) {
- sMap.put(Locale.forLanguageTag(data.mLanguageTag), h);
- }
+ synchronized (sLock) {
+ sMap.put(null, null);
+
+ loadData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Assamese
+ loadData("bg", 2, 2); // Bulgarian
+ loadData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Bengali
+ loadData("cu", 1, 2); // Church Slavonic
+ loadData("cy", 2, 3); // Welsh
+ loadData("da", 2, 2); // Danish
+ loadData("de-1901", 2, 2); // German 1901 orthography
+ loadData("de-1996", 2, 2); // German 1996 orthography
+ loadData("de-CH-1901", 2, 2); // Swiss High German 1901 orthography
+ loadData("en-GB", 2, 3); // British English
+ loadData("en-US", 2, 3); // American English
+ loadData("es", 2, 2); // Spanish
+ loadData("et", 2, 3); // Estonian
+ loadData("eu", 2, 2); // Basque
+ loadData("fr", 2, 3); // French
+ loadData("ga", 2, 3); // Irish
+ loadData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Gujarati
+ loadData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Hindi
+ loadData("hr", 2, 2); // Croatian
+ loadData("hu", 2, 2); // Hungarian
+ // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
+ // Going with a more conservative value of (2, 2) for now.
+ loadData("hy", 2, 2); // Armenian
+ loadData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Kannada
+ loadData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Malayalam
+ loadData("mn-Cyrl", 2, 2); // Mongolian in Cyrillic script
+ loadData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Marathi
+ loadData("nb", 2, 2); // Norwegian Bokmål
+ loadData("nn", 2, 2); // Norwegian Nynorsk
+ loadData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya
+ loadData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi
+ loadData("pt", 2, 3); // Portuguese
+ loadData("sl", 2, 2); // Slovenian
+ loadData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Tamil
+ loadData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Telugu
+ loadData("tk", 2, 2); // Turkmen
+ loadData("und-Ethi", 1, 1); // Any language in Ethiopic script
+
+ // English locales that fall back to en-US. The data is
+ // from CLDR. It's all English locales, minus the locales whose
+ // parent is en-001 (from supplementalData.xml, under <parentLocales>).
+ // TODO: Figure out how to get this from ICU.
+ addAliasByTag("en-AS", "en-US"); // English (American Samoa)
+ addAliasByTag("en-GU", "en-US"); // English (Guam)
+ addAliasByTag("en-MH", "en-US"); // English (Marshall Islands)
+ addAliasByTag("en-MP", "en-US"); // English (Northern Mariana Islands)
+ addAliasByTag("en-PR", "en-US"); // English (Puerto Rico)
+ addAliasByTag("en-UM", "en-US"); // English (United States Minor Outlying Islands)
+ addAliasByTag("en-VI", "en-US"); // English (Virgin Islands)
+
+ // All English locales other than those falling back to en-US are mapped to en-GB.
+ addAliasByTag("en", "en-GB");
+
+ // For German, we're assuming the 1996 (and later) orthography by default.
+ addAliasByTag("de", "de-1996");
+ // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
+ addAliasByTag("de-LI-1901", "de-CH-1901");
+
+ // Norwegian is very probably Norwegian Bokmål.
+ addAliasByTag("no", "nb");
+
+ // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
+ addAliasByTag("mn", "mn-Cyrl"); // Mongolian
+
+ // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
+ // Data is from CLDR's likelySubtags.xml.
+ // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
+ addAliasByTag("am", "und-Ethi"); // Amharic
+ addAliasByTag("byn", "und-Ethi"); // Blin
+ addAliasByTag("gez", "und-Ethi"); // Geʻez
+ addAliasByTag("ti", "und-Ethi"); // Tigrinya
+ addAliasByTag("wal", "und-Ethi"); // Wolaytta
}
+ };
- for (int i = 0; i < LOCALE_FALLBACK_DATA.length; i++) {
- String language = LOCALE_FALLBACK_DATA[i][0];
- String fallback = LOCALE_FALLBACK_DATA[i][1];
- sMap.put(Locale.forLanguageTag(language), sMap.get(Locale.forLanguageTag(fallback)));
- }
- }
+ private static native long nBuildHyphenator(/* non-zero */ long dataAddress,
+ @NonNull String langTag, @IntRange(from = 1) int minPrefix,
+ @IntRange(from = 1) int minSuffix);
}
diff --git a/android/text/Hyphenator_Delegate.java b/android/text/Hyphenator_Delegate.java
deleted file mode 100644
index 499e58a5..00000000
--- a/android/text/Hyphenator_Delegate.java
+++ /dev/null
@@ -1,46 +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.text;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-
-/**
- * Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
- * <p/>
- * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced
- * by calls to methods of the same name in this delegate class.
- */
-public class Hyphenator_Delegate {
-
- private static final DelegateManager<Hyphenator_Delegate> sDelegateManager = new
- DelegateManager<Hyphenator_Delegate>(Hyphenator_Delegate.class);
-
- @LayoutlibDelegate
- /*package*/ static File getSystemHyphenatorLocation() {
- // FIXME
- return null;
- }
-
- /*package*/ @SuppressWarnings("UnusedParameters") // TODO implement this.
- static long loadHyphenator(ByteBuffer buffer, int offset, int minPrefix, int minSuffix) {
- return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
- }
-}
diff --git a/android/text/Layout.java b/android/text/Layout.java
index 25f791bc..60fff738 100644
--- a/android/text/Layout.java
+++ b/android/text/Layout.java
@@ -1915,8 +1915,7 @@ public abstract class Layout {
return margin;
}
- /* package */
- static float measurePara(TextPaint paint, CharSequence text, int start, int end,
+ private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
TextDirectionHeuristic textDir) {
MeasuredText mt = MeasuredText.obtain();
TextLine tl = TextLine.obtain();
@@ -2146,18 +2145,14 @@ public abstract class Layout {
* text within the layout of a line.
*/
public static class Directions {
- // Directions represents directional runs within a line of text.
- // Runs are pairs of ints listed in visual order, starting from the
- // leading margin. The first int of each pair is the offset from
- // the first character of the line to the start of the run. The
- // second int represents both the length and level of the run.
- // The length is in the lower bits, accessed by masking with
- // DIR_LENGTH_MASK. The level is in the higher bits, accessed
- // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
- // To simply test for an RTL direction, test the bit using
- // DIR_RTL_FLAG, if set then the direction is rtl.
-
/**
+ * Directions represents directional runs within a line of text. Runs are pairs of ints
+ * listed in visual order, starting from the leading margin. The first int of each pair is
+ * the offset from the first character of the line to the start of the run. The second int
+ * represents both the length and level of the run. The length is in the lower bits,
+ * accessed by masking with RUN_LENGTH_MASK. The level is in the higher bits, accessed by
+ * shifting by RUN_LEVEL_SHIFT and masking by RUN_LEVEL_MASK. To simply test for an RTL
+ * direction, test the bit using RUN_RTL_FLAG, if set then the direction is rtl.
* @hide
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
diff --git a/android/text/MeasuredText.java b/android/text/MeasuredText.java
index ce3e2822..b09ccc29 100644
--- a/android/text/MeasuredText.java
+++ b/android/text/MeasuredText.java
@@ -90,10 +90,6 @@ class MeasuredText {
}
}
- void setPos(int pos) {
- mPos = pos - mTextStart;
- }
-
/**
* Analyzes text for bidirectional runs. Allocates working buffers.
*/
@@ -160,47 +156,43 @@ class MeasuredText {
}
}
+ /**
+ * Apply the style.
+ *
+ * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
+ * text width.
+ * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
+ * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
+ */
float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
if (fm != null) {
paint.getFontMetricsInt(fm);
}
- int p = mPos;
+ final int p = mPos;
mPos = p + len;
- // try to do widths measurement in native code, but use Java if paint has been subclassed
- // FIXME: may want to eliminate special case for subclass
- float[] widths = null;
- if (mBuilder == null || paint.getClass() != TextPaint.class) {
- widths = mWidths;
- }
if (mEasy) {
- boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
- float width = 0;
- if (widths != null) {
- width = paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, widths, p);
- if (mBuilder != null) {
- mBuilder.addMeasuredRun(p, p + len, widths);
- }
+ final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
+ if (mBuilder == null) {
+ return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
} else {
- width = mBuilder.addStyleRun(paint, p, p + len, isRtl);
+ mBuilder.addStyleRun(paint, p, p + len, isRtl);
+ return 0.0f; // Builder.addStyleRun doesn't return the width.
}
- return width;
}
float totalAdvance = 0;
int level = mLevels[p];
for (int q = p, i = p + 1, e = p + len;; ++i) {
if (i == e || mLevels[i] != level) {
- boolean isRtl = (level & 0x1) != 0;
- if (widths != null) {
+ final boolean isRtl = (level & 0x1) != 0;
+ if (mBuilder == null) {
totalAdvance +=
- paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, widths, q);
- if (mBuilder != null) {
- mBuilder.addMeasuredRun(q, i, widths);
- }
+ paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
} else {
- totalAdvance += mBuilder.addStyleRun(paint, q, i, isRtl);
+ // Builder.addStyleRun doesn't return the width.
+ mBuilder.addStyleRun(paint, q, i, isRtl);
}
if (i == e) {
break;
@@ -209,7 +201,7 @@ class MeasuredText {
level = mLevels[i];
}
}
- return totalAdvance;
+ return totalAdvance; // If mBuilder is null, the result is zero.
}
float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
@@ -243,7 +235,7 @@ class MeasuredText {
for (int i = mPos + 1, e = mPos + len; i < e; i++)
w[i] = 0;
} else {
- mBuilder.addReplacementRun(mPos, mPos + len, wid);
+ mBuilder.addReplacementRun(paint, mPos, mPos + len, wid);
}
mPos += len;
}
diff --git a/android/text/NonEditableTextGenerator.java b/android/text/NonEditableTextGenerator.java
new file mode 100644
index 00000000..7c0cf0ee
--- /dev/null
+++ b/android/text/NonEditableTextGenerator.java
@@ -0,0 +1,138 @@
+package android.text;
+
+import static android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE;
+
+import android.text.style.BulletSpan;
+
+import java.util.Random;
+
+/**
+ *
+ */
+public class NonEditableTextGenerator {
+
+ enum TextType {
+ STRING,
+ SPANNED,
+ SPANNABLE_BUILDER
+ }
+
+ private boolean mCreateBoring;
+ private TextType mTextType;
+ private int mSequenceLength;
+ private final Random mRandom;
+
+ public NonEditableTextGenerator(Random random) {
+ mRandom = random;
+ }
+
+ public NonEditableTextGenerator setCreateBoring(boolean createBoring) {
+ mCreateBoring = createBoring;
+ return this;
+ }
+
+ public NonEditableTextGenerator setTextType(TextType textType) {
+ mTextType = textType;
+ return this;
+ }
+
+ public NonEditableTextGenerator setSequenceLength(int sequenceLength) {
+ mSequenceLength = sequenceLength;
+ return this;
+ }
+
+ /**
+ * Sample charSequence generated:
+ * NRjPzjvUadHmH ExoEoTqfx pCLw qtndsqfpk AqajVCbgjGZ igIeC dfnXRgA
+ */
+ public CharSequence build() {
+ final RandomCharSequenceGenerator sequenceGenerator = new RandomCharSequenceGenerator(
+ mRandom);
+ if (mSequenceLength > 0) {
+ sequenceGenerator.setSequenceLength(mSequenceLength);
+ }
+
+ final CharSequence charSequence = sequenceGenerator.buildLatinSequence();
+
+ switch (mTextType) {
+ case SPANNED:
+ case SPANNABLE_BUILDER:
+ return createSpannable(charSequence);
+ case STRING:
+ default:
+ return createString(charSequence);
+ }
+ }
+
+ private Spannable createSpannable(CharSequence charSequence) {
+ final Spannable spannable = (mTextType == TextType.SPANNABLE_BUILDER) ?
+ new SpannableStringBuilder(charSequence) : new SpannableString(charSequence);
+
+ if (!mCreateBoring) {
+ // add a paragraph style to make it non boring
+ spannable.setSpan(new BulletSpan(), 0, spannable.length(), SPAN_INCLUSIVE_INCLUSIVE);
+ }
+
+ spannable.setSpan(new Object(), 0, spannable.length(), SPAN_INCLUSIVE_INCLUSIVE);
+ spannable.setSpan(new Object(), 0, 1, SPAN_INCLUSIVE_INCLUSIVE);
+
+ return spannable;
+ }
+
+ private String createString(CharSequence charSequence) {
+ if (mCreateBoring) {
+ return charSequence.toString();
+ } else {
+ // BoringLayout checks to see if there is a surrogate pair and if so tells that
+ // the charSequence is not suitable for boring. Add an emoji to make it non boring.
+ // Emoji is added instead of RTL, since emoji stays in the same run and is a more
+ // common case.
+ return charSequence.toString() + "\uD83D\uDC68\uD83C\uDFFF";
+ }
+ }
+
+ public static class RandomCharSequenceGenerator {
+
+ private static final int DEFAULT_MIN_WORD_LENGTH = 3;
+ private static final int DEFAULT_MAX_WORD_LENGTH = 15;
+ private static final int DEFAULT_SEQUENCE_LENGTH = 256;
+
+ private int mMinWordLength = DEFAULT_MIN_WORD_LENGTH;
+ private int mMaxWordLength = DEFAULT_MAX_WORD_LENGTH;
+ private int mSequenceLength = DEFAULT_SEQUENCE_LENGTH;
+ private final Random mRandom;
+
+ public RandomCharSequenceGenerator(Random random) {
+ mRandom = random;
+ }
+
+ public RandomCharSequenceGenerator setSequenceLength(int sequenceLength) {
+ mSequenceLength = sequenceLength;
+ return this;
+ }
+
+ public CharSequence buildLatinSequence() {
+ final StringBuilder result = new StringBuilder();
+ while (result.length() < mSequenceLength) {
+ // add random word
+ result.append(buildLatinWord());
+ result.append(' ');
+ }
+ return result.substring(0, mSequenceLength);
+ }
+
+ public CharSequence buildLatinWord() {
+ final StringBuilder result = new StringBuilder();
+ // create a random length that is (mMinWordLength + random amount of chars) where
+ // total size is less than mMaxWordLength
+ final int length = mRandom.nextInt(mMaxWordLength - mMinWordLength) + mMinWordLength;
+ while (result.length() < length) {
+ // add random letter
+ int base = mRandom.nextInt(2) == 0 ? 'A' : 'a';
+ result.append(Character.toChars(mRandom.nextInt(26) + base));
+ }
+ return result.toString();
+ }
+ }
+
+}
diff --git a/android/text/PaintMeasureDrawPerfTest.java b/android/text/PaintMeasureDrawPerfTest.java
new file mode 100644
index 00000000..00b60add
--- /dev/null
+++ b/android/text/PaintMeasureDrawPerfTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for single line measure and draw using {@link Paint} and {@link Canvas}.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class PaintMeasureDrawPerfTest {
+
+ private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+ @Parameterized.Parameters(name = "cached={1},{0} chars")
+ public static Collection cases() {
+ final List<Object[]> params = new ArrayList<>();
+ for (int length : new int[]{128}) {
+ for (boolean cached : BOOLEANS) {
+ params.add(new Object[]{length, cached});
+ }
+ }
+ return params;
+ }
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private final int mLength;
+ private final boolean mCached;
+ private final TextPaint mTextPaint;
+
+
+ public PaintMeasureDrawPerfTest(int length, boolean cached) {
+ mLength = length;
+ mCached = cached;
+ mTextPaint = new TextPaint();
+ mTextPaint.setTextSize(10);
+ }
+
+ /**
+ * Measure the time for {@link Paint#measureText(String)}
+ */
+ @Test
+ public void timeMeasure() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final String text = createRandomText();
+ if (mCached) mTextPaint.measureText(text);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ mTextPaint.measureText(text);
+ }
+ }
+
+ /**
+ * Measures the time for {@link Canvas#drawText(String, float, float, Paint)}
+ */
+ @Test
+ public void timeDraw() throws Throwable {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final RenderNode node = RenderNode.create("benchmark", null);
+ final String text = createRandomText();
+ if (mCached) mTextPaint.measureText(text);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+
+ state.pauseTiming();
+ final DisplayListCanvas canvas = node.start(1200, 200);
+ final int save = canvas.save();
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ canvas.drawText(text, 0 /*x*/, 100 /*y*/, mTextPaint);
+
+ state.pauseTiming();
+ canvas.restoreToCount(save);
+ node.end(canvas);
+ state.resumeTiming();
+ }
+ }
+
+ private String createRandomText() {
+ return (String) new NonEditableTextGenerator(new Random(0))
+ .setSequenceLength(mLength)
+ .setCreateBoring(true)
+ .setTextType(NonEditableTextGenerator.TextType.STRING)
+ .build();
+ }
+}
diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java
index c124c7fd..961cd8ee 100644
--- a/android/text/StaticLayout.java
+++ b/android/text/StaticLayout.java
@@ -28,12 +28,12 @@ import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.TabStopSpan;
import android.util.Log;
+import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
-import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Locale;
@@ -101,6 +101,7 @@ public class StaticLayout extends Layout {
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
+ b.mLocales = null;
b.mMeasuredText = MeasuredText.obtain();
return b;
@@ -117,6 +118,9 @@ public class StaticLayout extends Layout {
b.mMeasuredText = null;
b.mLeftIndents = null;
b.mRightIndents = null;
+ b.mLocales = null;
+ b.mLeftPaddings = null;
+ b.mRightPaddings = null;
nFinishBuilder(b.mNativePtr);
sPool.release(b);
}
@@ -128,6 +132,8 @@ public class StaticLayout extends Layout {
mPaint = null;
mLeftIndents = null;
mRightIndents = null;
+ mLeftPaddings = null;
+ mRightPaddings = null;
mMeasuredText.finish();
}
@@ -356,6 +362,28 @@ public class StaticLayout extends Layout {
}
/**
+ * Set available paddings to draw overhanging text on. Arguments are arrays holding the
+ * amount of padding available, one per line, measured in pixels. For lines past the last
+ * element in the array, the last element repeats.
+ *
+ * The individual padding amounts should be non-negative. The result of passing negative
+ * paddings is undefined.
+ *
+ * @param leftPaddings array of amounts of available padding for left margin, in pixels
+ * @param rightPaddings array of amounts of available padding for right margin, in pixels
+ * @return this builder, useful for chaining
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setAvailablePaddings(@Nullable int[] leftPaddings,
+ @Nullable int[] rightPaddings) {
+ mLeftPaddings = leftPaddings;
+ mRightPaddings = rightPaddings;
+ return this;
+ }
+
+ /**
* Set paragraph justification mode. The default value is
* {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
* the last line will be displayed with the alignment set by {@link #setAlignment}.
@@ -401,10 +429,8 @@ public class StaticLayout extends Layout {
* future).
*
* Then, for each run within the paragraph:
- * - setLocales (this must be done at least for the first run, optional afterwards)
* - one of the following, depending on the type of run:
* + addStyleRun (a text run, to be measured in native code)
- * + addMeasuredRun (a run already measured in Java, passed into native code)
* + addReplacementRun (a replacement run, width is given)
*
* After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
@@ -413,24 +439,29 @@ public class StaticLayout extends Layout {
* After all paragraphs, call finish() to release expensive buffers.
*/
- private void setLocales(LocaleList locales) {
+ private Pair<String, long[]> getLocaleAndHyphenatorIfChanged(TextPaint paint) {
+ final LocaleList locales = paint.getTextLocales();
+ final String languageTags;
+ long[] hyphenators;
if (!locales.equals(mLocales)) {
- nSetLocales(mNativePtr, locales.toLanguageTags(), getHyphenators(locales));
mLocales = locales;
+ return new Pair(locales.toLanguageTags(), getHyphenators(locales));
+ } else {
+ // passing null means keep current locale.
+ // TODO: move locale change detection to native.
+ return new Pair(null, null);
}
}
- /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
- setLocales(paint.getTextLocales());
- return nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
- }
-
- /* package */ void addMeasuredRun(int start, int end, float[] widths) {
- nAddMeasuredRun(mNativePtr, start, end, widths);
+ /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
+ Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
+ nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl, locHyph.first,
+ locHyph.second);
}
- /* package */ void addReplacementRun(int start, int end, float width) {
- nAddReplacementRun(mNativePtr, start, end, width);
+ /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
+ Pair<String, long[]> locHyph = getLocaleAndHyphenatorIfChanged(paint);
+ nAddReplacementRun(mNativePtr, start, end, width, locHyph.first, locHyph.second);
}
/**
@@ -478,6 +509,8 @@ public class StaticLayout extends Layout {
private int mHyphenationFrequency;
@Nullable private int[] mLeftIndents;
@Nullable private int[] mRightIndents;
+ @Nullable private int[] mLeftPaddings;
+ @Nullable private int[] mRightPaddings;
private int mJustificationMode;
private boolean mAddLastLineLineSpacing;
@@ -616,7 +649,7 @@ public class StaticLayout extends Layout {
: (b.mText instanceof Spanned)
? new SpannedEllipsizer(b.mText)
: new Ellipsizer(b.mText),
- b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
+ b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
if (b.mEllipsize != null) {
Ellipsizer e = (Ellipsizer) getText();
@@ -638,6 +671,8 @@ public class StaticLayout extends Layout {
mLeftIndents = b.mLeftIndents;
mRightIndents = b.mRightIndents;
+ mLeftPaddings = b.mLeftPaddings;
+ mRightPaddings = b.mRightPaddings;
setJustificationMode(b.mJustificationMode);
generate(b, b.mIncludePad, b.mIncludePad);
@@ -662,7 +697,6 @@ public class StaticLayout extends Layout {
// store fontMetrics per span range
// must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
int[] fmCache = new int[4 * 4];
- b.setLocales(paint.getTextLocales());
mLineCount = 0;
mEllipsized = false;
@@ -776,11 +810,17 @@ public class StaticLayout extends Layout {
}
}
+ // TODO: Move locale tracking code to native.
+ b.mLocales = null; // Reset the locale tracking.
+
nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
firstWidth, firstWidthLineCount, restWidth,
variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
// TODO: Support more justification mode, e.g. letter spacing, stretching.
- b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLineCount);
+ b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+ // TODO: indents and paddings don't need to get passed to native code for every
+ // paragraph. Pass them to native code just once.
+ indents, mLeftPaddings, mRightPaddings, mLineCount);
// measurement has to be done before performing line breaking
// but we don't want to recompute fontmetrics or span ranges the
@@ -1491,28 +1531,25 @@ public class StaticLayout extends Layout {
private static native void nFreeBuilder(long nativePtr);
private static native void nFinishBuilder(long nativePtr);
- /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset,
- int minPrefix, int minSuffix);
-
- private static native void nSetLocales(long nativePtr, String locales,
- long[] nativeHyphenators);
-
// Set up paragraph text and settings; done as one big method to minimize jni crossings
private static native void nSetupParagraph(
- @NonNull long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
+ /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
@FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
@FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
int defaultTabStop, @BreakStrategy int breakStrategy,
@HyphenationFrequency int hyphenationFrequency, boolean isJustified,
- @Nullable int[] indents, @IntRange(from = 0) int indentsOffset);
-
- private static native float nAddStyleRun(long nativePtr, long nativePaint, int start, int end,
- boolean isRtl);
+ @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
+ @IntRange(from = 0) int indentsOffset);
- private static native void nAddMeasuredRun(long nativePtr,
- int start, int end, float[] widths);
+ private static native void nAddStyleRun(
+ /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
+ @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl,
+ @Nullable String languageTags, @Nullable long[] hyphenators);
- private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
+ private static native void nAddReplacementRun(/* non-zero */ long nativePtr,
+ @IntRange(from = 0) int start, @IntRange(from = 0) int end,
+ @FloatRange(from = 0.0f) float width, @Nullable String languageTags,
+ @Nullable long[] hyphenators);
private static native void nGetWidths(long nativePtr, float[] widths);
@@ -1590,4 +1627,6 @@ public class StaticLayout extends Layout {
@Nullable private int[] mLeftIndents;
@Nullable private int[] mRightIndents;
+ @Nullable private int[] mLeftPaddings;
+ @Nullable private int[] mRightPaddings;
}
diff --git a/android/text/StaticLayoutCreateDrawPerfTest.java b/android/text/StaticLayoutCreateDrawPerfTest.java
new file mode 100644
index 00000000..356e2e0d
--- /dev/null
+++ b/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for multi line, single style {@link StaticLayout} creation/draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class StaticLayoutCreateDrawPerfTest {
+
+ private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+ private static final float SPACING_ADD = 10f;
+ private static final float SPACING_MULT = 1.5f;
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ public static Collection cases() {
+ final List<Object[]> params = new ArrayList<>();
+ for (int length : new int[]{128}) {
+ for (boolean cached : BOOLEANS) {
+ for (TextType textType : new TextType[]{TextType.STRING,
+ TextType.SPANNABLE_BUILDER}) {
+ params.add(new Object[]{textType.name(), length, textType, cached});
+ }
+ }
+ }
+ return params;
+ }
+
+ private final int mLineWidth;
+ private final int mLength;
+ private final TextType mTextType;
+ private final boolean mCached;
+ private final TextPaint mTextPaint;
+
+ public StaticLayoutCreateDrawPerfTest(String label, int length, TextType textType,
+ boolean cached) {
+ mLength = length;
+ mTextType = textType;
+ mCached = cached;
+ mTextPaint = new TextPaint();
+ mTextPaint.setTextSize(10);
+ mLineWidth = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Measures the creation time for a multi line {@link StaticLayout}.
+ */
+ @Test
+ public void timeCreate() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final CharSequence text = createRandomText(mLength);
+ createLayout(text);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ createLayout(text);
+ }
+ }
+
+ /**
+ * Measures the draw time for a multi line {@link StaticLayout}.
+ */
+ @Test
+ public void timeDraw() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final RenderNode node = RenderNode.create("benchmark", null);
+ final CharSequence text = createRandomText(mLength);
+ final Layout layout = createLayout(text);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+
+ state.pauseTiming();
+ final DisplayListCanvas canvas = node.start(1200, 200);
+ int save = canvas.save();
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ layout.draw(canvas);
+
+ state.pauseTiming();
+ canvas.restoreToCount(save);
+ node.end(canvas);
+ state.resumeTiming();
+ }
+ }
+
+ private Layout createLayout(CharSequence text) {
+ return StaticLayout.Builder.obtain(text, 0 /*start*/, text.length() /*end*/, mTextPaint,
+ mLineWidth)
+ .setAlignment(ALIGN_NORMAL)
+ .setIncludePad(true)
+ .setLineSpacing(SPACING_ADD, SPACING_MULT)
+ .build();
+ }
+
+ private CharSequence createRandomText(int length) {
+ return new NonEditableTextGenerator(new Random(0))
+ .setSequenceLength(length)
+ .setTextType(mTextType)
+ .build();
+ }
+}
diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java
index 0d58bcc2..63337f08 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -53,26 +53,11 @@ public class StaticLayout_Delegate {
}
@LayoutlibDelegate
- /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset, int minPrefix,
- int minSuffix) {
- return Hyphenator_Delegate.loadHyphenator(buf, offset, minPrefix, minSuffix);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetLocales(long nativeBuilder, String locales,
- long[] nativeHyphenators) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- builder.mLocales = locales;
- builder.mNativeHyphenators = nativeHyphenators;
- }
- }
-
- @LayoutlibDelegate
/*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
float firstWidth, int firstWidthLineCount, float restWidth,
int[] variableTabStops, int defaultTabStop, int breakStrategy,
- int hyphenationFrequency, boolean isJustified, int[] indents, int intentsOffset) {
+ int hyphenationFrequency, boolean isJustified, int[] indents, int[] leftPaddings,
+ int[] rightPaddings, int intentsOffset) {
// TODO: implement justified alignment
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
@@ -86,30 +71,29 @@ public class StaticLayout_Delegate {
}
@LayoutlibDelegate
- /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, int start,
- int end, boolean isRtl) {
+ /*package*/ static void nAddStyleRun(long nativeBuilder, long nativePaint, int start,
+ int end, boolean isRtl, String languageTags, long[] hyphenators) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ if (builder == null) {
+ return;
+ }
+ builder.mLocales = languageTags;
+ builder.mNativeHyphenators = hyphenators;
int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
- return builder == null ? 0 :
- measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
- bidiFlags);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- System.arraycopy(widths, start, builder.mWidths, start, end - start);
- }
+ measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
+ bidiFlags);
}
@LayoutlibDelegate
- /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
+ /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width,
+ String languageTags, long[] hyphenators) {
Builder builder = sBuilderManager.getDelegate(nativeBuilder);
if (builder == null) {
return;
}
+ builder.mLocales = languageTags;
+ builder.mNativeHyphenators = hyphenators;
builder.mWidths[start] = width;
Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
}
diff --git a/android/text/TextViewSetTextMeasurePerfTest.java b/android/text/TextViewSetTextMeasurePerfTest.java
new file mode 100644
index 00000000..a2bf33e1
--- /dev/null
+++ b/android/text/TextViewSetTextMeasurePerfTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.text;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.widget.TextView;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+
+/**
+ * Performance test for multi line, single style {@link StaticLayout} creation/draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class TextViewSetTextMeasurePerfTest {
+
+ private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Parameterized.Parameters(name = "cached={3},{1} chars,{0}")
+ public static Collection cases() {
+ final List<Object[]> params = new ArrayList<>();
+ for (int length : new int[]{128}) {
+ for (boolean cached : BOOLEANS) {
+ for (TextType textType : new TextType[]{TextType.STRING,
+ TextType.SPANNABLE_BUILDER}) {
+ params.add(new Object[]{textType.name(), length, textType, cached});
+ }
+ }
+ }
+ return params;
+ }
+
+ private final int mLineWidth;
+ private final int mLength;
+ private final TextType mTextType;
+ private final boolean mCached;
+ private final TextPaint mTextPaint;
+
+ public TextViewSetTextMeasurePerfTest(String label, int length, TextType textType,
+ boolean cached) {
+ mLength = length;
+ mTextType = textType;
+ mCached = cached;
+ mTextPaint = new TextPaint();
+ mTextPaint.setTextSize(10);
+ mLineWidth = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Measures the time to setText and measure for a {@link TextView}.
+ */
+ @Test
+ public void timeCreate() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final CharSequence text = createRandomText(mLength);
+ final TextView textView = new TextView(InstrumentationRegistry.getTargetContext());
+ textView.setText(text);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+ state.pauseTiming();
+ textView.setTextLocale(Locale.UK);
+ textView.setTextLocale(Locale.US);
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ textView.setText(text);
+ textView.measure(AT_MOST | mLineWidth, UNSPECIFIED);
+ }
+ }
+
+ /**
+ * Measures the time to draw for a {@link TextView}.
+ */
+ @Test
+ public void timeDraw() throws Exception {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ state.pauseTiming();
+ Canvas.freeTextLayoutCaches();
+ final RenderNode node = RenderNode.create("benchmark", null);
+ final CharSequence text = createRandomText(mLength);
+ final TextView textView = new TextView(InstrumentationRegistry.getTargetContext());
+ textView.setText(text);
+ state.resumeTiming();
+
+ while (state.keepRunning()) {
+
+ state.pauseTiming();
+ final DisplayListCanvas canvas = node.start(1200, 200);
+ int save = canvas.save();
+ textView.setTextLocale(Locale.UK);
+ textView.setTextLocale(Locale.US);
+ if (!mCached) Canvas.freeTextLayoutCaches();
+ state.resumeTiming();
+
+ textView.draw(canvas);
+
+ state.pauseTiming();
+ canvas.restoreToCount(save);
+ node.end(canvas);
+ state.resumeTiming();
+ }
+ }
+
+ private CharSequence createRandomText(int length) {
+ return new NonEditableTextGenerator(new Random(0))
+ .setSequenceLength(length)
+ .setCreateBoring(false)
+ .setTextType(mTextType)
+ .build();
+ }
+}
diff --git a/android/text/format/Formatter.java b/android/text/format/Formatter.java
index fc564552..2c83fc4d 100644
--- a/android/text/format/Formatter.java
+++ b/android/text/format/Formatter.java
@@ -32,6 +32,7 @@ import android.text.BidiFormatter;
import android.text.TextUtils;
import android.view.View;
+import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.util.Locale;
@@ -194,13 +195,29 @@ public final class Formatter {
/**
* ICU doesn't support PETABYTE yet. Fake it so that we can treat all units the same way.
- * {@hide}
*/
- public static final MeasureUnit PETABYTE = MeasureUnit.internalGetInstance(
- "digital", "petabyte");
+ private static final MeasureUnit PETABYTE = createPetaByte();
- /** {@hide} */
- public static class RoundedBytesResult {
+ /**
+ * Create a petabyte MeasureUnit without registering it with ICU.
+ * ICU doesn't support user-create MeasureUnit and the only public (but hidden) method to do so
+ * is {@link MeasureUnit#internalGetInstance(String, String)} which also registers the unit as
+ * an available type and thus leaks it to code that doesn't expect or support it.
+ * <p>This method uses reflection to create an instance of MeasureUnit to avoid leaking it. This
+ * instance is <b>only</b> to be used in this class.
+ */
+ private static MeasureUnit createPetaByte() {
+ try {
+ Constructor<MeasureUnit> constructor = MeasureUnit.class
+ .getDeclaredConstructor(String.class, String.class);
+ constructor.setAccessible(true);
+ return constructor.newInstance("digital", "petabyte");
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Failed to create petabyte MeasureUnit", e);
+ }
+ }
+
+ private static class RoundedBytesResult {
public final float value;
public final MeasureUnit units;
public final int fractionDigits;
@@ -218,7 +235,7 @@ public final class Formatter {
* Returns a RoundedBytesResult object based on the input size in bytes and the rounding
* flags. The result can be used for formatting.
*/
- public static RoundedBytesResult roundBytes(long sizeBytes, int flags) {
+ static RoundedBytesResult roundBytes(long sizeBytes, int flags) {
final boolean isNegative = (sizeBytes < 0);
float result = isNegative ? -sizeBytes : sizeBytes;
MeasureUnit units = MeasureUnit.BYTE;
diff --git a/android/transition/TransitionUtils.java b/android/transition/TransitionUtils.java
index 4951237e..084b79d5 100644
--- a/android/transition/TransitionUtils.java
+++ b/android/transition/TransitionUtils.java
@@ -101,7 +101,7 @@ public class TransitionUtils {
ImageView copy = new ImageView(view.getContext());
copy.setScaleType(ImageView.ScaleType.CENTER_CROP);
- Bitmap bitmap = createViewBitmap(view, matrix, bounds);
+ Bitmap bitmap = createViewBitmap(view, matrix, bounds, sceneRoot);
if (bitmap != null) {
copy.setImageBitmap(bitmap);
}
@@ -115,7 +115,7 @@ public class TransitionUtils {
/**
* Get a copy of bitmap of given drawable, return null if intrinsic size is zero
*/
- public static Bitmap createDrawableBitmap(Drawable drawable) {
+ public static Bitmap createDrawableBitmap(Drawable drawable, View hostView) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
if (width <= 0 || height <= 0) {
@@ -128,7 +128,7 @@ public class TransitionUtils {
}
int bitmapWidth = (int) (width * scale);
int bitmapHeight = (int) (height * scale);
- final RenderNode node = RenderNode.create("TransitionUtils", null);
+ final RenderNode node = RenderNode.create("TransitionUtils", hostView);
node.setLeftTopRightBottom(0, 0, width, height);
node.setClipToBounds(false);
final DisplayListCanvas canvas = node.start(width, height);
@@ -156,20 +156,30 @@ public class TransitionUtils {
* returning.
* @param bounds The bounds of the bitmap in the destination coordinate system (where the
* view should be presented. Typically, this is matrix.mapRect(viewBounds);
+ * @param sceneRoot A ViewGroup that is attached to the window to temporarily contain the view
+ * if it isn't attached to the window.
* @return A bitmap of the given view or null if bounds has no width or height.
*/
- public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds) {
+ public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds,
+ ViewGroup sceneRoot) {
+ final boolean addToOverlay = !view.isAttachedToWindow();
+ if (addToOverlay) {
+ if (sceneRoot == null || !sceneRoot.isAttachedToWindow()) {
+ return null;
+ }
+ sceneRoot.getOverlay().add(view);
+ }
Bitmap bitmap = null;
int bitmapWidth = Math.round(bounds.width());
int bitmapHeight = Math.round(bounds.height());
if (bitmapWidth > 0 && bitmapHeight > 0) {
- float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
+ float scale = Math.min(1f, ((float) MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
bitmapWidth *= scale;
bitmapHeight *= scale;
matrix.postTranslate(-bounds.left, -bounds.top);
matrix.postScale(scale, scale);
- final RenderNode node = RenderNode.create("TransitionUtils", null);
+ final RenderNode node = RenderNode.create("TransitionUtils", view);
node.setLeftTopRightBottom(0, 0, bitmapWidth, bitmapHeight);
node.setClipToBounds(false);
final DisplayListCanvas canvas = node.start(bitmapWidth, bitmapHeight);
@@ -178,6 +188,9 @@ public class TransitionUtils {
node.end(canvas);
bitmap = ThreadedRenderer.createHardwareBitmap(node, bitmapWidth, bitmapHeight);
}
+ if (addToOverlay) {
+ sceneRoot.getOverlay().remove(view);
+ }
return bitmap;
}
diff --git a/android/util/FeatureFlagUtils.java b/android/util/FeatureFlagUtils.java
index 5838f959..fc1d4873 100644
--- a/android/util/FeatureFlagUtils.java
+++ b/android/util/FeatureFlagUtils.java
@@ -50,6 +50,13 @@ public class FeatureFlagUtils {
}
/**
+ * Override feature flag to new state.
+ */
+ public static void setEnabled(String feature, boolean enabled) {
+ SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
+ }
+
+ /**
* Returns all feature flags in their raw form.
*/
public static Map<String, String> getAllFeatureFlags() {
diff --git a/android/util/Log.java b/android/util/Log.java
index 8691136d..02998653 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -392,7 +392,7 @@ public final class Log {
// 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 = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
+ int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD // Base.
- 2 // Two terminators.
- (tag != null ? tag.length() : 0) // Tag length.
- 32; // Some slack.
@@ -429,10 +429,10 @@ public final class Log {
}
/**
- * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
+ * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
* a JNI call during logging.
*/
- static class NoPreloadHolder {
+ static class PreloadHolder {
public final static int LOGGER_ENTRY_MAX_PAYLOAD =
logger_entry_max_payload_native();
}
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 52086065..40154880 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,10 +20,6 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
- * BEGIN LAYOUTLIB CHANGE
- * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
- * END LAYOUTLIB CHANGE
- *
* A cache that holds strong references to a limited number of values. Each time
* a value is accessed, it is moved to the head of a queue. When a value is
* added to a full cache, the value at the end of that queue is evicted and may
@@ -91,9 +87,8 @@ public class LruCache<K, V> {
/**
* Sets the size of the cache.
- * @param maxSize The new maximum size.
*
- * @hide
+ * @param maxSize The new maximum size.
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
@@ -190,10 +185,13 @@ public class LruCache<K, V> {
}
/**
+ * Remove the eldest entries until the total of remaining entries is at or
+ * below the requested size.
+ *
* @param maxSize the maximum size of the cache before returning. May be -1
- * to evict even 0-sized elements.
+ * to evict even 0-sized elements.
*/
- private void trimToSize(int maxSize) {
+ public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
@@ -207,16 +205,7 @@ public class LruCache<K, V> {
break;
}
- // BEGIN LAYOUTLIB CHANGE
- // get the last item in the linked list.
- // This is not efficient, the goal here is to minimize the changes
- // compared to the platform version.
- Map.Entry<K, V> toEvict = null;
- for (Map.Entry<K, V> entry : map.entrySet()) {
- toEvict = entry;
- }
- // END LAYOUTLIB CHANGE
-
+ Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
diff --git a/android/util/StatsLogKey.java b/android/util/StatsLogKey.java
new file mode 100644
index 00000000..9ad0a23d
--- /dev/null
+++ b/android/util/StatsLogKey.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+// THIS FILE IS AUTO-GENERATED.
+// DO NOT MODIFY.
+
+package android.util;
+
+/** @hide */
+public class StatsLogKey {
+ private StatsLogKey() {}
+
+ /** Constants for android.os.statsd.ScreenStateChange. */
+
+ /** display_state */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE = 1;
+
+ /** Constants for android.os.statsd.ProcessStateChange. */
+
+ /** state */
+ public static final int PROCESS_STATE_CHANGE__STATE = 1;
+
+ /** uid */
+ public static final int PROCESS_STATE_CHANGE__UID = 2;
+
+ /** package_name */
+ public static final int PROCESS_STATE_CHANGE__PACKAGE_NAME = 1002;
+
+ /** package_version */
+ public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION = 3;
+
+ /** package_version_string */
+ public static final int PROCESS_STATE_CHANGE__PACKAGE_VERSION_STRING = 4;
+
+}
diff --git a/android/util/StatsLogTag.java b/android/util/StatsLogTag.java
new file mode 100644
index 00000000..5e5a8287
--- /dev/null
+++ b/android/util/StatsLogTag.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+// THIS FILE IS AUTO-GENERATED.
+// DO NOT MODIFY.
+
+package android.util;
+
+/** @hide */
+public class StatsLogTag {
+ private StatsLogTag() {}
+
+ /** android.os.statsd.ScreenStateChange. */
+ public static final int SCREEN_STATE_CHANGE = 2;
+
+ /** android.os.statsd.ProcessStateChange. */
+ public static final int PROCESS_STATE_CHANGE = 1112;
+
+}
diff --git a/android/util/StatsLogValue.java b/android/util/StatsLogValue.java
new file mode 100644
index 00000000..05b9d933
--- /dev/null
+++ b/android/util/StatsLogValue.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.
+ */
+
+// THIS FILE IS AUTO-GENERATED.
+// DO NOT MODIFY.
+
+package android.util;
+
+/** @hide */
+public class StatsLogValue {
+ private StatsLogValue() {}
+
+ /** Constants for android.os.statsd.ScreenStateChange. */
+
+ /** display_state: STATE_UNKNOWN */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_UNKNOWN = 0;
+
+ /** display_state: STATE_OFF */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF = 1;
+
+ /** display_state: STATE_ON */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON = 2;
+
+ /** display_state: STATE_DOZE */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE = 3;
+
+ /** display_state: STATE_DOZE_SUSPEND */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_DOZE_SUSPEND = 4;
+
+ /** display_state: STATE_VR */
+ public static final int SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_VR = 5;
+
+ /** Constants for android.os.statsd.ProcessStateChange. */
+
+ /** state: START */
+ public static final int PROCESS_STATE_CHANGE__STATE__START = 1;
+
+ /** state: CRASH */
+ public static final int PROCESS_STATE_CHANGE__STATE__CRASH = 2;
+
+}
diff --git a/android/util/proto/ProtoOutputStream.java b/android/util/proto/ProtoOutputStream.java
index 9afa56dd..43a97897 100644
--- a/android/util/proto/ProtoOutputStream.java
+++ b/android/util/proto/ProtoOutputStream.java
@@ -29,8 +29,8 @@ import java.io.UnsupportedEncodingException;
* Class to write to a protobuf stream.
*
* Each write method takes an ID code from the protoc generated classes
- * and the value to write. To make a nested object, call startObject
- * and then endObject when you are done.
+ * and the value to write. To make a nested object, call #start
+ * and then #end when you are done.
*
* The ID codes have type information embedded into them, so if you call
* the incorrect function you will get an IllegalArgumentException.
@@ -60,16 +60,16 @@ import java.io.UnsupportedEncodingException;
* Message objects. We need to find another way.
*
* So what we do here is to let the calling code write the data into a
- * byte[] (actually a collection of them wrapped in the EncodedBuffer) class,
+ * byte[] (actually a collection of them wrapped in the EncodedBuffer class),
* but not do the varint encoding of the sub-message sizes. Then, we do a
* recursive traversal of the buffer itself, calculating the sizes (which are
* then knowable, although still not the actual sizes in the buffer because of
* possible further nesting). Then we do a third pass, compacting the
* buffer and varint encoding the sizes.
*
- * This gets us a relatively small number number of fixed-size allocations,
+ * This gets us a relatively small number of fixed-size allocations,
* which is less likely to cause memory fragmentation or churn the GC, and
- * the same number of data copies as would have gotten with setting it
+ * the same number of data copies as we would have gotten with setting it
* field-by-field in generated code, and no code bloat from generated code.
* The final data copy is also done with System.arraycopy, which will be
* more efficient, in general, than doing the individual fields twice (as in
@@ -77,26 +77,26 @@ import java.io.UnsupportedEncodingException;
*
* To accomplish the multiple passes, whenever we write a
* WIRE_TYPE_LENGTH_DELIMITED field, we write the size occupied in our
- * buffer as a fixed 32 bit int (called childRawSize), not variable length
+ * buffer as a fixed 32 bit int (called childRawSize), not a variable length
* one. We reserve another 32 bit slot for the computed size (called
* childEncodedSize). If we know the size up front, as we do for strings
* and byte[], then we also put that into childEncodedSize, if we don't, we
- * write the negative of childRawSize, as a sentiel that we need to
+ * write the negative of childRawSize, as a sentinel that we need to
* compute it during the second pass and recursively compact it during the
* third pass.
*
- * Unsgigned size varints can be up to five bytes long, but we reserve eight
+ * Unsigned size varints can be up to five bytes long, but we reserve eight
* bytes for overhead, so we know that when we compact the buffer, there
* will always be space for the encoded varint.
*
* When we can figure out the size ahead of time, we do, in order
* to save overhead with recalculating it, and with the later arraycopy.
*
- * During the period between when the caller has called startObject, but
- * not yet called endObject, we maintain a linked list of the tokens
- * returned by startObject, stored in those 8 bytes of size storage space.
+ * During the period between when the caller has called #start, but
+ * not yet called #end, we maintain a linked list of the tokens
+ * returned by #start, stored in those 8 bytes of size storage space.
* We use that linked list of tokens to ensure that the caller has
- * correctly matched pairs of startObject and endObject calls, and issue
+ * correctly matched pairs of #start and #end calls, and issue
* errors if they are not matched.
*/
@TestApi
@@ -2375,6 +2375,9 @@ public final class ProtoOutputStream {
if (countString == null) {
countString = "fieldCount=" + fieldCount;
}
+ if (countString.length() > 0) {
+ countString += " ";
+ }
final long fieldType = fieldId & FIELD_TYPE_MASK;
String typeString = getFieldTypeString(fieldType);
@@ -2382,7 +2385,7 @@ public final class ProtoOutputStream {
typeString = "fieldType=" + fieldType;
}
- return fieldCount + " " + typeString + " tag=" + ((int)fieldId)
+ return countString + typeString + " tag=" + ((int) fieldId)
+ " fieldId=0x" + Long.toHexString(fieldId);
}
diff --git a/android/util/proto/ProtoUtils.java b/android/util/proto/ProtoUtils.java
new file mode 100644
index 00000000..449baca5
--- /dev/null
+++ b/android/util/proto/ProtoUtils.java
@@ -0,0 +1,39 @@
+/*
+ * 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.proto;
+
+import android.util.AggStats;
+
+/**
+ * This class contains a list of helper functions to write common proto in
+ * //frameworks/base/core/proto/android/base directory
+ */
+public class ProtoUtils {
+
+ /**
+ * Dump AggStats to ProtoOutputStream
+ * @hide
+ */
+ public static void toAggStatsProto(ProtoOutputStream proto, long fieldId,
+ long min, long average, long max) {
+ final long aggStatsToken = proto.start(fieldId);
+ proto.write(AggStats.MIN, min);
+ proto.write(AggStats.AVERAGE, average);
+ proto.write(AggStats.MAX, max);
+ proto.end(aggStatsToken);
+ }
+}
diff --git a/android/view/DragEvent.java b/android/view/DragEvent.java
index 16f2d7d0..2c9f8712 100644
--- a/android/view/DragEvent.java
+++ b/android/view/DragEvent.java
@@ -103,7 +103,7 @@ import com.android.internal.view.IDragAndDropPermissions;
* <tr>
* <td>ACTION_DRAG_ENDED</td>
* <td style="text-align: center;">&nbsp;</td>
- * <td style="text-align: center;">&nbsp;</td>
+ * <td style="text-align: center;">X</td>
* <td style="text-align: center;">&nbsp;</td>
* <td style="text-align: center;">&nbsp;</td>
* <td style="text-align: center;">&nbsp;</td>
@@ -112,6 +112,7 @@ import com.android.internal.view.IDragAndDropPermissions;
* </table>
* <p>
* The {@link android.view.DragEvent#getAction()},
+ * {@link android.view.DragEvent#getLocalState()}
* {@link android.view.DragEvent#describeContents()},
* {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and
* {@link android.view.DragEvent#toString()} methods always return valid data.
@@ -397,7 +398,7 @@ public class DragEvent implements Parcelable {
* operation. In all other activities this method will return null
* </p>
* <p>
- * This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
+ * This method returns valid data for all event actions.
* </p>
* @return The local state object sent to the system by startDragAndDrop().
*/
diff --git a/android/view/Gravity.java b/android/view/Gravity.java
index 324a1ae3..232ff255 100644
--- a/android/view/Gravity.java
+++ b/android/view/Gravity.java
@@ -440,4 +440,57 @@ public class Gravity
}
return result;
}
+
+ /**
+ * @hide
+ */
+ public static String toString(int gravity) {
+ final StringBuilder result = new StringBuilder();
+ if ((gravity & FILL) != 0) {
+ result.append("FILL").append(' ');
+ } else {
+ if ((gravity & FILL_VERTICAL) != 0) {
+ result.append("FILL_VERTICAL").append(' ');
+ } else {
+ if ((gravity & TOP) != 0) {
+ result.append("TOP").append(' ');
+ }
+ if ((gravity & BOTTOM) != 0) {
+ result.append("BOTTOM").append(' ');
+ }
+ }
+ if ((gravity & FILL_HORIZONTAL) != 0) {
+ result.append("FILL_HORIZONTAL").append(' ');
+ } else {
+ if ((gravity & START) != 0) {
+ result.append("START").append(' ');
+ } else if ((gravity & LEFT) != 0) {
+ result.append("LEFT").append(' ');
+ }
+ if ((gravity & END) != 0) {
+ result.append("END").append(' ');
+ } else if ((gravity & RIGHT) != 0) {
+ result.append("RIGHT").append(' ');
+ }
+ }
+ }
+ if ((gravity & CENTER) != 0) {
+ result.append("CENTER").append(' ');
+ } else {
+ if ((gravity & CENTER_VERTICAL) != 0) {
+ result.append("CENTER_VERTICAL").append(' ');
+ }
+ if ((gravity & CENTER_HORIZONTAL) != 0) {
+ result.append("CENTER_HORIZONTAL").append(' ');
+ }
+ }
+ if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+ result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+ }
+ if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+ result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+ }
+ result.deleteCharAt(result.length() - 1);
+ return result.toString();
+ }
}
diff --git a/android/view/MenuInflater_Delegate.java b/android/view/MenuInflater_Delegate.java
index 08a97d64..977a2a72 100644
--- a/android/view/MenuInflater_Delegate.java
+++ b/android/view/MenuInflater_Delegate.java
@@ -42,7 +42,6 @@ import android.util.AttributeSet;
* ViewInfo}, we check the corresponding view key in the menu item for the view and add it
*/
public class MenuInflater_Delegate {
-
@LayoutlibDelegate
/*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
AttributeSet attrs) {
@@ -56,10 +55,15 @@ public class MenuInflater_Delegate {
return;
}
}
- // This means that Bridge did not take over the instantiation of some object properly.
- // This is most likely a bug in the LayoutLib code.
- Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
- "Action Bar Menu rendering may be incorrect.", null);
+
+ if (menuItem == null || !menuItem.getClass().getName().startsWith("android.support.")) {
+ // This means that Bridge did not take over the instantiation of some object properly.
+ // This is most likely a bug in the LayoutLib code.
+ // We suppress this error for AppCompat menus since we do not support them in the menu
+ // editor yet.
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Action Bar Menu rendering may be incorrect.", null);
+ }
}
diff --git a/android/view/Surface.java b/android/view/Surface.java
index 2c1f7346..ddced6cd 100644
--- a/android/view/Surface.java
+++ b/android/view/Surface.java
@@ -762,7 +762,7 @@ public class Surface implements Parcelable {
return "ROTATION_270";
}
default: {
- throw new IllegalArgumentException("Invalid rotation: " + rotation);
+ return Integer.toString(rotation);
}
}
}
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index 91932dd4..31daefff 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -18,6 +18,7 @@ package android.view;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import android.annotation.Size;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
@@ -65,6 +66,7 @@ public class SurfaceControl {
private static native void nativeSetSize(long nativeObject, int w, int h);
private static native void nativeSetTransparentRegionHint(long nativeObject, Region region);
private static native void nativeSetAlpha(long nativeObject, float alpha);
+ private static native void nativeSetColor(long nativeObject, float[] color);
private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx,
float dtdy, float dsdy);
private static native void nativeSetFlags(long nativeObject, int flags, int mask);
@@ -105,8 +107,8 @@ public class SurfaceControl {
long surfaceObject, long frame);
private static native void nativeReparentChildren(long nativeObject,
IBinder handle);
- private static native void nativeReparentChild(long nativeObject,
- IBinder parentHandle, IBinder childHandle);
+ private static native void nativeReparent(long nativeObject,
+ IBinder parentHandle);
private static native void nativeSeverChildren(long nativeObject);
private static native void nativeSetOverrideScalingMode(long nativeObject,
int scalingMode);
@@ -455,9 +457,9 @@ public class SurfaceControl {
nativeReparentChildren(mNativeObject, newParentHandle);
}
- /** Re-parents a specific child layer to a new parent */
- public void reparentChild(IBinder newParentHandle, IBinder childHandle) {
- nativeReparentChild(mNativeObject, newParentHandle, childHandle);
+ /** Re-parents this layer to a new parent. */
+ public void reparent(IBinder newParentHandle) {
+ nativeReparent(mNativeObject, newParentHandle);
}
public void detachChildren() {
@@ -552,6 +554,15 @@ public class SurfaceControl {
nativeSetAlpha(mNativeObject, alpha);
}
+ /**
+ * Sets a color for the Surface.
+ * @param color A float array with three values to represent r, g, b in range [0..1]
+ */
+ public void setColor(@Size(3) float[] color) {
+ checkNotReleased();
+ nativeSetColor(mNativeObject, color);
+ }
+
public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
checkNotReleased();
nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index ebb2af45..462dad3f 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,115 +16,1208 @@
package android.view;
-import com.android.layoutlib.bridge.MockView;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
import android.content.Context;
+import android.content.res.CompatibilityInfo.Translator;
+import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.view.SurfaceCallbackHelper;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
/**
- * Mock version of the SurfaceView.
- * Only non override public methods from the real SurfaceView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ * Provides a dedicated drawing surface embedded inside of a view hierarchy.
+ * You can control the format of this surface and, if you like, its size; the
+ * SurfaceView takes care of placing the surface at the correct location on the
+ * screen
+ *
+ * <p>The surface is Z ordered so that it is behind the window holding its
+ * SurfaceView; the SurfaceView punches a hole in its window to allow its
+ * surface to be displayed. The view hierarchy will take care of correctly
+ * compositing with the Surface any siblings of the SurfaceView that would
+ * normally appear on top of it. This can be used to place overlays such as
+ * buttons on top of the Surface, though note however that it can have an
+ * impact on performance since a full alpha-blended composite will be performed
+ * each time the Surface changes.
+ *
+ * <p> The transparent region that makes the surface visible is based on the
+ * layout positions in the view hierarchy. If the post-layout transform
+ * properties are used to draw a sibling view on top of the SurfaceView, the
+ * view may not be properly composited with the surface.
*
- * TODO: generate automatically.
+ * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
+ * which can be retrieved by calling {@link #getHolder}.
*
+ * <p>The Surface will be created for you while the SurfaceView's window is
+ * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
+ * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
+ * Surface is created and destroyed as the window is shown and hidden.
+ *
+ * <p>One of the purposes of this class is to provide a surface in which a
+ * secondary thread can render into the screen. If you are going to use it
+ * this way, you need to be aware of some threading semantics:
+ *
+ * <ul>
+ * <li> All SurfaceView and
+ * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
+ * from the thread running the SurfaceView's window (typically the main thread
+ * of the application). They thus need to correctly synchronize with any
+ * state that is also touched by the drawing thread.
+ * <li> You must ensure that the drawing thread only touches the underlying
+ * Surface while it is valid -- between
+ * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
+ * and
+ * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
+ * </ul>
+ *
+ * <p class="note"><strong>Note:</strong> Starting in platform version
+ * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
+ * updated synchronously with other View rendering. This means that translating
+ * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
+ * artifacts may occur on previous versions of the platform when its window is
+ * positioned asynchronously.</p>
*/
-public class SurfaceView extends MockView {
+public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
+ private static final String TAG = "SurfaceView";
+ private static final boolean DEBUG = false;
+
+ final ArrayList<SurfaceHolder.Callback> mCallbacks
+ = new ArrayList<SurfaceHolder.Callback>();
+
+ final int[] mLocation = new int[2];
+
+ final ReentrantLock mSurfaceLock = new ReentrantLock();
+ final Surface mSurface = new Surface(); // Current surface in use
+ boolean mDrawingStopped = true;
+ // We use this to track if the application has produced a frame
+ // in to the Surface. Up until that point, we should be careful not to punch
+ // holes.
+ boolean mDrawFinished = false;
+
+ final Rect mScreenRect = new Rect();
+ SurfaceSession mSurfaceSession;
+
+ SurfaceControl mSurfaceControl;
+ // In the case of format changes we switch out the surface in-place
+ // we need to preserve the old one until the new one has drawn.
+ SurfaceControl mDeferredDestroySurfaceControl;
+ final Rect mTmpRect = new Rect();
+ final Configuration mConfiguration = new Configuration();
+
+ int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+
+ boolean mIsCreating = false;
+ private volatile boolean mRtHandlingPositionUpdates = false;
+
+ private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+ = new ViewTreeObserver.OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ updateSurface();
+ }
+ };
+
+ private final ViewTreeObserver.OnPreDrawListener mDrawListener =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ // reposition ourselves where the surface is
+ mHaveFrame = getWidth() > 0 && getHeight() > 0;
+ updateSurface();
+ return true;
+ }
+ };
+
+ boolean mRequestedVisible = false;
+ boolean mWindowVisibility = false;
+ boolean mLastWindowVisibility = false;
+ boolean mViewVisibility = false;
+ boolean mWindowStopped = false;
+
+ int mRequestedWidth = -1;
+ int mRequestedHeight = -1;
+ /* Set SurfaceView's format to 565 by default to maintain backward
+ * compatibility with applications assuming this format.
+ */
+ int mRequestedFormat = PixelFormat.RGB_565;
+
+ boolean mHaveFrame = false;
+ boolean mSurfaceCreated = false;
+ long mLastLockTime = 0;
+
+ boolean mVisible = false;
+ int mWindowSpaceLeft = -1;
+ int mWindowSpaceTop = -1;
+ int mSurfaceWidth = -1;
+ int mSurfaceHeight = -1;
+ int mFormat = -1;
+ final Rect mSurfaceFrame = new Rect();
+ int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
+ private Translator mTranslator;
+
+ private boolean mGlobalListenersAdded;
+ private boolean mAttachedToWindow;
+
+ private int mSurfaceFlags = SurfaceControl.HIDDEN;
+
+ private int mPendingReportDraws;
public SurfaceView(Context context) {
this(context, null);
}
public SurfaceView(Context context, AttributeSet attrs) {
- this(context, attrs , 0);
+ this(context, attrs, 0);
}
- public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mRenderNode.requestPositionUpdates(this);
+
+ setWillNotDraw(true);
+ }
+
+ /**
+ * Return the SurfaceHolder providing access and control over this
+ * SurfaceView's underlying surface.
+ *
+ * @return SurfaceHolder The holder of the surface.
+ */
+ public SurfaceHolder getHolder() {
+ return mSurfaceHolder;
+ }
+
+ private void updateRequestedVisibility() {
+ mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
+ }
+
+ /** @hide */
+ @Override
+ public void windowStopped(boolean stopped) {
+ mWindowStopped = stopped;
+ updateRequestedVisibility();
+ updateSurface();
}
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ getViewRootImpl().addWindowStoppedCallback(this);
+ mWindowStopped = false;
+
+ mViewVisibility = getVisibility() == VISIBLE;
+ updateRequestedVisibility();
+
+ mAttachedToWindow = true;
+ mParent.requestTransparentRegion(SurfaceView.this);
+ if (!mGlobalListenersAdded) {
+ ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnScrollChangedListener(mScrollChangedListener);
+ observer.addOnPreDrawListener(mDrawListener);
+ mGlobalListenersAdded = true;
+ }
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ mWindowVisibility = visibility == VISIBLE;
+ updateRequestedVisibility();
+ updateSurface();
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ mViewVisibility = visibility == VISIBLE;
+ boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
+ if (newRequestedVisible != mRequestedVisible) {
+ // our base class (View) invalidates the layout only when
+ // we go from/to the GONE state. However, SurfaceView needs
+ // to request a re-layout when the visibility changes at all.
+ // This is needed because the transparent region is computed
+ // as part of the layout phase, and it changes (obviously) when
+ // the visibility changes.
+ requestLayout();
+ }
+ mRequestedVisible = newRequestedVisible;
+ updateSurface();
+ }
+
+ private void performDrawFinished() {
+ if (mPendingReportDraws > 0) {
+ mDrawFinished = true;
+ if (mAttachedToWindow) {
+ notifyDrawFinished();
+ invalidate();
+ }
+ } else {
+ Log.e(TAG, System.identityHashCode(this) + "finished drawing"
+ + " but no pending report draw (extra call"
+ + " to draw completion runnable?)");
+ }
+ }
+
+ void notifyDrawFinished() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.pendingDrawFinished();
+ }
+ mPendingReportDraws--;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ // It's possible to create a SurfaceView using the default constructor and never
+ // attach it to a view hierarchy, this is a common use case when dealing with
+ // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
+ // the lifecycle. Instead of attaching it to a view, he/she can just pass
+ // the SurfaceHolder forward, most live wallpapers do it.
+ if (viewRoot != null) {
+ viewRoot.removeWindowStoppedCallback(this);
+ }
+
+ mAttachedToWindow = false;
+ if (mGlobalListenersAdded) {
+ ViewTreeObserver observer = getViewTreeObserver();
+ observer.removeOnScrollChangedListener(mScrollChangedListener);
+ observer.removeOnPreDrawListener(mDrawListener);
+ mGlobalListenersAdded = false;
+ }
+
+ while (mPendingReportDraws > 0) {
+ notifyDrawFinished();
+ }
+
+ mRequestedVisible = false;
+
+ updateSurface();
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
+ }
+ mSurfaceControl = null;
+
+ mHaveFrame = false;
+
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = mRequestedWidth >= 0
+ ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
+ : getDefaultSize(0, widthMeasureSpec);
+ int height = mRequestedHeight >= 0
+ ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
+ : getDefaultSize(0, heightMeasureSpec);
+ setMeasuredDimension(width, height);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean result = super.setFrame(left, top, right, bottom);
+ updateSurface();
+ return result;
+ }
+
+ @Override
public boolean gatherTransparentRegion(Region region) {
- return false;
+ if (isAboveParent() || !mDrawFinished) {
+ return super.gatherTransparentRegion(region);
+ }
+
+ boolean opaque = true;
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+ // this view draws, remove it from the transparent region
+ opaque = super.gatherTransparentRegion(region);
+ } else if (region != null) {
+ int w = getWidth();
+ int h = getHeight();
+ if (w>0 && h>0) {
+ getLocationInWindow(mLocation);
+ // otherwise, punch a hole in the whole hierarchy
+ int l = mLocation[0];
+ int t = mLocation[1];
+ region.op(l, t, l+w, t+h, Region.Op.UNION);
+ }
+ }
+ if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ opaque = false;
+ }
+ return opaque;
}
+ @Override
+ public void draw(Canvas canvas) {
+ if (mDrawFinished && !isAboveParent()) {
+ // draw() is not called when SKIP_DRAW is set
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+ // punch a whole in the view-hierarchy below us
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ }
+ }
+ super.draw(canvas);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mDrawFinished && !isAboveParent()) {
+ // draw() is not called when SKIP_DRAW is set
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ // punch a whole in the view-hierarchy below us
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ }
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ /**
+ * Control whether the surface view's surface is placed on top of another
+ * regular surface view in the window (but still behind the window itself).
+ * This is typically used to place overlays on top of an underlying media
+ * surface view.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+ */
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+ mSubLayer = isMediaOverlay
+ ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
}
+ /**
+ * Control whether the surface view's surface is placed on top of its
+ * window. Normally it is placed behind the window, to allow it to
+ * (for the most part) appear to composite with the views in the
+ * hierarchy. By setting this, you cause it to be placed above the
+ * window. This means that none of the contents of the window this
+ * SurfaceView is in will be visible on top of its surface.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+ */
public void setZOrderOnTop(boolean onTop) {
+ if (onTop) {
+ mSubLayer = APPLICATION_PANEL_SUBLAYER;
+ } else {
+ mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+ }
}
+ /**
+ * Control whether the surface view's content should be treated as secure,
+ * preventing it from appearing in screenshots or from being viewed on
+ * non-secure displays.
+ *
+ * <p>Note that this must be set before the surface view's containing
+ * window is attached to the window manager.
+ *
+ * <p>See {@link android.view.Display#FLAG_SECURE} for details.
+ *
+ * @param isSecure True if the surface view is secure.
+ */
public void setSecure(boolean isSecure) {
+ if (isSecure) {
+ mSurfaceFlags |= SurfaceControl.SECURE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.SECURE;
+ }
}
- public SurfaceHolder getHolder() {
- return mSurfaceHolder;
+ private void updateOpaqueFlag() {
+ if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
+ mSurfaceFlags |= SurfaceControl.OPAQUE;
+ } else {
+ mSurfaceFlags &= ~SurfaceControl.OPAQUE;
+ }
}
- private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ private Rect getParentSurfaceInsets() {
+ final ViewRootImpl root = getViewRootImpl();
+ if (root == null) {
+ return null;
+ } else {
+ return root.mWindowAttributes.surfaceInsets;
+ }
+ }
+
+ /** @hide */
+ protected void updateSurface() {
+ if (!mHaveFrame) {
+ return;
+ }
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+ return;
+ }
+
+ mTranslator = viewRoot.mTranslator;
+ if (mTranslator != null) {
+ mSurface.setCompatibilityTranslator(mTranslator);
+ }
+
+ int myWidth = mRequestedWidth;
+ if (myWidth <= 0) myWidth = getWidth();
+ int myHeight = mRequestedHeight;
+ if (myHeight <= 0) myHeight = getHeight();
+
+ final boolean formatChanged = mFormat != mRequestedFormat;
+ final boolean visibleChanged = mVisible != mRequestedVisible;
+ final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
+ && mRequestedVisible;
+ final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
+ final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
+ boolean redrawNeeded = false;
+
+ if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+ getLocationInWindow(mLocation);
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Changes: creating=" + creating
+ + " format=" + formatChanged + " size=" + sizeChanged
+ + " visible=" + visibleChanged
+ + " left=" + (mWindowSpaceLeft != mLocation[0])
+ + " top=" + (mWindowSpaceTop != mLocation[1]));
+
+ try {
+ final boolean visible = mVisible = mRequestedVisible;
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ mSurfaceWidth = myWidth;
+ mSurfaceHeight = myHeight;
+ mFormat = mRequestedFormat;
+ mLastWindowVisibility = mWindowVisibility;
+
+ mScreenRect.left = mWindowSpaceLeft;
+ mScreenRect.top = mWindowSpaceTop;
+ mScreenRect.right = mWindowSpaceLeft + getWidth();
+ mScreenRect.bottom = mWindowSpaceTop + getHeight();
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ final Rect surfaceInsets = getParentSurfaceInsets();
+ mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+
+ if (creating) {
+ mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
+ mDeferredDestroySurfaceControl = mSurfaceControl;
+
+ updateOpaqueFlag();
+ mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
+ "SurfaceView - " + viewRoot.getTitle().toString(),
+ mSurfaceWidth, mSurfaceHeight, mFormat,
+ mSurfaceFlags);
+ } else if (mSurfaceControl == null) {
+ return;
+ }
+
+ boolean realSizeChanged = false;
+
+ mSurfaceLock.lock();
+ try {
+ mDrawingStopped = !visible;
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Cur surface: " + mSurface);
+
+ SurfaceControl.openTransaction();
+ try {
+ mSurfaceControl.setLayer(mSubLayer);
+ if (mViewVisibility) {
+ mSurfaceControl.show();
+ } else {
+ mSurfaceControl.hide();
+ }
+
+ // While creating the surface, we will set it's initial
+ // geometry. Outside of that though, we should generally
+ // leave it to the RenderThread.
+ //
+ // There is one more case when the buffer size changes we aren't yet
+ // prepared to sync (as even following the transaction applying
+ // we still need to latch a buffer).
+ // b/28866173
+ if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
+ mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
+ mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ mScreenRect.height() / (float) mSurfaceHeight);
+ }
+ if (sizeChanged) {
+ mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+ }
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+
+ if (sizeChanged || creating) {
+ redrawNeeded = true;
+ }
+
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (mTranslator == null) {
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
+ } else {
+ float appInvertedScale = mTranslator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+ }
+
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+ } finally {
+ mSurfaceLock.unlock();
+ }
+
+ try {
+ redrawNeeded |= visible && !mDrawFinished;
+
+ SurfaceHolder.Callback callbacks[] = null;
+
+ final boolean surfaceChanged = creating;
+ if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
+ mSurfaceCreated = false;
+ if (mSurface.isValid()) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceDestroyed");
+ callbacks = getSurfaceCallbacks();
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceDestroyed(mSurfaceHolder);
+ }
+ // Since Android N the same surface may be reused and given to us
+ // again by the system server at a later point. However
+ // as we didn't do this in previous releases, clients weren't
+ // necessarily required to clean up properly in
+ // surfaceDestroyed. This leads to problems for example when
+ // clients don't destroy their EGL context, and try
+ // and create a new one on the same surface following reuse.
+ // Since there is no valid use of the surface in-between
+ // surfaceDestroyed and surfaceCreated, we force a disconnect,
+ // so the next connect will always work if we end up reusing
+ // the surface.
+ if (mSurface.isValid()) {
+ mSurface.forceScopedDisconnect();
+ }
+ }
+ }
+
+ if (creating) {
+ mSurface.copyFrom(mSurfaceControl);
+ }
+
+ if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ mSurface.createFrom(mSurfaceControl);
+ }
+
+ if (visible && mSurface.isValid()) {
+ if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ mSurfaceCreated = true;
+ mIsCreating = true;
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceCreated");
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
+ }
+ if (creating || formatChanged || sizeChanged
+ || visibleChanged || realSizeChanged) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceChanged -- format=" + mFormat
+ + " w=" + myWidth + " h=" + myHeight);
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+ }
+ }
+ if (redrawNeeded) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceRedrawNeeded");
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+
+ mPendingReportDraws++;
+ viewRoot.drawPending();
+ SurfaceCallbackHelper sch =
+ new SurfaceCallbackHelper(this::onDrawFinished);
+ sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+ }
+ }
+ } finally {
+ mIsCreating = false;
+ if (mSurfaceControl != null && !mSurfaceCreated) {
+ mSurface.release();
+ // If we are not in the stopped state, then the destruction of the Surface
+ // represents a visual change we need to display, and we should go ahead
+ // and destroy the SurfaceControl. However if we are in the stopped state,
+ // we can just leave the Surface around so it can be a part of animations,
+ // and we let the life-time be tied to the parent surface.
+ if (!mWindowStopped) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ }
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ if (DEBUG) Log.v(
+ TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
+ + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
+ + ", frame=" + mSurfaceFrame);
+ } else {
+ // Calculate the window position in case RT loses the window
+ // and we need to fallback to a UI-thread driven position update
+ getLocationInSurface(mLocation);
+ final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
+ || mWindowSpaceTop != mLocation[1];
+ final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
+ || getHeight() != mScreenRect.height();
+ if (positionChanged || layoutSizeChanged) { // Only the position has changed
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
+ // in view local space.
+ mLocation[0] = getWidth();
+ mLocation[1] = getHeight();
+
+ mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
+ mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+ }
+
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
+ try {
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, -1);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ }
+ }
+ }
+ }
+
+ private void onDrawFinished() {
+ if (DEBUG) {
+ Log.i(TAG, System.identityHashCode(this) + " "
+ + "finishedDrawing");
+ }
+
+ if (mDeferredDestroySurfaceControl != null) {
+ mDeferredDestroySurfaceControl.destroy();
+ mDeferredDestroySurfaceControl = null;
+ }
+
+ runOnUiThread(() -> {
+ performDrawFinished();
+ });
+ }
+
+ private void setParentSpaceRectangle(Rect position, long frameNumber) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+
+ SurfaceControl.openTransaction();
+ try {
+ if (frameNumber > 0) {
+ mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
+ }
+ mSurfaceControl.setPosition(position.left, position.top);
+ mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ position.height() / (float) mSurfaceHeight);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+
+ private Rect mRTLastReportedPosition = new Rect();
+
+ /**
+ * Called by native by a Rendering Worker thread to update the window position
+ * @hide
+ */
+ public final void updateSurfacePosition_renderWorker(long frameNumber,
+ int left, int top, int right, int bottom) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+
+ // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
+ // its 2nd frame if RenderThread is running slowly could potentially see
+ // this as false, enter the branch, get pre-empted, then this comes along
+ // and reports a new position, then the UI thread resumes and reports
+ // its position. This could therefore be de-sync'd in that interval, but
+ // the synchronization would violate the rule that RT must never block
+ // on the UI thread which would open up potential deadlocks. The risk of
+ // a single-frame desync is therefore preferable for now.
+ mRtHandlingPositionUpdates = true;
+ if (mRTLastReportedPosition.left == left
+ && mRTLastReportedPosition.top == top
+ && mRTLastReportedPosition.right == right
+ && mRTLastReportedPosition.bottom == bottom) {
+ return;
+ }
+ try {
+ if (DEBUG) {
+ Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ frameNumber, left, top, right, bottom));
+ }
+ mRTLastReportedPosition.set(left, top, right, bottom);
+ setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+ // Now overwrite mRTLastReportedPosition with our values
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception from repositionChild", ex);
+ }
+ }
+
+ /**
+ * Called by native on RenderThread to notify that the view is no longer in the
+ * draw tree. UI thread is blocked at this point.
+ * @hide
+ */
+ public final void surfacePositionLost_uiRtSync(long frameNumber) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
+ System.identityHashCode(this), frameNumber));
+ }
+ mRTLastReportedPosition.setEmpty();
+
+ if (mSurfaceControl == null) {
+ return;
+ }
+ if (mRtHandlingPositionUpdates) {
+ mRtHandlingPositionUpdates = false;
+ // This callback will happen while the UI thread is blocked, so we can
+ // safely access other member variables at this time.
+ // So do what the UI thread would have done if RT wasn't handling position
+ // updates.
+ if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
+ try {
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
+ "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, frameNumber);
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
+ }
+ }
+ }
+ }
+
+ private SurfaceHolder.Callback[] getSurfaceCallbacks() {
+ SurfaceHolder.Callback callbacks[];
+ synchronized (mCallbacks) {
+ callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
+ mCallbacks.toArray(callbacks);
+ }
+ return callbacks;
+ }
+
+ /**
+ * This method still exists only for compatibility reasons because some applications have relied
+ * on this method via reflection. See Issue 36345857 for details.
+ *
+ * @deprecated No platform code is using this method anymore.
+ * @hide
+ */
+ @Deprecated
+ public void setWindowType(int type) {
+ if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+ throw new UnsupportedOperationException(
+ "SurfaceView#setWindowType() has never been a public API.");
+ }
+
+ if (type == TYPE_APPLICATION_PANEL) {
+ Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
+ + "just to make the SurfaceView to be placed on top of its window, you must "
+ + "call setZOrderOnTop(true) instead.", new Throwable());
+ setZOrderOnTop(true);
+ return;
+ }
+ Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
+ + "type=" + type, new Throwable());
+ }
+
+ private void runOnUiThread(Runnable runnable) {
+ Handler handler = getHandler();
+ if (handler != null && handler.getLooper() != Looper.myLooper()) {
+ handler.post(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
+ /**
+ * Check to see if the surface has fixed size dimensions or if the surface's
+ * dimensions are dimensions are dependent on its current layout.
+ *
+ * @return true if the surface has dimensions that are fixed in size
+ * @hide
+ */
+ public boolean isFixedSize() {
+ return (mRequestedWidth != -1 || mRequestedHeight != -1);
+ }
+
+ private boolean isAboveParent() {
+ return mSubLayer >= 0;
+ }
+
+ private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+ private static final String LOG_TAG = "SurfaceHolder";
@Override
public boolean isCreating() {
- return false;
+ return mIsCreating;
}
@Override
public void addCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ // This is a linear search, but in practice we'll
+ // have only a couple callbacks, so it doesn't matter.
+ if (mCallbacks.contains(callback) == false) {
+ mCallbacks.add(callback);
+ }
+ }
}
@Override
public void removeCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
}
@Override
public void setFixedSize(int width, int height) {
+ if (mRequestedWidth != width || mRequestedHeight != height) {
+ mRequestedWidth = width;
+ mRequestedHeight = height;
+ requestLayout();
+ }
}
@Override
public void setSizeFromLayout() {
+ if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+ mRequestedWidth = mRequestedHeight = -1;
+ requestLayout();
+ }
}
@Override
public void setFormat(int format) {
+ // for backward compatibility reason, OPAQUE always
+ // means 565 for SurfaceView
+ if (format == PixelFormat.OPAQUE)
+ format = PixelFormat.RGB_565;
+
+ mRequestedFormat = format;
+ if (mSurfaceControl != null) {
+ updateSurface();
+ }
}
+ /**
+ * @deprecated setType is now ignored.
+ */
@Override
- public void setType(int type) {
- }
+ @Deprecated
+ public void setType(int type) { }
@Override
public void setKeepScreenOn(boolean screenOn) {
+ runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
}
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * The caller must redraw the entire surface.
+ * @return A canvas for drawing into the surface.
+ */
@Override
public Canvas lockCanvas() {
- return null;
+ return internalLockCanvas(null, false);
}
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * @param inOutDirty A rectangle that represents the dirty region that the caller wants
+ * to redraw. This function may choose to expand the dirty rectangle if for example
+ * the surface has been resized or if the previous contents of the surface were
+ * not available. The caller must redraw the entire dirty region as represented
+ * by the contents of the inOutDirty rectangle upon return from this function.
+ * The caller may also pass <code>null</code> instead, in the case where the
+ * entire surface should be redrawn.
+ * @return A canvas for drawing into the surface.
+ */
@Override
- public Canvas lockCanvas(Rect dirty) {
+ public Canvas lockCanvas(Rect inOutDirty) {
+ return internalLockCanvas(inOutDirty, false);
+ }
+
+ @Override
+ public Canvas lockHardwareCanvas() {
+ return internalLockCanvas(null, true);
+ }
+
+ private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
+ mSurfaceLock.lock();
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
+ + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
+
+ Canvas c = null;
+ if (!mDrawingStopped && mSurfaceControl != null) {
+ try {
+ if (hardware) {
+ c = mSurface.lockHardwareCanvas();
+ } else {
+ c = mSurface.lockCanvas(dirty);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception locking surface", e);
+ }
+ }
+
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
+ if (c != null) {
+ mLastLockTime = SystemClock.uptimeMillis();
+ return c;
+ }
+
+ // If the Surface is not ready to be drawn, then return null,
+ // but throttle calls to this function so it isn't called more
+ // than every 100ms.
+ long now = SystemClock.uptimeMillis();
+ long nextTime = mLastLockTime + 100;
+ if (nextTime > now) {
+ try {
+ Thread.sleep(nextTime-now);
+ } catch (InterruptedException e) {
+ }
+ now = SystemClock.uptimeMillis();
+ }
+ mLastLockTime = now;
+ mSurfaceLock.unlock();
+
return null;
}
+ /**
+ * Posts the new contents of the {@link Canvas} to the surface and
+ * releases the {@link Canvas}.
+ *
+ * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+ */
@Override
public void unlockCanvasAndPost(Canvas canvas) {
+ mSurface.unlockCanvasAndPost(canvas);
+ mSurfaceLock.unlock();
}
@Override
public Surface getSurface() {
- return null;
+ return mSurface;
}
@Override
public Rect getSurfaceFrame() {
- return null;
+ return mSurfaceFrame;
}
};
-}
+ class SurfaceControlWithBackground extends SurfaceControl {
+ private SurfaceControl mBackgroundControl;
+ private boolean mOpaque = true;
+ public boolean mVisible = false;
+
+ public SurfaceControlWithBackground(SurfaceSession s,
+ String name, int w, int h, int format, int flags)
+ throws Exception {
+ super(s, name, w, h, format, flags);
+ mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
+ PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
+ mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ mBackgroundControl.setAlpha(alpha);
+ }
+
+ @Override
+ public void setLayer(int zorder) {
+ super.setLayer(zorder);
+ // -3 is below all other child layers as SurfaceView never goes below -2
+ mBackgroundControl.setLayer(-3);
+ }
+
+ @Override
+ public void setPosition(float x, float y) {
+ super.setPosition(x, y);
+ mBackgroundControl.setPosition(x, y);
+ }
+
+ @Override
+ public void setSize(int w, int h) {
+ super.setSize(w, h);
+ mBackgroundControl.setSize(w, h);
+ }
+
+ @Override
+ public void setWindowCrop(Rect crop) {
+ super.setWindowCrop(crop);
+ mBackgroundControl.setWindowCrop(crop);
+ }
+
+ @Override
+ public void setFinalCrop(Rect crop) {
+ super.setFinalCrop(crop);
+ mBackgroundControl.setFinalCrop(crop);
+ }
+
+ @Override
+ public void setLayerStack(int layerStack) {
+ super.setLayerStack(layerStack);
+ mBackgroundControl.setLayerStack(layerStack);
+ }
+
+ @Override
+ public void setOpaque(boolean isOpaque) {
+ super.setOpaque(isOpaque);
+ mOpaque = isOpaque;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void setSecure(boolean isSecure) {
+ super.setSecure(isSecure);
+ }
+
+ @Override
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
+ }
+
+ @Override
+ public void hide() {
+ super.hide();
+ mVisible = false;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ mVisible = true;
+ updateBackgroundVisibility();
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ mBackgroundControl.destroy();
+ }
+
+ @Override
+ public void release() {
+ super.release();
+ mBackgroundControl.release();
+ }
+
+ @Override
+ public void setTransparentRegionHint(Region region) {
+ super.setTransparentRegionHint(region);
+ mBackgroundControl.setTransparentRegionHint(region);
+ }
+
+ @Override
+ public void deferTransactionUntil(IBinder handle, long frame) {
+ super.deferTransactionUntil(handle, frame);
+ mBackgroundControl.deferTransactionUntil(handle, frame);
+ }
+
+ @Override
+ public void deferTransactionUntil(Surface barrier, long frame) {
+ super.deferTransactionUntil(barrier, frame);
+ mBackgroundControl.deferTransactionUntil(barrier, frame);
+ }
+
+ void updateBackgroundVisibility() {
+ if (mOpaque && mVisible) {
+ mBackgroundControl.show();
+ } else {
+ mBackgroundControl.hide();
+ }
+ }
+ }
+}
diff --git a/android/view/View.java b/android/view/View.java
index e5bd5ac0..b6be2961 100644
--- a/android/view/View.java
+++ b/android/view/View.java
@@ -127,6 +127,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -1078,6 +1079,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
* value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}</code>).
*
+ * <p>When annotating a view with this hint, it's recommended to use a date autofill value to
+ * avoid ambiguity when the autofill service provides a value for it. To understand why a
+ * value can be ambiguous, consider "April of 2020", which could be represented as either of
+ * the following options:
+ *
+ * <ul>
+ * <li>{@code "04/2020"}
+ * <li>{@code "4/2020"}
+ * <li>{@code "2020/04"}
+ * <li>{@code "2020/4"}
+ * <li>{@code "April/2020"}
+ * <li>{@code "Apr/2020"}
+ * </ul>
+ *
+ * <p>You define a date autofill value for the view by overriding the following methods:
+ *
+ * <ol>
+ * <li>{@link #getAutofillType()} to return {@link #AUTOFILL_TYPE_DATE}.
+ * <li>{@link #getAutofillValue()} to return a
+ * {@link AutofillValue#forDate(long) date autofillvalue}.
+ * <li>{@link #autofill(AutofillValue)} to expect a data autofillvalue.
+ * </ol>
+ *
* <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
*/
public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE =
@@ -1090,6 +1114,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <a href="#attr_android:autofillHint"> {@code android:autofillHint}</a> (in which case the
* value should be <code>{@value #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}</code>).
*
+ * <p>When annotating a view with this hint, it's recommended to use a text autofill value
+ * whose value is the numerical representation of the month, starting on {@code 1} to avoid
+ * ambiguity when the autofill service provides a value for it. To understand why a
+ * value can be ambiguous, consider "January", which could be represented as either of
+ *
+ * <ul>
+ * <li>{@code "1"}: recommended way.
+ * <li>{@code "0"}: if following the {@link Calendar#MONTH} convention.
+ * <li>{@code "January"}: full name, in English.
+ * <li>{@code "jan"}: abbreviated name, in English.
+ * <li>{@code "Janeiro"}: full name, in another language.
+ * </ul>
+ *
+ * <p>Another recommended approach is to use a date autofill value - see
+ * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE} for more details.
+ *
* <p>See {@link #setAutofillHints(String...)} for more info about autofill hints.
*/
public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH =
@@ -3702,15 +3742,90 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@ViewDebug.ExportedProperty(flagMapping = {
- @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
- equals = SYSTEM_UI_FLAG_LOW_PROFILE,
- name = "SYSTEM_UI_FLAG_LOW_PROFILE", outputIf = true),
- @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
- equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
- name = "SYSTEM_UI_FLAG_HIDE_NAVIGATION", outputIf = true),
- @ViewDebug.FlagToString(mask = PUBLIC_STATUS_BAR_VISIBILITY_MASK,
- equals = SYSTEM_UI_FLAG_VISIBLE,
- name = "SYSTEM_UI_FLAG_VISIBLE", outputIf = true)
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
+ equals = SYSTEM_UI_FLAG_LOW_PROFILE,
+ name = "LOW_PROFILE"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+ equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+ name = "HIDE_NAVIGATION"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_FULLSCREEN,
+ equals = SYSTEM_UI_FLAG_FULLSCREEN,
+ name = "FULLSCREEN"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+ equals = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+ name = "LAYOUT_STABLE"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+ equals = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+ name = "LAYOUT_HIDE_NAVIGATION"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+ equals = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+ name = "LAYOUT_FULLSCREEN"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE,
+ equals = SYSTEM_UI_FLAG_IMMERSIVE,
+ name = "IMMERSIVE"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+ equals = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+ name = "IMMERSIVE_STICKY"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+ equals = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+ name = "LIGHT_STATUS_BAR"),
+ @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+ equals = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+ name = "LIGHT_NAVIGATION_BAR"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_EXPAND,
+ equals = STATUS_BAR_DISABLE_EXPAND,
+ name = "STATUS_BAR_DISABLE_EXPAND"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+ equals = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+ name = "STATUS_BAR_DISABLE_NOTIFICATION_ICONS"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+ equals = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+ name = "STATUS_BAR_DISABLE_NOTIFICATION_ALERTS"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+ equals = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+ name = "STATUS_BAR_DISABLE_NOTIFICATION_TICKER"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SYSTEM_INFO,
+ equals = STATUS_BAR_DISABLE_SYSTEM_INFO,
+ name = "STATUS_BAR_DISABLE_SYSTEM_INFO"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_HOME,
+ equals = STATUS_BAR_DISABLE_HOME,
+ name = "STATUS_BAR_DISABLE_HOME"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_BACK,
+ equals = STATUS_BAR_DISABLE_BACK,
+ name = "STATUS_BAR_DISABLE_BACK"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_CLOCK,
+ equals = STATUS_BAR_DISABLE_CLOCK,
+ name = "STATUS_BAR_DISABLE_CLOCK"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_RECENT,
+ equals = STATUS_BAR_DISABLE_RECENT,
+ name = "STATUS_BAR_DISABLE_RECENT"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SEARCH,
+ equals = STATUS_BAR_DISABLE_SEARCH,
+ name = "STATUS_BAR_DISABLE_SEARCH"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSIENT,
+ equals = STATUS_BAR_TRANSIENT,
+ name = "STATUS_BAR_TRANSIENT"),
+ @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSIENT,
+ equals = NAVIGATION_BAR_TRANSIENT,
+ name = "NAVIGATION_BAR_TRANSIENT"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_UNHIDE,
+ equals = STATUS_BAR_UNHIDE,
+ name = "STATUS_BAR_UNHIDE"),
+ @ViewDebug.FlagToString(mask = NAVIGATION_BAR_UNHIDE,
+ equals = NAVIGATION_BAR_UNHIDE,
+ name = "NAVIGATION_BAR_UNHIDE"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSLUCENT,
+ equals = STATUS_BAR_TRANSLUCENT,
+ name = "STATUS_BAR_TRANSLUCENT"),
+ @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSLUCENT,
+ equals = NAVIGATION_BAR_TRANSLUCENT,
+ name = "NAVIGATION_BAR_TRANSLUCENT"),
+ @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSPARENT,
+ equals = NAVIGATION_BAR_TRANSPARENT,
+ name = "NAVIGATION_BAR_TRANSPARENT"),
+ @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSPARENT,
+ equals = STATUS_BAR_TRANSPARENT,
+ name = "STATUS_BAR_TRANSPARENT")
}, formatToHexString = true)
int mSystemUiVisibility;
@@ -15414,7 +15529,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@code dirty}.
*
* @param dirty the rectangle representing the bounds of the dirty region
+ *
+ * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+ * the importance of the dirty rectangle. In API 21 the given rectangle is
+ * ignored entirely in favor of an internally-calculated area instead.
+ * Because of this, clients are encouraged to just call {@link #invalidate()}.
*/
+ @Deprecated
public void invalidate(Rect dirty) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
@@ -15435,7 +15556,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param t the top position of the dirty region
* @param r the right position of the dirty region
* @param b the bottom position of the dirty region
+ *
+ * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+ * the importance of the dirty rectangle. In API 21 the given rectangle is
+ * ignored entirely in favor of an internally-calculated area instead.
+ * Because of this, clients are encouraged to just call {@link #invalidate()}.
*/
+ @Deprecated
public void invalidate(int l, int t, int r, int b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
diff --git a/android/view/ViewDebug.java b/android/view/ViewDebug.java
index 66c05785..3426485e 100644
--- a/android/view/ViewDebug.java
+++ b/android/view/ViewDebug.java
@@ -1375,6 +1375,81 @@ public class ViewDebug {
}
}
+ /**
+ * Converts an integer from a field that is mapped with {@link IntToString} to its string
+ * representation.
+ *
+ * @param clazz The class the field is defined on.
+ * @param field The field on which the {@link ExportedProperty} is defined on.
+ * @param integer The value to convert.
+ * @return The value converted into its string representation.
+ * @hide
+ */
+ public static String intToString(Class<?> clazz, String field, int integer) {
+ final IntToString[] mapping = getMapping(clazz, field);
+ if (mapping == null) {
+ return Integer.toString(integer);
+ }
+ final int count = mapping.length;
+ for (int j = 0; j < count; j++) {
+ final IntToString map = mapping[j];
+ if (map.from() == integer) {
+ return map.to();
+ }
+ }
+ return Integer.toString(integer);
+ }
+
+ /**
+ * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
+ * representation.
+ *
+ * @param clazz The class the field is defined on.
+ * @param field The field on which the {@link ExportedProperty} is defined on.
+ * @param flags The flags to convert.
+ * @return The flags converted into their string representations.
+ * @hide
+ */
+ public static String flagsToString(Class<?> clazz, String field, int flags) {
+ final FlagToString[] mapping = getFlagMapping(clazz, field);
+ if (mapping == null) {
+ return Integer.toHexString(flags);
+ }
+ final StringBuilder result = new StringBuilder();
+ final int count = mapping.length;
+ for (int j = 0; j < count; j++) {
+ final FlagToString flagMapping = mapping[j];
+ final boolean ifTrue = flagMapping.outputIf();
+ final int maskResult = flags & flagMapping.mask();
+ final boolean test = maskResult == flagMapping.equals();
+ if (test && ifTrue) {
+ final String name = flagMapping.name();
+ result.append(name).append(' ');
+ }
+ }
+ if (result.length() > 0) {
+ result.deleteCharAt(result.length() - 1);
+ }
+ return result.toString();
+ }
+
+ private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
+ try {
+ return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
+ .flagMapping();
+ } catch (NoSuchFieldException e) {
+ return null;
+ }
+ }
+
+ private static IntToString[] getMapping(Class<?> clazz, String field) {
+ try {
+ return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
+ } catch (NoSuchFieldException e) {
+ return null;
+ }
+ }
+
private static void exportUnrolledArray(Context context, BufferedWriter out,
ExportedProperty property, int[] array, String prefix, String suffix)
throws IOException {
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 415aad54..71106ada 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -366,7 +366,7 @@ public final class ViewRootImpl implements ViewParent,
// These can be accessed by any thread, must be protected with a lock.
// Surface can never be reassigned or cleared (use Surface.clear()).
- final Surface mSurface = new Surface();
+ public final Surface mSurface = new Surface();
boolean mAdded;
boolean mAddedTouchMode;
@@ -512,7 +512,7 @@ public final class ViewRootImpl implements ViewParent,
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
- sAlwaysAssignFocus = true;
+ sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
sCompatibilityDone = true;
}
@@ -7714,7 +7714,7 @@ public final class ViewRootImpl implements ViewParent,
public void onAccessibilityStateChanged(boolean enabled) {
if (enabled) {
ensureConnection();
- if (mAttachInfo.mHasWindowFocus) {
+ if (mAttachInfo.mHasWindowFocus && (mView != null)) {
mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
View focusedView = mView.findFocus();
if (focusedView != null && focusedView != mView) {
diff --git a/android/view/ViewStructure.java b/android/view/ViewStructure.java
index 0ecd20da..f671c349 100644
--- a/android/view/ViewStructure.java
+++ b/android/view/ViewStructure.java
@@ -378,7 +378,7 @@ public abstract class ViewStructure {
*
* <p>Typically used when the view is a container for an HTML document.
*
- * @param domain URL representing the domain; only the host part will be used.
+ * @param domain RFC 2396-compliant URI representing the domain.
*/
public abstract void setWebDomain(@Nullable String domain);
diff --git a/android/view/WindowManager.java b/android/view/WindowManager.java
index e56a82ff..c29a1daf 100644
--- a/android/view/WindowManager.java
+++ b/android/view/WindowManager.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+
import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -268,93 +270,93 @@ public interface WindowManager extends ViewManager {
*/
@ViewDebug.ExportedProperty(mapping = {
@ViewDebug.IntToString(from = TYPE_BASE_APPLICATION,
- to = "TYPE_BASE_APPLICATION"),
+ to = "BASE_APPLICATION"),
@ViewDebug.IntToString(from = TYPE_APPLICATION,
- to = "TYPE_APPLICATION"),
+ to = "APPLICATION"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_STARTING,
- to = "TYPE_APPLICATION_STARTING"),
+ to = "APPLICATION_STARTING"),
@ViewDebug.IntToString(from = TYPE_DRAWN_APPLICATION,
- to = "TYPE_DRAWN_APPLICATION"),
+ to = "DRAWN_APPLICATION"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL,
- to = "TYPE_APPLICATION_PANEL"),
+ to = "APPLICATION_PANEL"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA,
- to = "TYPE_APPLICATION_MEDIA"),
+ to = "APPLICATION_MEDIA"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL,
- to = "TYPE_APPLICATION_SUB_PANEL"),
+ to = "APPLICATION_SUB_PANEL"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_ABOVE_SUB_PANEL,
- to = "TYPE_APPLICATION_ABOVE_SUB_PANEL"),
+ to = "APPLICATION_ABOVE_SUB_PANEL"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG,
- to = "TYPE_APPLICATION_ATTACHED_DIALOG"),
+ to = "APPLICATION_ATTACHED_DIALOG"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY,
- to = "TYPE_APPLICATION_MEDIA_OVERLAY"),
+ to = "APPLICATION_MEDIA_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_STATUS_BAR,
- to = "TYPE_STATUS_BAR"),
+ to = "STATUS_BAR"),
@ViewDebug.IntToString(from = TYPE_SEARCH_BAR,
- to = "TYPE_SEARCH_BAR"),
+ to = "SEARCH_BAR"),
@ViewDebug.IntToString(from = TYPE_PHONE,
- to = "TYPE_PHONE"),
+ to = "PHONE"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_ALERT,
- to = "TYPE_SYSTEM_ALERT"),
+ to = "SYSTEM_ALERT"),
@ViewDebug.IntToString(from = TYPE_TOAST,
- to = "TYPE_TOAST"),
+ to = "TOAST"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY,
- to = "TYPE_SYSTEM_OVERLAY"),
+ to = "SYSTEM_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE,
- to = "TYPE_PRIORITY_PHONE"),
+ to = "PRIORITY_PHONE"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG,
- to = "TYPE_SYSTEM_DIALOG"),
+ to = "SYSTEM_DIALOG"),
@ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG,
- to = "TYPE_KEYGUARD_DIALOG"),
+ to = "KEYGUARD_DIALOG"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR,
- to = "TYPE_SYSTEM_ERROR"),
+ to = "SYSTEM_ERROR"),
@ViewDebug.IntToString(from = TYPE_INPUT_METHOD,
- to = "TYPE_INPUT_METHOD"),
+ to = "INPUT_METHOD"),
@ViewDebug.IntToString(from = TYPE_INPUT_METHOD_DIALOG,
- to = "TYPE_INPUT_METHOD_DIALOG"),
+ to = "INPUT_METHOD_DIALOG"),
@ViewDebug.IntToString(from = TYPE_WALLPAPER,
- to = "TYPE_WALLPAPER"),
+ to = "WALLPAPER"),
@ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL,
- to = "TYPE_STATUS_BAR_PANEL"),
+ to = "STATUS_BAR_PANEL"),
@ViewDebug.IntToString(from = TYPE_SECURE_SYSTEM_OVERLAY,
- to = "TYPE_SECURE_SYSTEM_OVERLAY"),
+ to = "SECURE_SYSTEM_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_DRAG,
- to = "TYPE_DRAG"),
+ to = "DRAG"),
@ViewDebug.IntToString(from = TYPE_STATUS_BAR_SUB_PANEL,
- to = "TYPE_STATUS_BAR_SUB_PANEL"),
+ to = "STATUS_BAR_SUB_PANEL"),
@ViewDebug.IntToString(from = TYPE_POINTER,
- to = "TYPE_POINTER"),
+ to = "POINTER"),
@ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR,
- to = "TYPE_NAVIGATION_BAR"),
+ to = "NAVIGATION_BAR"),
@ViewDebug.IntToString(from = TYPE_VOLUME_OVERLAY,
- to = "TYPE_VOLUME_OVERLAY"),
+ to = "VOLUME_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS,
- to = "TYPE_BOOT_PROGRESS"),
+ to = "BOOT_PROGRESS"),
@ViewDebug.IntToString(from = TYPE_INPUT_CONSUMER,
- to = "TYPE_INPUT_CONSUMER"),
+ to = "INPUT_CONSUMER"),
@ViewDebug.IntToString(from = TYPE_DREAM,
- to = "TYPE_DREAM"),
+ to = "DREAM"),
@ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL,
- to = "TYPE_NAVIGATION_BAR_PANEL"),
+ to = "NAVIGATION_BAR_PANEL"),
@ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY,
- to = "TYPE_DISPLAY_OVERLAY"),
+ to = "DISPLAY_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY,
- to = "TYPE_MAGNIFICATION_OVERLAY"),
+ to = "MAGNIFICATION_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_PRESENTATION,
- to = "TYPE_PRESENTATION"),
+ to = "PRESENTATION"),
@ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION,
- to = "TYPE_PRIVATE_PRESENTATION"),
+ to = "PRIVATE_PRESENTATION"),
@ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION,
- to = "TYPE_VOICE_INTERACTION"),
+ to = "VOICE_INTERACTION"),
@ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING,
- to = "TYPE_VOICE_INTERACTION_STARTING"),
+ to = "VOICE_INTERACTION_STARTING"),
@ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER,
- to = "TYPE_DOCK_DIVIDER"),
+ to = "DOCK_DIVIDER"),
@ViewDebug.IntToString(from = TYPE_QS_DIALOG,
- to = "TYPE_QS_DIALOG"),
+ to = "QS_DIALOG"),
@ViewDebug.IntToString(from = TYPE_SCREENSHOT,
- to = "TYPE_SCREENSHOT"),
+ to = "SCREENSHOT"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_OVERLAY,
- to = "TYPE_APPLICATION_OVERLAY")
+ to = "APPLICATION_OVERLAY")
})
public int type;
@@ -1198,63 +1200,69 @@ public interface WindowManager extends ViewManager {
*/
@ViewDebug.ExportedProperty(flagMapping = {
@ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
- name = "FLAG_ALLOW_LOCK_WHILE_SCREEN_ON"),
+ name = "ALLOW_LOCK_WHILE_SCREEN_ON"),
@ViewDebug.FlagToString(mask = FLAG_DIM_BEHIND, equals = FLAG_DIM_BEHIND,
- name = "FLAG_DIM_BEHIND"),
+ name = "DIM_BEHIND"),
@ViewDebug.FlagToString(mask = FLAG_BLUR_BEHIND, equals = FLAG_BLUR_BEHIND,
- name = "FLAG_BLUR_BEHIND"),
+ name = "BLUR_BEHIND"),
@ViewDebug.FlagToString(mask = FLAG_NOT_FOCUSABLE, equals = FLAG_NOT_FOCUSABLE,
- name = "FLAG_NOT_FOCUSABLE"),
+ name = "NOT_FOCUSABLE"),
@ViewDebug.FlagToString(mask = FLAG_NOT_TOUCHABLE, equals = FLAG_NOT_TOUCHABLE,
- name = "FLAG_NOT_TOUCHABLE"),
+ name = "NOT_TOUCHABLE"),
@ViewDebug.FlagToString(mask = FLAG_NOT_TOUCH_MODAL, equals = FLAG_NOT_TOUCH_MODAL,
- name = "FLAG_NOT_TOUCH_MODAL"),
+ name = "NOT_TOUCH_MODAL"),
@ViewDebug.FlagToString(mask = FLAG_TOUCHABLE_WHEN_WAKING, equals = FLAG_TOUCHABLE_WHEN_WAKING,
- name = "FLAG_TOUCHABLE_WHEN_WAKING"),
+ name = "TOUCHABLE_WHEN_WAKING"),
@ViewDebug.FlagToString(mask = FLAG_KEEP_SCREEN_ON, equals = FLAG_KEEP_SCREEN_ON,
- name = "FLAG_KEEP_SCREEN_ON"),
+ name = "KEEP_SCREEN_ON"),
@ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_SCREEN, equals = FLAG_LAYOUT_IN_SCREEN,
- name = "FLAG_LAYOUT_IN_SCREEN"),
+ name = "LAYOUT_IN_SCREEN"),
@ViewDebug.FlagToString(mask = FLAG_LAYOUT_NO_LIMITS, equals = FLAG_LAYOUT_NO_LIMITS,
- name = "FLAG_LAYOUT_NO_LIMITS"),
+ name = "LAYOUT_NO_LIMITS"),
@ViewDebug.FlagToString(mask = FLAG_FULLSCREEN, equals = FLAG_FULLSCREEN,
- name = "FLAG_FULLSCREEN"),
+ name = "FULLSCREEN"),
@ViewDebug.FlagToString(mask = FLAG_FORCE_NOT_FULLSCREEN, equals = FLAG_FORCE_NOT_FULLSCREEN,
- name = "FLAG_FORCE_NOT_FULLSCREEN"),
+ name = "FORCE_NOT_FULLSCREEN"),
@ViewDebug.FlagToString(mask = FLAG_DITHER, equals = FLAG_DITHER,
- name = "FLAG_DITHER"),
+ name = "DITHER"),
@ViewDebug.FlagToString(mask = FLAG_SECURE, equals = FLAG_SECURE,
- name = "FLAG_SECURE"),
+ name = "SECURE"),
@ViewDebug.FlagToString(mask = FLAG_SCALED, equals = FLAG_SCALED,
- name = "FLAG_SCALED"),
+ name = "SCALED"),
@ViewDebug.FlagToString(mask = FLAG_IGNORE_CHEEK_PRESSES, equals = FLAG_IGNORE_CHEEK_PRESSES,
- name = "FLAG_IGNORE_CHEEK_PRESSES"),
+ name = "IGNORE_CHEEK_PRESSES"),
@ViewDebug.FlagToString(mask = FLAG_LAYOUT_INSET_DECOR, equals = FLAG_LAYOUT_INSET_DECOR,
- name = "FLAG_LAYOUT_INSET_DECOR"),
+ name = "LAYOUT_INSET_DECOR"),
@ViewDebug.FlagToString(mask = FLAG_ALT_FOCUSABLE_IM, equals = FLAG_ALT_FOCUSABLE_IM,
- name = "FLAG_ALT_FOCUSABLE_IM"),
+ name = "ALT_FOCUSABLE_IM"),
@ViewDebug.FlagToString(mask = FLAG_WATCH_OUTSIDE_TOUCH, equals = FLAG_WATCH_OUTSIDE_TOUCH,
- name = "FLAG_WATCH_OUTSIDE_TOUCH"),
+ name = "WATCH_OUTSIDE_TOUCH"),
@ViewDebug.FlagToString(mask = FLAG_SHOW_WHEN_LOCKED, equals = FLAG_SHOW_WHEN_LOCKED,
- name = "FLAG_SHOW_WHEN_LOCKED"),
+ name = "SHOW_WHEN_LOCKED"),
@ViewDebug.FlagToString(mask = FLAG_SHOW_WALLPAPER, equals = FLAG_SHOW_WALLPAPER,
- name = "FLAG_SHOW_WALLPAPER"),
+ name = "SHOW_WALLPAPER"),
@ViewDebug.FlagToString(mask = FLAG_TURN_SCREEN_ON, equals = FLAG_TURN_SCREEN_ON,
- name = "FLAG_TURN_SCREEN_ON"),
+ name = "TURN_SCREEN_ON"),
@ViewDebug.FlagToString(mask = FLAG_DISMISS_KEYGUARD, equals = FLAG_DISMISS_KEYGUARD,
- name = "FLAG_DISMISS_KEYGUARD"),
+ name = "DISMISS_KEYGUARD"),
@ViewDebug.FlagToString(mask = FLAG_SPLIT_TOUCH, equals = FLAG_SPLIT_TOUCH,
- name = "FLAG_SPLIT_TOUCH"),
+ name = "SPLIT_TOUCH"),
@ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED, equals = FLAG_HARDWARE_ACCELERATED,
- name = "FLAG_HARDWARE_ACCELERATED"),
- @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
- name = "FLAG_LOCAL_FOCUS_MODE"),
+ name = "HARDWARE_ACCELERATED"),
+ @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_OVERSCAN, equals = FLAG_LAYOUT_IN_OVERSCAN,
+ name = "LOCAL_FOCUS_MODE"),
@ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_STATUS, equals = FLAG_TRANSLUCENT_STATUS,
- name = "FLAG_TRANSLUCENT_STATUS"),
+ name = "TRANSLUCENT_STATUS"),
@ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_NAVIGATION, equals = FLAG_TRANSLUCENT_NAVIGATION,
- name = "FLAG_TRANSLUCENT_NAVIGATION"),
+ name = "TRANSLUCENT_NAVIGATION"),
+ @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
+ name = "LOCAL_FOCUS_MODE"),
+ @ViewDebug.FlagToString(mask = FLAG_SLIPPERY, equals = FLAG_SLIPPERY,
+ name = "FLAG_SLIPPERY"),
+ @ViewDebug.FlagToString(mask = FLAG_LAYOUT_ATTACHED_IN_DECOR, equals = FLAG_LAYOUT_ATTACHED_IN_DECOR,
+ name = "FLAG_LAYOUT_ATTACHED_IN_DECOR"),
@ViewDebug.FlagToString(mask = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, equals = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
- name = "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS")
+ name = "DRAWS_SYSTEM_BAR_BACKGROUNDS")
}, formatToHexString = true)
public int flags;
@@ -1438,6 +1446,88 @@ public interface WindowManager extends ViewManager {
* Control flags that are private to the platform.
* @hide
*/
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
+ equals = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
+ name = "FAKE_HARDWARE_ACCELERATED"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+ equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+ name = "FORCE_HARDWARE_ACCELERATED"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+ equals = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+ name = "WANTS_OFFSET_NOTIFICATIONS"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+ equals = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+ name = "SHOW_FOR_ALL_USERS"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+ equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+ name = "NO_MOVE_ANIMATION"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+ equals = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+ name = "COMPATIBLE_WINDOW"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_SYSTEM_ERROR,
+ equals = PRIVATE_FLAG_SYSTEM_ERROR,
+ name = "SYSTEM_ERROR"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR,
+ equals = PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR,
+ name = "INHERIT_TRANSLUCENT_DECOR"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_KEYGUARD,
+ equals = PRIVATE_FLAG_KEYGUARD,
+ name = "KEYGUARD"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+ equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+ name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
+ equals = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
+ name = "FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_PRESERVE_GEOMETRY,
+ equals = PRIVATE_FLAG_PRESERVE_GEOMETRY,
+ name = "PRESERVE_GEOMETRY"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+ equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+ name = "FORCE_DECOR_VIEW_VISIBILITY"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+ equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+ name = "WILL_NOT_REPLACE_ON_RELAUNCH"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+ equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+ name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND,
+ equals = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND,
+ name = "FORCE_DRAW_STATUS_BAR_BACKGROUND"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+ equals = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+ name = "SUSTAINED_PERFORMANCE_MODE"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ equals = PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ name = "HIDE_NON_SYSTEM_OVERLAY_WINDOWS"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+ equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+ name = "IS_ROUNDED_CORNERS_OVERLAY"),
+ @ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
+ equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
+ name = "ACQUIRES_SLEEP_TOKEN")
+ })
@TestApi
public int privateFlags;
@@ -1977,7 +2067,7 @@ public interface WindowManager extends ViewManager {
* @hide
*/
@ActivityInfo.ColorMode
- private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+ private int mColorMode = COLOR_MODE_DEFAULT;
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
@@ -2442,9 +2532,15 @@ public interface WindowManager extends ViewManager {
@Override
public String toString() {
+ return toString("");
+ }
+
+ /**
+ * @hide
+ */
+ public String toString(String prefix) {
StringBuilder sb = new StringBuilder(256);
- sb.append("WM.LayoutParams{");
- sb.append("(");
+ sb.append("{(");
sb.append(x);
sb.append(',');
sb.append(y);
@@ -2464,26 +2560,19 @@ public interface WindowManager extends ViewManager {
sb.append(verticalMargin);
}
if (gravity != 0) {
- sb.append(" gr=#");
- sb.append(Integer.toHexString(gravity));
+ sb.append(" gr=");
+ sb.append(Gravity.toString(gravity));
}
if (softInputMode != 0) {
- sb.append(" sim=#");
- sb.append(Integer.toHexString(softInputMode));
+ sb.append(" sim={");
+ sb.append(softInputModeToString(softInputMode));
+ sb.append('}');
}
sb.append(" ty=");
- sb.append(type);
- sb.append(" fl=#");
- sb.append(Integer.toHexString(flags));
- if (privateFlags != 0) {
- if ((privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
- sb.append(" compatible=true");
- }
- sb.append(" pfl=0x").append(Integer.toHexString(privateFlags));
- }
+ sb.append(ViewDebug.intToString(LayoutParams.class, "type", type));
if (format != PixelFormat.OPAQUE) {
sb.append(" fmt=");
- sb.append(format);
+ sb.append(PixelFormat.formatToString(format));
}
if (windowAnimations != 0) {
sb.append(" wanim=0x");
@@ -2491,7 +2580,7 @@ public interface WindowManager extends ViewManager {
}
if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
sb.append(" or=");
- sb.append(screenOrientation);
+ sb.append(ActivityInfo.screenOrientationToString(screenOrientation));
}
if (alpha != 1.0f) {
sb.append(" alpha=");
@@ -2507,7 +2596,7 @@ public interface WindowManager extends ViewManager {
}
if (rotationAnimation != ROTATION_ANIMATION_ROTATE) {
sb.append(" rotAnim=");
- sb.append(rotationAnimation);
+ sb.append(rotationAnimationToString(rotationAnimation));
}
if (preferredRefreshRate != 0) {
sb.append(" preferredRefreshRate=");
@@ -2517,20 +2606,12 @@ public interface WindowManager extends ViewManager {
sb.append(" preferredDisplayMode=");
sb.append(preferredDisplayModeId);
}
- if (systemUiVisibility != 0) {
- sb.append(" sysui=0x");
- sb.append(Integer.toHexString(systemUiVisibility));
- }
- if (subtreeSystemUiVisibility != 0) {
- sb.append(" vsysui=0x");
- sb.append(Integer.toHexString(subtreeSystemUiVisibility));
- }
if (hasSystemUiListeners) {
sb.append(" sysuil=");
sb.append(hasSystemUiListeners);
}
if (inputFeatures != 0) {
- sb.append(" if=0x").append(Integer.toHexString(inputFeatures));
+ sb.append(" if=").append(inputFeatureToString(inputFeatures));
}
if (userActivityTimeout >= 0) {
sb.append(" userActivityTimeout=").append(userActivityTimeout);
@@ -2546,11 +2627,30 @@ public interface WindowManager extends ViewManager {
sb.append(" (!preservePreviousSurfaceInsets)");
}
}
- if (needsMenuKey != NEEDS_MENU_UNSET) {
- sb.append(" needsMenuKey=");
- sb.append(needsMenuKey);
+ if (needsMenuKey == NEEDS_MENU_SET_TRUE) {
+ sb.append(" needsMenuKey");
+ }
+ if (mColorMode != COLOR_MODE_DEFAULT) {
+ sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
+ }
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" fl=").append(
+ ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+ if (privateFlags != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" pfl=").append(ViewDebug.flagsToString(
+ LayoutParams.class, "privateFlags", privateFlags));
+ }
+ if (systemUiVisibility != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" sysui=").append(ViewDebug.flagsToString(
+ View.class, "mSystemUiVisibility", systemUiVisibility));
+ }
+ if (subtreeSystemUiVisibility != 0) {
+ sb.append(System.lineSeparator());
+ sb.append(prefix).append(" vsysui=").append(ViewDebug.flagsToString(
+ View.class, "mSystemUiVisibility", subtreeSystemUiVisibility));
}
- sb.append(" colorMode=").append(mColorMode);
sb.append('}');
return sb.toString();
}
@@ -2634,5 +2734,88 @@ public interface WindowManager extends ViewManager {
&& width == WindowManager.LayoutParams.MATCH_PARENT
&& height == WindowManager.LayoutParams.MATCH_PARENT;
}
+
+ private static String softInputModeToString(@SoftInputModeFlags int softInputMode) {
+ final StringBuilder result = new StringBuilder();
+ final int state = softInputMode & SOFT_INPUT_MASK_STATE;
+ if (state != 0) {
+ result.append("state=");
+ switch (state) {
+ case SOFT_INPUT_STATE_UNCHANGED:
+ result.append("unchanged");
+ break;
+ case SOFT_INPUT_STATE_HIDDEN:
+ result.append("hidden");
+ break;
+ case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+ result.append("always_hidden");
+ break;
+ case SOFT_INPUT_STATE_VISIBLE:
+ result.append("visible");
+ break;
+ case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+ result.append("always_visible");
+ break;
+ default:
+ result.append(state);
+ break;
+ }
+ result.append(' ');
+ }
+ final int adjust = softInputMode & SOFT_INPUT_MASK_ADJUST;
+ if (adjust != 0) {
+ result.append("adjust=");
+ switch (adjust) {
+ case SOFT_INPUT_ADJUST_RESIZE:
+ result.append("resize");
+ break;
+ case SOFT_INPUT_ADJUST_PAN:
+ result.append("pan");
+ break;
+ case SOFT_INPUT_ADJUST_NOTHING:
+ result.append("nothing");
+ break;
+ default:
+ result.append(adjust);
+ break;
+ }
+ result.append(' ');
+ }
+ if ((softInputMode & SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+ result.append("forwardNavigation").append(' ');
+ }
+ result.deleteCharAt(result.length() - 1);
+ return result.toString();
+ }
+
+ private static String rotationAnimationToString(int rotationAnimation) {
+ switch (rotationAnimation) {
+ case ROTATION_ANIMATION_UNSPECIFIED:
+ return "UNSPECIFIED";
+ case ROTATION_ANIMATION_ROTATE:
+ return "ROTATE";
+ case ROTATION_ANIMATION_CROSSFADE:
+ return "CROSSFADE";
+ case ROTATION_ANIMATION_JUMPCUT:
+ return "JUMPCUT";
+ case ROTATION_ANIMATION_SEAMLESS:
+ return "SEAMLESS";
+ default:
+ return Integer.toString(rotationAnimation);
+ }
+ }
+
+ private static String inputFeatureToString(int inputFeature) {
+ switch (inputFeature) {
+ case INPUT_FEATURE_DISABLE_POINTER_GESTURES:
+ return "DISABLE_POINTER_GESTURES";
+ case INPUT_FEATURE_NO_INPUT_CHANNEL:
+ return "NO_INPUT_CHANNEL";
+ case INPUT_FEATURE_DISABLE_USER_ACTIVITY:
+ return "DISABLE_USER_ACTIVITY";
+ default:
+ return Integer.toString(inputFeature);
+ }
+ }
}
}
diff --git a/android/view/WindowManagerPolicy.java b/android/view/WindowManagerPolicy.java
index 66506a18..da72535d 100644
--- a/android/view/WindowManagerPolicy.java
+++ b/android/view/WindowManagerPolicy.java
@@ -592,9 +592,10 @@ public interface WindowManagerPolicy {
int getDockedDividerInsetsLw();
/**
- * Retrieves the {@param outBounds} from the stack with id {@param stackId}.
+ * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
+ * {@param activityType}.
*/
- void getStackBounds(int stackId, Rect outBounds);
+ void getStackBounds(int windowingMode, int activityType, Rect outBounds);
/**
* Notifies window manager that {@link #isShowingDreamLw} has changed.
@@ -617,6 +618,38 @@ public interface WindowManagerPolicy {
* @param listener callback to call when display can be turned off
*/
void screenTurningOff(ScreenOffListener listener);
+
+ /**
+ * Convert the lid state to a human readable format.
+ */
+ static String lidStateToString(int lid) {
+ switch (lid) {
+ case LID_ABSENT:
+ return "LID_ABSENT";
+ case LID_CLOSED:
+ return "LID_CLOSED";
+ case LID_OPEN:
+ return "LID_OPEN";
+ default:
+ return Integer.toString(lid);
+ }
+ }
+
+ /**
+ * Convert the camera lens state to a human readable format.
+ */
+ static String cameraLensStateToString(int lens) {
+ switch (lens) {
+ case CAMERA_LENS_COVER_ABSENT:
+ return "CAMERA_LENS_COVER_ABSENT";
+ case CAMERA_LENS_UNCOVERED:
+ return "CAMERA_LENS_UNCOVERED";
+ case CAMERA_LENS_COVERED:
+ return "CAMERA_LENS_COVERED";
+ default:
+ return Integer.toString(lens);
+ }
+ }
}
public interface PointerEventListener {
@@ -1750,4 +1783,34 @@ public interface WindowManagerPolicy {
* @return true if ready; false otherwise.
*/
boolean canDismissBootAnimation();
+
+ /**
+ * Convert the user rotation mode to a human readable format.
+ */
+ static String userRotationModeToString(int mode) {
+ switch(mode) {
+ case USER_ROTATION_FREE:
+ return "USER_ROTATION_FREE";
+ case USER_ROTATION_LOCKED:
+ return "USER_ROTATION_LOCKED";
+ default:
+ return Integer.toString(mode);
+ }
+ }
+
+ /**
+ * Convert the off reason to a human readable format.
+ */
+ static String offReasonToString(int why) {
+ switch (why) {
+ case OFF_BECAUSE_OF_ADMIN:
+ return "OFF_BECAUSE_OF_ADMIN";
+ case OFF_BECAUSE_OF_USER:
+ return "OFF_BECAUSE_OF_USER";
+ case OFF_BECAUSE_OF_TIMEOUT:
+ return "OFF_BECAUSE_OF_TIMEOUT";
+ default:
+ return Integer.toString(why);
+ }
+ }
}
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 11cb046a..0b9bc576 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,46 +16,152 @@
package android.view.accessibility;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
+
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
import android.view.IWindow;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IntPair;
+
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
- * Such events are generated when something notable happens in the user interface,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
+ * and provides facilities for querying the accessibility state of the system.
+ * Accessibility events are generated when something notable happens in the user interface,
* for example an {@link android.app.Activity} starts, the focus or selection of a
* {@link android.view.View} changes etc. Parties interested in handling accessibility
* events implement and register an accessibility service which extends
- * {@code android.accessibilityservice.AccessibilityService}.
+ * {@link android.accessibilityservice.AccessibilityService}.
*
* @see AccessibilityEvent
- * @see android.content.Context#getSystemService
+ * @see AccessibilityNodeInfo
+ * @see android.accessibilityservice.AccessibilityService
+ * @see Context#getSystemService
+ * @see Context#ACCESSIBILITY_SERVICE
*/
-@SuppressWarnings("UnusedDeclaration")
+@SystemService(Context.ACCESSIBILITY_SERVICE)
public final class AccessibilityManager {
+ private static final boolean DEBUG = false;
+
+ private static final String LOG_TAG = "AccessibilityManager";
+
+ /** @hide */
+ public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
+
+ /** @hide */
+ public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
+
+ /** @hide */
+ public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
+
+ /** @hide */
+ public static final int DALTONIZER_DISABLED = -1;
+
+ /** @hide */
+ public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
+
+ /** @hide */
+ public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
+
+ /** @hide */
+ public static final int AUTOCLICK_DELAY_DEFAULT = 600;
+
+ /**
+ * Activity action: Launch UI to manage which accessibility service or feature is assigned
+ * to the navigation bar Accessibility button.
+ * <p>
+ * Input: Nothing.
+ * </p>
+ * <p>
+ * Output: Nothing.
+ * </p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
+ "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+
+ static final Object sInstanceSync = new Object();
+
+ private static AccessibilityManager sInstance;
+
+ private final Object mLock = new Object();
+
+ private IAccessibilityManager mService;
+
+ final int mUserId;
+
+ final Handler mHandler;
+
+ final Handler.Callback mCallback;
+
+ boolean mIsEnabled;
- private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
+ int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ boolean mIsTouchExplorationEnabled;
+
+ boolean mIsHighTextContrastEnabled;
+
+ private final ArrayMap<AccessibilityStateChangeListener, Handler>
+ mAccessibilityStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<TouchExplorationStateChangeListener, Handler>
+ mTouchExplorationStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<HighTextContrastChangeListener, Handler>
+ mHighTextContrastStateChangeListeners = new ArrayMap<>();
+
+ private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
+ mServicesStateChangeListeners = new ArrayMap<>();
+
+ /**
+ * Map from a view's accessibility id to the list of request preparers set for that view
+ */
+ private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
/**
- * Listener for the accessibility state.
+ * Listener for the system accessibility state. To listen for changes to the
+ * accessibility state on the device, implement this interface and register
+ * it with the system by calling {@link #addAccessibilityStateChangeListener}.
*/
public interface AccessibilityStateChangeListener {
/**
- * Called back on change in the accessibility state.
+ * Called when the accessibility enabled state changes.
*
* @param enabled Whether accessibility is enabled.
*/
- public void onAccessibilityStateChanged(boolean enabled);
+ void onAccessibilityStateChanged(boolean enabled);
}
/**
@@ -71,7 +177,24 @@ public final class AccessibilityManager {
*
* @param enabled Whether touch exploration is enabled.
*/
- public void onTouchExplorationStateChanged(boolean enabled);
+ void onTouchExplorationStateChanged(boolean enabled);
+ }
+
+ /**
+ * Listener for changes to the state of accessibility services. Changes include services being
+ * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
+ * {@see #addAccessibilityServicesStateChangeListener}.
+ *
+ * @hide
+ */
+ public interface AccessibilityServicesStateChangeListener {
+
+ /**
+ * Called when the state of accessibility services changes.
+ *
+ * @param manager The manager that is calling back
+ */
+ void onAccessibilityServicesStateChanged(AccessibilityManager manager);
}
/**
@@ -79,6 +202,8 @@ public final class AccessibilityManager {
* the high text contrast state on the device, implement this interface and
* register it with the system by calling
* {@link #addHighTextContrastStateChangeListener}.
+ *
+ * @hide
*/
public interface HighTextContrastChangeListener {
@@ -87,26 +212,72 @@ public final class AccessibilityManager {
*
* @param enabled Whether high text contrast is enabled.
*/
- public void onHighTextContrastStateChanged(boolean enabled);
+ void onHighTextContrastStateChanged(boolean enabled);
}
private final IAccessibilityManagerClient.Stub mClient =
new IAccessibilityManagerClient.Stub() {
- public void setState(int state) {
- }
+ @Override
+ public void setState(int state) {
+ // We do not want to change this immediately as the application may
+ // have already checked that accessibility is on and fired an event,
+ // that is now propagating up the view tree, Hence, if accessibility
+ // is now off an exception will be thrown. We want to have the exception
+ // enforcement to guard against apps that fire unnecessary accessibility
+ // events when accessibility is off.
+ mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
+ }
- public void notifyServicesStateChanged() {
+ @Override
+ public void notifyServicesStateChanged() {
+ final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mServicesStateChangeListeners.isEmpty()) {
+ return;
}
+ listeners = new ArrayMap<>(mServicesStateChangeListeners);
+ }
- public void setRelevantEventTypes(int eventTypes) {
- }
- };
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final AccessibilityServicesStateChangeListener listener =
+ mServicesStateChangeListeners.keyAt(i);
+ mServicesStateChangeListeners.valueAt(i).post(() -> listener
+ .onAccessibilityServicesStateChanged(AccessibilityManager.this));
+ }
+ }
+
+ @Override
+ public void setRelevantEventTypes(int eventTypes) {
+ mRelevantEventTypes = eventTypes;
+ }
+ };
/**
* Get an AccessibilityManager instance (create one if necessary).
*
+ * @param context Context in which this manager operates.
+ *
+ * @hide
*/
public static AccessibilityManager getInstance(Context context) {
+ synchronized (sInstanceSync) {
+ if (sInstance == null) {
+ final int userId;
+ if (Binder.getCallingUid() == Process.SYSTEM_UID
+ || context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS)
+ == PackageManager.PERMISSION_GRANTED
+ || context.checkCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ userId = UserHandle.USER_CURRENT;
+ } else {
+ userId = UserHandle.myUserId();
+ }
+ sInstance = new AccessibilityManager(context, null, userId);
+ }
+ }
return sInstance;
}
@@ -114,21 +285,68 @@ public final class AccessibilityManager {
* Create an instance.
*
* @param context A {@link Context}.
+ * @param service An interface to the backing service.
+ * @param userId User id under which to run.
+ *
+ * @hide
*/
public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
+ // Constructor can't be chained because we can't create an instance of an inner class
+ // before calling another constructor.
+ mCallback = new MyCallback();
+ mHandler = new Handler(context.getMainLooper(), mCallback);
+ mUserId = userId;
+ synchronized (mLock) {
+ tryConnectToServiceLocked(service);
+ }
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param handler The handler to use
+ * @param service An interface to the backing service.
+ * @param userId User id under which to run.
+ *
+ * @hide
+ */
+ public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
+ mCallback = new MyCallback();
+ mHandler = handler;
+ mUserId = userId;
+ synchronized (mLock) {
+ tryConnectToServiceLocked(service);
+ }
}
+ /**
+ * @hide
+ */
public IAccessibilityManagerClient getClient() {
return mClient;
}
/**
- * Returns if the {@link AccessibilityManager} is enabled.
+ * @hide
+ */
+ @VisibleForTesting
+ public Handler.Callback getCallback() {
+ return mCallback;
+ }
+
+ /**
+ * Returns if the accessibility in the system is enabled.
*
- * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+ * @return True if accessibility is enabled, false otherwise.
*/
public boolean isEnabled() {
- return false;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsEnabled;
+ }
}
/**
@@ -137,7 +355,13 @@ public final class AccessibilityManager {
* @return True if touch exploration is enabled, false otherwise.
*/
public boolean isTouchExplorationEnabled() {
- return true;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsTouchExplorationEnabled;
+ }
}
/**
@@ -147,35 +371,169 @@ public final class AccessibilityManager {
* doing its own rendering and does not rely on the platform rendering pipeline.
* </p>
*
+ * @return True if high text contrast is enabled, false otherwise.
+ *
+ * @hide
*/
public boolean isHighTextContrastEnabled() {
- return false;
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ return mIsHighTextContrastEnabled;
+ }
}
/**
* Sends an {@link AccessibilityEvent}.
+ *
+ * @param event The event to send.
+ *
+ * @throws IllegalStateException if accessibility is not enabled.
+ *
+ * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
+ * events is through calling
+ * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+ * instead of this method to allow predecessors to augment/filter events sent by
+ * their descendants.
*/
public void sendAccessibilityEvent(AccessibilityEvent event) {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ if (!mIsEnabled) {
+ Looper myLooper = Looper.myLooper();
+ if (myLooper == Looper.getMainLooper()) {
+ throw new IllegalStateException(
+ "Accessibility off. Did you forget to check that?");
+ } else {
+ // If we're not running on the thread with the main looper, it's possible for
+ // the state of accessibility to change between checking isEnabled and
+ // calling this method. So just log the error rather than throwing the
+ // exception.
+ Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
+ return;
+ }
+ }
+ if ((event.getEventType() & mRelevantEventTypes) == 0) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
+ + " that is not among "
+ + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
+ }
+ return;
+ }
+ userId = mUserId;
+ }
+ try {
+ event.setEventTime(SystemClock.uptimeMillis());
+ // it is possible that this manager is in the same process as the service but
+ // 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);
+ if (DEBUG) {
+ Log.i(LOG_TAG, event + " sent");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error during sending " + event + " ", re);
+ } finally {
+ event.recycle();
+ }
}
/**
- * Requests interruption of the accessibility feedback from all accessibility services.
+ * Requests feedback interruption from all accessibility services.
*/
public void interrupt() {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ if (!mIsEnabled) {
+ Looper myLooper = Looper.myLooper();
+ if (myLooper == Looper.getMainLooper()) {
+ throw new IllegalStateException(
+ "Accessibility off. Did you forget to check that?");
+ } else {
+ // If we're not running on the thread with the main looper, it's possible for
+ // the state of accessibility to change between checking isEnabled and
+ // calling this method. So just log the error rather than throwing the
+ // exception.
+ Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
+ return;
+ }
+ }
+ userId = mUserId;
+ }
+ try {
+ service.interrupt(userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Requested interrupt from all services");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
+ }
}
/**
* Returns the {@link ServiceInfo}s of the installed accessibility services.
*
* @return An unmodifiable list with {@link ServiceInfo}s.
+ *
+ * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
*/
@Deprecated
public List<ServiceInfo> getAccessibilityServiceList() {
- return Collections.emptyList();
+ List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
+ List<ServiceInfo> services = new ArrayList<>();
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityServiceInfo info = infos.get(i);
+ services.add(info.getResolveInfo().serviceInfo);
+ }
+ return Collections.unmodifiableList(services);
}
+ /**
+ * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+ */
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
- return Collections.emptyList();
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return Collections.emptyList();
+ }
+ userId = mUserId;
+ }
+
+ List<AccessibilityServiceInfo> services = null;
+ try {
+ services = service.getInstalledAccessibilityServiceList(userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ if (services != null) {
+ return Collections.unmodifiableList(services);
+ } else {
+ return Collections.emptyList();
+ }
}
/**
@@ -190,21 +548,48 @@ public final class AccessibilityManager {
* @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
* @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
* @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+ * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
*/
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
int feedbackTypeFlags) {
- return Collections.emptyList();
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return Collections.emptyList();
+ }
+ userId = mUserId;
+ }
+
+ List<AccessibilityServiceInfo> services = null;
+ try {
+ services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ if (services != null) {
+ return Collections.unmodifiableList(services);
+ } else {
+ return Collections.emptyList();
+ }
}
/**
* Registers an {@link AccessibilityStateChangeListener} for changes in
- * the global accessibility state of the system.
+ * the global accessibility state of the system. Equivalent to calling
+ * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
+ * with a null handler.
*
* @param listener The listener.
- * @return True if successfully registered.
+ * @return Always returns {@code true}.
*/
public boolean addAccessibilityStateChangeListener(
- AccessibilityStateChangeListener listener) {
+ @NonNull AccessibilityStateChangeListener listener) {
+ addAccessibilityStateChangeListener(listener, null);
return true;
}
@@ -218,22 +603,40 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addAccessibilityStateChangeListener(
- @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
+ @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mAccessibilityStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+ /**
+ * Unregisters an {@link AccessibilityStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if the listener was previously registered.
+ */
public boolean removeAccessibilityStateChangeListener(
- AccessibilityStateChangeListener listener) {
- return true;
+ @NonNull AccessibilityStateChangeListener listener) {
+ synchronized (mLock) {
+ int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
+ mAccessibilityStateChangeListeners.remove(listener);
+ return (index >= 0);
+ }
}
/**
* Registers a {@link TouchExplorationStateChangeListener} for changes in
- * the global touch exploration state of the system.
+ * the global touch exploration state of the system. Equivalent to calling
+ * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
+ * with a null handler.
*
* @param listener The listener.
- * @return True if successfully registered.
+ * @return Always returns {@code true}.
*/
public boolean addTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
+ addTouchExplorationStateChangeListener(listener, null);
return true;
}
@@ -247,17 +650,103 @@ public final class AccessibilityManager {
* for a callback on the process's main handler.
*/
public void addTouchExplorationStateChangeListener(
- @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
+ @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mTouchExplorationStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
/**
* Unregisters a {@link TouchExplorationStateChangeListener}.
*
* @param listener The listener.
- * @return True if successfully unregistered.
+ * @return True if listener was previously registered.
*/
public boolean removeTouchExplorationStateChangeListener(
@NonNull TouchExplorationStateChangeListener listener) {
- return true;
+ synchronized (mLock) {
+ int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
+ mTouchExplorationStateChangeListeners.remove(listener);
+ return (index >= 0);
+ }
+ }
+
+ /**
+ * Registers a {@link AccessibilityServicesStateChangeListener}.
+ *
+ * @param listener The listener.
+ * @param handler The handler on which the listener should be called back, or {@code null}
+ * for a callback on the process's main handler.
+ * @hide
+ */
+ public void addAccessibilityServicesStateChangeListener(
+ @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mServicesStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
+
+ /**
+ * Unregisters a {@link AccessibilityServicesStateChangeListener}.
+ *
+ * @param listener The listener.
+ *
+ * @hide
+ */
+ public void removeAccessibilityServicesStateChangeListener(
+ @NonNull AccessibilityServicesStateChangeListener listener) {
+ // Final CopyOnWriteArrayList - no lock needed.
+ mServicesStateChangeListeners.remove(listener);
+ }
+
+ /**
+ * Registers a {@link AccessibilityRequestPreparer}.
+ */
+ public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+ if (mRequestPreparerLists == null) {
+ mRequestPreparerLists = new SparseArray<>(1);
+ }
+ int id = preparer.getView().getAccessibilityViewId();
+ List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
+ if (requestPreparerList == null) {
+ requestPreparerList = new ArrayList<>(1);
+ mRequestPreparerLists.put(id, requestPreparerList);
+ }
+ requestPreparerList.add(preparer);
+ }
+
+ /**
+ * Unregisters a {@link AccessibilityRequestPreparer}.
+ */
+ public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+ if (mRequestPreparerLists == null) {
+ return;
+ }
+ int viewId = preparer.getView().getAccessibilityViewId();
+ List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
+ if (requestPreparerList != null) {
+ requestPreparerList.remove(preparer);
+ if (requestPreparerList.isEmpty()) {
+ mRequestPreparerLists.remove(viewId);
+ }
+ }
+ }
+
+ /**
+ * Get the preparers that are registered for an accessibility ID
+ *
+ * @param id The ID of interest
+ * @return The list of preparers, or {@code null} if there are none.
+ *
+ * @hide
+ */
+ public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
+ if (mRequestPreparerLists == null) {
+ return null;
+ }
+ return mRequestPreparerLists.get(id);
}
/**
@@ -269,7 +758,12 @@ public final class AccessibilityManager {
* @hide
*/
public void addHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
+ @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+ synchronized (mLock) {
+ mHighTextContrastStateChangeListeners
+ .put(listener, (handler == null) ? mHandler : handler);
+ }
+ }
/**
* Unregisters a {@link HighTextContrastChangeListener}.
@@ -279,7 +773,51 @@ public final class AccessibilityManager {
* @hide
*/
public void removeHighTextContrastStateChangeListener(
- @NonNull HighTextContrastChangeListener listener) {}
+ @NonNull HighTextContrastChangeListener listener) {
+ synchronized (mLock) {
+ mHighTextContrastStateChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Check if the accessibility volume stream is active.
+ *
+ * @return True if accessibility volume is active (i.e. some service has requested it). False
+ * otherwise.
+ * @hide
+ */
+ public boolean isAccessibilityVolumeStreamActive() {
+ List<AccessibilityServiceInfo> serviceInfos =
+ getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ for (int i = 0; i < serviceInfos.size(); i++) {
+ if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Report a fingerprint gesture to accessibility. Only available for the system process.
+ *
+ * @param keyCode The key code of the gesture
+ * @return {@code true} if accessibility consumes the event. {@code false} if not.
+ * @hide
+ */
+ public boolean sendFingerprintGesture(int keyCode) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
+ }
+ try {
+ return service.sendFingerprintGesture(keyCode);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
/**
* Sets the current state and notifies listeners, if necessary.
@@ -287,14 +825,314 @@ public final class AccessibilityManager {
* @param stateFlags The state flags.
*/
private void setStateLocked(int stateFlags) {
+ final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
+ final boolean touchExplorationEnabled =
+ (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
+ final boolean highTextContrastEnabled =
+ (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
+
+ final boolean wasEnabled = mIsEnabled;
+ final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
+ final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
+
+ // Ensure listeners get current state from isZzzEnabled() calls.
+ mIsEnabled = enabled;
+ mIsTouchExplorationEnabled = touchExplorationEnabled;
+ mIsHighTextContrastEnabled = highTextContrastEnabled;
+
+ if (wasEnabled != enabled) {
+ notifyAccessibilityStateChanged();
+ }
+
+ if (wasTouchExplorationEnabled != touchExplorationEnabled) {
+ notifyTouchExplorationStateChanged();
+ }
+
+ if (wasHighTextContrastEnabled != highTextContrastEnabled) {
+ notifyHighTextContrastStateChanged();
+ }
}
+ /**
+ * Find an installed service with the specified {@link ComponentName}.
+ *
+ * @param componentName The name to match to the service.
+ *
+ * @return The info corresponding to the installed service, or {@code null} if no such service
+ * is installed.
+ * @hide
+ */
+ public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
+ ComponentName componentName) {
+ final List<AccessibilityServiceInfo> installedServiceInfos =
+ getInstalledAccessibilityServiceList();
+ if ((installedServiceInfos == null) || (componentName == null)) {
+ return null;
+ }
+ for (int i = 0; i < installedServiceInfos.size(); i++) {
+ if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
+ return installedServiceInfos.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is added.
+ * @param connection The connection.
+ *
+ * @hide
+ */
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection) {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return View.NO_ID;
+ }
+ userId = mUserId;
+ }
+ try {
+ return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
+ }
return View.NO_ID;
}
+ /**
+ * Removed an accessibility interaction connection interface for a given window.
+ * @param windowToken The window token to which a connection is removed.
+ *
+ * @hide
+ */
public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.removeAccessibilityInteractionConnection(windowToken);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
+ }
+ }
+
+ /**
+ * Perform the accessibility shortcut if the caller has permission.
+ *
+ * @hide
+ */
+ public void performAccessibilityShortcut() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.performAccessibilityShortcut();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
+ }
+ }
+
+ /**
+ * Notifies that the accessibility button in the system's navigation area has been clicked
+ *
+ * @hide
+ */
+ public void notifyAccessibilityButtonClicked() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.notifyAccessibilityButtonClicked();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
+ }
+ }
+
+ /**
+ * Notifies that the visibility of the accessibility button in the system's navigation area
+ * has changed.
+ *
+ * @param shown {@code true} if the accessibility button is visible within the system
+ * navigation area, {@code false} otherwise
+ * @hide
+ */
+ public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.notifyAccessibilityButtonVisibilityChanged(shown);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
+ }
+ }
+
+ /**
+ * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
+ * window. Intended for use by the System UI only.
+ *
+ * @param connection The connection to handle the actions. Set to {@code null} to avoid
+ * affecting the actions.
+ *
+ * @hide
+ */
+ public void setPictureInPictureActionReplacingConnection(
+ @Nullable IAccessibilityInteractionConnection connection) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.setPictureInPictureActionReplacingConnection(connection);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
+ }
}
+ private IAccessibilityManager getServiceLocked() {
+ if (mService == null) {
+ tryConnectToServiceLocked(null);
+ }
+ return mService;
+ }
+
+ private void tryConnectToServiceLocked(IAccessibilityManager service) {
+ if (service == null) {
+ IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ if (iBinder == null) {
+ return;
+ }
+ service = IAccessibilityManager.Stub.asInterface(iBinder);
+ }
+
+ try {
+ final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
+ setStateLocked(IntPair.first(userStateAndRelevantEvents));
+ mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
+ mService = service;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
+ }
+ }
+
+ /**
+ * Notifies the registered {@link AccessibilityStateChangeListener}s.
+ */
+ private void notifyAccessibilityStateChanged() {
+ final boolean isEnabled;
+ final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mAccessibilityStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isEnabled = mIsEnabled;
+ listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final AccessibilityStateChangeListener listener =
+ mAccessibilityStateChangeListeners.keyAt(i);
+ mAccessibilityStateChangeListeners.valueAt(i)
+ .post(() -> listener.onAccessibilityStateChanged(isEnabled));
+ }
+ }
+
+ /**
+ * Notifies the registered {@link TouchExplorationStateChangeListener}s.
+ */
+ private void notifyTouchExplorationStateChanged() {
+ final boolean isTouchExplorationEnabled;
+ final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mTouchExplorationStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isTouchExplorationEnabled = mIsTouchExplorationEnabled;
+ listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final TouchExplorationStateChangeListener listener =
+ mTouchExplorationStateChangeListeners.keyAt(i);
+ mTouchExplorationStateChangeListeners.valueAt(i)
+ .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
+ }
+ }
+
+ /**
+ * Notifies the registered {@link HighTextContrastChangeListener}s.
+ */
+ private void notifyHighTextContrastStateChanged() {
+ final boolean isHighTextContrastEnabled;
+ final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
+ synchronized (mLock) {
+ if (mHighTextContrastStateChangeListeners.isEmpty()) {
+ return;
+ }
+ isHighTextContrastEnabled = mIsHighTextContrastEnabled;
+ listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
+ }
+
+ int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final HighTextContrastChangeListener listener =
+ mHighTextContrastStateChangeListeners.keyAt(i);
+ mHighTextContrastStateChangeListeners.valueAt(i)
+ .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
+ }
+ }
+
+ /**
+ * Determines if the accessibility button within the system navigation area is supported.
+ *
+ * @return {@code true} if the accessibility button is supported on this device,
+ * {@code false} otherwise
+ */
+ public static boolean isAccessibilityButtonSupported() {
+ final Resources res = Resources.getSystem();
+ return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
+ }
+
+ private final class MyCallback implements Handler.Callback {
+ public static final int MSG_SET_STATE = 1;
+
+ @Override
+ public boolean handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_SET_STATE: {
+ // See comment at mClient
+ final int state = message.arg1;
+ synchronized (mLock) {
+ setStateLocked(state);
+ }
+ } break;
+ }
+ return true;
+ }
+ }
}
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index 61cbce97..4fb2a99a 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -37,14 +37,13 @@ import android.service.autofill.AutofillService;
import android.service.autofill.FillEventHistory;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.DebugUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -202,9 +201,12 @@ public final class AutofillManager {
* Initial state of the autofill context, set when there is no session (i.e., when
* {@link #mSessionId} is {@link #NO_SESSION}).
*
+ * <p>In this state, app callbacks (such as {@link #notifyViewEntered(View)}) are notified to
+ * the server.
+ *
* @hide
*/
- public static final int STATE_UNKNOWN = 1;
+ public static final int STATE_UNKNOWN = 0;
/**
* State where the autofill context hasn't been {@link #commit() finished} nor
@@ -212,7 +214,18 @@ public final class AutofillManager {
*
* @hide
*/
- public static final int STATE_ACTIVE = 2;
+ public static final int STATE_ACTIVE = 1;
+
+ /**
+ * State where the autofill context was finished by the server because the autofill
+ * service could not autofill the page.
+ *
+ * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
+ * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
+ *
+ * @hide
+ */
+ public static final int STATE_FINISHED = 2;
/**
* State where the autofill context has been {@link #commit() finished} but the server still has
@@ -220,7 +233,7 @@ public final class AutofillManager {
*
* @hide
*/
- public static final int STATE_SHOWING_SAVE_UI = 4;
+ public static final int STATE_SHOWING_SAVE_UI = 3;
/**
* Makes an authentication id from a request id and a dataset id.
@@ -559,6 +572,14 @@ public final class AutofillManager {
}
AutofillCallback callback = null;
synchronized (mLock) {
+ if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
+ + "): ignored on state " + getStateAsStringLocked());
+ }
+ return;
+ }
+
ensureServiceClientAddedIfNeededLocked();
if (!mEnabled) {
@@ -682,6 +703,14 @@ public final class AutofillManager {
}
AutofillCallback callback = null;
synchronized (mLock) {
+ if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
+ + ", virtualId=" + virtualId
+ + "): ignored on state " + getStateAsStringLocked());
+ }
+ return;
+ }
ensureServiceClientAddedIfNeededLocked();
if (!mEnabled) {
@@ -765,6 +794,10 @@ public final class AutofillManager {
}
if (!mEnabled || !isActiveLocked()) {
+ if (sVerbose && mEnabled) {
+ Log.v(TAG, "notifyValueChanged(" + view + "): ignoring on state "
+ + getStateAsStringLocked());
+ }
return;
}
@@ -904,10 +937,7 @@ public final class AutofillManager {
}
private AutofillClient getClientLocked() {
- if (mContext instanceof AutofillClient) {
- return (AutofillClient) mContext;
- }
- return null;
+ return mContext.getAutofillClient();
}
/** @hide */
@@ -950,10 +980,13 @@ public final class AutofillManager {
@NonNull AutofillValue value, int flags) {
if (sVerbose) {
Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
- + ", flags=" + flags + ", state=" + mState);
+ + ", flags=" + flags + ", state=" + getStateAsStringLocked());
}
- if (mState != STATE_UNKNOWN) {
- if (sDebug) Log.d(TAG, "not starting session for " + id + " on state " + mState);
+ if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "not automatically starting session for " + id
+ + " on state " + getStateAsStringLocked());
+ }
return;
}
try {
@@ -973,7 +1006,7 @@ public final class AutofillManager {
}
private void finishSessionLocked() {
- if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + mState);
+ if (sVerbose) Log.v(TAG, "finishSessionLocked(): " + getStateAsStringLocked());
if (!isActiveLocked()) return;
@@ -987,7 +1020,7 @@ public final class AutofillManager {
}
private void cancelSessionLocked() {
- if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + mState);
+ if (sVerbose) Log.v(TAG, "cancelSessionLocked(): " + getStateAsStringLocked());
if (!isActiveLocked()) return;
@@ -1245,10 +1278,10 @@ public final class AutofillManager {
}
}
- final LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED);
- log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount);
- log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED,
- numApplied);
+ final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED)
+ .setPackageName(mContext.getPackageName())
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
mMetricsLogger.write(log);
}
}
@@ -1306,6 +1339,20 @@ public final class AutofillManager {
}
}
+ /**
+ * Marks the state of the session as finished.
+ *
+ * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
+ * FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed).
+ */
+ private void setSessionFinished(int newState) {
+ synchronized (mLock) {
+ if (sVerbose) Log.v(TAG, "setSessionFinished(): from " + mState + " to " + newState);
+ resetSessionLocked();
+ mState = newState;
+ }
+ }
+
private void requestHideFillUi(AutofillId id) {
final View anchor = findView(id);
if (sVerbose) Log.v(TAG, "requestHideFillUi(" + id + "): anchor = " + anchor);
@@ -1341,7 +1388,11 @@ public final class AutofillManager {
}
}
- private void notifyNoFillUi(int sessionId, AutofillId id) {
+ private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+ if (sVerbose) {
+ Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
+ + ", finished=" + sessionFinished);
+ }
final View anchor = findView(id);
if (anchor == null) {
return;
@@ -1361,7 +1412,11 @@ public final class AutofillManager {
} else {
callback.onAutofillEvent(anchor, AutofillCallback.EVENT_INPUT_UNAVAILABLE);
}
+ }
+ if (sessionFinished) {
+ // Callback call was "hijacked" to also update the session state.
+ setSessionFinished(STATE_FINISHED);
}
}
@@ -1434,8 +1489,7 @@ public final class AutofillManager {
pw.print(outerPrefix); pw.println("AutofillManager:");
final String pfx = outerPrefix + " ";
pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
- pw.print(pfx); pw.print("state: "); pw.println(
- DebugUtils.flagsToString(AutofillManager.class, "STATE_", mState));
+ pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
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);
@@ -1452,10 +1506,29 @@ public final class AutofillManager {
pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
}
+ private String getStateAsStringLocked() {
+ switch (mState) {
+ case STATE_UNKNOWN:
+ return "STATE_UNKNOWN";
+ case STATE_ACTIVE:
+ return "STATE_ACTIVE";
+ case STATE_FINISHED:
+ return "STATE_FINISHED";
+ case STATE_SHOWING_SAVE_UI:
+ return "STATE_SHOWING_SAVE_UI";
+ default:
+ return "INVALID:" + mState;
+ }
+ }
+
private boolean isActiveLocked() {
return mState == STATE_ACTIVE;
}
+ private boolean isFinishedLocked() {
+ return mState == STATE_FINISHED;
+ }
+
private void post(Runnable runnable) {
final AutofillClient client = getClientLocked();
if (client == null) {
@@ -1787,10 +1860,10 @@ public final class AutofillManager {
}
@Override
- public void notifyNoFillUi(int sessionId, AutofillId id) {
+ public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() -> afm.notifyNoFillUi(sessionId, id));
+ afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished));
}
}
@@ -1823,7 +1896,15 @@ public final class AutofillManager {
public void setSaveUiState(int sessionId, boolean shown) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
- afm.post(() ->afm.setSaveUiState(sessionId, shown));
+ afm.post(() -> afm.setSaveUiState(sessionId, shown));
+ }
+ }
+
+ @Override
+ public void setSessionFinished(int newState) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.setSessionFinished(newState));
}
}
}
diff --git a/android/view/autofill/AutofillPopupWindow.java b/android/view/autofill/AutofillPopupWindow.java
index 5f476380..b4688bb1 100644
--- a/android/view/autofill/AutofillPopupWindow.java
+++ b/android/view/autofill/AutofillPopupWindow.java
@@ -47,6 +47,19 @@ public class AutofillPopupWindow extends PopupWindow {
private final WindowPresenter mWindowPresenter;
private WindowManager.LayoutParams mWindowLayoutParams;
+ private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ /* ignore - handled by the super class */
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ dismiss();
+ }
+ };
+
/**
* Creates a popup window with a presenter owning the window and responsible for
* showing/hiding/updating the backing window. This can be useful of the window is
@@ -208,7 +221,21 @@ public class AutofillPopupWindow extends PopupWindow {
p.packageName = anchor.getContext().getPackageName();
mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
anchor.getLayoutDirection());
- return;
+ }
+
+ @Override
+ protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+ super.attachToAnchor(anchor, xoff, yoff, gravity);
+ anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ }
+
+ @Override
+ protected void detachFromAnchor() {
+ final View anchor = getAnchor();
+ if (anchor != null) {
+ anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ }
+ super.detachFromAnchor();
}
@Override
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 1849368f..8c3b8a2e 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -28,6 +28,7 @@ import android.view.textclassifier.TextClassifier.EntityType;
import com.android.internal.util.Preconditions;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -41,10 +42,10 @@ public final class TextClassification {
static final TextClassification EMPTY = new TextClassification.Builder().build();
@NonNull private final String mText;
- @Nullable private final Drawable mIcon;
- @Nullable private final String mLabel;
- @Nullable private final Intent mIntent;
- @Nullable private final OnClickListener mOnClickListener;
+ @NonNull private final List<Drawable> mIcons;
+ @NonNull private final List<String> mLabels;
+ @NonNull private final List<Intent> mIntents;
+ @NonNull private final List<OnClickListener> mOnClickListeners;
@NonNull private final EntityConfidence<String> mEntityConfidence;
@NonNull private final List<String> mEntities;
private int mLogType;
@@ -52,18 +53,21 @@ public final class TextClassification {
private TextClassification(
@Nullable String text,
- @Nullable Drawable icon,
- @Nullable String label,
- @Nullable Intent intent,
- @Nullable OnClickListener onClickListener,
+ @NonNull List<Drawable> icons,
+ @NonNull List<String> labels,
+ @NonNull List<Intent> intents,
+ @NonNull List<OnClickListener> onClickListeners,
@NonNull EntityConfidence<String> entityConfidence,
int logType,
@NonNull String versionInfo) {
+ Preconditions.checkArgument(labels.size() == intents.size());
+ Preconditions.checkArgument(icons.size() == intents.size());
+ Preconditions.checkArgument(onClickListeners.size() == intents.size());
mText = text;
- mIcon = icon;
- mLabel = label;
- mIntent = intent;
- mOnClickListener = onClickListener;
+ mIcons = icons;
+ mLabels = labels;
+ mIntents = intents;
+ mOnClickListeners = onClickListeners;
mEntityConfidence = new EntityConfidence<>(entityConfidence);
mEntities = mEntityConfidence.getEntities();
mLogType = logType;
@@ -109,35 +113,106 @@ public final class TextClassification {
}
/**
- * Returns an icon that may be rendered on a widget used to act on the classified text.
+ * Returns the number of actions that are available to act on the classified text.
+ * @see #getIntent(int)
+ * @see #getLabel(int)
+ * @see #getIcon(int)
+ * @see #getOnClickListener(int)
+ */
+ @IntRange(from = 0)
+ public int getActionCount() {
+ return mIntents.size();
+ }
+
+ /**
+ * Returns one of the icons that maybe rendered on a widget used to act on the classified text.
+ * @param index Index of the action to get the icon for.
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getActionCount() for the number of entities available.
+ * @see #getIntent(int)
+ * @see #getLabel(int)
+ * @see #getOnClickListener(int)
+ */
+ @Nullable
+ public Drawable getIcon(int index) {
+ return mIcons.get(index);
+ }
+
+ /**
+ * Returns an icon for the default intent that may be rendered on a widget used to act on the
+ * classified text.
*/
@Nullable
public Drawable getIcon() {
- return mIcon;
+ return mIcons.isEmpty() ? null : mIcons.get(0);
}
/**
- * Returns a label that may be rendered on a widget used to act on the classified text.
+ * Returns one of the labels that may be rendered on a widget used to act on the classified
+ * text.
+ * @param index Index of the action to get the label for.
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getActionCount()
+ * @see #getIntent(int)
+ * @see #getIcon(int)
+ * @see #getOnClickListener(int)
+ */
+ @Nullable
+ public CharSequence getLabel(int index) {
+ return mLabels.get(index);
+ }
+
+ /**
+ * Returns a label for the default intent that may be rendered on a widget used to act on the
+ * classified text.
*/
@Nullable
public CharSequence getLabel() {
- return mLabel;
+ return mLabels.isEmpty() ? null : mLabels.get(0);
}
/**
- * Returns an intent that may be fired to act on the classified text.
+ * Returns one of the intents that may be fired to act on the classified text.
+ * @param index Index of the action to get the intent for.
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getActionCount()
+ * @see #getLabel(int)
+ * @see #getIcon(int)
+ * @see #getOnClickListener(int)
+ */
+ @Nullable
+ public Intent getIntent(int index) {
+ return mIntents.get(index);
+ }
+
+ /**
+ * Returns the default intent that may be fired to act on the classified text.
*/
@Nullable
public Intent getIntent() {
- return mIntent;
+ return mIntents.isEmpty() ? null : mIntents.get(0);
}
/**
- * Returns an OnClickListener that may be triggered to act on the classified text.
+ * Returns one of the OnClickListeners that may be triggered to act on the classified text.
+ * @param index Index of the action to get the click listener for.
+ * @throws IndexOutOfBoundsException if the specified index is out of range.
+ * @see #getActionCount()
+ * @see #getIntent(int)
+ * @see #getLabel(int)
+ * @see #getIcon(int)
+ */
+ @Nullable
+ public OnClickListener getOnClickListener(int index) {
+ return mOnClickListeners.get(index);
+ }
+
+ /**
+ * Returns the default OnClickListener that may be triggered to act on the classified text.
*/
@Nullable
public OnClickListener getOnClickListener() {
- return mOnClickListener;
+ return mOnClickListeners.isEmpty() ? null : mOnClickListeners.get(0);
}
/**
@@ -160,8 +235,8 @@ public final class TextClassification {
@Override
public String toString() {
return String.format("TextClassification {"
- + "text=%s, entities=%s, label=%s, intent=%s}",
- mText, mEntityConfidence, mLabel, mIntent);
+ + "text=%s, entities=%s, labels=%s, intents=%s}",
+ mText, mEntityConfidence, mLabels, mIntents);
}
/**
@@ -184,10 +259,10 @@ public final class TextClassification {
public static final class Builder {
@NonNull private String mText;
- @Nullable private Drawable mIcon;
- @Nullable private String mLabel;
- @Nullable private Intent mIntent;
- @Nullable private OnClickListener mOnClickListener;
+ @NonNull private final List<Drawable> mIcons = new ArrayList<>();
+ @NonNull private final List<String> mLabels = new ArrayList<>();
+ @NonNull private final List<Intent> mIntents = new ArrayList<>();
+ @NonNull private final List<OnClickListener> mOnClickListeners = new ArrayList<>();
@NonNull private final EntityConfidence<String> mEntityConfidence =
new EntityConfidence<>();
private int mLogType;
@@ -216,26 +291,57 @@ public final class TextClassification {
}
/**
- * Sets an icon that may be rendered on a widget used to act on the classified text.
+ * Adds an action that may be performed on the classified text. The label and icon are used
+ * for rendering of widgets that offer the intent. Actions should be added in order of
+ * priority and the first one will be treated as the default.
+ */
+ public Builder addAction(
+ Intent intent, @Nullable String label, @Nullable Drawable icon,
+ @Nullable OnClickListener onClickListener) {
+ mIntents.add(intent);
+ mLabels.add(label);
+ mIcons.add(icon);
+ mOnClickListeners.add(onClickListener);
+ return this;
+ }
+
+ /**
+ * Removes all actions.
+ */
+ public Builder clearActions() {
+ mIntents.clear();
+ mOnClickListeners.clear();
+ mLabels.clear();
+ mIcons.clear();
+ return this;
+ }
+
+ /**
+ * Sets the icon for the default action that may be rendered on a widget used to act on the
+ * classified text.
*/
public Builder setIcon(@Nullable Drawable icon) {
- mIcon = icon;
+ ensureDefaultActionAvailable();
+ mIcons.set(0, icon);
return this;
}
/**
- * Sets a label that may be rendered on a widget used to act on the classified text.
+ * Sets the label for the default action that may be rendered on a widget used to act on the
+ * classified text.
*/
public Builder setLabel(@Nullable String label) {
- mLabel = label;
+ ensureDefaultActionAvailable();
+ mLabels.set(0, label);
return this;
}
/**
- * Sets an intent that may be fired to act on the classified text.
+ * Sets the intent for the default action that may be fired to act on the classified text.
*/
public Builder setIntent(@Nullable Intent intent) {
- mIntent = intent;
+ ensureDefaultActionAvailable();
+ mIntents.set(0, intent);
return this;
}
@@ -249,10 +355,12 @@ public final class TextClassification {
}
/**
- * Sets an OnClickListener that may be triggered to act on the classified text.
+ * Sets the OnClickListener for the default action that may be triggered to act on the
+ * classified text.
*/
public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
- mOnClickListener = onClickListener;
+ ensureDefaultActionAvailable();
+ mOnClickListeners.set(0, onClickListener);
return this;
}
@@ -266,11 +374,21 @@ public final class TextClassification {
}
/**
+ * Ensures that we have at we have storage for the default action.
+ */
+ private void ensureDefaultActionAvailable() {
+ if (mIntents.isEmpty()) mIntents.add(null);
+ if (mLabels.isEmpty()) mLabels.add(null);
+ if (mIcons.isEmpty()) mIcons.add(null);
+ if (mOnClickListeners.isEmpty()) mOnClickListeners.add(null);
+ }
+
+ /**
* Builds and returns a {@link TextClassification} object.
*/
public TextClassification build() {
return new TextClassification(
- mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence,
+ mText, mIcons, mLabels, mIntents, mOnClickListeners, mEntityConfidence,
mLogType, mVersionInfo);
}
}
diff --git a/android/view/textclassifier/TextClassifierImpl.java b/android/view/textclassifier/TextClassifierImpl.java
index 7e93b78c..2aa81a2c 100644
--- a/android/view/textclassifier/TextClassifierImpl.java
+++ b/android/view/textclassifier/TextClassifierImpl.java
@@ -29,6 +29,7 @@ import android.net.Uri;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
+import android.provider.ContactsContract;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.method.WordIterator;
@@ -356,7 +357,16 @@ final class TextClassifierImpl implements TextClassifier {
final String type = getHighestScoringType(classifications);
builder.setLogType(IntentFactory.getLogType(type));
- final Intent intent = IntentFactory.create(mContext, type, text.toString());
+ final List<Intent> intents = IntentFactory.create(mContext, type, text.toString());
+ for (Intent intent : intents) {
+ extendClassificationWithIntent(intent, builder);
+ }
+
+ return builder.setVersionInfo(getVersionInfo()).build();
+ }
+
+ /** Extends the classification with the intent if it can be resolved. */
+ private void extendClassificationWithIntent(Intent intent, TextClassification.Builder builder) {
final PackageManager pm;
final ResolveInfo resolveInfo;
if (intent != null) {
@@ -367,30 +377,29 @@ final class TextClassifierImpl implements TextClassifier {
resolveInfo = null;
}
if (resolveInfo != null && resolveInfo.activityInfo != null) {
- builder.setIntent(intent)
- .setOnClickListener(TextClassification.createStartActivityOnClickListener(
- mContext, intent));
-
final String packageName = resolveInfo.activityInfo.packageName;
+ CharSequence label;
+ Drawable icon;
if ("android".equals(packageName)) {
// Requires the chooser to find an activity to handle the intent.
- builder.setLabel(IntentFactory.getLabel(mContext, type));
+ label = IntentFactory.getLabel(mContext, intent);
+ icon = null;
} else {
// A default activity will handle the intent.
intent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name));
- Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
+ icon = resolveInfo.activityInfo.loadIcon(pm);
if (icon == null) {
icon = resolveInfo.loadIcon(pm);
}
- builder.setIcon(icon);
- CharSequence label = resolveInfo.activityInfo.loadLabel(pm);
+ label = resolveInfo.activityInfo.loadLabel(pm);
if (label == null) {
label = resolveInfo.loadLabel(pm);
}
- builder.setLabel(label != null ? label.toString() : null);
}
+ builder.addAction(
+ intent, label != null ? label.toString() : null, icon,
+ TextClassification.createStartActivityOnClickListener(mContext, intent));
}
- return builder.setVersionInfo(getVersionInfo()).build();
}
private static int getHintFlags(CharSequence text, int start, int end) {
@@ -477,10 +486,11 @@ final class TextClassifierImpl implements TextClassifier {
if (results.length > 0) {
final String type = getHighestScoringType(results);
if (matches(type, linkMask)) {
- final Intent intent = IntentFactory.create(
+ // For links without disambiguation, we simply use the default intent.
+ final List<Intent> intents = IntentFactory.create(
context, type, text.substring(selectionStart, selectionEnd));
- if (hasActivityHandler(context, intent)) {
- final ClickableSpan span = createSpan(context, intent);
+ if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
+ final ClickableSpan span = createSpan(context, intents.get(0));
spans.add(new SpanSpec(selectionStart, selectionEnd, span));
}
}
@@ -564,7 +574,7 @@ final class TextClassifierImpl implements TextClassifier {
};
}
- private static boolean hasActivityHandler(Context context, @Nullable Intent intent) {
+ private static boolean hasActivityHandler(Context context, Intent intent) {
if (intent == null) {
return false;
}
@@ -625,20 +635,32 @@ final class TextClassifierImpl implements TextClassifier {
private IntentFactory() {}
- @Nullable
- public static Intent create(Context context, String type, String text) {
+ @NonNull
+ public static List<Intent> create(Context context, String type, String text) {
+ final List<Intent> intents = new ArrayList<>();
type = type.trim().toLowerCase(Locale.ENGLISH);
text = text.trim();
switch (type) {
case TextClassifier.TYPE_EMAIL:
- return new Intent(Intent.ACTION_SENDTO)
- .setData(Uri.parse(String.format("mailto:%s", text)));
+ 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));
+ break;
case TextClassifier.TYPE_PHONE:
- return new Intent(Intent.ACTION_DIAL)
- .setData(Uri.parse(String.format("tel:%s", text)));
+ intents.add(new Intent(Intent.ACTION_DIAL)
+ .setData(Uri.parse(String.format("tel:%s", text))));
+ intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+ .putExtra(ContactsContract.Intents.Insert.PHONE, text));
+ intents.add(new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("smsto:%s", text))));
+ break;
case TextClassifier.TYPE_ADDRESS:
- return new Intent(Intent.ACTION_VIEW)
- .setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+ intents.add(new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(String.format("geo:0,0?q=%s", text))));
+ break;
case TextClassifier.TYPE_URL:
final String httpPrefix = "http://";
final String httpsPrefix = "https://";
@@ -649,25 +671,47 @@ final class TextClassifierImpl implements TextClassifier {
} else {
text = httpPrefix + text;
}
- return new Intent(Intent.ACTION_VIEW, Uri.parse(text))
- .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
- default:
- return null;
+ intents.add(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
+ .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()));
+ break;
}
+ return intents;
}
@Nullable
- public static String getLabel(Context context, String type) {
- type = type.trim().toLowerCase(Locale.ENGLISH);
- switch (type) {
- case TextClassifier.TYPE_EMAIL:
- return context.getString(com.android.internal.R.string.email);
- case TextClassifier.TYPE_PHONE:
+ public static String getLabel(Context context, @Nullable Intent intent) {
+ if (intent == null || intent.getAction() == null) {
+ return null;
+ }
+ switch (intent.getAction()) {
+ case Intent.ACTION_DIAL:
return context.getString(com.android.internal.R.string.dial);
- case TextClassifier.TYPE_ADDRESS:
- return context.getString(com.android.internal.R.string.map);
- case TextClassifier.TYPE_URL:
- return context.getString(com.android.internal.R.string.browse);
+ case Intent.ACTION_SENDTO:
+ switch (intent.getScheme()) {
+ case "mailto":
+ return context.getString(com.android.internal.R.string.email);
+ case "smsto":
+ return context.getString(com.android.internal.R.string.sms);
+ default:
+ return null;
+ }
+ case Intent.ACTION_INSERT_OR_EDIT:
+ switch (intent.getDataString()) {
+ case ContactsContract.Contacts.CONTENT_ITEM_TYPE:
+ return context.getString(com.android.internal.R.string.add_contact);
+ default:
+ return null;
+ }
+ case Intent.ACTION_VIEW:
+ switch (intent.getScheme()) {
+ case "geo":
+ return context.getString(com.android.internal.R.string.map);
+ case "http": // fall through
+ case "https":
+ return context.getString(com.android.internal.R.string.browse);
+ default:
+ return null;
+ }
default:
return null;
}
diff --git a/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 8d88ba60..83af19bb 100644
--- a/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -581,6 +581,7 @@ public final class SmartSelectionEventTracker {
case ActionType.SMART_SHARE: // fall through
case ActionType.DRAG: // fall through
case ActionType.ABANDON: // fall through
+ case ActionType.OTHER: // fall through
return true;
default:
return false;
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index 8e1f2183..f368c74a 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,58 +1,213 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR 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.textservice;
+import android.annotation.SystemService;
+import android.content.Context;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.Log;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesManager;
+
import java.util.Locale;
/**
- * A stub class of TextServicesManager for Layout-Lib.
+ * System API to the overall text services, which arbitrates interaction between applications
+ * and text services.
+ *
+ * The user can change the current text services in Settings. And also applications can specify
+ * the target text services.
+ *
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the text services
+ * framework (TSF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>text services manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts. It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> A <strong>text service</strong> implements a particular
+ * interaction model allowing the client application to retrieve information of text.
+ * The system binds to the current text service that is in use, causing it to be created and run.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the text service
+ * manager for connections to text services.
+ * </ul>
+ *
+ * <h3>Text services sessions</h3>
+ * <ul>
+ * <li>The <strong>spell checker session</strong> is one of the text services.
+ * {@link android.view.textservice.SpellCheckerSession}</li>
+ * </ul>
+ *
*/
+@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
public final class TextServicesManager {
- private static final TextServicesManager sInstance = new TextServicesManager();
- private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
+ private static final String TAG = TextServicesManager.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ private static TextServicesManager sInstance;
+
+ private final ITextServicesManager mService;
+
+ private TextServicesManager() throws ServiceNotFoundException {
+ mService = ITextServicesManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
+ }
/**
* Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
* @hide
*/
public static TextServicesManager getInstance() {
- return sInstance;
+ synchronized (TextServicesManager.class) {
+ if (sInstance == null) {
+ try {
+ sInstance = new TextServicesManager();
+ } catch (ServiceNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Returns the language component of a given locale string.
+ */
+ private static String parseLanguageFromLocaleString(String locale) {
+ final int idx = locale.indexOf('_');
+ if (idx < 0) {
+ return locale;
+ } else {
+ return locale.substring(0, idx);
+ }
}
+ /**
+ * Get a spell checker session for the specified spell checker
+ * @param locale the locale for the spell checker. If {@code locale} is null and
+ * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
+ * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
+ * the locale specified in Settings will be returned only when it is same as {@code locale}.
+ * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
+ * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+ * selected.
+ * @param listener a spell checker session lister for getting results from a spell checker.
+ * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
+ * languages in settings will be returned.
+ * @return the spell checker session of the spell checker
+ */
public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
- return null;
+ if (listener == null) {
+ throw new NullPointerException();
+ }
+ if (!referToSpellCheckerLanguageSettings && locale == null) {
+ throw new IllegalArgumentException("Locale should not be null if you don't refer"
+ + " settings.");
+ }
+
+ if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
+ return null;
+ }
+
+ final SpellCheckerInfo sci;
+ try {
+ sci = mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ return null;
+ }
+ if (sci == null) {
+ return null;
+ }
+ SpellCheckerSubtype subtypeInUse = null;
+ if (referToSpellCheckerLanguageSettings) {
+ subtypeInUse = getCurrentSpellCheckerSubtype(true);
+ if (subtypeInUse == null) {
+ return null;
+ }
+ if (locale != null) {
+ final String subtypeLocale = subtypeInUse.getLocale();
+ final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+ if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
+ return null;
+ }
+ }
+ } else {
+ final String localeStr = locale.toString();
+ for (int i = 0; i < sci.getSubtypeCount(); ++i) {
+ final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+ final String tempSubtypeLocale = subtype.getLocale();
+ final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
+ if (tempSubtypeLocale.equals(localeStr)) {
+ subtypeInUse = subtype;
+ break;
+ } else if (tempSubtypeLanguage.length() >= 2 &&
+ locale.getLanguage().equals(tempSubtypeLanguage)) {
+ subtypeInUse = subtype;
+ }
+ }
+ }
+ if (subtypeInUse == null) {
+ return null;
+ }
+ final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
+ try {
+ mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
+ session.getTextServicesSessionListener(),
+ session.getSpellCheckerSessionListener(), bundle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return session;
}
/**
* @hide
*/
public SpellCheckerInfo[] getEnabledSpellCheckers() {
- return EMPTY_SPELL_CHECKER_INFO;
+ try {
+ final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
+ if (DBG) {
+ Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
+ }
+ return retval;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* @hide
*/
public SpellCheckerInfo getCurrentSpellChecker() {
- return null;
+ try {
+ // Passing null as a locale for ICS
+ return mService.getCurrentSpellChecker(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -60,13 +215,22 @@ public final class TextServicesManager {
*/
public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
boolean allowImplicitlySelectedSubtype) {
- return null;
+ try {
+ // Passing null as a locale until we support multiple enabled spell checker subtypes.
+ return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
* @hide
*/
public boolean isSpellCheckerEnabled() {
- return false;
+ try {
+ return mService.isSpellCheckerEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
diff --git a/android/webkit/CacheManager.java b/android/webkit/CacheManager.java
index b8394203..fc76029a 100644
--- a/android/webkit/CacheManager.java
+++ b/android/webkit/CacheManager.java
@@ -16,13 +16,14 @@
package android.webkit;
+import android.annotation.Nullable;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
-
/**
* Manages the HTTP cache used by an application's {@link WebView} instances.
* @deprecated Access to the HTTP cache will be removed in a future release.
@@ -233,6 +234,7 @@ public final class CacheManager {
* @deprecated This method no longer has any effect and always returns {@code null}.
*/
@Deprecated
+ @Nullable
public static File getCacheFileBaseDir() {
return null;
}
@@ -287,6 +289,7 @@ public final class CacheManager {
* @deprecated This method no longer has any effect and always returns {@code null}.
*/
@Deprecated
+ @Nullable
public static CacheResult getCacheFile(String url,
Map<String, String> headers) {
return null;
diff --git a/android/webkit/ClientCertRequest.java b/android/webkit/ClientCertRequest.java
index de17534b..0fc47f1e 100644
--- a/android/webkit/ClientCertRequest.java
+++ b/android/webkit/ClientCertRequest.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.Nullable;
+
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
@@ -42,14 +44,16 @@ public abstract class ClientCertRequest {
public ClientCertRequest() { }
/**
- * Returns the acceptable types of asymmetric keys (can be {@code null}).
+ * Returns the acceptable types of asymmetric keys.
*/
+ @Nullable
public abstract String[] getKeyTypes();
/**
* Returns the acceptable certificate issuers for the certificate
- * matching the private key (can be {@code null}).
+ * matching the private key.
*/
+ @Nullable
public abstract Principal[] getPrincipals();
/**
diff --git a/android/webkit/CookieManager.java b/android/webkit/CookieManager.java
index 89892930..ae6a2fd7 100644
--- a/android/webkit/CookieManager.java
+++ b/android/webkit/CookieManager.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.WebAddress;
@@ -116,7 +117,8 @@ public abstract class CookieManager {
* HTTP response header
* @param callback a callback to be executed when the cookie has been set
*/
- public abstract void setCookie(String url, String value, ValueCallback<Boolean> callback);
+ public abstract void setCookie(String url, String value, @Nullable ValueCallback<Boolean>
+ callback);
/**
* Gets the cookies for the given URL.
@@ -175,7 +177,7 @@ public abstract class CookieManager {
* method from a thread without a Looper.
* @param callback a callback which is executed when the session cookies have been removed
*/
- public abstract void removeSessionCookies(ValueCallback<Boolean> callback);
+ public abstract void removeSessionCookies(@Nullable ValueCallback<Boolean> callback);
/**
* Removes all cookies.
@@ -197,7 +199,7 @@ public abstract class CookieManager {
* method from a thread without a Looper.
* @param callback a callback which is executed when the cookies have been removed
*/
- public abstract void removeAllCookies(ValueCallback<Boolean> callback);
+ public abstract void removeAllCookies(@Nullable ValueCallback<Boolean> callback);
/**
* Gets whether there are stored cookies.
diff --git a/android/webkit/FindActionModeCallback.java b/android/webkit/FindActionModeCallback.java
index 71f85d70..e011d51c 100644
--- a/android/webkit/FindActionModeCallback.java
+++ b/android/webkit/FindActionModeCallback.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.res.Resources;
@@ -69,7 +70,7 @@ public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
mActionMode.finish();
}
- /*
+ /**
* Place text in the text field so it can be searched for. Need to press
* the find next or find previous button to find all of the matches.
*/
@@ -87,10 +88,12 @@ public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
mMatchesFound = false;
}
- /*
- * Set the WebView to search. Must be non null.
+ /**
+ * Set the WebView to search.
+ *
+ * @param webView an implementation of WebView
*/
- public void setWebView(WebView webView) {
+ public void setWebView(@NonNull WebView webView) {
if (null == webView) {
throw new AssertionError("WebView supplied to "
+ "FindActionModeCallback cannot be null");
@@ -107,7 +110,7 @@ public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
}
}
- /*
+ /**
* Move the highlight to the next match.
* @param next If {@code true}, find the next match further down in the document.
* If {@code false}, find the previous match, up in the document.
@@ -130,7 +133,7 @@ public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
updateMatchesString();
}
- /*
+ /**
* Highlight all the instances of the string from mEditText in mWebView.
*/
public void findAll() {
@@ -169,7 +172,7 @@ public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
}
}
- /*
+ /**
* Update the string which tells the user how many matches were found, and
* which match is currently highlighted.
*/
diff --git a/android/webkit/MimeTypeMap.java b/android/webkit/MimeTypeMap.java
index e172c02e..39874e82 100644
--- a/android/webkit/MimeTypeMap.java
+++ b/android/webkit/MimeTypeMap.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Nullable;
import android.text.TextUtils;
import libcore.net.MimeUtils;
@@ -86,6 +87,7 @@ public class MimeTypeMap {
* @param extension A file extension without the leading '.'
* @return The MIME type for the given extension or {@code null} iff there is none.
*/
+ @Nullable
public String getMimeTypeFromExtension(String extension) {
return MimeUtils.guessMimeTypeFromExtension(extension);
}
@@ -111,6 +113,7 @@ public class MimeTypeMap {
* @param mimeType A MIME type (i.e. text/plain)
* @return The extension for the given MIME type or {@code null} iff there is none.
*/
+ @Nullable
public String getExtensionFromMimeType(String mimeType) {
return MimeUtils.guessExtensionFromMimeType(mimeType);
}
@@ -125,7 +128,7 @@ public class MimeTypeMap {
* @param contentDisposition Content-disposition header given by the server.
* @return The MIME type that should be used for this data.
*/
- /* package */ String remapGenericMimeType(String mimeType, String url,
+ /* package */ String remapGenericMimeType(@Nullable String mimeType, String url,
String contentDisposition) {
// If we have one of "generic" MIME types, try to deduce
// the right MIME type from the file extension (if any):
diff --git a/android/webkit/Plugin.java b/android/webkit/Plugin.java
index 072e02aa..29a5edc3 100644
--- a/android/webkit/Plugin.java
+++ b/android/webkit/Plugin.java
@@ -16,12 +16,12 @@
package android.webkit;
-import com.android.internal.R;
-
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import com.android.internal.R;
+
/**
* Represents a plugin (Java equivalent of the PluginPackageAndroid
* C++ class in libs/WebKitLib/WebKit/WebCore/plugins/android/)
@@ -32,13 +32,13 @@ import android.content.DialogInterface;
*/
@Deprecated
public class Plugin {
- /*
+ /**
* @hide
* @deprecated This interface was intended to be used by Gears. Since Gears was
* deprecated, so is this class.
*/
public interface PreferencesClickHandler {
- /*
+ /**
* @hide
* @deprecated This interface was intended to be used by Gears. Since Gears was
* deprecated, so is this class.
diff --git a/android/webkit/ServiceWorkerClient.java b/android/webkit/ServiceWorkerClient.java
index b4964fd2..d6e8f36c 100644
--- a/android/webkit/ServiceWorkerClient.java
+++ b/android/webkit/ServiceWorkerClient.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.Nullable;
+
/**
* Base class for clients to capture Service Worker related callbacks,
* see {@link ServiceWorkerController} for usage example.
@@ -37,6 +39,7 @@ public class ServiceWorkerClient {
* resource itself.
* @see WebViewClient#shouldInterceptRequest(WebView, WebResourceRequest)
*/
+ @Nullable
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
return null;
}
diff --git a/android/webkit/TokenBindingService.java b/android/webkit/TokenBindingService.java
index 43565c23..b37e1b89 100644
--- a/android/webkit/TokenBindingService.java
+++ b/android/webkit/TokenBindingService.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.Uri;
@@ -84,31 +86,30 @@ public abstract class TokenBindingService {
* The user can pass {@code null} if any algorithm is acceptable.
*
* @param origin The origin for the server.
- * @param algorithm The list of algorithms. Can be {@code null}. An
- * IllegalArgumentException is thrown if array is empty.
+ * @param algorithm The list of algorithms. An IllegalArgumentException is thrown if array is
+ * empty.
* @param callback The callback that will be called when key is available.
- * Cannot be {@code null}.
*/
public abstract void getKey(Uri origin,
- String[] algorithm,
- ValueCallback<TokenBindingKey> callback);
+ @Nullable String[] algorithm,
+ @NonNull ValueCallback<TokenBindingKey> callback);
/**
* Deletes specified key (for use when associated cookie is cleared).
*
* @param origin The origin of the server.
* @param callback The callback that will be called when key is deleted. The
* callback parameter (Boolean) will indicate if operation is
- * successful or if failed. The callback can be {@code null}.
+ * successful or if failed.
*/
public abstract void deleteKey(Uri origin,
- ValueCallback<Boolean> callback);
+ @Nullable ValueCallback<Boolean> callback);
/**
* Deletes all the keys (for use when cookies are cleared).
*
* @param callback The callback that will be called when keys are deleted.
* The callback parameter (Boolean) will indicate if operation is
- * successful or if failed. The callback can be {@code null}.
+ * successful or if failed.
*/
- public abstract void deleteAllKeys(ValueCallback<Boolean> callback);
+ public abstract void deleteAllKeys(@Nullable ValueCallback<Boolean> callback);
}
diff --git a/android/webkit/URLUtil.java b/android/webkit/URLUtil.java
index c8bfb779..c2f121a1 100644
--- a/android/webkit/URLUtil.java
+++ b/android/webkit/URLUtil.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Nullable;
import android.net.ParseException;
import android.net.Uri;
import android.net.WebAddress;
@@ -300,8 +301,8 @@ public final class URLUtil {
*/
public static final String guessFileName(
String url,
- String contentDisposition,
- String mimeType) {
+ @Nullable String contentDisposition,
+ @Nullable String mimeType) {
String filename = null;
String extension = null;
@@ -388,7 +389,7 @@ public final class URLUtil {
Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
Pattern.CASE_INSENSITIVE);
- /*
+ /**
* Parse the Content-Disposition HTTP Header. The format of the header
* is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
* This header provides a filename for content that is going to be
diff --git a/android/webkit/UrlInterceptHandler.java b/android/webkit/UrlInterceptHandler.java
index aa5c6dc7..0a6e51f7 100644
--- a/android/webkit/UrlInterceptHandler.java
+++ b/android/webkit/UrlInterceptHandler.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Nullable;
import android.webkit.CacheManager.CacheResult;
import android.webkit.PluginData;
@@ -35,14 +36,15 @@ public interface UrlInterceptHandler {
* not interested.
*
* @param url URL string.
- * @param headers The headers associated with the request. May be {@code null}.
+ * @param headers The headers associated with the request.
* @return The CacheResult containing the surrogate response.
*
* @hide
* @deprecated Do not use, this interface is deprecated.
*/
@Deprecated
- public CacheResult service(String url, Map<String, String> headers);
+ @Nullable
+ CacheResult service(String url, @Nullable Map<String, String> headers);
/**
* Given an URL, returns the PluginData which contains the
@@ -50,12 +52,13 @@ public interface UrlInterceptHandler {
* not interested.
*
* @param url URL string.
- * @param headers The headers associated with the request. May be {@code null}.
+ * @param headers The headers associated with the request.
* @return The PluginData containing the surrogate response.
*
* @hide
* @deprecated Do not use, this interface is deprecated.
*/
@Deprecated
- public PluginData getPluginData(String url, Map<String, String> headers);
+ @Nullable
+ PluginData getPluginData(String url, @Nullable Map<String, String> headers);
}
diff --git a/android/webkit/UrlInterceptRegistry.java b/android/webkit/UrlInterceptRegistry.java
index 67af2ad0..700d6d93 100644
--- a/android/webkit/UrlInterceptRegistry.java
+++ b/android/webkit/UrlInterceptRegistry.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Nullable;
import android.webkit.CacheManager.CacheResult;
import android.webkit.PluginData;
import android.webkit.UrlInterceptHandler;
@@ -121,6 +122,7 @@ public final class UrlInterceptRegistry {
* deprecated, so is this class.
*/
@Deprecated
+ @Nullable
public static synchronized CacheResult getSurrogate(
String url, Map<String, String> headers) {
if (urlInterceptDisabled()) {
@@ -149,6 +151,7 @@ public final class UrlInterceptRegistry {
* deprecated, so is this class.
*/
@Deprecated
+ @Nullable
public static synchronized PluginData getPluginData(
String url, Map<String, String> headers) {
if (urlInterceptDisabled()) {
diff --git a/android/webkit/WebBackForwardList.java b/android/webkit/WebBackForwardList.java
index 3349b065..0c34e3c1 100644
--- a/android/webkit/WebBackForwardList.java
+++ b/android/webkit/WebBackForwardList.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.Nullable;
+
import java.io.Serializable;
/**
@@ -29,6 +31,7 @@ public abstract class WebBackForwardList implements Cloneable, Serializable {
* empty.
* @return The current history item.
*/
+ @Nullable
public abstract WebHistoryItem getCurrentItem();
/**
diff --git a/android/webkit/WebChromeClient.java b/android/webkit/WebChromeClient.java
index 742daa91..4aa1c4a6 100644
--- a/android/webkit/WebChromeClient.java
+++ b/android/webkit/WebChromeClient.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -383,6 +384,7 @@ public class WebChromeClient {
* @return Bitmap The image to use as a default poster, or {@code null} if no such image is
* available.
*/
+ @Nullable
public Bitmap getDefaultVideoPoster() {
return null;
}
@@ -394,6 +396,7 @@ public class WebChromeClient {
*
* @return View The View to be displayed whilst the video is loading.
*/
+ @Nullable
public View getVideoLoadingProgressView() {
return null;
}
@@ -452,6 +455,7 @@ public class WebChromeClient {
* @return the Uris of selected file(s) or {@code null} if the resultCode indicates
* activity canceled or any other error.
*/
+ @Nullable
public static Uri[] parseResult(int resultCode, Intent data) {
return WebViewFactory.getProvider().getStatics().parseFileChooserResult(resultCode, data);
}
@@ -477,14 +481,16 @@ public class WebChromeClient {
public abstract boolean isCaptureEnabled();
/**
- * Returns the title to use for this file selector, or null. If {@code null} a default
- * title should be used.
+ * Returns the title to use for this file selector. If {@code null} a default title should
+ * be used.
*/
+ @Nullable
public abstract CharSequence getTitle();
/**
* The file name of a default selection if specified, or {@code null}.
*/
+ @Nullable
public abstract String getFilenameHint();
/**
diff --git a/android/webkit/WebHistoryItem.java b/android/webkit/WebHistoryItem.java
index 1591833e..74db039e 100644
--- a/android/webkit/WebHistoryItem.java
+++ b/android/webkit/WebHistoryItem.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.graphics.Bitmap;
@@ -70,6 +71,7 @@ public abstract class WebHistoryItem implements Cloneable {
* Note: The VM ensures 32-bit atomic read/write operations so we don't have
* to synchronize this method.
*/
+ @Nullable
public abstract Bitmap getFavicon();
/**
diff --git a/android/webkit/WebMessage.java b/android/webkit/WebMessage.java
index 7fe66dc8..bfc00e7a 100644
--- a/android/webkit/WebMessage.java
+++ b/android/webkit/WebMessage.java
@@ -16,6 +16,8 @@
package android.webkit;
+import android.annotation.Nullable;
+
/**
* The Java representation of the HTML5 PostMessage event. See
* https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces
@@ -56,6 +58,7 @@ public class WebMessage {
* Returns the ports that are sent with the message, or {@code null} if no port
* is sent.
*/
+ @Nullable
public WebMessagePort[] getPorts() {
return mPorts;
}
diff --git a/android/webkit/WebResourceResponse.java b/android/webkit/WebResourceResponse.java
index 80c43c14..7bc7b07d 100644
--- a/android/webkit/WebResourceResponse.java
+++ b/android/webkit/WebResourceResponse.java
@@ -16,12 +16,13 @@
package android.webkit;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.util.Map;
-import android.annotation.SystemApi;
-
/**
* Encapsulates a resource response. Applications can return an instance of this
* class from {@link WebViewClient#shouldInterceptRequest} to provide a custom
@@ -63,15 +64,15 @@ public class WebResourceResponse {
* @param encoding the resource response's encoding
* @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
* Causing a redirect by specifying a 3xx code is not supported.
- * @param reasonPhrase the phrase describing the status code, for example "OK". Must be non-null
- * and not empty.
+ * @param reasonPhrase the phrase describing the status code, for example "OK". Must be
+ * non-empty.
* @param responseHeaders the resource response's headers represented as a mapping of header
* name -> header value.
* @param data the input stream that provides the resource response's data. Must not be a
* StringBufferInputStream.
*/
public WebResourceResponse(String mimeType, String encoding, int statusCode,
- String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
+ @NonNull String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
this(mimeType, encoding, data);
setStatusCodeAndReasonPhrase(statusCode, reasonPhrase);
setResponseHeaders(responseHeaders);
@@ -121,10 +122,10 @@ public class WebResourceResponse {
*
* @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
* Causing a redirect by specifying a 3xx code is not supported.
- * @param reasonPhrase the phrase describing the status code, for example "OK". Must be non-null
- * and not empty.
+ * @param reasonPhrase the phrase describing the status code, for example "OK". Must be
+ * non-empty.
*/
- public void setStatusCodeAndReasonPhrase(int statusCode, String reasonPhrase) {
+ public void setStatusCodeAndReasonPhrase(int statusCode, @NonNull String reasonPhrase) {
checkImmutable();
if (statusCode < 100)
throw new IllegalArgumentException("statusCode can't be less than 100.");
diff --git a/android/webkit/WebSettings.java b/android/webkit/WebSettings.java
index 22d8561d..203de9c2 100644
--- a/android/webkit/WebSettings.java
+++ b/android/webkit/WebSettings.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
@@ -1238,7 +1239,7 @@ public abstract class WebSettings {
*
* @param ua new user-agent string
*/
- public abstract void setUserAgentString(String ua);
+ public abstract void setUserAgentString(@Nullable String ua);
/**
* Gets the WebView's user-agent string.
diff --git a/android/webkit/WebSyncManager.java b/android/webkit/WebSyncManager.java
index 801be128..03b94e71 100644
--- a/android/webkit/WebSyncManager.java
+++ b/android/webkit/WebSyncManager.java
@@ -18,7 +18,7 @@ package android.webkit;
import android.content.Context;
-/*
+/**
* @deprecated The WebSyncManager no longer does anything.
*/
@Deprecated
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 202f2046..dfc81b2b 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,223 +16,3001 @@
package android.webkit;
-import com.android.layoutlib.bridge.MockView;
-
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.Widget;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Picture;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.http.SslCertificate;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.print.PrintDocumentAdapter;
+import android.security.KeyChain;
import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.DragEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
+import android.view.ViewStructure;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.textclassifier.TextClassifier;
+import android.widget.AbsoluteLayout;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
/**
- * Mock version of the WebView.
- * Only non override public methods from the real WebView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
- *
- * TODO: generate automatically.
+ * <p>A View that displays web pages. This class is the basis upon which you
+ * can roll your own web browser or simply display some online content within your Activity.
+ * It uses the WebKit rendering engine to display
+ * web pages and includes methods to navigate forward and backward
+ * through a history, zoom in and out, perform text searches and more.
+ *
+ * <p>Note that, in order for your Activity to access the Internet and load web pages
+ * in a WebView, you must add the {@code INTERNET} permissions to your
+ * Android Manifest file:
+ *
+ * <pre>
+ * {@code <uses-permission android:name="android.permission.INTERNET" />}
+ * </pre>
+ *
+ * <p>This must be a child of the <a
+ * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
+ * element.
+ *
+ * <p>For more information, read
+ * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>.
+ *
+ * <h3>Basic usage</h3>
+ *
+ * <p>By default, a WebView provides no browser-like widgets, does not
+ * enable JavaScript and web page errors are ignored. If your goal is only
+ * to display some HTML as a part of your UI, this is probably fine;
+ * the user won't need to interact with the web page beyond reading
+ * it, and the web page won't need to interact with the user. If you
+ * actually want a full-blown web browser, then you probably want to
+ * invoke the Browser application with a URL Intent rather than show it
+ * with a WebView. For example:
+ * <pre>
+ * Uri uri = Uri.parse("https://www.example.com");
+ * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ * startActivity(intent);
+ * </pre>
+ * <p>See {@link android.content.Intent} for more information.
+ *
+ * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
+ * or set the entire Activity window as a WebView during {@link
+ * android.app.Activity#onCreate(Bundle) onCreate()}:
+ *
+ * <pre class="prettyprint">
+ * WebView webview = new WebView(this);
+ * setContentView(webview);
+ * </pre>
+ *
+ * <p>Then load the desired web page:
+ *
+ * <pre>
+ * // Simplest usage: note that an exception will NOT be thrown
+ * // if there is an error loading this page (see below).
+ * webview.loadUrl("https://example.com/");
+ *
+ * // OR, you can also load from an HTML string:
+ * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
+ * webview.loadData(summary, "text/html", null);
+ * // ... although note that there are restrictions on what this HTML can do.
+ * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
+ * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
+ * </pre>
+ *
+ * <p>A WebView has several customization points where you can add your
+ * own behavior. These are:
+ *
+ * <ul>
+ * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
+ * This class is called when something that might impact a
+ * browser UI happens, for instance, progress updates and
+ * JavaScript alerts are sent here (see <a
+ * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
+ * </li>
+ * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
+ * It will be called when things happen that impact the
+ * rendering of the content, eg, errors or form submissions. You
+ * can also intercept URL loading here (via {@link
+ * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
+ * shouldOverrideUrlLoading()}).</li>
+ * <li>Modifying the {@link android.webkit.WebSettings}, such as
+ * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
+ * setJavaScriptEnabled()}. </li>
+ * <li>Injecting Java objects into the WebView using the
+ * {@link android.webkit.WebView#addJavascriptInterface} method. This
+ * method allows you to inject Java objects into a page's JavaScript
+ * context, so that they can be accessed by JavaScript in the page.</li>
+ * </ul>
+ *
+ * <p>Here's a more complicated example, showing error handling,
+ * settings, and progress notification:
+ *
+ * <pre class="prettyprint">
+ * // Let's display the progress in the activity title bar, like the
+ * // browser app does.
+ * getWindow().requestFeature(Window.FEATURE_PROGRESS);
+ *
+ * webview.getSettings().setJavaScriptEnabled(true);
+ *
+ * final Activity activity = this;
+ * webview.setWebChromeClient(new WebChromeClient() {
+ * public void onProgressChanged(WebView view, int progress) {
+ * // Activities and WebViews measure progress with different scales.
+ * // The progress meter will automatically disappear when we reach 100%
+ * activity.setProgress(progress * 1000);
+ * }
+ * });
+ * webview.setWebViewClient(new WebViewClient() {
+ * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
+ * }
+ * });
+ *
+ * webview.loadUrl("https://developer.android.com/");
+ * </pre>
+ *
+ * <h3>Zoom</h3>
+ *
+ * <p>To enable the built-in zoom, set
+ * {@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
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
+ * and should be avoided.
+ *
+ * <h3>Cookie and window management</h3>
+ *
+ * <p>For obvious security reasons, your application has its own
+ * cache, cookie store etc.&mdash;it does not share the Browser
+ * application's data.
+ *
+ * <p>By default, requests by the HTML to open new windows are
+ * ignored. This is {@code true} whether they be opened by JavaScript or by
+ * the target attribute on a link. You can customize your
+ * {@link WebChromeClient} to provide your own behavior for opening multiple windows,
+ * and render them in whatever manner you want.
+ *
+ * <p>The standard behavior for an Activity is to be destroyed and
+ * recreated when the device orientation or any other configuration changes. This will cause
+ * the WebView to reload the current page. If you don't want that, you
+ * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
+ * changes, and then just leave the WebView alone. It'll automatically
+ * re-orient itself as appropriate. Read <a
+ * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
+ * more information about how to handle configuration changes during runtime.
+ *
+ *
+ * <h3>Building web pages to support different screen densities</h3>
+ *
+ * <p>The screen density of a device is based on the screen resolution. A screen with low density
+ * has fewer available pixels per inch, where a screen with high density
+ * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
+ * screen is important because, other things being equal, a UI element (such as a button) whose
+ * height and width are defined in terms of screen pixels will appear larger on the lower density
+ * screen and smaller on the higher density screen.
+ * For simplicity, Android collapses all actual screen densities into three generalized densities:
+ * high, medium, and low.
+ * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
+ * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
+ * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
+ * are bigger).
+ * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS,
+ * and meta tag features to help you (as a web developer) target screens with different screen
+ * densities.
+ * <p>Here's a summary of the features you can use to handle different screen densities:
+ * <ul>
+ * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
+ * default scaling factor used for the current device. For example, if the value of {@code
+ * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
+ * and default scaling is not applied to the web page; if the value is "1.5", then the device is
+ * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
+ * value is "0.75", then the device is considered a low density device (ldpi) and the content is
+ * scaled 0.75x.</li>
+ * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
+ * densities for which this style sheet is to be used. The corresponding value should be either
+ * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
+ * density, or high density screens, respectively. For example:
+ * <pre>
+ * &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;</pre>
+ * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
+ * which is the high density pixel ratio.
+ * </li>
+ * </ul>
+ *
+ * <h3>HTML5 Video support</h3>
+ *
+ * <p>In order to support inline HTML5 video in your application you need to have hardware
+ * acceleration turned on.
+ *
+ * <h3>Full screen support</h3>
+ *
+ * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
+ * {@link android.webkit.WebChromeClient} and implement both
+ * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
+ * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
+ * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
+ * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
+ * is loading.
+ *
+ * <h3>HTML5 Geolocation API support</h3>
+ *
+ * <p>For applications targeting Android N and later releases
+ * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on
+ * secure origins such as https. For such applications requests to geolocation api on non-secure
+ * origins are automatically denied without invoking the corresponding
+ * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)}
+ * method.
+ *
+ * <h3>Layout size</h3>
+ * <p>
+ * It is recommended to set the WebView layout height to a fixed value or to
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
+ * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+ * for the height none of the WebView's parents should use a
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
+ * incorrect sizing of the views.
+ *
+ * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * enables the following behaviors:
+ * <ul>
+ * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
+ * relative to the HTML body may not be sized correctly. </li>
+ * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
+ * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
+ * </ul>
+ *
+ * <p>
+ * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
+ * supported. If such a width is used the WebView will attempt to use the width of the parent
+ * instead.
+ *
+ * <h3>Metrics</h3>
+ *
+ * <p>
+ * 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:
+ * <pre>
+ * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
+ * android:value="true" /&gt;
+ * </pre>
+ * <p>
+ * Data will only be uploaded for a given app if the user has consented AND the app has not opted
+ * out.
+ *
+ * <h3>Safe Browsing</h3>
+ *
+ * <p>
+ * 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:
+ * <p>
+ * <pre>
+ * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
+ * android:value="true" /&gt;
+ * </pre>
*
*/
-public class WebView extends MockView {
+// Implementation notes.
+// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
+// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons.
+// Methods are delegated to the provider implementation: all public API methods introduced in this
+// file are fully delegated, whereas public and protected methods from the View base classes are
+// only delegated where a specific need exists for them to do so.
+@Widget
+public class WebView extends AbsoluteLayout
+ implements ViewTreeObserver.OnGlobalFocusChangeListener,
+ ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
+
+ private static final String LOGTAG = "WebView";
+
+ // Throwing an exception for incorrect thread usage if the
+ // build target is JB MR2 or newer. Defaults to false, and is
+ // set in the WebView constructor.
+ private static volatile boolean sEnforceThreadChecking = false;
+
+ /**
+ * Transportation object for returning WebView across thread boundaries.
+ */
+ public class WebViewTransport {
+ private WebView mWebview;
+ /**
+ * Sets the WebView to the transportation object.
+ *
+ * @param webview the WebView to transport
+ */
+ public synchronized void setWebView(WebView webview) {
+ mWebview = webview;
+ }
+
+ /**
+ * Gets the WebView object.
+ *
+ * @return the transported WebView object
+ */
+ public synchronized WebView getWebView() {
+ return mWebview;
+ }
+ }
+
+ /**
+ * URI scheme for telephone number.
+ */
+ public static final String SCHEME_TEL = "tel:";
/**
- * Construct a new WebView with a Context object.
- * @param context A Context object used to access application assets.
+ * URI scheme for email address.
+ */
+ public static final String SCHEME_MAILTO = "mailto:";
+ /**
+ * URI scheme for map address.
+ */
+ public static final String SCHEME_GEO = "geo:0,0?q=";
+
+ /**
+ * Interface to listen for find results.
+ */
+ public interface FindListener {
+ /**
+ * Notifies the listener about progress made by a find operation.
+ *
+ * @param activeMatchOrdinal the zero-based ordinal of the currently selected match
+ * @param numberOfMatches how many matches have been found
+ * @param isDoneCounting whether the find operation has actually completed. The listener
+ * may be notified multiple times while the
+ * operation is underway, and the numberOfMatches
+ * value should not be considered final unless
+ * isDoneCounting is {@code true}.
+ */
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting);
+ }
+
+ /**
+ * Callback interface supplied to {@link #postVisualStateCallback} for receiving
+ * notifications about the visual state.
+ */
+ public static abstract class VisualStateCallback {
+ /**
+ * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}.
+ *
+ * @param requestId The identifier passed to {@link #postVisualStateCallback} when this
+ * callback was posted.
+ */
+ public abstract void onComplete(long requestId);
+ }
+
+ /**
+ * Interface to listen for new pictures as they change.
+ *
+ * @deprecated This interface is now obsolete.
+ */
+ @Deprecated
+ public interface PictureListener {
+ /**
+ * Used to provide notification that the WebView's picture has changed.
+ * See {@link WebView#capturePicture} for details of the picture.
+ *
+ * @param view the WebView that owns the picture
+ * @param picture the new picture. Applications targeting
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
+ * will always receive a {@code null} Picture.
+ * @deprecated Deprecated due to internal changes.
+ */
+ @Deprecated
+ void onNewPicture(WebView view, @Nullable Picture picture);
+ }
+
+ public static class HitTestResult {
+ /**
+ * Default HitTestResult, where the target is unknown.
+ */
+ public static final int UNKNOWN_TYPE = 0;
+ /**
+ * @deprecated This type is no longer used.
+ */
+ @Deprecated
+ public static final int ANCHOR_TYPE = 1;
+ /**
+ * HitTestResult for hitting a phone number.
+ */
+ public static final int PHONE_TYPE = 2;
+ /**
+ * HitTestResult for hitting a map address.
+ */
+ public static final int GEO_TYPE = 3;
+ /**
+ * HitTestResult for hitting an email address.
+ */
+ public static final int EMAIL_TYPE = 4;
+ /**
+ * HitTestResult for hitting an HTML::img tag.
+ */
+ public static final int IMAGE_TYPE = 5;
+ /**
+ * @deprecated This type is no longer used.
+ */
+ @Deprecated
+ public static final int IMAGE_ANCHOR_TYPE = 6;
+ /**
+ * HitTestResult for hitting a HTML::a tag with src=http.
+ */
+ public static final int SRC_ANCHOR_TYPE = 7;
+ /**
+ * HitTestResult for hitting a HTML::a tag with src=http + HTML::img.
+ */
+ public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
+ /**
+ * HitTestResult for hitting an edit text area.
+ */
+ public static final int EDIT_TEXT_TYPE = 9;
+
+ private int mType;
+ private String mExtra;
+
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ @SystemApi
+ public HitTestResult() {
+ mType = UNKNOWN_TYPE;
+ }
+
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ @SystemApi
+ public void setType(int type) {
+ mType = type;
+ }
+
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ @SystemApi
+ public void setExtra(String extra) {
+ mExtra = extra;
+ }
+
+ /**
+ * Gets the type of the hit test result. See the XXX_TYPE constants
+ * defined in this class.
+ *
+ * @return the type of the hit test result
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets additional type-dependant information about the result. See
+ * {@link WebView#getHitTestResult()} for details. May either be {@code null}
+ * or contain extra information about this result.
+ *
+ * @return additional type-dependant information about the result
+ */
+ @Nullable
+ public String getExtra() {
+ return mExtra;
+ }
+ }
+
+ /**
+ * Constructs a new WebView with a Context object.
+ *
+ * @param context a Context object used to access application assets
*/
public WebView(Context context) {
this(context, null);
}
/**
- * Construct a new WebView with layout parameters.
- * @param context A Context object used to access application assets.
- * @param attrs An AttributeSet passed to our parent.
+ * Constructs a new WebView with layout parameters.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
*/
public WebView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.webViewStyle);
}
/**
- * Construct a new WebView with layout parameters and a default style.
- * @param context A Context object used to access application assets.
- * @param attrs An AttributeSet passed to our parent.
- * @param defStyle The default style resource ID.
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public WebView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
-
- // START FAKE PUBLIC METHODS
-
+
+ /**
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes a resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ this(context, attrs, defStyleAttr, defStyleRes, null, false);
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param privateBrowsing whether this WebView will be initialized in
+ * private mode
+ *
+ * @deprecated Private browsing is no longer supported directly via
+ * WebView and will be removed in a future release. Prefer using
+ * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
+ * and {@link WebStorage} for fine-grained control of privacy data.
+ */
+ @Deprecated
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr,
+ boolean privateBrowsing) {
+ this(context, attrs, defStyleAttr, 0, null, privateBrowsing);
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters, a default style and a set
+ * of custom JavaScript interfaces to be added to this WebView at initialization
+ * 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 attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param javaScriptInterfaces a Map of interface names, as keys, and
+ * object implementing those interfaces, as
+ * values
+ * @param privateBrowsing whether this WebView will be initialized in
+ * private mode
+ * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to
+ * be added synchronously, before a subsequent loadUrl call takes effect.
+ */
+ protected WebView(Context context, AttributeSet attrs, int defStyleAttr,
+ Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
+ this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
+ }
+
+ /**
+ * @hide
+ */
+ @SuppressWarnings("deprecation") // for super() call into deprecated base class constructor.
+ protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
+ Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ // WebView is important by default, unless app developer overrode attribute.
+ if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
+ setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
+ }
+
+ if (context == null) {
+ throw new IllegalArgumentException("Invalid context argument");
+ }
+ sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.JELLY_BEAN_MR2;
+ checkThread();
+
+ ensureProviderCreated();
+ mProvider.init(javaScriptInterfaces, privateBrowsing);
+ // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
+ CookieSyncManager.setGetInstanceIsAllowed();
+ }
+
+ /**
+ * Specifies whether the horizontal scrollbar has overlay style.
+ *
+ * @deprecated This method has no effect.
+ * @param overlay {@code true} if horizontal scrollbar should have overlay style
+ */
+ @Deprecated
public void setHorizontalScrollbarOverlay(boolean overlay) {
}
+ /**
+ * Specifies whether the vertical scrollbar has overlay style.
+ *
+ * @deprecated This method has no effect.
+ * @param overlay {@code true} if vertical scrollbar should have overlay style
+ */
+ @Deprecated
public void setVerticalScrollbarOverlay(boolean overlay) {
}
+ /**
+ * Gets whether horizontal scrollbar has overlay style.
+ *
+ * @deprecated This method is now obsolete.
+ * @return {@code true}
+ */
+ @Deprecated
public boolean overlayHorizontalScrollbar() {
- return false;
+ // The old implementation defaulted to true, so return true for consistency
+ return true;
}
+ /**
+ * Gets whether vertical scrollbar has overlay style.
+ *
+ * @deprecated This method is now obsolete.
+ * @return {@code false}
+ */
+ @Deprecated
public boolean overlayVerticalScrollbar() {
+ // The old implementation defaulted to false, so return false for consistency
return false;
}
+ /**
+ * Gets the visible height (in pixels) of the embedded title bar (if any).
+ *
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public int getVisibleTitleHeight() {
+ checkThread();
+ return mProvider.getVisibleTitleHeight();
+ }
+
+ /**
+ * Gets the SSL certificate for the main top-level page or {@code null} if there is
+ * no certificate (the site is not secure).
+ *
+ * @return the SSL certificate for the main top-level page
+ */
+ @Nullable
+ public SslCertificate getCertificate() {
+ checkThread();
+ return mProvider.getCertificate();
+ }
+
+ /**
+ * Sets the SSL certificate for the main top-level page.
+ *
+ * @deprecated Calling this function has no useful effect, and will be
+ * ignored in future releases.
+ */
+ @Deprecated
+ public void setCertificate(SslCertificate certificate) {
+ checkThread();
+ mProvider.setCertificate(certificate);
+ }
+
+ //-------------------------------------------------------------------------
+ // Methods called by activity
+ //-------------------------------------------------------------------------
+
+ /**
+ * Sets a username and password pair for the specified host. This data is
+ * used by the WebView to autocomplete username and password fields in web
+ * forms. Note that this is unrelated to the credentials used for HTTP
+ * authentication.
+ *
+ * @param host the host that required the credentials
+ * @param username the username for the given host
+ * @param password the password for the given host
+ * @see WebViewDatabase#clearUsernamePassword
+ * @see WebViewDatabase#hasUsernamePassword
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
+ */
+ @Deprecated
public void savePassword(String host, String username, String password) {
+ checkThread();
+ mProvider.savePassword(host, username, password);
}
+ /**
+ * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase}
+ * instance.
+ *
+ * @param host the host to which the credentials apply
+ * @param realm the realm to which the credentials apply
+ * @param username the username
+ * @param password the password
+ * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead
+ */
+ @Deprecated
public void setHttpAuthUsernamePassword(String host, String realm,
String username, String password) {
+ checkThread();
+ mProvider.setHttpAuthUsernamePassword(host, realm, username, password);
}
+ /**
+ * Retrieves HTTP authentication credentials for a given host and realm from the {@link
+ * WebViewDatabase} instance.
+ * @param host the host to which the credentials apply
+ * @param realm the realm to which the credentials apply
+ * @return the credentials as a String array, if found. The first element
+ * is the username and the second element is the password. {@code null} if
+ * no credentials are found.
+ * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead
+ */
+ @Deprecated
+ @Nullable
public String[] getHttpAuthUsernamePassword(String host, String realm) {
- return null;
+ checkThread();
+ return mProvider.getHttpAuthUsernamePassword(host, realm);
}
+ /**
+ * Destroys the internal state of this WebView. This method should be called
+ * after this WebView has been removed from the view system. No other
+ * methods may be called on this WebView after destroy.
+ */
public void destroy() {
+ checkThread();
+ mProvider.destroy();
}
+ /**
+ * Enables platform notifications of data state and proxy changes.
+ * Notifications are enabled by default.
+ *
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
public static void enablePlatformNotifications() {
+ // noop
}
+ /**
+ * Disables platform notifications of data state and proxy changes.
+ * Notifications are enabled by default.
+ *
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
public static void disablePlatformNotifications() {
+ // noop
}
+ /**
+ * Used only by internal tests to free up memory.
+ *
+ * @hide
+ */
+ public static void freeMemoryForTests() {
+ getFactory().getStatics().freeMemoryForTests();
+ }
+
+ /**
+ * Informs WebView of the network state. This is used to set
+ * the JavaScript property window.navigator.isOnline and
+ * generates the online/offline event as specified in HTML5, sec. 5.7.7
+ *
+ * @param networkUp a boolean indicating if network is available
+ */
+ public void setNetworkAvailable(boolean networkUp) {
+ checkThread();
+ mProvider.setNetworkAvailable(networkUp);
+ }
+
+ /**
+ * Saves the state of this WebView used in
+ * {@link android.app.Activity#onSaveInstanceState}. Please note that this
+ * method no longer stores the display data for this WebView. The previous
+ * behavior could potentially leak files if {@link #restoreState} was never
+ * called.
+ *
+ * @param outState the Bundle to store this WebView's state
+ * @return the same copy of the back/forward list used to save the state, {@code null} if the
+ * method fails.
+ */
+ @Nullable
+ public WebBackForwardList saveState(Bundle outState) {
+ checkThread();
+ return mProvider.saveState(outState);
+ }
+
+ /**
+ * Saves the current display data to the Bundle given. Used in conjunction
+ * with {@link #saveState}.
+ * @param b a Bundle to store the display data
+ * @param dest the file to store the serialized picture data. Will be
+ * overwritten with this WebView's picture data.
+ * @return {@code true} if the picture was successfully saved
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public boolean savePicture(Bundle b, final File dest) {
+ checkThread();
+ return mProvider.savePicture(b, dest);
+ }
+
+ /**
+ * Restores the display data that was saved in {@link #savePicture}. Used in
+ * conjunction with {@link #restoreState}. Note that this will not work if
+ * this WebView is hardware accelerated.
+ *
+ * @param b a Bundle containing the saved display data
+ * @param src the file where the picture data was stored
+ * @return {@code true} if the picture was successfully restored
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public boolean restorePicture(Bundle b, File src) {
+ checkThread();
+ return mProvider.restorePicture(b, src);
+ }
+
+ /**
+ * Restores the state of this WebView from the given Bundle. This method is
+ * intended for use in {@link android.app.Activity#onRestoreInstanceState}
+ * and should be called to restore the state of this WebView. If
+ * it is called after this WebView has had a chance to build state (load
+ * pages, create a back/forward list, etc.) there may be undesirable
+ * side-effects. Please note that this method no longer restores the
+ * display data for this WebView.
+ *
+ * @param inState the incoming Bundle of state
+ * @return the restored back/forward list or {@code null} if restoreState failed
+ */
+ @Nullable
+ public WebBackForwardList restoreState(Bundle inState) {
+ checkThread();
+ return mProvider.restoreState(inState);
+ }
+
+ /**
+ * Loads the given URL with the specified additional HTTP headers.
+ * <p>
+ * Also see compatibility note on {@link #evaluateJavascript}.
+ *
+ * @param url the URL of the resource to load
+ * @param additionalHttpHeaders the additional headers to be used in the
+ * HTTP request for this URL, specified as a map from name to
+ * value. Note that if this map contains any of the headers
+ * that are set by default by this WebView, such as those
+ * controlling caching, accept types or the User-Agent, their
+ * values may be overridden by this WebView's defaults.
+ */
+ public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
+ checkThread();
+ mProvider.loadUrl(url, additionalHttpHeaders);
+ }
+
+ /**
+ * Loads the given URL.
+ * <p>
+ * Also see compatibility note on {@link #evaluateJavascript}.
+ *
+ * @param url the URL of the resource to load
+ */
public void loadUrl(String url) {
+ checkThread();
+ mProvider.loadUrl(url);
+ }
+
+ /**
+ * Loads the URL with postData using "POST" method into this WebView. If url
+ * is not a network URL, it will be loaded with {@link #loadUrl(String)}
+ * instead, ignoring the postData param.
+ *
+ * @param url the URL of the resource to load
+ * @param postData the data will be passed to "POST" request, which must be
+ * be "application/x-www-form-urlencoded" encoded.
+ */
+ public void postUrl(String url, byte[] postData) {
+ checkThread();
+ if (URLUtil.isNetworkUrl(url)) {
+ mProvider.postUrl(url, postData);
+ } else {
+ mProvider.loadUrl(url);
+ }
}
- public void loadData(String data, String mimeType, String encoding) {
+ /**
+ * Loads the given data into this WebView using a 'data' scheme URL.
+ * <p>
+ * Note that JavaScript's same origin policy means that script running in a
+ * page loaded using this method will be unable to access content loaded
+ * using any scheme other than 'data', including 'http(s)'. To avoid this
+ * restriction, use {@link
+ * #loadDataWithBaseURL(String,String,String,String,String)
+ * loadDataWithBaseURL()} with an appropriate base URL.
+ * <p>
+ * The encoding parameter specifies whether the data is base64 or URL
+ * encoded. If the data is base64 encoded, the value of the encoding
+ * parameter must be 'base64'. For all other values of the parameter,
+ * including {@code null}, it is assumed that the data uses ASCII encoding for
+ * octets inside the range of safe URL characters and use the standard %xx
+ * hex encoding of URLs for octets outside that range. For example, '#',
+ * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
+ * <p>
+ * The 'data' scheme URL formed by this method uses the default US-ASCII
+ * charset. If you need need to set a different charset, you should form a
+ * 'data' scheme URL which explicitly specifies a charset parameter in the
+ * mediatype portion of the URL and call {@link #loadUrl(String)} instead.
+ * Note that the charset obtained from the mediatype portion of a data URL
+ * always overrides that specified in the HTML or XML document itself.
+ *
+ * @param data a String of data in the given encoding
+ * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
+ * defaults to 'text/html'.
+ * @param encoding the encoding of the data
+ */
+ public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) {
+ checkThread();
+ mProvider.loadData(data, mimeType, encoding);
}
- public void loadDataWithBaseURL(String baseUrl, String data,
- String mimeType, String encoding, String failUrl) {
+ /**
+ * Loads the given data into this WebView, using baseUrl as the base URL for
+ * the content. The base URL is used both to resolve relative URLs and when
+ * applying JavaScript's same origin policy. The historyUrl is used for the
+ * history entry.
+ * <p>
+ * Note that content specified in this way can access local device files
+ * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
+ * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
+ * <p>
+ * If the base URL uses the data scheme, this method is equivalent to
+ * calling {@link #loadData(String,String,String) loadData()} and the
+ * historyUrl is ignored, and the data will be treated as part of a data: URL.
+ * If the base URL uses any other scheme, then the data will be loaded into
+ * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded
+ * entities in the string will not be decoded.
+ * <p>
+ * Note that the baseUrl is sent in the 'Referer' HTTP header when
+ * requesting subresources (images, etc.) of the page loaded using this method.
+ *
+ * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to
+ * 'about:blank'.
+ * @param data a String of data in the given encoding
+ * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
+ * defaults to 'text/html'.
+ * @param encoding the encoding of the data
+ * @param historyUrl the URL to use as the history entry. If {@code null} defaults
+ * to 'about:blank'. If non-null, this must be a valid URL.
+ */
+ public void loadDataWithBaseURL(@Nullable String baseUrl, String data,
+ @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) {
+ checkThread();
+ mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
+ /**
+ * Asynchronously evaluates JavaScript in the context of the currently displayed page.
+ * If non-null, |resultCallback| will be invoked with any result returned from that
+ * execution. This method must be called on the UI thread and the callback will
+ * be made on the UI thread.
+ * <p>
+ * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or
+ * later, JavaScript state from an empty WebView is no longer persisted across navigations like
+ * {@link #loadUrl(String)}. For example, global variables and functions defined before calling
+ * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use
+ * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations.
+ *
+ * @param script the JavaScript to execute.
+ * @param resultCallback A callback to be invoked when the script execution
+ * completes with the result of the execution (if any).
+ * May be {@code null} if no notification of the result is required.
+ */
+ public void evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback) {
+ checkThread();
+ mProvider.evaluateJavaScript(script, resultCallback);
+ }
+
+ /**
+ * Saves the current view as a web archive.
+ *
+ * @param filename the filename where the archive should be placed
+ */
+ public void saveWebArchive(String filename) {
+ checkThread();
+ mProvider.saveWebArchive(filename);
+ }
+
+ /**
+ * Saves the current view as a web archive.
+ *
+ * @param basename the filename where the archive should be placed
+ * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename
+ * is assumed to be a directory in which a filename will be
+ * chosen according to the URL of the current page.
+ * @param callback called after the web archive has been saved. The
+ * parameter for onReceiveValue will either be the filename
+ * under which the file was saved, or {@code null} if saving the
+ * file failed.
+ */
+ public void saveWebArchive(String basename, boolean autoname, @Nullable ValueCallback<String>
+ callback) {
+ checkThread();
+ mProvider.saveWebArchive(basename, autoname, callback);
+ }
+
+ /**
+ * Stops the current load.
+ */
public void stopLoading() {
+ checkThread();
+ mProvider.stopLoading();
}
+ /**
+ * Reloads the current URL.
+ */
public void reload() {
+ checkThread();
+ mProvider.reload();
}
+ /**
+ * Gets whether this WebView has a back history item.
+ *
+ * @return {@code true} iff this WebView has a back history item
+ */
public boolean canGoBack() {
- return false;
+ checkThread();
+ return mProvider.canGoBack();
}
+ /**
+ * Goes back in the history of this WebView.
+ */
public void goBack() {
+ checkThread();
+ mProvider.goBack();
}
+ /**
+ * Gets whether this WebView has a forward history item.
+ *
+ * @return {@code true} iff this WebView has a forward history item
+ */
public boolean canGoForward() {
- return false;
+ checkThread();
+ return mProvider.canGoForward();
}
+ /**
+ * Goes forward in the history of this WebView.
+ */
public void goForward() {
+ checkThread();
+ mProvider.goForward();
}
+ /**
+ * Gets whether the page can go back or forward the given
+ * number of steps.
+ *
+ * @param steps the negative or positive number of steps to move the
+ * history
+ */
public boolean canGoBackOrForward(int steps) {
- return false;
+ checkThread();
+ return mProvider.canGoBackOrForward(steps);
}
+ /**
+ * Goes to the history item that is the number of steps away from
+ * the current item. Steps is negative if backward and positive
+ * if forward.
+ *
+ * @param steps the number of steps to take back or forward in the back
+ * forward list
+ */
public void goBackOrForward(int steps) {
+ checkThread();
+ mProvider.goBackOrForward(steps);
+ }
+
+ /**
+ * Gets whether private browsing is enabled in this WebView.
+ */
+ public boolean isPrivateBrowsingEnabled() {
+ checkThread();
+ return mProvider.isPrivateBrowsingEnabled();
}
+ /**
+ * Scrolls the contents of this WebView up by half the view size.
+ *
+ * @param top {@code true} to jump to the top of the page
+ * @return {@code true} if the page was scrolled
+ */
public boolean pageUp(boolean top) {
- return false;
+ checkThread();
+ return mProvider.pageUp(top);
}
-
+
+ /**
+ * Scrolls the contents of this WebView down by half the page size.
+ *
+ * @param bottom {@code true} to jump to bottom of page
+ * @return {@code true} if the page was scrolled
+ */
public boolean pageDown(boolean bottom) {
- return false;
+ checkThread();
+ return mProvider.pageDown(bottom);
}
+ /**
+ * Posts a {@link VisualStateCallback}, which will be called when
+ * the current state of the WebView is ready to be drawn.
+ *
+ * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not
+ * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The
+ * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of
+ * the DOM at the current time are ready to be drawn the next time the {@link WebView}
+ * draws.
+ *
+ * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the
+ * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also
+ * contain updates applied after the callback was posted.
+ *
+ * <p>The state of the DOM covered by this API includes the following:
+ * <ul>
+ * <li>primitive HTML elements (div, img, span, etc..)</li>
+ * <li>images</li>
+ * <li>CSS animations</li>
+ * <li>WebGL</li>
+ * <li>canvas</li>
+ * </ul>
+ * It does not include the state of:
+ * <ul>
+ * <li>the video tag</li>
+ * </ul>
+ *
+ * <p>To guarantee that the {@link WebView} will successfully render the first frame
+ * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions
+ * must be met:
+ * <ul>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then
+ * the {@link WebView} must be attached to the view hierarchy.</li>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE}
+ * then the {@link WebView} must be attached to the view hierarchy and must be made
+ * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li>
+ * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the
+ * {@link WebView} must be attached to the view hierarchy and its
+ * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed
+ * values and must be made {@link View#VISIBLE VISIBLE} from the
+ * {@link VisualStateCallback#onComplete} method.</li>
+ * </ul>
+ *
+ * <p>When using this API it is also recommended to enable pre-rasterization if the {@link
+ * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for
+ * more details and do consider its caveats.
+ *
+ * @param requestId An id that will be returned in the callback to allow callers to match
+ * requests with callbacks.
+ * @param callback The callback to be invoked.
+ */
+ public void postVisualStateCallback(long requestId, VisualStateCallback callback) {
+ checkThread();
+ mProvider.insertVisualStateCallback(requestId, callback);
+ }
+
+ /**
+ * Clears this WebView so that onDraw() will draw nothing but white background,
+ * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
+ * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
+ * and release page resources (including any running JavaScript).
+ */
+ @Deprecated
public void clearView() {
+ checkThread();
+ mProvider.clearView();
}
-
+
+ /**
+ * Gets a new picture that captures the current contents of this WebView.
+ * The picture is of the entire document being displayed, and is not
+ * limited to the area currently displayed by this WebView. Also, the
+ * picture is a static copy and is unaffected by later changes to the
+ * content being displayed.
+ * <p>
+ * Note that due to internal changes, for API levels between
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and
+ * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
+ * picture does not include fixed position elements or scrollable divs.
+ * <p>
+ * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture
+ * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve
+ * additional conversion at a cost in memory and performance. Also the
+ * {@link android.graphics.Picture#createFromStream} and
+ * {@link android.graphics.Picture#writeToStream} methods are not supported on the
+ * returned object.
+ *
+ * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or
+ * {@link #saveWebArchive} to save the content to a file.
+ *
+ * @return a picture that captures the current contents of this WebView
+ */
+ @Deprecated
public Picture capturePicture() {
- return null;
+ checkThread();
+ return mProvider.capturePicture();
+ }
+
+ /**
+ * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
+ * to provide a print document name.
+ */
+ @Deprecated
+ public PrintDocumentAdapter createPrintDocumentAdapter() {
+ checkThread();
+ return mProvider.createPrintDocumentAdapter("default");
}
+ /**
+ * Creates a PrintDocumentAdapter that provides the content of this WebView for printing.
+ *
+ * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot
+ * be drawn during the conversion process - any such draws are undefined. It is recommended
+ * to use a dedicated off screen WebView for the printing. If necessary, an application may
+ * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
+ * wrapped around the object returned and observing the onStart and onFinish methods. See
+ * {@link android.print.PrintDocumentAdapter} for more information.
+ *
+ * @param documentName The user-facing name of the printed document. See
+ * {@link android.print.PrintDocumentInfo}
+ */
+ public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
+ checkThread();
+ return mProvider.createPrintDocumentAdapter(documentName);
+ }
+
+ /**
+ * Gets the current scale of this WebView.
+ *
+ * @return the current scale
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
+ */
+ @Deprecated
+ @ViewDebug.ExportedProperty(category = "webview")
public float getScale() {
- return 0;
+ checkThread();
+ return mProvider.getScale();
}
+ /**
+ * Sets the initial scale for this WebView. 0 means default.
+ * The behavior for the default scale depends on the state of
+ * {@link WebSettings#getUseWideViewPort()} and
+ * {@link WebSettings#getLoadWithOverviewMode()}.
+ * If the content fits into the WebView control by width, then
+ * the zoom is set to 100%. For wide content, the behavior
+ * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}.
+ * If its value is {@code true}, the content will be zoomed out to be fit
+ * by width into the WebView control, otherwise not.
+ *
+ * If initial scale is greater than 0, WebView starts with this value
+ * as initial scale.
+ * Please note that unlike the scale properties in the viewport meta tag,
+ * this method doesn't take the screen density into account.
+ *
+ * @param scaleInPercent the initial scale in percent
+ */
public void setInitialScale(int scaleInPercent) {
+ checkThread();
+ mProvider.setInitialScale(scaleInPercent);
}
+ /**
+ * Invokes the graphical zoom picker widget for this WebView. This will
+ * result in the zoom widget appearing on the screen to control the zoom
+ * level of this WebView.
+ */
public void invokeZoomPicker() {
+ checkThread();
+ mProvider.invokeZoomPicker();
+ }
+
+ /**
+ * Gets a HitTestResult based on the current cursor node. If a HTML::a
+ * tag is found and the anchor has a non-JavaScript URL, the HitTestResult
+ * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field.
+ * If the anchor does not have a URL or if it is a JavaScript URL, the type
+ * will be UNKNOWN_TYPE and the URL has to be retrieved through
+ * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
+ * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in
+ * the "extra" field. A type of
+ * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as
+ * a child node. If a phone number is found, the HitTestResult type is set
+ * to PHONE_TYPE and the phone number is set in the "extra" field of
+ * HitTestResult. If a map address is found, the HitTestResult type is set
+ * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
+ * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
+ * and the email is set in the "extra" field of HitTestResult. Otherwise,
+ * HitTestResult type is set to UNKNOWN_TYPE.
+ */
+ public HitTestResult getHitTestResult() {
+ checkThread();
+ return mProvider.getHitTestResult();
}
- public void requestFocusNodeHref(Message hrefMsg) {
+ /**
+ * Requests the anchor or image element URL at the last tapped point.
+ * If hrefMsg is {@code null}, this method returns immediately and does not
+ * dispatch hrefMsg to its target. If the tapped point hits an image,
+ * an anchor, or an image in an anchor, the message associates
+ * strings in named keys in its data. The value paired with the key
+ * may be an empty string.
+ *
+ * @param hrefMsg the message to be dispatched with the result of the
+ * request. The message data contains three keys. "url"
+ * returns the anchor's href attribute. "title" returns the
+ * anchor's text. "src" returns the image's src attribute.
+ */
+ public void requestFocusNodeHref(@Nullable Message hrefMsg) {
+ checkThread();
+ mProvider.requestFocusNodeHref(hrefMsg);
}
+ /**
+ * Requests the URL of the image last touched by the user. msg will be sent
+ * to its target with a String representing the URL as its object.
+ *
+ * @param msg the message to be dispatched with the result of the request
+ * as the data member with "url" as key. The result can be {@code null}.
+ */
public void requestImageRef(Message msg) {
+ checkThread();
+ mProvider.requestImageRef(msg);
}
+ /**
+ * Gets the URL for the current page. This is not always the same as the URL
+ * passed to WebViewClient.onPageStarted because although the load for
+ * that URL has begun, the current page may not have changed.
+ *
+ * @return the URL for the current page
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
public String getUrl() {
- return null;
+ checkThread();
+ return mProvider.getUrl();
}
+ /**
+ * Gets the original URL for the current page. This is not always the same
+ * as the URL passed to WebViewClient.onPageStarted because although the
+ * load for that URL has begun, the current page may not have changed.
+ * Also, there may have been redirects resulting in a different URL to that
+ * originally requested.
+ *
+ * @return the URL that was originally requested for the current page
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
+ public String getOriginalUrl() {
+ checkThread();
+ return mProvider.getOriginalUrl();
+ }
+
+ /**
+ * Gets the title for the current page. This is the title of the current page
+ * until WebViewClient.onReceivedTitle is called.
+ *
+ * @return the title for the current page
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
public String getTitle() {
- return null;
+ checkThread();
+ return mProvider.getTitle();
}
+ /**
+ * Gets the favicon for the current page. This is the favicon of the current
+ * page until WebViewClient.onReceivedIcon is called.
+ *
+ * @return the favicon for the current page
+ */
public Bitmap getFavicon() {
- return null;
+ checkThread();
+ return mProvider.getFavicon();
}
+ /**
+ * Gets the touch icon URL for the apple-touch-icon <link> element, or
+ * a URL on this site's server pointing to the standard location of a
+ * touch icon.
+ *
+ * @hide
+ */
+ public String getTouchIconUrl() {
+ return mProvider.getTouchIconUrl();
+ }
+
+ /**
+ * Gets the progress for the current page.
+ *
+ * @return the progress for the current page between 0 and 100
+ */
public int getProgress() {
- return 0;
+ checkThread();
+ return mProvider.getProgress();
}
-
+
+ /**
+ * Gets the height of the HTML content.
+ *
+ * @return the height of the HTML content
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
public int getContentHeight() {
- return 0;
+ checkThread();
+ return mProvider.getContentHeight();
+ }
+
+ /**
+ * Gets the width of the HTML content.
+ *
+ * @return the width of the HTML content
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "webview")
+ public int getContentWidth() {
+ return mProvider.getContentWidth();
}
+ /**
+ * Pauses all layout, parsing, and JavaScript timers for all WebViews. This
+ * is a global requests, not restricted to just this WebView. This can be
+ * useful if the application has been paused.
+ */
public void pauseTimers() {
+ checkThread();
+ mProvider.pauseTimers();
}
+ /**
+ * Resumes all layout, parsing, and JavaScript timers for all WebViews.
+ * This will resume dispatching all timers.
+ */
public void resumeTimers() {
+ checkThread();
+ mProvider.resumeTimers();
+ }
+
+ /**
+ * Does a best-effort attempt to pause any processing that can be paused
+ * safely, such as animations and geolocation. Note that this call
+ * does not pause JavaScript. To pause JavaScript globally, use
+ * {@link #pauseTimers}.
+ *
+ * To resume WebView, call {@link #onResume}.
+ */
+ public void onPause() {
+ checkThread();
+ mProvider.onPause();
+ }
+
+ /**
+ * Resumes a WebView after a previous call to {@link #onPause}.
+ */
+ public void onResume() {
+ checkThread();
+ mProvider.onResume();
+ }
+
+ /**
+ * Gets whether this WebView is paused, meaning onPause() was called.
+ * Calling onResume() sets the paused state back to {@code false}.
+ *
+ * @hide
+ */
+ public boolean isPaused() {
+ return mProvider.isPaused();
+ }
+
+ /**
+ * Informs this WebView that memory is low so that it can free any available
+ * memory.
+ * @deprecated Memory caches are automatically dropped when no longer needed, and in response
+ * to system memory pressure.
+ */
+ @Deprecated
+ public void freeMemory() {
+ checkThread();
+ mProvider.freeMemory();
}
- public void clearCache() {
+ /**
+ * Clears the resource cache. Note that the cache is per-application, so
+ * this will clear the cache for all WebViews used.
+ *
+ * @param includeDiskFiles if {@code false}, only the RAM cache is cleared
+ */
+ public void clearCache(boolean includeDiskFiles) {
+ checkThread();
+ mProvider.clearCache(includeDiskFiles);
}
+ /**
+ * Removes the autocomplete popup from the currently focused form field, if
+ * present. Note this only affects the display of the autocomplete popup,
+ * it does not remove any saved form data from this WebView's store. To do
+ * that, use {@link WebViewDatabase#clearFormData}.
+ */
public void clearFormData() {
+ checkThread();
+ mProvider.clearFormData();
}
+ /**
+ * Tells this WebView to clear its internal back/forward list.
+ */
public void clearHistory() {
+ checkThread();
+ mProvider.clearHistory();
}
+ /**
+ * Clears the SSL preferences table stored in response to proceeding with
+ * SSL certificate errors.
+ */
public void clearSslPreferences() {
+ checkThread();
+ mProvider.clearSslPreferences();
+ }
+
+ /**
+ * Clears the client certificate preferences stored in response
+ * to proceeding/cancelling client cert requests. Note that WebView
+ * automatically clears these preferences when it receives a
+ * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are
+ * shared by all the WebViews that are created by the embedder application.
+ *
+ * @param onCleared A runnable to be invoked when client certs are cleared.
+ * The runnable will be called in UI thread.
+ */
+ public static void clearClientCertPreferences(@Nullable Runnable onCleared) {
+ getFactory().getStatics().clearClientCertPreferences(onCleared);
+ }
+
+ /**
+ * Starts Safe Browsing initialization.
+ * <p>
+ * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
+ * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
+ * devices {@code callback} will receive {@code false}.
+ * <p>
+ * This does not enable the Safe Browsing feature itself, and should only be called if Safe
+ * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
+ * prepares resources used for Safe Browsing.
+ * <p>
+ * This should be called with the Application Context (and will always use the Application
+ * context to do its work regardless).
+ *
+ * @param context Application Context.
+ * @param callback will be called on the UI thread with {@code true} if initialization is
+ * successful, {@code false} otherwise.
+ */
+ public static void startSafeBrowsing(Context context,
+ @Nullable ValueCallback<Boolean> callback) {
+ getFactory().getStatics().initSafeBrowsing(context, callback);
+ }
+
+ /**
+ * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
+ * global for all the WebViews.
+ * <p>
+ * Each rule should take one of these:
+ * <table>
+ * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
+ * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
+ * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
+ * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
+ * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
+ * </table>
+ * <p>
+ * All other rules, including wildcards, are invalid.
+ *
+ * @param urls the list of URLs
+ * @param callback will be called with {@code true} if URLs are successfully added to the
+ * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will
+ * be run on the UI thread
+ */
+ public static void setSafeBrowsingWhitelist(@NonNull List<String> urls,
+ @Nullable ValueCallback<Boolean> callback) {
+ getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback);
+ }
+
+ /**
+ * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
+ *
+ * @return the url pointing to a privacy policy document which can be displayed to users.
+ */
+ @NonNull
+ public static Uri getSafeBrowsingPrivacyPolicyUrl() {
+ return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
+ }
+
+ /**
+ * Gets the WebBackForwardList for this WebView. This contains the
+ * back/forward list for use in querying each item in the history stack.
+ * This is a copy of the private WebBackForwardList so it contains only a
+ * snapshot of the current state. Multiple calls to this method may return
+ * different objects. The object returned from this method will not be
+ * updated to reflect any new state.
+ */
+ public WebBackForwardList copyBackForwardList() {
+ checkThread();
+ return mProvider.copyBackForwardList();
+
+ }
+
+ /**
+ * Registers the listener to be notified as find-on-page operations
+ * progress. This will replace the current listener.
+ *
+ * @param listener an implementation of {@link FindListener}
+ */
+ public void setFindListener(FindListener listener) {
+ checkThread();
+ setupFindListenerIfNeeded();
+ mFindListener.mUserFindListener = listener;
+ }
+
+ /**
+ * Highlights and scrolls to the next match found by
+ * {@link #findAllAsync}, wrapping around page boundaries as necessary.
+ * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
+ * has not been called yet, or if {@link #clearMatches} has been called since the
+ * last find operation, this function does nothing.
+ *
+ * @param forward the direction to search
+ * @see #setFindListener
+ */
+ public void findNext(boolean forward) {
+ checkThread();
+ mProvider.findNext(forward);
+ }
+
+ /**
+ * Finds all instances of find on the page and highlights them.
+ * Notifies any registered {@link FindListener}.
+ *
+ * @param find the string to find
+ * @return the number of occurrences of the String "find" that were found
+ * @deprecated {@link #findAllAsync} is preferred.
+ * @see #setFindListener
+ */
+ @Deprecated
+ public int findAll(String find) {
+ checkThread();
+ StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
+ return mProvider.findAll(find);
+ }
+
+ /**
+ * Finds all instances of find on the page and highlights them,
+ * asynchronously. Notifies any registered {@link FindListener}.
+ * Successive calls to this will cancel any pending searches.
+ *
+ * @param find the string to find.
+ * @see #setFindListener
+ */
+ public void findAllAsync(String find) {
+ checkThread();
+ mProvider.findAllAsync(find);
+ }
+
+ /**
+ * Starts an ActionMode for finding text in this WebView. Only works if this
+ * WebView is attached to the view system.
+ *
+ * @param text if non-null, will be the initial text to search for.
+ * Otherwise, the last String searched for in this WebView will
+ * be used to start.
+ * @param showIme if {@code true}, show the IME, assuming the user will begin typing.
+ * If {@code false} and text is non-null, perform a find all.
+ * @return {@code true} if the find dialog is shown, {@code false} otherwise
+ * @deprecated This method does not work reliably on all Android versions;
+ * implementing a custom find dialog using WebView.findAllAsync()
+ * provides a more robust solution.
+ */
+ @Deprecated
+ public boolean showFindDialog(@Nullable String text, boolean showIme) {
+ checkThread();
+ return mProvider.showFindDialog(text, showIme);
}
+ /**
+ * Gets the first substring consisting of the address of a physical
+ * location. Currently, only addresses in the United States are detected,
+ * and consist of:
+ * <ul>
+ * <li>a house number</li>
+ * <li>a street name</li>
+ * <li>a street type (Road, Circle, etc), either spelled out or
+ * abbreviated</li>
+ * <li>a city name</li>
+ * <li>a state or territory, either spelled out or two-letter abbr</li>
+ * <li>an optional 5 digit or 9 digit zip code</li>
+ * </ul>
+ * All names must be correctly capitalized, and the zip code, if present,
+ * must be valid for the state. The street type must be a standard USPS
+ * spelling or abbreviation. The state or territory must also be spelled
+ * or abbreviated using USPS standards. The house number may not exceed
+ * five digits.
+ *
+ * @param addr the string to search for addresses
+ * @return the address, or if no address is found, {@code null}
+ */
+ @Nullable
public static String findAddress(String addr) {
- return null;
+ // TODO: Rewrite this in Java so it is not needed to start up chromium
+ // Could also be deprecated
+ return getFactory().getStatics().findAddress(addr);
+ }
+
+ /**
+ * For apps targeting the L release, WebView has a new default behavior that reduces
+ * memory footprint and increases performance by intelligently choosing
+ * the portion of the HTML document that needs to be drawn. These
+ * optimizations are transparent to the developers. However, under certain
+ * circumstances, an App developer may want to disable them:
+ * <ol>
+ * <li>When an app uses {@link #onDraw} to do own drawing and accesses portions
+ * of the page that is way outside the visible portion of the page.</li>
+ * <li>When an app uses {@link #capturePicture} to capture a very large HTML document.
+ * Note that capturePicture is a deprecated API.</li>
+ * </ol>
+ * Enabling drawing the entire HTML document has a significant performance
+ * cost. This method should be called before any WebViews are created.
+ */
+ public static void enableSlowWholeDocumentDraw() {
+ getFactory().getStatics().enableSlowWholeDocumentDraw();
}
+ /**
+ * Clears the highlighting surrounding text matches created by
+ * {@link #findAllAsync}.
+ */
+ public void clearMatches() {
+ checkThread();
+ mProvider.clearMatches();
+ }
+
+ /**
+ * Queries the document to see if it contains any image references. The
+ * message object will be dispatched with arg1 being set to 1 if images
+ * were found and 0 if the document does not reference any images.
+ *
+ * @param response the message that will be dispatched with the result
+ */
public void documentHasImages(Message response) {
+ checkThread();
+ mProvider.documentHasImages(response);
}
+ /**
+ * Sets the WebViewClient that will receive various notifications and
+ * requests. This will replace the current handler.
+ *
+ * @param client an implementation of WebViewClient
+ * @see #getWebViewClient
+ */
public void setWebViewClient(WebViewClient client) {
+ checkThread();
+ mProvider.setWebViewClient(client);
+ }
+
+ /**
+ * Gets the WebViewClient.
+ *
+ * @return the WebViewClient, or a default client if not yet set
+ * @see #setWebViewClient
+ */
+ public WebViewClient getWebViewClient() {
+ checkThread();
+ return mProvider.getWebViewClient();
}
+ /**
+ * Registers the interface to be used when content can not be handled by
+ * the rendering engine, and should be downloaded instead. This will replace
+ * the current handler.
+ *
+ * @param listener an implementation of DownloadListener
+ */
public void setDownloadListener(DownloadListener listener) {
+ checkThread();
+ mProvider.setDownloadListener(listener);
}
+ /**
+ * Sets the chrome handler. This is an implementation of WebChromeClient for
+ * use in handling JavaScript dialogs, favicons, titles, and the progress.
+ * This will replace the current handler.
+ *
+ * @param client an implementation of WebChromeClient
+ * @see #getWebChromeClient
+ */
public void setWebChromeClient(WebChromeClient client) {
+ checkThread();
+ mProvider.setWebChromeClient(client);
}
- public void addJavascriptInterface(Object obj, String interfaceName) {
+ /**
+ * Gets the chrome handler.
+ *
+ * @return the WebChromeClient, or {@code null} if not yet set
+ * @see #setWebChromeClient
+ */
+ @Nullable
+ public WebChromeClient getWebChromeClient() {
+ checkThread();
+ return mProvider.getWebChromeClient();
+ }
+
+ /**
+ * Sets the Picture listener. This is an interface used to receive
+ * notifications of a new Picture.
+ *
+ * @param listener an implementation of WebView.PictureListener
+ * @deprecated This method is now obsolete.
+ */
+ @Deprecated
+ public void setPictureListener(PictureListener listener) {
+ checkThread();
+ mProvider.setPictureListener(listener);
+ }
+
+ /**
+ * Injects the supplied Java object into this WebView. The object is
+ * injected into the JavaScript context of the main frame, using the
+ * supplied name. This allows the Java object's methods to be
+ * accessed from JavaScript. For applications targeted to API
+ * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ * and above, only public methods that are annotated with
+ * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
+ * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
+ * all public methods (including the inherited ones) can be accessed, see the
+ * important security note below for implications.
+ * <p> Note that injected objects will not appear in JavaScript until the page is next
+ * (re)loaded. JavaScript should be enabled before injecting the object. For example:
+ * <pre>
+ * class JsObject {
+ * {@literal @}JavascriptInterface
+ * public String toString() { return "injectedObject"; }
+ * }
+ * webview.getSettings().setJavaScriptEnabled(true);
+ * webView.addJavascriptInterface(new JsObject(), "injectedObject");
+ * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
+ * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
+ * <p>
+ * <strong>IMPORTANT:</strong>
+ * <ul>
+ * <li> This method can be used to allow JavaScript to control the host
+ * application. This is a powerful feature, but also presents a security
+ * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier.
+ * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+ * are still vulnerable if the app runs on a device running Android earlier than 4.2.
+ * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ * and to ensure the method is called only when running on Android 4.2 or later.
+ * With these older versions, JavaScript could use reflection to access an
+ * injected object's public fields. Use of this method in a WebView
+ * containing untrusted content could allow an attacker to manipulate the
+ * host application in unintended ways, executing Java code with the
+ * permissions of the host application. Use extreme care when using this
+ * method in a WebView which could contain untrusted content.</li>
+ * <li> JavaScript interacts with Java object on a private, background
+ * thread of this WebView. Care is therefore required to maintain thread
+ * safety.
+ * </li>
+ * <li> The Java object's fields are not accessible.</li>
+ * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
+ * and above, methods of injected Java objects are enumerable from
+ * JavaScript.</li>
+ * </ul>
+ *
+ * @param object the Java object to inject into this WebView's JavaScript
+ * context. {@code null} values are ignored.
+ * @param name the name used to expose the object in JavaScript
+ */
+ public void addJavascriptInterface(Object object, String name) {
+ checkThread();
+ mProvider.addJavascriptInterface(object, name);
+ }
+
+ /**
+ * Removes a previously injected Java object from this WebView. Note that
+ * the removal will not be reflected in JavaScript until the page is next
+ * (re)loaded. See {@link #addJavascriptInterface}.
+ *
+ * @param name the name used to expose the object in JavaScript
+ */
+ public void removeJavascriptInterface(@NonNull String name) {
+ checkThread();
+ mProvider.removeJavascriptInterface(name);
+ }
+
+ /**
+ * Creates a message channel to communicate with JS and returns the message
+ * ports that represent the endpoints of this message channel. The HTML5 message
+ * channel functionality is described
+ * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
+ * </a>
+ *
+ * <p>The returned message channels are entangled and already in started state.
+ *
+ * @return the two message ports that form the message channel.
+ */
+ public WebMessagePort[] createWebMessageChannel() {
+ checkThread();
+ return mProvider.createWebMessageChannel();
+ }
+
+ /**
+ * Post a message to main frame. The embedded application can restrict the
+ * messages to a certain target origin. See
+ * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
+ * HTML5 spec</a> for how target origin can be used.
+ * <p>
+ * A target origin can be set as a wildcard ("*"). However this is not recommended.
+ * See the page above for security issues.
+ *
+ * @param message the WebMessage
+ * @param targetOrigin the target origin.
+ */
+ public void postWebMessage(WebMessage message, Uri targetOrigin) {
+ checkThread();
+ mProvider.postMessageToMainFrame(message, targetOrigin);
+ }
+
+ /**
+ * Gets the WebSettings object used to control the settings for this
+ * WebView.
+ *
+ * @return a WebSettings object that can be used to control this WebView's
+ * settings
+ */
+ public WebSettings getSettings() {
+ checkThread();
+ return mProvider.getSettings();
+ }
+
+ /**
+ * Enables debugging of web contents (HTML / CSS / JavaScript)
+ * loaded into any WebViews of this application. This flag can be enabled
+ * in order to facilitate debugging of web layouts and JavaScript
+ * code running inside WebViews. Please refer to WebView documentation
+ * for the debugging guide.
+ *
+ * The default is {@code false}.
+ *
+ * @param enabled whether to enable web contents debugging
+ */
+ public static void setWebContentsDebuggingEnabled(boolean enabled) {
+ getFactory().getStatics().setWebContentsDebuggingEnabled(enabled);
+ }
+
+ /**
+ * Gets the list of currently loaded plugins.
+ *
+ * @return the list of currently loaded plugins
+ * @deprecated This was used for Gears, which has been deprecated.
+ * @hide
+ */
+ @Deprecated
+ public static synchronized PluginList getPluginList() {
+ return new PluginList();
+ }
+
+ /**
+ * @deprecated This was used for Gears, which has been deprecated.
+ * @hide
+ */
+ @Deprecated
+ public void refreshPlugins(boolean reloadOpenPages) {
+ checkThread();
+ }
+
+ /**
+ * Puts this WebView into text selection mode. Do not rely on this
+ * functionality; it will be deprecated in the future.
+ *
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public void emulateShiftHeld() {
+ checkThread();
+ }
+
+ /**
+ * @deprecated WebView no longer needs to implement
+ * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
+ */
+ @Override
+ // Cannot add @hide as this can always be accessed via the interface.
+ @Deprecated
+ public void onChildViewAdded(View parent, View child) {}
+
+ /**
+ * @deprecated WebView no longer needs to implement
+ * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
+ */
+ @Override
+ // Cannot add @hide as this can always be accessed via the interface.
+ @Deprecated
+ public void onChildViewRemoved(View p, View child) {}
+
+ /**
+ * @deprecated WebView should not have implemented
+ * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now.
+ */
+ @Override
+ // Cannot add @hide as this can always be accessed via the interface.
+ @Deprecated
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ }
+
+ /**
+ * @deprecated Only the default case, {@code true}, will be supported in a future version.
+ */
+ @Deprecated
+ public void setMapTrackballToArrowKeys(boolean setMap) {
+ checkThread();
+ mProvider.setMapTrackballToArrowKeys(setMap);
}
+
+ public void flingScroll(int vx, int vy) {
+ checkThread();
+ mProvider.flingScroll(vx, vy);
+ }
+
+ /**
+ * Gets the zoom controls for this WebView, as a separate View. The caller
+ * is responsible for inserting this View into the layout hierarchy.
+ * <p/>
+ * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced
+ * built-in zoom mechanisms for the WebView, as opposed to these separate
+ * zoom controls. The built-in mechanisms are preferred and can be enabled
+ * using {@link WebSettings#setBuiltInZoomControls}.
+ *
+ * @deprecated the built-in zoom mechanisms are preferred
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+ */
+ @Deprecated
public View getZoomControls() {
- return null;
+ checkThread();
+ return mProvider.getZoomControls();
}
+ /**
+ * Gets whether this WebView can be zoomed in.
+ *
+ * @return {@code true} if this WebView can be zoomed in
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
+ */
+ @Deprecated
+ public boolean canZoomIn() {
+ checkThread();
+ return mProvider.canZoomIn();
+ }
+
+ /**
+ * Gets whether this WebView can be zoomed out.
+ *
+ * @return {@code true} if this WebView can be zoomed out
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
+ */
+ @Deprecated
+ public boolean canZoomOut() {
+ checkThread();
+ return mProvider.canZoomOut();
+ }
+
+ /**
+ * Performs a zoom operation in this WebView.
+ *
+ * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's
+ * zoom limits. This value must be in the range 0.01 to 100.0 inclusive.
+ */
+ public void zoomBy(float zoomFactor) {
+ checkThread();
+ if (zoomFactor < 0.01)
+ throw new IllegalArgumentException("zoomFactor must be greater than 0.01.");
+ if (zoomFactor > 100.0)
+ throw new IllegalArgumentException("zoomFactor must be less than 100.");
+ mProvider.zoomBy(zoomFactor);
+ }
+
+ /**
+ * Performs zoom in in this WebView.
+ *
+ * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes
+ */
public boolean zoomIn() {
- return false;
+ checkThread();
+ return mProvider.zoomIn();
}
+ /**
+ * Performs zoom out in this WebView.
+ *
+ * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
+ */
public boolean zoomOut() {
- return false;
+ checkThread();
+ return mProvider.zoomOut();
+ }
+
+ /**
+ * @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
+ @Deprecated
+ public void debugDump() {
+ checkThread();
+ }
+
+ /**
+ * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)}
+ * @hide
+ */
+ @Override
+ public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
+ mProvider.dumpViewHierarchyWithProperties(out, level);
+ }
+
+ /**
+ * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)}
+ * @hide
+ */
+ @Override
+ public View findHierarchyView(String className, int hashCode) {
+ return mProvider.findHierarchyView(className, hashCode);
+ }
+
+ /** @hide */
+ @IntDef({
+ RENDERER_PRIORITY_WAIVED,
+ RENDERER_PRIORITY_BOUND,
+ RENDERER_PRIORITY_IMPORTANT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RendererPriority {}
+
+ /**
+ * The renderer associated with this WebView is bound with
+ * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
+ * {@link WebView} renderers will be strong targets for out of memory
+ * killing.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_WAIVED = 0;
+ /**
+ * The renderer associated with this WebView is bound with
+ * the default priority for services.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_BOUND = 1;
+ /**
+ * The renderer associated with this WebView is bound with
+ * {@link Context#BIND_IMPORTANT}.
+ *
+ * Use with {@link #setRendererPriorityPolicy}.
+ */
+ public static final int RENDERER_PRIORITY_IMPORTANT = 2;
+
+ /**
+ * Set the renderer priority policy for this {@link WebView}. The
+ * priority policy will be used to determine whether an out of
+ * process renderer should be considered to be a target for OOM
+ * killing.
+ *
+ * Because a renderer can be associated with more than one
+ * WebView, the final priority it is computed as the maximum of
+ * any attached WebViews. When a WebView is destroyed it will
+ * cease to be considerered when calculating the renderer
+ * priority. Once no WebViews remain associated with the renderer,
+ * the priority of the renderer will be reduced to
+ * {@link #RENDERER_PRIORITY_WAIVED}.
+ *
+ * The default policy is to set the priority to
+ * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
+ * and this should not be changed unless the caller also handles
+ * renderer crashes with
+ * {@link WebViewClient#onRenderProcessGone}. Any other setting
+ * will result in WebView renderers being killed by the system
+ * more aggressively than the application.
+ *
+ * @param rendererRequestedPriority the minimum priority at which
+ * this WebView desires the renderer process to be bound.
+ * @param waivedWhenNotVisible if {@code true}, this flag specifies that
+ * when this WebView is not visible, it will be treated as
+ * if it had requested a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED}.
+ */
+ public void setRendererPriorityPolicy(
+ @RendererPriority int rendererRequestedPriority,
+ boolean waivedWhenNotVisible) {
+ mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
+ }
+
+ /**
+ * Get the requested renderer priority for this WebView.
+ *
+ * @return the requested renderer priority policy.
+ */
+ @RendererPriority
+ public int getRendererRequestedPriority() {
+ return mProvider.getRendererRequestedPriority();
+ }
+
+ /**
+ * Return whether this WebView requests a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+ *
+ * @return whether this WebView requests a priority of
+ * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+ */
+ public boolean getRendererPriorityWaivedWhenNotVisible() {
+ return mProvider.getRendererPriorityWaivedWhenNotVisible();
+ }
+
+ /**
+ * Sets the {@link TextClassifier} for this WebView.
+ */
+ public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+ mProvider.setTextClassifier(textClassifier);
+ }
+
+ /**
+ * Returns the {@link TextClassifier} used by this WebView.
+ * If no TextClassifier has been set, this WebView uses the default set by the system.
+ */
+ @NonNull
+ public TextClassifier getTextClassifier() {
+ return mProvider.getTextClassifier();
+ }
+
+ //-------------------------------------------------------------------------
+ // Interface for WebView providers
+ //-------------------------------------------------------------------------
+
+ /**
+ * Gets the WebViewProvider. Used by providers to obtain the underlying
+ * implementation, e.g. when the application responds to
+ * WebViewClient.onCreateWindow() request.
+ *
+ * @hide WebViewProvider is not public API.
+ */
+ @SystemApi
+ public WebViewProvider getWebViewProvider() {
+ return mProvider;
+ }
+
+ /**
+ * Callback interface, allows the provider implementation to access non-public methods
+ * and fields, and make super-class calls in this WebView instance.
+ * @hide Only for use by WebViewProvider implementations
+ */
+ @SystemApi
+ public class PrivateAccess {
+ // ---- Access to super-class methods ----
+ public int super_getScrollBarStyle() {
+ return WebView.super.getScrollBarStyle();
+ }
+
+ public void super_scrollTo(int scrollX, int scrollY) {
+ WebView.super.scrollTo(scrollX, scrollY);
+ }
+
+ public void super_computeScroll() {
+ WebView.super.computeScroll();
+ }
+
+ public boolean super_onHoverEvent(MotionEvent event) {
+ return WebView.super.onHoverEvent(event);
+ }
+
+ public boolean super_performAccessibilityAction(int action, Bundle arguments) {
+ return WebView.super.performAccessibilityActionInternal(action, arguments);
+ }
+
+ public boolean super_performLongClick() {
+ return WebView.super.performLongClick();
+ }
+
+ public boolean super_setFrame(int left, int top, int right, int bottom) {
+ return WebView.super.setFrame(left, top, right, bottom);
+ }
+
+ public boolean super_dispatchKeyEvent(KeyEvent event) {
+ return WebView.super.dispatchKeyEvent(event);
+ }
+
+ public boolean super_onGenericMotionEvent(MotionEvent event) {
+ return WebView.super.onGenericMotionEvent(event);
+ }
+
+ public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) {
+ return WebView.super.requestFocus(direction, previouslyFocusedRect);
+ }
+
+ public void super_setLayoutParams(ViewGroup.LayoutParams params) {
+ WebView.super.setLayoutParams(params);
+ }
+
+ public void super_startActivityForResult(Intent intent, int requestCode) {
+ WebView.super.startActivityForResult(intent, requestCode);
+ }
+
+ // ---- Access to non-public methods ----
+ public void overScrollBy(int deltaX, int deltaY,
+ int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY,
+ int maxOverScrollX, int maxOverScrollY,
+ boolean isTouchEvent) {
+ WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
+ maxOverScrollX, maxOverScrollY, isTouchEvent);
+ }
+
+ public void awakenScrollBars(int duration) {
+ WebView.this.awakenScrollBars(duration);
+ }
+
+ public void awakenScrollBars(int duration, boolean invalidate) {
+ WebView.this.awakenScrollBars(duration, invalidate);
+ }
+
+ public float getVerticalScrollFactor() {
+ return WebView.this.getVerticalScrollFactor();
+ }
+
+ public float getHorizontalScrollFactor() {
+ return WebView.this.getHorizontalScrollFactor();
+ }
+
+ public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+ WebView.this.setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ public void onScrollChanged(int l, int t, int oldl, int oldt) {
+ WebView.this.onScrollChanged(l, t, oldl, oldt);
+ }
+
+ public int getHorizontalScrollbarHeight() {
+ return WebView.this.getHorizontalScrollbarHeight();
+ }
+
+ public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+ int l, int t, int r, int b) {
+ WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
+ }
+
+ // ---- Access to (non-public) fields ----
+ /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */
+ public void setScrollXRaw(int scrollX) {
+ WebView.this.mScrollX = scrollX;
+ }
+
+ /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */
+ public void setScrollYRaw(int scrollY) {
+ WebView.this.mScrollY = scrollY;
+ }
+
+ }
+
+ //-------------------------------------------------------------------------
+ // Package-private internal stuff
+ //-------------------------------------------------------------------------
+
+ // Only used by android.webkit.FindActionModeCallback.
+ void setFindDialogFindListener(FindListener listener) {
+ checkThread();
+ setupFindListenerIfNeeded();
+ mFindListener.mFindDialogFindListener = listener;
+ }
+
+ // Only used by android.webkit.FindActionModeCallback.
+ void notifyFindDialogDismissed() {
+ checkThread();
+ mProvider.notifyFindDialogDismissed();
+ }
+
+ //-------------------------------------------------------------------------
+ // Private internal stuff
+ //-------------------------------------------------------------------------
+
+ private WebViewProvider mProvider;
+
+ /**
+ * In addition to the FindListener that the user may set via the WebView.setFindListener
+ * API, FindActionModeCallback will register it's own FindListener. We keep them separate
+ * via this class so that the two FindListeners can potentially exist at once.
+ */
+ private class FindListenerDistributor implements FindListener {
+ private FindListener mFindDialogFindListener;
+ private FindListener mUserFindListener;
+
+ @Override
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting) {
+ if (mFindDialogFindListener != null) {
+ mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+ isDoneCounting);
+ }
+
+ if (mUserFindListener != null) {
+ mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+ isDoneCounting);
+ }
+ }
+ }
+ private FindListenerDistributor mFindListener;
+
+ private void setupFindListenerIfNeeded() {
+ if (mFindListener == null) {
+ mFindListener = new FindListenerDistributor();
+ mProvider.setFindListener(mFindListener);
+ }
+ }
+
+ private void ensureProviderCreated() {
+ checkThread();
+ if (mProvider == null) {
+ // As this can get called during the base class constructor chain, pass the minimum
+ // number of dependencies here; the rest are deferred to init().
+ mProvider = getFactory().createWebView(this, new PrivateAccess());
+ }
+ }
+
+ private static WebViewFactoryProvider getFactory() {
+ return WebViewFactory.getProvider();
+ }
+
+ private final Looper mWebViewThread = Looper.myLooper();
+
+ private void checkThread() {
+ // Ignore mWebViewThread == null because this can be called during in the super class
+ // constructor, before this class's own constructor has even started.
+ if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
+ Throwable throwable = new Throwable(
+ "A WebView method was called on thread '" +
+ Thread.currentThread().getName() + "'. " +
+ "All WebView methods must be called on the same thread. " +
+ "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
+ ", FYI main Looper is " + Looper.getMainLooper() + ")");
+ Log.w(LOGTAG, Log.getStackTraceString(throwable));
+ StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
+
+ if (sEnforceThreadChecking) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ }
+
+ //-------------------------------------------------------------------------
+ // Override View methods
+ //-------------------------------------------------------------------------
+
+ // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures
+ // there's a corresponding override (or better, caller) for each of them in here.
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mProvider.getViewDelegate().onAttachedToWindow();
+ }
+
+ /** @hide */
+ @Override
+ protected void onDetachedFromWindowInternal() {
+ mProvider.getViewDelegate().onDetachedFromWindow();
+ super.onDetachedFromWindowInternal();
+ }
+
+ /** @hide */
+ @Override
+ public void onMovedToDisplay(int displayId, Configuration config) {
+ mProvider.getViewDelegate().onMovedToDisplay(displayId, config);
+ }
+
+ @Override
+ public void setLayoutParams(ViewGroup.LayoutParams params) {
+ mProvider.getViewDelegate().setLayoutParams(params);
+ }
+
+ @Override
+ public void setOverScrollMode(int mode) {
+ super.setOverScrollMode(mode);
+ // This method may be called in the constructor chain, before the WebView provider is
+ // created.
+ ensureProviderCreated();
+ mProvider.getViewDelegate().setOverScrollMode(mode);
+ }
+
+ @Override
+ public void setScrollBarStyle(int style) {
+ mProvider.getViewDelegate().setScrollBarStyle(style);
+ super.setScrollBarStyle(style);
+ }
+
+ @Override
+ protected int computeHorizontalScrollRange() {
+ return mProvider.getScrollDelegate().computeHorizontalScrollRange();
+ }
+
+ @Override
+ protected int computeHorizontalScrollOffset() {
+ return mProvider.getScrollDelegate().computeHorizontalScrollOffset();
+ }
+
+ @Override
+ protected int computeVerticalScrollRange() {
+ return mProvider.getScrollDelegate().computeVerticalScrollRange();
+ }
+
+ @Override
+ protected int computeVerticalScrollOffset() {
+ return mProvider.getScrollDelegate().computeVerticalScrollOffset();
+ }
+
+ @Override
+ protected int computeVerticalScrollExtent() {
+ return mProvider.getScrollDelegate().computeVerticalScrollExtent();
+ }
+
+ @Override
+ public void computeScroll() {
+ mProvider.getScrollDelegate().computeScroll();
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ return mProvider.getViewDelegate().onHoverEvent(event);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return mProvider.getViewDelegate().onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return mProvider.getViewDelegate().onGenericMotionEvent(event);
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ return mProvider.getViewDelegate().onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event);
+ }
+
+ /*
+ TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not
+ to be delegating them too.
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyPreIme(keyCode, event);
+ }
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyLongPress(keyCode, event);
+ }
+ @Override
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ return mProvider.getViewDelegate().onKeyShortcut(keyCode, event);
+ }
+ */
+
+ @Override
+ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+ AccessibilityNodeProvider provider =
+ mProvider.getViewDelegate().getAccessibilityNodeProvider();
+ return provider == null ? super.getAccessibilityNodeProvider() : provider;
+ }
+
+ @Deprecated
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return mProvider.getViewDelegate().shouldDelayChildPressedState();
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return WebView.class.getName();
+ }
+
+ @Override
+ public void onProvideVirtualStructure(ViewStructure structure) {
+ mProvider.getViewDelegate().onProvideVirtualStructure(structure);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
+ * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
+ * understood by the {@link android.service.autofill.AutofillService} implementations:
+ *
+ * <ol>
+ * <li>Only the HTML nodes inside a {@code FORM} are generated.
+ * <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the
+ * node representing the WebView.
+ * <li>If a web page has multiple {@code FORM}s, only the data for the current form is
+ * represented&mdash;if the user taps a field from another form, then the current autofill
+ * context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and
+ * a new context is created for that {@code FORM}.
+ * <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in
+ * the view structure until the user taps a field from a {@code FORM} inside the
+ * {@code IFRAME}, in which case it would be treated the same way as multiple forms described
+ * above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the
+ * {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node.
+ * <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
+ * {@link ViewStructure#setAutofillHints(String[])}.
+ * <li>If the view is editable, the {@link ViewStructure#setAutofillType(int)} and
+ * {@link ViewStructure#setAutofillValue(AutofillValue)} must be set.
+ * <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
+ * <li>Other HTML attributes can be represented through
+ * {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}.
+ * </ol>
+ *
+ * <p>If the WebView implementation can determine that the value of a field was set statically
+ * (for example, not through Javascript), it should also call
+ * {@code structure.setDataIsSensitive(false)}.
+ *
+ * <p>For example, an HTML form with 2 fields for username and password:
+ *
+ * <pre class="prettyprint">
+ * &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
+ * &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
+ * </pre>
+ *
+ * <p>Would map to:
+ *
+ * <pre class="prettyprint">
+ * int index = structure.addChildCount(2);
+ * ViewStructure username = structure.newChild(index);
+ * username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child
+ * username.setAutofillHints("username");
+ * username.setHtmlInfo(username.newHtmlInfoBuilder("input")
+ * .addAttribute("type", "text")
+ * .addAttribute("name", "username")
+ * .build());
+ * username.setHint("Email or username");
+ * username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ * username.setAutofillValue(AutofillValue.forText("Type your username"));
+ * // Value of the field is not sensitive because it was created statically and not changed.
+ * username.setDataIsSensitive(false);
+ *
+ * ViewStructure password = structure.newChild(index + 1);
+ * username.setAutofillId(structure, 2); // id 2 - second child
+ * password.setAutofillHints("current-password");
+ * password.setHtmlInfo(password.newHtmlInfoBuilder("input")
+ * .addAttribute("type", "password")
+ * .addAttribute("name", "password")
+ * .build());
+ * password.setHint("Password");
+ * password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+ * </pre>
+ */
+ @Override
+ public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+ mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
+ }
+
+ @Override
+ public void autofill(SparseArray<AutofillValue>values) {
+ mProvider.getViewDelegate().autofill(values);
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
+ mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
+ mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
+ }
+
+ /** @hide */
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
+ }
+
+ /** @hide */
+ @Override
+ protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+ int l, int t, int r, int b) {
+ mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
+ }
+
+ @Override
+ protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
+ mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY);
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ mProvider.getViewDelegate().onWindowVisibilityChanged(visibility);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mProvider.getViewDelegate().onDraw(canvas);
+ }
+
+ @Override
+ public boolean performLongClick() {
+ return mProvider.getViewDelegate().performLongClick();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ mProvider.getViewDelegate().onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Creates a new InputConnection for an InputMethod to interact with the WebView.
+ * This is similar to {@link View#onCreateInputConnection} but note that WebView
+ * calls InputConnection methods on a thread other than the UI thread.
+ * If these methods are overridden, then the overriding methods should respect
+ * thread restrictions when calling View methods or accessing data.
+ */
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return mProvider.getViewDelegate().onCreateInputConnection(outAttrs);
+ }
+
+ @Override
+ public boolean onDragEvent(DragEvent event) {
+ return mProvider.getViewDelegate().onDragEvent(event);
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ // This method may be called in the constructor chain, before the WebView provider is
+ // created.
+ ensureProviderCreated();
+ mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus);
+ super.onWindowFocusChanged(hasWindowFocus);
+ }
+
+ @Override
+ protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect);
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ }
+
+ /** @hide */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ return mProvider.getViewDelegate().setFrame(left, top, right, bottom);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int ow, int oh) {
+ super.onSizeChanged(w, h, ow, oh);
+ mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh);
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mProvider.getViewDelegate().dispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+ return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate);
+ }
+
+ @Override
+ public void setBackgroundColor(int color) {
+ mProvider.getViewDelegate().setBackgroundColor(color);
+ }
+
+ @Override
+ public void setLayerType(int layerType, Paint paint) {
+ super.setLayerType(layerType, paint);
+ mProvider.getViewDelegate().setLayerType(layerType, paint);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ mProvider.getViewDelegate().preDispatchDraw(canvas);
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ public void onStartTemporaryDetach() {
+ super.onStartTemporaryDetach();
+ mProvider.getViewDelegate().onStartTemporaryDetach();
+ }
+
+ @Override
+ public void onFinishTemporaryDetach() {
+ super.onFinishTemporaryDetach();
+ mProvider.getViewDelegate().onFinishTemporaryDetach();
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mProvider.getViewDelegate().getHandler(super.getHandler());
+ }
+
+ @Override
+ public View findFocus() {
+ return mProvider.getViewDelegate().findFocus(super.findFocus());
+ }
+
+ /**
+ * If WebView has already been loaded into the current process this method will return the
+ * package that was used to load it. Otherwise, the package that would be used if the WebView
+ * was loaded right now will be returned; this does not cause WebView to be loaded, so this
+ * information may become outdated at any time.
+ * The WebView package changes either when the current WebView package is updated, disabled, or
+ * uninstalled. It can also be changed through a Developer Setting.
+ * If the WebView package changes, any app process that has loaded WebView will be killed. The
+ * next time the app starts and loads WebView it will use the new WebView package instead.
+ * @return the current WebView package, or {@code null} if there is none.
+ */
+ @Nullable
+ public static PackageInfo getCurrentWebViewPackage() {
+ PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
+ if (webviewPackage != null) {
+ return webviewPackage;
+ }
+
+ IWebViewUpdateService service = WebViewFactory.getUpdateService();
+ if (service == null) {
+ return null;
+ }
+ try {
+ return service.getCurrentWebViewPackage();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
+ *
+ * @param requestCode The integer request code originally supplied to
+ * startActivityForResult(), allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ * @hide
+ */
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data);
+ }
+
+ /** @hide */
+ @Override
+ protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+ super.encodeProperties(encoder);
+
+ checkThread();
+ encoder.addProperty("webview:contentHeight", mProvider.getContentHeight());
+ encoder.addProperty("webview:contentWidth", mProvider.getContentWidth());
+ encoder.addProperty("webview:scale", mProvider.getScale());
+ encoder.addProperty("webview:title", mProvider.getTitle());
+ encoder.addProperty("webview:url", mProvider.getUrl());
+ encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl());
}
}
diff --git a/android/webkit/WebViewClient.java b/android/webkit/WebViewClient.java
index af7026d9..c5b64eb8 100644
--- a/android/webkit/WebViewClient.java
+++ b/android/webkit/WebViewClient.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Message;
@@ -167,6 +168,7 @@ public class WebViewClient {
* shouldInterceptRequest(WebView, WebResourceRequest)} instead.
*/
@Deprecated
+ @Nullable
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
return null;
@@ -191,6 +193,7 @@ public class WebViewClient {
* response information or {@code null} if the WebView should load the
* resource itself.
*/
+ @Nullable
public WebResourceResponse shouldInterceptRequest(WebView view,
WebResourceRequest request) {
return shouldInterceptRequest(view, request.getUrl().toString());
@@ -496,7 +499,7 @@ public class WebViewClient {
* @param args Authenticator specific arguments used to log in the user.
*/
public void onReceivedLoginRequest(WebView view, String realm,
- String account, String args) {
+ @Nullable String account, String args) {
}
/**
diff --git a/android/webkit/WebViewDatabase.java b/android/webkit/WebViewDatabase.java
index de75d5d0..f6166c58 100644
--- a/android/webkit/WebViewDatabase.java
+++ b/android/webkit/WebViewDatabase.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Nullable;
import android.content.Context;
/**
@@ -135,6 +136,7 @@ public abstract class WebViewDatabase {
* @see #hasHttpAuthUsernamePassword
* @see #clearHttpAuthUsernamePassword
*/
+ @Nullable
public abstract String[] getHttpAuthUsernamePassword(String host, String realm);
/**
diff --git a/android/webkit/WebViewFactory.java b/android/webkit/WebViewFactory.java
index 7c4154f5..95cb4549 100644
--- a/android/webkit/WebViewFactory.java
+++ b/android/webkit/WebViewFactory.java
@@ -51,9 +51,6 @@ public final class WebViewFactory {
private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
- private static final String NULL_WEBVIEW_FACTORY =
- "com.android.webview.nullwebview.NullWebViewFactoryProvider";
-
public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
"persist.sys.webview.vmsize";
@@ -66,6 +63,7 @@ public final class WebViewFactory {
private static WebViewFactoryProvider sProviderInstance;
private static final Object sProviderLock = new Object();
private static PackageInfo sPackageInfo;
+ private static Boolean sWebViewSupported;
// Error codes for loadWebViewNativeLibraryFromPackage
public static final int LIBLOAD_SUCCESS = 0;
@@ -105,6 +103,16 @@ public final class WebViewFactory {
public MissingWebViewPackageException(Exception e) { super(e); }
}
+ private static boolean isWebViewSupported() {
+ // No lock; this is a benign race as Boolean's state is final and the PackageManager call
+ // will always return the same value.
+ if (sWebViewSupported == null) {
+ sWebViewSupported = AppGlobals.getInitialApplication().getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
+ }
+ return sWebViewSupported;
+ }
+
/**
* @hide
*/
@@ -135,6 +143,10 @@ public final class WebViewFactory {
*/
public static int loadWebViewNativeLibraryFromPackage(String packageName,
ClassLoader clazzLoader) {
+ if (!isWebViewSupported()) {
+ return LIBLOAD_WRONG_PACKAGE_NAME;
+ }
+
WebViewProviderResponse response = null;
try {
response = getUpdateService().waitForAndGetProvider();
@@ -188,6 +200,11 @@ public final class WebViewFactory {
"For security reasons, WebView is not allowed in privileged processes");
}
+ if (!isWebViewSupported()) {
+ // Device doesn't support WebView; don't try to load it, just throw.
+ throw new UnsupportedOperationException();
+ }
+
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
@@ -410,15 +427,6 @@ public final class WebViewFactory {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (MissingWebViewPackageException e) {
- // If the package doesn't exist, then try loading the null WebView instead.
- // If that succeeds, then this is a device without WebView support; if it fails then
- // swallow the failure, complain that the real WebView is missing and rethrow the
- // original exception.
- try {
- return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
- } catch (ClassNotFoundException e2) {
- // Ignore.
- }
Log.e(LOGTAG, "Chromium WebView package does not exist", e);
throw new AndroidRuntimeException(e);
}
@@ -446,13 +454,13 @@ public final class WebViewFactory {
// waiting on relro creation.
if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
- WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths);
+ WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths[0]);
numRelros++;
}
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
- WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths);
+ WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths[1]);
numRelros++;
}
return numRelros;
@@ -463,7 +471,7 @@ public final class WebViewFactory {
*/
public static int onWebViewProviderChanged(PackageInfo packageInfo) {
String[] nativeLibs = null;
- String originalSourceDir = packageInfo.applicationInfo.sourceDir;
+ ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
try {
fixupStubApplicationInfo(packageInfo.applicationInfo,
AppGlobals.getInitialApplication().getPackageManager());
@@ -474,7 +482,7 @@ public final class WebViewFactory {
Log.e(LOGTAG, "error preparing webview native library", t);
}
- WebViewZygote.onWebViewProviderChanged(packageInfo, originalSourceDir);
+ WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
return prepareWebViewInSystemServer(nativeLibs);
}
@@ -483,6 +491,15 @@ public final class WebViewFactory {
/** @hide */
public static IWebViewUpdateService getUpdateService() {
+ if (isWebViewSupported()) {
+ return getUpdateServiceUnchecked();
+ } else {
+ return null;
+ }
+ }
+
+ /** @hide */
+ static IWebViewUpdateService getUpdateServiceUnchecked() {
return IWebViewUpdateService.Stub.asInterface(
ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
}
diff --git a/android/webkit/WebViewLibraryLoader.java b/android/webkit/WebViewLibraryLoader.java
index 6f9e8ece..fa1a3907 100644
--- a/android/webkit/WebViewLibraryLoader.java
+++ b/android/webkit/WebViewLibraryLoader.java
@@ -62,25 +62,23 @@ class WebViewLibraryLoader {
boolean result = false;
boolean is64Bit = VMRuntime.getRuntime().is64Bit();
try {
- if (args.length != 2 || args[0] == null || args[1] == null) {
+ if (args.length != 1 || args[0] == null) {
Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
return;
}
- Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), "
- + " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
+ Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), lib: " + args[0]);
if (!sAddressSpaceReserved) {
Log.e(LOGTAG, "can't create relro file; address space not reserved");
return;
}
- result = nativeCreateRelroFile(args[0] /* path32 */,
- args[1] /* path64 */,
- CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
- CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
+ result = nativeCreateRelroFile(args[0] /* path */,
+ is64Bit ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
+ CHROMIUM_WEBVIEW_NATIVE_RELRO_32);
if (result && DEBUG) Log.v(LOGTAG, "created relro file");
} finally {
// We must do our best to always notify the update service, even if something fails.
try {
- WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
+ WebViewFactory.getUpdateServiceUnchecked().notifyRelroCreationCompleted();
} catch (RemoteException e) {
Log.e(LOGTAG, "error notifying update service", e);
}
@@ -96,7 +94,7 @@ class WebViewLibraryLoader {
/**
* Create a single relro file by invoking an isolated process that to do the actual work.
*/
- static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
+ static void createRelroFile(final boolean is64Bit, String nativeLibraryPath) {
final String abi =
is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
@@ -114,13 +112,12 @@ class WebViewLibraryLoader {
};
try {
- if (nativeLibraryPaths == null
- || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
+ if (nativeLibraryPath == null) {
throw new IllegalArgumentException(
"Native library paths to the WebView RelRo process must not be null!");
}
int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
- RelroFileCreator.class.getName(), nativeLibraryPaths,
+ RelroFileCreator.class.getName(), new String[] { nativeLibraryPath },
"WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
} catch (Throwable t) {
@@ -217,8 +214,9 @@ class WebViewLibraryLoader {
final String libraryFileName =
WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo);
- int result = nativeLoadWithRelroFile(libraryFileName, CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
- CHROMIUM_WEBVIEW_NATIVE_RELRO_64, clazzLoader);
+ String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
+ CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
+ int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
if (result != WebViewFactory.LIBLOAD_SUCCESS) {
Log.w(LOGTAG, "failed to load with relro file, proceeding without");
} else if (DEBUG) {
@@ -313,8 +311,6 @@ class WebViewLibraryLoader {
}
static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
- static native boolean nativeCreateRelroFile(String lib32, String lib64,
- String relro32, String relro64);
- static native int nativeLoadWithRelroFile(String lib, String relro32, String relro64,
- ClassLoader clazzLoader);
+ static native boolean nativeCreateRelroFile(String lib, String relro);
+ static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
}
diff --git a/android/webkit/WebViewUpdateService.java b/android/webkit/WebViewUpdateService.java
index 2f7d6854..629891cc 100644
--- a/android/webkit/WebViewUpdateService.java
+++ b/android/webkit/WebViewUpdateService.java
@@ -31,8 +31,12 @@ public final class WebViewUpdateService {
* Fetch all packages that could potentially implement WebView.
*/
public static WebViewProviderInfo[] getAllWebViewPackages() {
+ IWebViewUpdateService service = getUpdateService();
+ if (service == null) {
+ return new WebViewProviderInfo[0];
+ }
try {
- return getUpdateService().getAllWebViewPackages();
+ return service.getAllWebViewPackages();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -42,8 +46,12 @@ public final class WebViewUpdateService {
* Fetch all packages that could potentially implement WebView and are currently valid.
*/
public static WebViewProviderInfo[] getValidWebViewPackages() {
+ IWebViewUpdateService service = getUpdateService();
+ if (service == null) {
+ return new WebViewProviderInfo[0];
+ }
try {
- return getUpdateService().getValidWebViewPackages();
+ return service.getValidWebViewPackages();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -53,8 +61,12 @@ public final class WebViewUpdateService {
* Used by DevelopmentSetting to get the name of the WebView provider currently in use.
*/
public static String getCurrentWebViewPackageName() {
+ IWebViewUpdateService service = getUpdateService();
+ if (service == null) {
+ return null;
+ }
try {
- return getUpdateService().getCurrentWebViewPackageName();
+ return service.getCurrentWebViewPackageName();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/android/webkit/WebViewZygote.java b/android/webkit/WebViewZygote.java
index 6e65c7a1..db60ad8d 100644
--- a/android/webkit/WebViewZygote.java
+++ b/android/webkit/WebViewZygote.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.app.LoadedApk;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.SystemService;
@@ -67,11 +68,11 @@ public class WebViewZygote {
private static PackageInfo sPackage;
/**
- * Cache key for the selected WebView package's classloader. This is set from
+ * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from
* #onWebViewProviderChanged().
*/
@GuardedBy("sLock")
- private static String sPackageCacheKey;
+ private static ApplicationInfo sPackageOriginalAppInfo;
/**
* Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
@@ -125,10 +126,11 @@ public class WebViewZygote {
}
}
- public static void onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey) {
+ public static void onWebViewProviderChanged(PackageInfo packageInfo,
+ ApplicationInfo originalAppInfo) {
synchronized (sLock) {
sPackage = packageInfo;
- sPackageCacheKey = cacheKey;
+ sPackageOriginalAppInfo = originalAppInfo;
// If multi-process is not enabled, then do not start the zygote service.
if (!sMultiprocessEnabled) {
@@ -217,10 +219,17 @@ public class WebViewZygote {
final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
TextUtils.join(File.pathSeparator, zipPaths);
+ // In the case where the ApplicationInfo has been modified by the stub WebView,
+ // we need to use the original ApplicationInfo to determine what the original classpath
+ // would have been to use as a cache key.
+ LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
+ final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
+ TextUtils.join(File.pathSeparator, zipPaths);
+
ZygoteProcess.waitForConnectionToZygote(WEBVIEW_ZYGOTE_SOCKET);
Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
- sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey,
+ sZygote.preloadPackageForAbi(zip, librarySearchPath, cacheKey,
Build.SUPPORTED_ABIS[0]);
} catch (Exception e) {
Log.e(LOGTAG, "Error connecting to " + serviceName, e);
diff --git a/android/widget/EditTextBackspacePerfTest.java b/android/widget/EditTextBackspacePerfTest.java
index 40b56f4a..d219d3a2 100644
--- a/android/widget/EditTextBackspacePerfTest.java
+++ b/android/widget/EditTextBackspacePerfTest.java
@@ -16,31 +16,26 @@
package android.widget;
-import android.app.Activity;
-import android.os.Bundle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/android/widget/EditTextCursorMovementPerfTest.java b/android/widget/EditTextCursorMovementPerfTest.java
index b100acba..b6cf7d3f 100644
--- a/android/widget/EditTextCursorMovementPerfTest.java
+++ b/android/widget/EditTextCursorMovementPerfTest.java
@@ -16,31 +16,26 @@
package android.widget;
-import android.app.Activity;
-import android.os.Bundle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
@LargeTest
@RunWith(Parameterized.class)
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index 0f617242..afd11881 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -119,6 +119,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
+import com.android.internal.widget.Magnifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -138,6 +139,9 @@ import java.util.List;
public class Editor {
private static final String TAG = "Editor";
private static final boolean DEBUG_UNDO = false;
+ // Specifies whether to use or not the magnifier when pressing the insertion or selection
+ // handles.
+ private static final boolean FLAG_USE_MAGNIFIER = true;
static final int BLINK = 500;
private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
@@ -161,6 +165,17 @@ public class Editor {
private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
+ private static final float MAGNIFIER_ZOOM = 1.5f;
+ @IntDef({MagnifierHandleTrigger.SELECTION_START,
+ MagnifierHandleTrigger.SELECTION_END,
+ MagnifierHandleTrigger.INSERTION})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface MagnifierHandleTrigger {
+ int INSERTION = 0;
+ int SELECTION_START = 1;
+ int SELECTION_END = 2;
+ }
+
// Each Editor manages its own undo stack.
private final UndoManager mUndoManager = new UndoManager();
private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
@@ -179,6 +194,8 @@ public class Editor {
private final boolean mHapticTextHandleEnabled;
+ private final Magnifier mMagnifier;
+
// Used to highlight a word when it is corrected by the IME
private CorrectionHighlighter mCorrectionHighlighter;
@@ -250,7 +267,7 @@ public class Editor {
SuggestionRangeSpan mSuggestionRangeSpan;
private Runnable mShowSuggestionRunnable;
- Drawable mCursorDrawable = null;
+ Drawable mDrawableForCursor = null;
private Drawable mSelectHandleLeft;
private Drawable mSelectHandleRight;
@@ -325,6 +342,8 @@ public class Editor {
mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this);
mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableHapticTextHandle);
+
+ mMagnifier = FLAG_USE_MAGNIFIER ? new Magnifier(mTextView) : null;
}
ParcelableParcel saveInstanceState() {
@@ -1678,7 +1697,7 @@ public class Editor {
mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
}
- if (highlight != null && selectionStart == selectionEnd && mCursorDrawable != null) {
+ if (highlight != null && selectionStart == selectionEnd && mDrawableForCursor != null) {
drawCursor(canvas, cursorOffsetVertical);
// Rely on the drawable entirely, do not draw the cursor line.
// Has to be done after the IMM related code above which relies on the highlight.
@@ -1873,8 +1892,8 @@ public class Editor {
private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
final boolean translate = cursorOffsetVertical != 0;
if (translate) canvas.translate(0, cursorOffsetVertical);
- if (mCursorDrawable != null) {
- mCursorDrawable.draw(canvas);
+ if (mDrawableForCursor != null) {
+ mDrawableForCursor.draw(canvas);
}
if (translate) canvas.translate(0, -cursorOffsetVertical);
}
@@ -1933,7 +1952,7 @@ public class Editor {
void updateCursorPosition() {
if (mTextView.mCursorDrawableRes == 0) {
- mCursorDrawable = null;
+ mDrawableForCursor = null;
return;
}
@@ -2314,17 +2333,17 @@ public class Editor {
@VisibleForTesting
@Nullable
public Drawable getCursorDrawable() {
- return mCursorDrawable;
+ return mDrawableForCursor;
}
private void updateCursorPosition(int top, int bottom, float horizontal) {
- if (mCursorDrawable == null) {
- mCursorDrawable = mTextView.getContext().getDrawable(
+ if (mDrawableForCursor == null) {
+ mDrawableForCursor = mTextView.getContext().getDrawable(
mTextView.mCursorDrawableRes);
}
- final int left = clampHorizontalPosition(mCursorDrawable, horizontal);
- final int width = mCursorDrawable.getIntrinsicWidth();
- mCursorDrawable.setBounds(left, top - mTempRect.top, left + width,
+ final int left = clampHorizontalPosition(mDrawableForCursor, horizontal);
+ final int width = mDrawableForCursor.getIntrinsicWidth();
+ mDrawableForCursor.setBounds(left, top - mTempRect.top, left + width,
bottom + mTempRect.bottom);
}
@@ -4353,6 +4372,9 @@ public class Editor {
protected abstract void updatePosition(float x, float y, boolean fromTouchScreen);
+ @MagnifierHandleTrigger
+ protected abstract int getMagnifierHandleTrigger();
+
protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
return layout.isRtlCharAt(offset);
}
@@ -4490,6 +4512,53 @@ public class Editor {
return 0;
}
+ protected final void showMagnifier() {
+ if (mMagnifier == null) {
+ return;
+ }
+
+ final int trigger = getMagnifierHandleTrigger();
+ final int offset;
+ switch (trigger) {
+ case MagnifierHandleTrigger.INSERTION: // Fall through.
+ case MagnifierHandleTrigger.SELECTION_START:
+ offset = mTextView.getSelectionStart();
+ break;
+ case MagnifierHandleTrigger.SELECTION_END:
+ offset = mTextView.getSelectionEnd();
+ break;
+ default:
+ offset = -1;
+ break;
+ }
+
+ if (offset == -1) {
+ dismissMagnifier();
+ }
+
+ final Layout layout = mTextView.getLayout();
+ final int lineNumber = layout.getLineForOffset(offset);
+ // Horizontally snap to character offset.
+ final float xPosInView = getHorizontal(mTextView.getLayout(), offset);
+ // Vertically snap to middle of current line.
+ final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
+ + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f;
+ final int[] coordinatesOnScreen = new int[2];
+ mTextView.getLocationOnScreen(coordinatesOnScreen);
+ final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft()
+ - mTextView.getScrollX() + coordinatesOnScreen[0];
+ final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop()
+ - mTextView.getScrollY() + coordinatesOnScreen[1];
+
+ mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
+ }
+
+ protected final void dismissMagnifier() {
+ if (mMagnifier != null) {
+ mMagnifier.dismiss();
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
updateFloatingToolbarVisibility(ev);
@@ -4542,10 +4611,7 @@ public class Editor {
case MotionEvent.ACTION_UP:
filterOnTouchUp(ev.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
- mIsDragging = false;
- updateDrawable();
- break;
-
+ // Fall through.
case MotionEvent.ACTION_CANCEL:
mIsDragging = false;
updateDrawable();
@@ -4646,9 +4712,9 @@ public class Editor {
@Override
protected int getCursorOffset() {
int offset = super.getCursorOffset();
- if (mCursorDrawable != null) {
- mCursorDrawable.getPadding(mTempRect);
- offset += (mCursorDrawable.getIntrinsicWidth()
+ if (mDrawableForCursor != null) {
+ mDrawableForCursor.getPadding(mTempRect);
+ offset += (mDrawableForCursor.getIntrinsicWidth()
- mTempRect.left - mTempRect.right) / 2;
}
return offset;
@@ -4656,9 +4722,9 @@ public class Editor {
@Override
int getCursorHorizontalPosition(Layout layout, int offset) {
- if (mCursorDrawable != null) {
+ if (mDrawableForCursor != null) {
final float horizontal = getHorizontal(layout, offset);
- return clampHorizontalPosition(mCursorDrawable, horizontal) + mTempRect.left;
+ return clampHorizontalPosition(mDrawableForCursor, horizontal) + mTempRect.left;
}
return super.getCursorHorizontalPosition(layout, offset);
}
@@ -4671,6 +4737,11 @@ public class Editor {
case MotionEvent.ACTION_DOWN:
mDownPositionX = ev.getRawX();
mDownPositionY = ev.getRawY();
+ showMagnifier();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ showMagnifier();
break;
case MotionEvent.ACTION_UP:
@@ -4696,11 +4767,10 @@ public class Editor {
mTextActionMode.invalidateContentRect();
}
}
- hideAfterDelay();
- break;
-
+ // Fall through.
case MotionEvent.ACTION_CANCEL:
hideAfterDelay();
+ dismissMagnifier();
break;
default:
@@ -4751,6 +4821,12 @@ public class Editor {
super.onDetached();
removeHiderCallback();
}
+
+ @Override
+ @MagnifierHandleTrigger
+ protected int getMagnifierHandleTrigger() {
+ return MagnifierHandleTrigger.INSERTION;
+ }
}
@Retention(RetentionPolicy.SOURCE)
@@ -5009,12 +5085,26 @@ public class Editor {
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean superResult = super.onTouchEvent(event);
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Reset the touch word offset and x value when the user
- // re-engages the handle.
- mTouchWordDelta = 0.0f;
- mPrevX = UNSET_X_VALUE;
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ // Reset the touch word offset and x value when the user
+ // re-engages the handle.
+ mTouchWordDelta = 0.0f;
+ mPrevX = UNSET_X_VALUE;
+ showMagnifier();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ showMagnifier();
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ dismissMagnifier();
+ break;
}
+
return superResult;
}
@@ -5110,6 +5200,13 @@ public class Editor {
return isRtlChar == isRtlParagraph ? primaryOffset : secondaryOffset;
}
}
+
+ @MagnifierHandleTrigger
+ protected int getMagnifierHandleTrigger() {
+ return isStartHandle()
+ ? MagnifierHandleTrigger.SELECTION_START
+ : MagnifierHandleTrigger.SELECTION_END;
+ }
}
private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java
index bf25915d..23ebadb3 100644
--- a/android/widget/PopupWindow.java
+++ b/android/widget/PopupWindow.java
@@ -2296,8 +2296,8 @@ public class PopupWindow {
}
/** @hide */
- protected final void detachFromAnchor() {
- final View anchor = mAnchor != null ? mAnchor.get() : null;
+ protected void detachFromAnchor() {
+ final View anchor = getAnchor();
if (anchor != null) {
final ViewTreeObserver vto = anchor.getViewTreeObserver();
vto.removeOnScrollChangedListener(mOnScrollChangedListener);
@@ -2316,7 +2316,7 @@ public class PopupWindow {
}
/** @hide */
- protected final void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+ protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
detachFromAnchor();
final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2339,6 +2339,11 @@ public class PopupWindow {
mAnchoredGravity = gravity;
}
+ /** @hide */
+ protected @Nullable View getAnchor() {
+ return mAnchor != null ? mAnchor.get() : null;
+ }
+
private void alignToAnchor() {
final View anchor = mAnchor != null ? mAnchor.get() : null;
if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index bc85fadb..1b26f8e2 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -16,9 +16,11 @@
package android.widget;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.annotation.ColorInt;
import android.annotation.DimenRes;
-import android.app.ActivityManager.StackId;
+import android.annotation.NonNull;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
@@ -107,9 +109,9 @@ public class RemoteViews implements Parcelable, Filter {
// The unique identifiers for each custom {@link Action}.
private static final int SET_ON_CLICK_PENDING_INTENT_TAG = 1;
private static final int REFLECTION_ACTION_TAG = 2;
- private static final int SET_DRAWABLE_PARAMETERS_TAG = 3;
+ private static final int SET_DRAWABLE_TINT_TAG = 3;
private static final int VIEW_GROUP_ACTION_ADD_TAG = 4;
- private static final int SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG = 5;
+ private static final int VIEW_CONTENT_NAVIGATION_TAG = 5;
private static final int SET_EMPTY_VIEW_ACTION_TAG = 6;
private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7;
private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8;
@@ -120,7 +122,6 @@ public class RemoteViews implements Parcelable, Filter {
private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13;
private static final int VIEW_PADDING_ACTION_TAG = 14;
private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15;
- private static final int TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG = 17;
private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18;
private static final int LAYOUT_PARAM_ACTION_TAG = 19;
private static final int OVERRIDE_TEXT_COLORS_TAG = 20;
@@ -324,11 +325,11 @@ public class RemoteViews implements Parcelable, Filter {
public boolean onClickHandler(View view, PendingIntent pendingIntent,
Intent fillInIntent) {
- return onClickHandler(view, pendingIntent, fillInIntent, StackId.INVALID_STACK_ID);
+ return onClickHandler(view, pendingIntent, fillInIntent, WINDOWING_MODE_UNDEFINED);
}
public boolean onClickHandler(View view, PendingIntent pendingIntent,
- Intent fillInIntent, int launchStackId) {
+ Intent fillInIntent, int windowingMode) {
try {
// TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
Context context = view.getContext();
@@ -339,8 +340,8 @@ public class RemoteViews implements Parcelable, Filter {
opts = ActivityOptions.makeBasic();
}
- if (launchStackId != StackId.INVALID_STACK_ID) {
- opts.setLaunchStackId(launchStackId);
+ if (windowingMode != WINDOWING_MODE_UNDEFINED) {
+ opts.setLaunchWindowingMode(windowingMode);
}
context.startIntentSender(
pendingIntent.getIntentSender(), fillInIntent,
@@ -388,10 +389,10 @@ public class RemoteViews implements Parcelable, Filter {
return MERGE_REPLACE;
}
- public abstract String getActionName();
+ public abstract int getActionTag();
public String getUniqueKey() {
- return (getActionName() + viewId);
+ return (getActionTag() + "_" + viewId);
}
/**
@@ -423,8 +424,8 @@ public class RemoteViews implements Parcelable, Filter {
*/
private static abstract class RuntimeAction extends Action {
@Override
- public final String getActionName() {
- return "RuntimeAction";
+ public final int getActionTag() {
+ return 0;
}
@Override
@@ -513,7 +514,6 @@ public class RemoteViews implements Parcelable, Filter {
}
private class SetEmptyView extends Action {
- int viewId;
int emptyViewId;
SetEmptyView(int viewId, int emptyViewId) {
@@ -527,7 +527,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(SET_EMPTY_VIEW_ACTION_TAG);
out.writeInt(this.viewId);
out.writeInt(this.emptyViewId);
}
@@ -545,8 +544,9 @@ public class RemoteViews implements Parcelable, Filter {
adapterView.setEmptyView(emptyView);
}
- public String getActionName() {
- return "SetEmptyView";
+ @Override
+ public int getActionTag() {
+ return SET_EMPTY_VIEW_ACTION_TAG;
}
}
@@ -558,13 +558,12 @@ public class RemoteViews implements Parcelable, Filter {
public SetOnClickFillInIntent(Parcel parcel) {
viewId = parcel.readInt();
- fillInIntent = Intent.CREATOR.createFromParcel(parcel);
+ fillInIntent = parcel.readTypedObject(Intent.CREATOR);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_ON_CLICK_FILL_IN_INTENT_TAG);
dest.writeInt(viewId);
- fillInIntent.writeToParcel(dest, 0 /* no flags */);
+ dest.writeTypedObject(fillInIntent, 0 /* no flags */);
}
@Override
@@ -623,8 +622,9 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
- return "SetOnClickFillInIntent";
+ @Override
+ public int getActionTag() {
+ return SET_ON_CLICK_FILL_IN_INTENT_TAG;
}
Intent fillInIntent;
@@ -642,9 +642,8 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_PENDING_INTENT_TEMPLATE_TAG);
dest.writeInt(viewId);
- pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
+ PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest);
}
@Override
@@ -698,8 +697,9 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
- return "SetPendingIntentTemplate";
+ @Override
+ public int getActionTag() {
+ return SET_PENDING_INTENT_TEMPLATE_TAG;
}
PendingIntent pendingIntentTemplate;
@@ -715,30 +715,13 @@ public class RemoteViews implements Parcelable, Filter {
public SetRemoteViewsAdapterList(Parcel parcel) {
viewId = parcel.readInt();
viewTypeCount = parcel.readInt();
- int count = parcel.readInt();
- list = new ArrayList<RemoteViews>();
-
- for (int i = 0; i < count; i++) {
- RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
- list.add(rv);
- }
+ list = parcel.createTypedArrayList(RemoteViews.CREATOR);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_REMOTE_VIEW_ADAPTER_LIST_TAG);
dest.writeInt(viewId);
dest.writeInt(viewTypeCount);
-
- if (list == null || list.size() == 0) {
- dest.writeInt(0);
- } else {
- int count = list.size();
- dest.writeInt(count);
- for (int i = 0; i < count; i++) {
- RemoteViews rv = list.get(i);
- rv.writeToParcel(dest, flags);
- }
- }
+ dest.writeTypedList(list, flags);
}
@Override
@@ -778,8 +761,9 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
- return "SetRemoteViewsAdapterList";
+ @Override
+ public int getActionTag() {
+ return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
}
int viewTypeCount;
@@ -794,13 +778,12 @@ public class RemoteViews implements Parcelable, Filter {
public SetRemoteViewsAdapterIntent(Parcel parcel) {
viewId = parcel.readInt();
- intent = Intent.CREATOR.createFromParcel(parcel);
+ intent = parcel.readTypedObject(Intent.CREATOR);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_REMOTE_VIEW_ADAPTER_INTENT_TAG);
dest.writeInt(viewId);
- intent.writeToParcel(dest, flags);
+ dest.writeTypedObject(intent, flags);
}
@Override
@@ -844,8 +827,9 @@ public class RemoteViews implements Parcelable, Filter {
return copy;
}
- public String getActionName() {
- return "SetRemoteViewsAdapterIntent";
+ @Override
+ public int getActionTag() {
+ return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
}
Intent intent;
@@ -865,22 +849,12 @@ public class RemoteViews implements Parcelable, Filter {
public SetOnClickPendingIntent(Parcel parcel) {
viewId = parcel.readInt();
-
- // We check a flag to determine if the parcel contains a PendingIntent.
- if (parcel.readInt() != 0) {
- pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
- }
+ pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_ON_CLICK_PENDING_INTENT_TAG);
dest.writeInt(viewId);
-
- // We use a flag to indicate whether the parcel contains a valid object.
- dest.writeInt(pendingIntent != null ? 1 : 0);
- if (pendingIntent != null) {
- pendingIntent.writeToParcel(dest, 0 /* no flags */);
- }
+ PendingIntent.writePendingIntentOrNullToParcel(pendingIntent, dest);
}
@Override
@@ -921,8 +895,9 @@ public class RemoteViews implements Parcelable, Filter {
target.setOnClickListener(listener);
}
- public String getActionName() {
- return "SetOnClickPendingIntent";
+ @Override
+ public int getActionTag() {
+ return SET_ON_CLICK_PENDING_INTENT_TAG;
}
PendingIntent pendingIntent;
@@ -1011,55 +986,37 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+ * Equivalent to calling
* {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
- * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
+ * on the {@link Drawable} of a given view.
* <p>
- * These operations will be performed on the {@link Drawable} returned by the
+ * The operation will be performed on the {@link Drawable} returned by the
* target {@link View#getBackground()} by default. If targetBackground is false,
* we assume the target is an {@link ImageView} and try applying the operations
* to {@link ImageView#getDrawable()}.
* <p>
- * You can omit specific calls by marking their values with null or -1.
*/
- private class SetDrawableParameters extends Action {
- public SetDrawableParameters(int id, boolean targetBackground, int alpha,
- int colorFilter, PorterDuff.Mode mode, int level) {
+ private class SetDrawableTint extends Action {
+ SetDrawableTint(int id, boolean targetBackground,
+ int colorFilter, @NonNull PorterDuff.Mode mode) {
this.viewId = id;
this.targetBackground = targetBackground;
- this.alpha = alpha;
this.colorFilter = colorFilter;
this.filterMode = mode;
- this.level = level;
}
- public SetDrawableParameters(Parcel parcel) {
+ SetDrawableTint(Parcel parcel) {
viewId = parcel.readInt();
targetBackground = parcel.readInt() != 0;
- alpha = parcel.readInt();
colorFilter = parcel.readInt();
- boolean hasMode = parcel.readInt() != 0;
- if (hasMode) {
- filterMode = PorterDuff.Mode.valueOf(parcel.readString());
- } else {
- filterMode = null;
- }
- level = parcel.readInt();
+ filterMode = PorterDuff.intToMode(parcel.readInt());
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_DRAWABLE_PARAMETERS_TAG);
dest.writeInt(viewId);
dest.writeInt(targetBackground ? 1 : 0);
- dest.writeInt(alpha);
dest.writeInt(colorFilter);
- if (filterMode != null) {
- dest.writeInt(1);
- dest.writeString(filterMode.toString());
- } else {
- dest.writeInt(0);
- }
- dest.writeInt(level);
+ dest.writeInt(PorterDuff.modeToInt(filterMode));
}
@Override
@@ -1077,47 +1034,36 @@ public class RemoteViews implements Parcelable, Filter {
}
if (targetDrawable != null) {
- // Perform modifications only if values are set correctly
- if (alpha != -1) {
- targetDrawable.mutate().setAlpha(alpha);
- }
- if (filterMode != null) {
- targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
- }
- if (level != -1) {
- targetDrawable.mutate().setLevel(level);
- }
+ targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
}
}
- public String getActionName() {
- return "SetDrawableParameters";
+ @Override
+ public int getActionTag() {
+ return SET_DRAWABLE_TINT_TAG;
}
boolean targetBackground;
- int alpha;
int colorFilter;
PorterDuff.Mode filterMode;
- int level;
}
- private final class ReflectionActionWithoutParams extends Action {
- final String methodName;
+ private final class ViewContentNavigation extends Action {
+ final boolean mNext;
- ReflectionActionWithoutParams(int viewId, String methodName) {
+ ViewContentNavigation(int viewId, boolean next) {
this.viewId = viewId;
- this.methodName = methodName;
+ this.mNext = next;
}
- ReflectionActionWithoutParams(Parcel in) {
+ ViewContentNavigation(Parcel in) {
this.viewId = in.readInt();
- this.methodName = in.readString();
+ this.mNext = in.readBoolean();
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG);
out.writeInt(this.viewId);
- out.writeString(this.methodName);
+ out.writeBoolean(this.mNext);
}
@Override
@@ -1126,23 +1072,20 @@ public class RemoteViews implements Parcelable, Filter {
if (view == null) return;
try {
- getMethod(view, this.methodName, null, false /* async */).invoke(view);
+ getMethod(view,
+ mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view);
} catch (Throwable ex) {
throw new ActionException(ex);
}
}
public int mergeBehavior() {
- // we don't need to build up showNext or showPrevious calls
- if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
- return MERGE_IGNORE;
- } else {
- return MERGE_REPLACE;
- }
+ return MERGE_IGNORE;
}
- public String getActionName() {
- return "ReflectionActionWithoutParams";
+ @Override
+ public int getActionTag() {
+ return VIEW_CONTENT_NAVIGATION_TAG;
}
}
@@ -1156,12 +1099,7 @@ public class RemoteViews implements Parcelable, Filter {
}
public BitmapCache(Parcel source) {
- int count = source.readInt();
- mBitmaps = new ArrayList<>(count);
- for (int i = 0; i < count; i++) {
- Bitmap b = Bitmap.CREATOR.createFromParcel(source);
- mBitmaps.add(b);
- }
+ mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
}
public int getBitmapId(Bitmap b) {
@@ -1187,11 +1125,7 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeBitmapsToParcel(Parcel dest, int flags) {
- int count = mBitmaps.size();
- dest.writeInt(count);
- for (int i = 0; i < count; i++) {
- mBitmaps.get(i).writeToParcel(dest, flags);
- }
+ dest.writeTypedList(mBitmaps, flags);
}
public int getBitmapMemory() {
@@ -1227,7 +1161,6 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(BITMAP_REFLECTION_ACTION_TAG);
dest.writeInt(viewId);
dest.writeString(methodName);
dest.writeInt(bitmapId);
@@ -1246,8 +1179,9 @@ public class RemoteViews implements Parcelable, Filter {
bitmapId = bitmapCache.getBitmapId(bitmap);
}
- public String getActionName() {
- return "BitmapReflectionAction";
+ @Override
+ public int getActionTag() {
+ return BITMAP_REFLECTION_ACTION_TAG;
}
}
@@ -1299,7 +1233,7 @@ public class RemoteViews implements Parcelable, Filter {
// written to the parcel.
switch (this.type) {
case BOOLEAN:
- this.value = in.readInt() != 0;
+ this.value = in.readBoolean();
break;
case BYTE:
this.value = in.readByte();
@@ -1329,39 +1263,28 @@ public class RemoteViews implements Parcelable, Filter {
this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
break;
case URI:
- if (in.readInt() != 0) {
- this.value = Uri.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(Uri.CREATOR);
break;
case BITMAP:
- if (in.readInt() != 0) {
- this.value = Bitmap.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(Bitmap.CREATOR);
break;
case BUNDLE:
this.value = in.readBundle();
break;
case INTENT:
- if (in.readInt() != 0) {
- this.value = Intent.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(Intent.CREATOR);
break;
case COLOR_STATE_LIST:
- if (in.readInt() != 0) {
- this.value = ColorStateList.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(ColorStateList.CREATOR);
break;
case ICON:
- if (in.readInt() != 0) {
- this.value = Icon.CREATOR.createFromParcel(in);
- }
+ this.value = in.readTypedObject(Icon.CREATOR);
default:
break;
}
}
public void writeToParcel(Parcel out, int flags) {
- out.writeInt(REFLECTION_ACTION_TAG);
out.writeInt(this.viewId);
out.writeString(this.methodName);
out.writeInt(this.type);
@@ -1375,7 +1298,7 @@ public class RemoteViews implements Parcelable, Filter {
// we have written a valid value to the parcel.
switch (this.type) {
case BOOLEAN:
- out.writeInt((Boolean) this.value ? 1 : 0);
+ out.writeBoolean((Boolean) this.value);
break;
case BYTE:
out.writeByte((Byte) this.value);
@@ -1412,10 +1335,7 @@ public class RemoteViews implements Parcelable, Filter {
case INTENT:
case COLOR_STATE_LIST:
case ICON:
- out.writeInt(this.value != null ? 1 : 0);
- if (this.value != null) {
- ((Parcelable) this.value).writeToParcel(out, flags);
- }
+ out.writeTypedObject((Parcelable) this.value, flags);
break;
default:
break;
@@ -1521,10 +1441,16 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
+ @Override
+ public int getActionTag() {
+ return REFLECTION_ACTION_TAG;
+ }
+
+ @Override
+ public String getUniqueKey() {
// Each type of reflection action corresponds to a setter, so each should be seen as
// unique from the standpoint of merging.
- return "ReflectionAction" + this.methodName + this.type;
+ return super.getUniqueKey() + this.methodName + this.type;
}
@Override
@@ -1586,7 +1512,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(VIEW_GROUP_ACTION_ADD_TAG);
dest.writeInt(viewId);
dest.writeInt(mIndex);
mNestedViews.writeToParcel(dest, flags);
@@ -1661,10 +1586,9 @@ public class RemoteViews implements Parcelable, Filter {
return mNestedViews.prefersAsyncApply();
}
-
@Override
- public String getActionName() {
- return "ViewGroupActionAdd";
+ public int getActionTag() {
+ return VIEW_GROUP_ACTION_ADD_TAG;
}
}
@@ -1696,7 +1620,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(VIEW_GROUP_ACTION_REMOVE_TAG);
dest.writeInt(viewId);
dest.writeInt(mViewIdToKeep);
}
@@ -1762,8 +1685,8 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public String getActionName() {
- return "ViewGroupActionRemove";
+ public int getActionTag() {
+ return VIEW_GROUP_ACTION_REMOVE_TAG;
}
@Override
@@ -1803,18 +1726,10 @@ public class RemoteViews implements Parcelable, Filter {
isRelative = (parcel.readInt() != 0);
useIcons = (parcel.readInt() != 0);
if (useIcons) {
- if (parcel.readInt() != 0) {
- i1 = Icon.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- i2 = Icon.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- i3 = Icon.CREATOR.createFromParcel(parcel);
- }
- if (parcel.readInt() != 0) {
- i4 = Icon.CREATOR.createFromParcel(parcel);
- }
+ i1 = parcel.readTypedObject(Icon.CREATOR);
+ i2 = parcel.readTypedObject(Icon.CREATOR);
+ i3 = parcel.readTypedObject(Icon.CREATOR);
+ i4 = parcel.readTypedObject(Icon.CREATOR);
} else {
d1 = parcel.readInt();
d2 = parcel.readInt();
@@ -1824,35 +1739,14 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TEXT_VIEW_DRAWABLE_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(isRelative ? 1 : 0);
dest.writeInt(useIcons ? 1 : 0);
if (useIcons) {
- if (i1 != null) {
- dest.writeInt(1);
- i1.writeToParcel(dest, 0);
- } else {
- dest.writeInt(0);
- }
- if (i2 != null) {
- dest.writeInt(1);
- i2.writeToParcel(dest, 0);
- } else {
- dest.writeInt(0);
- }
- if (i3 != null) {
- dest.writeInt(1);
- i3.writeToParcel(dest, 0);
- } else {
- dest.writeInt(0);
- }
- if (i4 != null) {
- dest.writeInt(1);
- i4.writeToParcel(dest, 0);
- } else {
- dest.writeInt(0);
- }
+ dest.writeTypedObject(i1, 0);
+ dest.writeTypedObject(i2, 0);
+ dest.writeTypedObject(i3, 0);
+ dest.writeTypedObject(i4, 0);
} else {
dest.writeInt(d1);
dest.writeInt(d2);
@@ -1923,8 +1817,9 @@ public class RemoteViews implements Parcelable, Filter {
return useIcons;
}
- public String getActionName() {
- return "TextViewDrawableAction";
+ @Override
+ public int getActionTag() {
+ return TEXT_VIEW_DRAWABLE_ACTION_TAG;
}
boolean isRelative = false;
@@ -1953,7 +1848,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TEXT_VIEW_SIZE_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(units);
dest.writeFloat(size);
@@ -1966,8 +1860,9 @@ public class RemoteViews implements Parcelable, Filter {
target.setTextSize(units, size);
}
- public String getActionName() {
- return "TextViewSizeAction";
+ @Override
+ public int getActionTag() {
+ return TEXT_VIEW_SIZE_ACTION_TAG;
}
int units;
@@ -1995,7 +1890,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(VIEW_PADDING_ACTION_TAG);
dest.writeInt(viewId);
dest.writeInt(left);
dest.writeInt(top);
@@ -2010,8 +1904,9 @@ public class RemoteViews implements Parcelable, Filter {
target.setPadding(left, top, right, bottom);
}
- public String getActionName() {
- return "ViewPaddingAction";
+ @Override
+ public int getActionTag() {
+ return VIEW_PADDING_ACTION_TAG;
}
int left, top, right, bottom;
@@ -2028,6 +1923,9 @@ public class RemoteViews implements Parcelable, Filter {
public static final int LAYOUT_WIDTH = 2;
public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3;
+ final int mProperty;
+ final int mValue;
+
/**
* @param viewId ID of the view alter
* @param property which layout parameter to alter
@@ -2035,21 +1933,20 @@ public class RemoteViews implements Parcelable, Filter {
*/
public LayoutParamAction(int viewId, int property, int value) {
this.viewId = viewId;
- this.property = property;
- this.value = value;
+ this.mProperty = property;
+ this.mValue = value;
}
public LayoutParamAction(Parcel parcel) {
viewId = parcel.readInt();
- property = parcel.readInt();
- value = parcel.readInt();
+ mProperty = parcel.readInt();
+ mValue = parcel.readInt();
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(LAYOUT_PARAM_ACTION_TAG);
dest.writeInt(viewId);
- dest.writeInt(property);
- dest.writeInt(value);
+ dest.writeInt(mProperty);
+ dest.writeInt(mValue);
}
@Override
@@ -2062,27 +1959,27 @@ public class RemoteViews implements Parcelable, Filter {
if (layoutParams == null) {
return;
}
- switch (property) {
+ switch (mProperty) {
case LAYOUT_MARGIN_END_DIMEN:
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
- int resolved = resolveDimenPixelOffset(target, value);
+ int resolved = resolveDimenPixelOffset(target, mValue);
((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(resolved);
target.setLayoutParams(layoutParams);
}
break;
case LAYOUT_MARGIN_BOTTOM_DIMEN:
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
- int resolved = resolveDimenPixelOffset(target, value);
+ int resolved = resolveDimenPixelOffset(target, mValue);
((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved;
target.setLayoutParams(layoutParams);
}
break;
case LAYOUT_WIDTH:
- layoutParams.width = value;
+ layoutParams.width = mValue;
target.setLayoutParams(layoutParams);
break;
default:
- throw new IllegalArgumentException("Unknown property " + property);
+ throw new IllegalArgumentException("Unknown property " + mProperty);
}
}
@@ -2093,79 +1990,15 @@ public class RemoteViews implements Parcelable, Filter {
return target.getContext().getResources().getDimensionPixelOffset(value);
}
- public String getActionName() {
- return "LayoutParamAction" + property + ".";
- }
-
- int property;
- int value;
- }
-
- /**
- * Helper action to set a color filter on a compound drawable on a TextView. Supports relative
- * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
- */
- private class TextViewDrawableColorFilterAction extends Action {
- public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index,
- int color, PorterDuff.Mode mode) {
- this.viewId = viewId;
- this.isRelative = isRelative;
- this.index = index;
- this.color = color;
- this.mode = mode;
- }
-
- public TextViewDrawableColorFilterAction(Parcel parcel) {
- viewId = parcel.readInt();
- isRelative = (parcel.readInt() != 0);
- index = parcel.readInt();
- color = parcel.readInt();
- mode = readPorterDuffMode(parcel);
- }
-
- private PorterDuff.Mode readPorterDuffMode(Parcel parcel) {
- int mode = parcel.readInt();
- if (mode >= 0 && mode < PorterDuff.Mode.values().length) {
- return PorterDuff.Mode.values()[mode];
- } else {
- return PorterDuff.Mode.CLEAR;
- }
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG);
- dest.writeInt(viewId);
- dest.writeInt(isRelative ? 1 : 0);
- dest.writeInt(index);
- dest.writeInt(color);
- dest.writeInt(mode.ordinal());
- }
-
@Override
- public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
- final TextView target = root.findViewById(viewId);
- if (target == null) return;
- Drawable[] drawables = isRelative
- ? target.getCompoundDrawablesRelative()
- : target.getCompoundDrawables();
- if (index < 0 || index >= 4) {
- throw new IllegalStateException("index must be in range [0, 3].");
- }
- Drawable d = drawables[index];
- if (d != null) {
- d.mutate();
- d.setColorFilter(color, mode);
- }
+ public int getActionTag() {
+ return LAYOUT_PARAM_ACTION_TAG;
}
- public String getActionName() {
- return "TextViewDrawableColorFilterAction";
+ @Override
+ public String getUniqueKey() {
+ return super.getUniqueKey() + mProperty;
}
-
- final boolean isRelative;
- final int index;
- final int color;
- final PorterDuff.Mode mode;
}
/**
@@ -2184,7 +2017,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(SET_REMOTE_INPUTS_ACTION_TAG);
dest.writeInt(viewId);
dest.writeTypedArray(remoteInputs, flags);
}
@@ -2197,8 +2029,9 @@ public class RemoteViews implements Parcelable, Filter {
target.setTagInternal(R.id.remote_input_tag, remoteInputs);
}
- public String getActionName() {
- return "SetRemoteInputsAction";
+ @Override
+ public int getActionTag() {
+ return SET_REMOTE_INPUTS_ACTION_TAG;
}
final Parcelable[] remoteInputs;
@@ -2220,7 +2053,6 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(OVERRIDE_TEXT_COLORS_TAG);
dest.writeInt(textColor);
}
@@ -2245,8 +2077,9 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- public String getActionName() {
- return "OverrideTextColorsAction";
+ @Override
+ public int getActionTag() {
+ return OVERRIDE_TEXT_COLORS_TAG;
}
}
@@ -2338,20 +2171,12 @@ public class RemoteViews implements Parcelable, Filter {
}
if (src.mActions != null) {
- mActions = new ArrayList<>();
-
Parcel p = Parcel.obtain();
- int count = src.mActions.size();
- for (int i = 0; i < count; i++) {
- p.setDataPosition(0);
- Action a = src.mActions.get(i);
- a.writeToParcel(
- p, a.hasSameAppInfo(mApplication) ? PARCELABLE_ELIDE_DUPLICATES : 0);
- p.setDataPosition(0);
- // Since src is already in memory, we do not care about stack overflow as it has
- // already been read once.
- mActions.add(getActionFromParcel(p, 0));
- }
+ src.writeActionsToParcel(p);
+ p.setDataPosition(0);
+ // Since src is already in memory, we do not care about stack overflow as it has
+ // already been read once.
+ readActionsFromParcel(p, 0);
p.recycle();
}
@@ -2392,13 +2217,7 @@ public class RemoteViews implements Parcelable, Filter {
mLayoutId = parcel.readInt();
mIsWidgetCollectionChild = parcel.readInt() == 1;
- int count = parcel.readInt();
- if (count > 0) {
- mActions = new ArrayList<>(count);
- for (int i = 0; i < count; i++) {
- mActions.add(getActionFromParcel(parcel, depth));
- }
- }
+ readActionsFromParcel(parcel, depth);
} else {
// MODE_HAS_LANDSCAPE_AND_PORTRAIT
mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth);
@@ -2409,21 +2228,31 @@ public class RemoteViews implements Parcelable, Filter {
mReapplyDisallowed = parcel.readInt() == 0;
}
+ private void readActionsFromParcel(Parcel parcel, int depth) {
+ int count = parcel.readInt();
+ if (count > 0) {
+ mActions = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ mActions.add(getActionFromParcel(parcel, depth));
+ }
+ }
+ }
+
private Action getActionFromParcel(Parcel parcel, int depth) {
int tag = parcel.readInt();
switch (tag) {
case SET_ON_CLICK_PENDING_INTENT_TAG:
return new SetOnClickPendingIntent(parcel);
- case SET_DRAWABLE_PARAMETERS_TAG:
- return new SetDrawableParameters(parcel);
+ case SET_DRAWABLE_TINT_TAG:
+ return new SetDrawableTint(parcel);
case REFLECTION_ACTION_TAG:
return new ReflectionAction(parcel);
case VIEW_GROUP_ACTION_ADD_TAG:
return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth);
case VIEW_GROUP_ACTION_REMOVE_TAG:
return new ViewGroupActionRemove(parcel);
- case SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG:
- return new ReflectionActionWithoutParams(parcel);
+ case VIEW_CONTENT_NAVIGATION_TAG:
+ return new ViewContentNavigation(parcel);
case SET_EMPTY_VIEW_ACTION_TAG:
return new SetEmptyView(parcel);
case SET_PENDING_INTENT_TEMPLATE_TAG:
@@ -2442,8 +2271,6 @@ public class RemoteViews implements Parcelable, Filter {
return new BitmapReflectionAction(parcel);
case SET_REMOTE_VIEW_ADAPTER_LIST_TAG:
return new SetRemoteViewsAdapterList(parcel);
- case TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG:
- return new TextViewDrawableColorFilterAction(parcel);
case SET_REMOTE_INPUTS_ACTION_TAG:
return new SetRemoteInputsAction(parcel);
case LAYOUT_PARAM_ACTION_TAG:
@@ -2600,7 +2427,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
*/
public void showNext(int viewId) {
- addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
+ addAction(new ViewContentNavigation(viewId, true /* next */));
}
/**
@@ -2609,7 +2436,7 @@ public class RemoteViews implements Parcelable, Filter {
* @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
*/
public void showPrevious(int viewId) {
- addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
+ addAction(new ViewContentNavigation(viewId, false /* next */));
}
/**
@@ -2683,28 +2510,6 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Equivalent to applying a color filter on one of the drawables in
- * {@link android.widget.TextView#getCompoundDrawablesRelative()}.
- *
- * @param viewId The id of the view whose text should change.
- * @param index The index of the drawable in the array of
- * {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color
- * filter on. Must be in [0, 3].
- * @param color The color of the color filter. See
- * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
- * @param mode The mode of the color filter. See
- * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
- * @hide
- */
- public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId,
- int index, int color, PorterDuff.Mode mode) {
- if (index < 0 || index >= 4) {
- throw new IllegalArgumentException("index must be in range [0, 3].");
- }
- addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode));
- }
-
- /**
* Equivalent to calling {@link
* TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
* using the drawables yielded by {@link Icon#loadDrawable(Context)}.
@@ -2901,12 +2706,10 @@ public class RemoteViews implements Parcelable, Filter {
/**
* @hide
- * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+ * Equivalent to calling
* {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
- * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
- * view.
+ * on the {@link Drawable} of a given view.
* <p>
- * You can omit specific calls by marking their values with null or -1.
*
* @param viewId The id of the view that contains the target
* {@link Drawable}
@@ -2915,20 +2718,15 @@ public class RemoteViews implements Parcelable, Filter {
* {@link android.view.View#getBackground()}. Otherwise, assume
* the target view is an {@link ImageView} and apply them to
* {@link ImageView#getDrawable()}.
- * @param alpha Specify an alpha value for the drawable, or -1 to leave
- * unchanged.
* @param colorFilter Specify a color for a
* {@link android.graphics.ColorFilter} for this drawable. This will be ignored if
* {@code mode} is {@code null}.
* @param mode Specify a PorterDuff mode for this drawable, or null to leave
* unchanged.
- * @param level Specify the level for the drawable, or -1 to leave
- * unchanged.
*/
- public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
- int colorFilter, PorterDuff.Mode mode, int level) {
- addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
- colorFilter, mode, level));
+ public void setDrawableTint(int viewId, boolean targetBackground,
+ int colorFilter, @NonNull PorterDuff.Mode mode) {
+ addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode));
}
/**
@@ -3695,18 +3493,7 @@ public class RemoteViews implements Parcelable, Filter {
}
dest.writeInt(mLayoutId);
dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
- int count;
- if (mActions != null) {
- count = mActions.size();
- } else {
- count = 0;
- }
- dest.writeInt(count);
- for (int i=0; i<count; i++) {
- Action a = mActions.get(i);
- a.writeToParcel(dest, a.hasSameAppInfo(mApplication)
- ? PARCELABLE_ELIDE_DUPLICATES : 0);
- }
+ writeActionsToParcel(dest);
} else {
dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
// We only write the bitmap cache if we are the root RemoteViews, as this cache
@@ -3721,6 +3508,22 @@ public class RemoteViews implements Parcelable, Filter {
dest.writeInt(mReapplyDisallowed ? 1 : 0);
}
+ private void writeActionsToParcel(Parcel parcel) {
+ int count;
+ if (mActions != null) {
+ count = mActions.size();
+ } else {
+ count = 0;
+ }
+ parcel.writeInt(count);
+ for (int i = 0; i < count; i++) {
+ Action a = mActions.get(i);
+ parcel.writeInt(a.getActionTag());
+ a.writeToParcel(parcel, a.hasSameAppInfo(mApplication)
+ ? PARCELABLE_ELIDE_DUPLICATES : 0);
+ }
+ }
+
private static ApplicationInfo getApplicationInfo(String packageName, int userId) {
if (packageName == null) {
return null;
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 36dc3308..3be42a5b 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -43,9 +43,11 @@ import com.android.internal.util.Preconditions;
import java.text.BreakIterator;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
@@ -58,11 +60,7 @@ import java.util.regex.Pattern;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class SelectionActionModeHelper {
- /**
- * Maximum time (in milliseconds) to wait for a result before timing out.
- */
- // TODO: Consider making this a ViewConfiguration.
- private static final int TIMEOUT_DURATION = 200;
+ private static final String LOG_TAG = "SelectActionModeHelper";
private static final boolean SMART_SELECT_ANIMATION_ENABLED = true;
@@ -83,7 +81,8 @@ public final class SelectionActionModeHelper {
mEditor = Preconditions.checkNotNull(editor);
mTextView = mEditor.getTextView();
mTextClassificationHelper = new TextClassificationHelper(
- mTextView.getTextClassifier(), mTextView.getText(),
+ mTextView.getTextClassifier(),
+ getText(mTextView),
0, 1, mTextView.getTextLocales());
mSelectionTracker = new SelectionTracker(mTextView);
@@ -97,7 +96,7 @@ public final class SelectionActionModeHelper {
public void startActionModeAsync(boolean adjustSelection) {
mSelectionTracker.onOriginalSelection(
- mTextView.getText(),
+ getText(mTextView),
mTextView.getSelectionStart(),
mTextView.getSelectionEnd(),
mTextView.isTextEditable());
@@ -108,7 +107,7 @@ public final class SelectionActionModeHelper {
resetTextClassificationHelper();
mTextClassificationAsyncTask = new TextClassificationAsyncTask(
mTextView,
- TIMEOUT_DURATION,
+ mTextClassificationHelper.getTimeoutDuration(),
adjustSelection
? mTextClassificationHelper::suggestSelection
: mTextClassificationHelper::classifyText,
@@ -127,7 +126,7 @@ public final class SelectionActionModeHelper {
resetTextClassificationHelper();
mTextClassificationAsyncTask = new TextClassificationAsyncTask(
mTextView,
- TIMEOUT_DURATION,
+ mTextClassificationHelper.getTimeoutDuration(),
mTextClassificationHelper::classifyText,
this::invalidateActionMode)
.execute();
@@ -195,7 +194,7 @@ public final class SelectionActionModeHelper {
}
private void startActionMode(@Nullable SelectionResult result) {
- final CharSequence text = mTextView.getText();
+ final CharSequence text = getText(mTextView);
if (result != null && text instanceof Spannable) {
Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
mTextClassification = result.mClassification;
@@ -229,7 +228,7 @@ public final class SelectionActionModeHelper {
return;
}
- final List<RectF> selectionRectangles =
+ final List<SmartSelectSprite.RectangleWithTextSelectionLayout> selectionRectangles =
convertSelectionToRectangles(layout, result.mStart, result.mEnd);
final PointF touchPoint = new PointF(
@@ -237,7 +236,8 @@ public final class SelectionActionModeHelper {
mEditor.getLastUpPositionY());
final PointF animationStartPoint =
- movePointInsideNearestRectangle(touchPoint, selectionRectangles);
+ movePointInsideNearestRectangle(touchPoint, selectionRectangles,
+ SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle);
mSmartSelectSprite.startAnimation(
animationStartPoint,
@@ -245,38 +245,58 @@ public final class SelectionActionModeHelper {
onAnimationEndCallback);
}
- private List<RectF> convertSelectionToRectangles(final Layout layout, final int start,
- final int end) {
- final List<RectF> result = new ArrayList<>();
- layout.getSelection(start, end, (left, top, right, bottom, textSelectionLayout) ->
- mergeRectangleIntoList(result, new RectF(left, top, right, bottom)));
+ private List<SmartSelectSprite.RectangleWithTextSelectionLayout> convertSelectionToRectangles(
+ final Layout layout, final int start, final int end) {
+ final List<SmartSelectSprite.RectangleWithTextSelectionLayout> result = new ArrayList<>();
+
+ final Layout.SelectionRectangleConsumer consumer =
+ (left, top, right, bottom, textSelectionLayout) -> mergeRectangleIntoList(
+ result,
+ new RectF(left, top, right, bottom),
+ SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
+ r -> new SmartSelectSprite.RectangleWithTextSelectionLayout(r,
+ textSelectionLayout)
+ );
+
+ layout.getSelection(start, end, consumer);
+
+ result.sort(Comparator.comparing(
+ SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
+ SmartSelectSprite.RECTANGLE_COMPARATOR));
- result.sort(SmartSelectSprite.RECTANGLE_COMPARATOR);
return result;
}
+ // TODO: Move public pure functions out of this class and make it package-private.
/**
- * Merges a {@link RectF} into an existing list of rectangles. While merging, this method
- * makes sure that:
+ * Merges a {@link RectF} into an existing list of any objects which contain a rectangle.
+ * While merging, this method makes sure that:
*
* <ol>
* <li>No rectangle is redundant (contained within a bigger rectangle)</li>
* <li>Rectangles of the same height and vertical position that intersect get merged</li>
* </ol>
*
- * @param list the list of rectangles to merge the new rectangle in
+ * @param list the list of rectangles (or other rectangle containers) to merge the new
+ * rectangle into
* @param candidate the {@link RectF} to merge into the list
+ * @param extractor a function that can extract a {@link RectF} from an element of the given
+ * list
+ * @param packer a function that can wrap the resulting {@link RectF} into an element that
+ * the list contains
* @hide
*/
@VisibleForTesting
- public static void mergeRectangleIntoList(List<RectF> list, RectF candidate) {
+ public static <T> void mergeRectangleIntoList(final List<T> list,
+ final RectF candidate, final Function<T, RectF> extractor,
+ final Function<RectF, T> packer) {
if (candidate.isEmpty()) {
return;
}
final int elementCount = list.size();
for (int index = 0; index < elementCount; ++index) {
- final RectF existingRectangle = list.get(index);
+ final RectF existingRectangle = extractor.apply(list.get(index));
if (existingRectangle.contains(candidate)) {
return;
}
@@ -299,26 +319,27 @@ public final class SelectionActionModeHelper {
}
for (int index = elementCount - 1; index >= 0; --index) {
- if (list.get(index).isEmpty()) {
+ final RectF rectangle = extractor.apply(list.get(index));
+ if (rectangle.isEmpty()) {
list.remove(index);
}
}
- list.add(candidate);
+ list.add(packer.apply(candidate));
}
/** @hide */
@VisibleForTesting
- public static PointF movePointInsideNearestRectangle(final PointF point,
- final List<RectF> rectangles) {
+ public static <T> PointF movePointInsideNearestRectangle(final PointF point,
+ final List<T> list, final Function<T, RectF> extractor) {
float bestX = -1;
float bestY = -1;
double bestDistance = Double.MAX_VALUE;
- final int elementCount = rectangles.size();
+ final int elementCount = list.size();
for (int index = 0; index < elementCount; ++index) {
- final RectF rectangle = rectangles.get(index);
+ final RectF rectangle = extractor.apply(list.get(index));
final float candidateY = rectangle.centerY();
final float candidateX;
@@ -356,7 +377,9 @@ public final class SelectionActionModeHelper {
}
private void resetTextClassificationHelper() {
- mTextClassificationHelper.reset(mTextView.getTextClassifier(), mTextView.getText(),
+ mTextClassificationHelper.reset(
+ mTextView.getTextClassifier(),
+ getText(mTextView),
mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
mTextView.getTextLocales());
}
@@ -382,6 +405,7 @@ public final class SelectionActionModeHelper {
private int mSelectionStart;
private int mSelectionEnd;
private boolean mAllowReset;
+ private final LogAbandonRunnable mDelayedLogAbandon = new LogAbandonRunnable();
SelectionTracker(TextView textView) {
mTextView = Preconditions.checkNotNull(textView);
@@ -393,6 +417,10 @@ public final class SelectionActionModeHelper {
*/
public void onOriginalSelection(
CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
+ // If we abandoned a selection and created a new one very shortly after, we may still
+ // have a pending request to log ABANDON, which we flush here.
+ mDelayedLogAbandon.flush();
+
mOriginalStart = mSelectionStart = selectionStart;
mOriginalEnd = mSelectionEnd = selectionEnd;
mAllowReset = false;
@@ -433,12 +461,7 @@ public final class SelectionActionModeHelper {
public void onSelectionDestroyed() {
mAllowReset = false;
// Wait a few ms to see if the selection was destroyed because of a text change event.
- mTextView.postDelayed(() -> {
- mLogger.logSelectionAction(
- mSelectionStart, mSelectionEnd,
- SelectionEvent.ActionType.ABANDON, null /* classification */);
- mSelectionStart = mSelectionEnd = -1;
- }, 100 /* ms */);
+ mDelayedLogAbandon.schedule(100 /* ms */);
}
/**
@@ -465,7 +488,7 @@ public final class SelectionActionModeHelper {
if (isSelectionStarted()
&& mAllowReset
&& textIndex >= mSelectionStart && textIndex <= mSelectionEnd
- && textView.getText() instanceof Spannable) {
+ && getText(textView) instanceof Spannable) {
mAllowReset = false;
boolean selected = editor.selectCurrentWord();
if (selected) {
@@ -495,6 +518,38 @@ public final class SelectionActionModeHelper {
private boolean isSelectionStarted() {
return mSelectionStart >= 0 && mSelectionEnd >= 0 && mSelectionStart != mSelectionEnd;
}
+
+ /** A helper for keeping track of pending abandon logging requests. */
+ private final class LogAbandonRunnable implements Runnable {
+ private boolean mIsPending;
+
+ /** Schedules an abandon to be logged with the given delay. Flush if necessary. */
+ void schedule(int delayMillis) {
+ if (mIsPending) {
+ Log.e(LOG_TAG, "Force flushing abandon due to new scheduling request");
+ flush();
+ }
+ mIsPending = true;
+ mTextView.postDelayed(this, delayMillis);
+ }
+
+ /** If there is a pending log request, execute it now. */
+ void flush() {
+ mTextView.removeCallbacks(this);
+ run();
+ }
+
+ @Override
+ public void run() {
+ if (mIsPending) {
+ mLogger.logSelectionAction(
+ mSelectionStart, mSelectionEnd,
+ SelectionEvent.ActionType.ABANDON, null /* classification */);
+ mSelectionStart = mSelectionEnd = -1;
+ mIsPending = false;
+ }
+ }
+ }
}
// TODO: Write tests
@@ -689,7 +744,7 @@ public final class SelectionActionModeHelper {
mSelectionResultSupplier = Preconditions.checkNotNull(selectionResultSupplier);
mSelectionResultCallback = Preconditions.checkNotNull(selectionResultCallback);
// Make a copy of the original text.
- mOriginalText = mTextView.getText().toString();
+ mOriginalText = getText(mTextView).toString();
}
@Override
@@ -705,7 +760,7 @@ public final class SelectionActionModeHelper {
@Override
@UiThread
protected void onPostExecute(SelectionResult result) {
- result = TextUtils.equals(mOriginalText, mTextView.getText()) ? result : null;
+ result = TextUtils.equals(mOriginalText, getText(mTextView)) ? result : null;
mSelectionResultCallback.accept(result);
}
@@ -752,6 +807,9 @@ public final class SelectionActionModeHelper {
private LocaleList mLastClassificationLocales;
private SelectionResult mLastClassificationResult;
+ /** Whether the TextClassifier has been initialized. */
+ private boolean mHot;
+
TextClassificationHelper(TextClassifier textClassifier,
CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
reset(textClassifier, text, selectionStart, selectionEnd, locales);
@@ -771,11 +829,13 @@ public final class SelectionActionModeHelper {
@WorkerThread
public SelectionResult classifyText() {
+ mHot = true;
return performClassification(null /* selection */);
}
@WorkerThread
public SelectionResult suggestSelection() {
+ mHot = true;
trimText();
final TextSelection selection = mTextClassifier.suggestSelection(
mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
@@ -784,6 +844,22 @@ public final class SelectionActionModeHelper {
return performClassification(selection);
}
+ /**
+ * Maximum time (in milliseconds) to wait for a textclassifier result before timing out.
+ */
+ // TODO: Consider making this a ViewConfiguration.
+ public int getTimeoutDuration() {
+ if (mHot) {
+ return 200;
+ } else {
+ // Return a slightly larger number than usual when the TextClassifier is first
+ // initialized. Initialization would usually take longer than subsequent calls to
+ // the TextClassifier. The impact of this on the UI is that we do not show the
+ // selection handles or toolbar until after this timeout.
+ return 500;
+ }
+ }
+
private SelectionResult performClassification(@Nullable TextSelection selection) {
if (!Objects.equals(mText, mLastClassificationText)
|| mSelectionStart != mLastClassificationSelectionStart
@@ -854,4 +930,14 @@ public final class SelectionActionModeHelper {
return SelectionEvent.ActionType.OTHER;
}
}
+
+ private static CharSequence getText(TextView textView) {
+ // Extracts the textView's text.
+ // TODO: Investigate why/when TextView.getText() is null.
+ final CharSequence text = textView.getText();
+ if (text != null) {
+ return text;
+ }
+ return "";
+ }
}
diff --git a/android/widget/SmartSelectSprite.java b/android/widget/SmartSelectSprite.java
index 27b93bc7..a391c6ee 100644
--- a/android/widget/SmartSelectSprite.java
+++ b/android/widget/SmartSelectSprite.java
@@ -35,6 +35,7 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;
+import android.text.Layout;
import android.util.TypedValue;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -42,9 +43,9 @@ import android.view.animation.Interpolator;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.LinkedList;
import java.util.List;
/**
@@ -76,6 +77,26 @@ final class SmartSelectSprite {
private Drawable mExistingDrawable = null;
private RectangleList mExistingRectangleList = null;
+ static final class RectangleWithTextSelectionLayout {
+ private final RectF mRectangle;
+ @Layout.TextSelectionLayout
+ private final int mTextSelectionLayout;
+
+ RectangleWithTextSelectionLayout(RectF rectangle, int textSelectionLayout) {
+ mRectangle = Preconditions.checkNotNull(rectangle);
+ mTextSelectionLayout = textSelectionLayout;
+ }
+
+ public RectF getRectangle() {
+ return mRectangle;
+ }
+
+ @Layout.TextSelectionLayout
+ public int getTextSelectionLayout() {
+ return mTextSelectionLayout;
+ }
+ }
+
/**
* A rounded rectangle with a configurable corner radius and the ability to expand outside of
* its bounding rectangle and clip against it.
@@ -84,12 +105,23 @@ final class SmartSelectSprite {
private static final String PROPERTY_ROUND_RATIO = "roundRatio";
+ /**
+ * The direction in which the rectangle will perform its expansion. A rectangle can expand
+ * from its left edge, its right edge or from the center (or, more precisely, the user's
+ * touch point). For example, in left-to-right text, a selection spanning two lines with the
+ * user's action being on the first line will have the top rectangle and expansion direction
+ * of CENTER, while the bottom one will have an expansion direction of RIGHT.
+ */
@Retention(SOURCE)
@IntDef({ExpansionDirection.LEFT, ExpansionDirection.CENTER, ExpansionDirection.RIGHT})
private @interface ExpansionDirection {
- int LEFT = 0;
- int CENTER = 1;
- int RIGHT = 2;
+ int LEFT = -1;
+ int CENTER = 0;
+ int RIGHT = 1;
+ }
+
+ private static @ExpansionDirection int invert(@ExpansionDirection int expansionDirection) {
+ return expansionDirection * -1;
}
@Retention(SOURCE)
@@ -114,20 +146,33 @@ final class SmartSelectSprite {
private final RectF mClipRect = new RectF();
private final Path mClipPath = new Path();
- /** How far offset the left edge of the rectangle is from the bounding box. */
+ /** How offset the left edge of the rectangle is from the left side of the bounding box. */
private float mLeftBoundary = 0;
- /** How far offset the right edge of the rectangle is from the bounding box. */
+ /** How offset the right edge of the rectangle is from the left side of the bounding box. */
private float mRightBoundary = 0;
+ /** Whether the horizontal bounds are inverted (for RTL scenarios). */
+ private final boolean mInverted;
+
+ private final float mBoundingWidth;
+
private RoundedRectangleShape(
final RectF boundingRectangle,
final @ExpansionDirection int expansionDirection,
final @RectangleBorderType int rectangleBorderType,
+ final boolean inverted,
final float strokeWidth) {
mBoundingRectangle = new RectF(boundingRectangle);
- mExpansionDirection = expansionDirection;
+ mBoundingWidth = boundingRectangle.width();
mRectangleBorderType = rectangleBorderType;
mStrokeWidth = strokeWidth;
+ mInverted = inverted && expansionDirection != ExpansionDirection.CENTER;
+
+ if (inverted) {
+ mExpansionDirection = invert(expansionDirection);
+ } else {
+ mExpansionDirection = expansionDirection;
+ }
if (boundingRectangle.height() > boundingRectangle.width()) {
setRoundRatio(0.0f);
@@ -148,6 +193,10 @@ final class SmartSelectSprite {
*/
@Override
public void draw(Canvas canvas, Paint paint) {
+ if (mLeftBoundary == mRightBoundary) {
+ return;
+ }
+
final float cornerRadius = getCornerRadius();
final float adjustedCornerRadius = getAdjustedCornerRadius();
@@ -157,7 +206,7 @@ final class SmartSelectSprite {
if (mRectangleBorderType == RectangleBorderType.OVERSHOOT) {
mDrawRect.left -= cornerRadius / 2;
- mDrawRect.right -= cornerRadius / 2;
+ mDrawRect.right += cornerRadius / 2;
} else {
switch (mExpansionDirection) {
case ExpansionDirection.CENTER:
@@ -173,7 +222,7 @@ final class SmartSelectSprite {
canvas.save();
mClipRect.set(mBoundingRectangle);
- mClipRect.inset(-mStrokeWidth, -mStrokeWidth);
+ mClipRect.inset(-mStrokeWidth / 2, -mStrokeWidth / 2);
canvas.clipRect(mClipRect);
canvas.drawRoundRect(mDrawRect, adjustedCornerRadius, adjustedCornerRadius, paint);
canvas.restore();
@@ -190,20 +239,28 @@ final class SmartSelectSprite {
canvas.restore();
}
- public void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
+ void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
mRoundRatio = roundRatio;
}
- public float getRoundRatio() {
+ float getRoundRatio() {
return mRoundRatio;
}
- private void setLeftBoundary(final float leftBoundary) {
- mLeftBoundary = leftBoundary;
+ private void setStartBoundary(final float startBoundary) {
+ if (mInverted) {
+ mRightBoundary = mBoundingWidth - startBoundary;
+ } else {
+ mLeftBoundary = startBoundary;
+ }
}
- private void setRightBoundary(final float rightBoundary) {
- mRightBoundary = rightBoundary;
+ private void setEndBoundary(final float endBoundary) {
+ if (mInverted) {
+ mLeftBoundary = mBoundingWidth - endBoundary;
+ } else {
+ mRightBoundary = endBoundary;
+ }
}
private float getCornerRadius() {
@@ -247,8 +304,8 @@ final class SmartSelectSprite {
private @DisplayType int mDisplayType = DisplayType.RECTANGLES;
private RectangleList(final List<RoundedRectangleShape> rectangles) {
- mRectangles = new LinkedList<>(rectangles);
- mReversedRectangles = new LinkedList<>(rectangles);
+ mRectangles = new ArrayList<>(rectangles);
+ mReversedRectangles = new ArrayList<>(rectangles);
Collections.reverse(mReversedRectangles);
mOutlinePolygonPath = generateOutlinePolygonPath(rectangles);
}
@@ -258,11 +315,11 @@ final class SmartSelectSprite {
for (RoundedRectangleShape rectangle : mReversedRectangles) {
final float rectangleLeftBoundary = boundarySoFar - rectangle.getBoundingWidth();
if (leftBoundary < rectangleLeftBoundary) {
- rectangle.setLeftBoundary(0);
+ rectangle.setStartBoundary(0);
} else if (leftBoundary > boundarySoFar) {
- rectangle.setLeftBoundary(rectangle.getBoundingWidth());
+ rectangle.setStartBoundary(rectangle.getBoundingWidth());
} else {
- rectangle.setLeftBoundary(
+ rectangle.setStartBoundary(
rectangle.getBoundingWidth() - boundarySoFar + leftBoundary);
}
@@ -275,11 +332,11 @@ final class SmartSelectSprite {
for (RoundedRectangleShape rectangle : mRectangles) {
final float rectangleRightBoundary = rectangle.getBoundingWidth() + boundarySoFar;
if (rectangleRightBoundary < rightBoundary) {
- rectangle.setRightBoundary(rectangle.getBoundingWidth());
+ rectangle.setEndBoundary(rectangle.getBoundingWidth());
} else if (boundarySoFar > rightBoundary) {
- rectangle.setRightBoundary(0);
+ rectangle.setEndBoundary(0);
} else {
- rectangle.setRightBoundary(rightBoundary - boundarySoFar);
+ rectangle.setEndBoundary(rightBoundary - boundarySoFar);
}
boundarySoFar = rectangleRightBoundary;
@@ -331,8 +388,8 @@ final class SmartSelectSprite {
}
/**
- * @param context The {@link Context} in which the animation will run
- * @param invalidator A {@link Runnable} which will be called every time the animation updates,
+ * @param context the {@link Context} in which the animation will run
+ * @param invalidator a {@link Runnable} which will be called every time the animation updates,
* indicating that the view drawing the animation should invalidate itself
*/
SmartSelectSprite(final Context context, final Runnable invalidator) {
@@ -356,67 +413,97 @@ final class SmartSelectSprite {
* "selection" and finally join them into a single polygon. In
* order to get the correct visual behavior, these rectangles
* should be sorted according to {@link #RECTANGLE_COMPARATOR}.
- * @param onAnimationEnd The callback which will be invoked once the whole animation
- * completes.
+ * @param onAnimationEnd the callback which will be invoked once the whole animation
+ * completes
* @throws IllegalArgumentException if the given start point is not in any of the
- * destinationRectangles.
+ * destinationRectangles
* @see #cancelAnimation()
*/
+ // TODO nullability checks on parameters
public void startAnimation(
final PointF start,
- final List<RectF> destinationRectangles,
- final Runnable onAnimationEnd) throws IllegalArgumentException {
+ final List<RectangleWithTextSelectionLayout> destinationRectangles,
+ final Runnable onAnimationEnd) {
cancelAnimation();
final ValueAnimator.AnimatorUpdateListener updateListener =
valueAnimator -> mInvalidator.run();
- final List<RoundedRectangleShape> shapes = new LinkedList<>();
- final List<Animator> cornerAnimators = new LinkedList<>();
+ final int rectangleCount = destinationRectangles.size();
+
+ final List<RoundedRectangleShape> shapes = new ArrayList<>(rectangleCount);
+ final List<Animator> cornerAnimators = new ArrayList<>(rectangleCount);
- final RectF centerRectangle = destinationRectangles
- .stream()
- .filter((r) -> contains(r, start))
- .findFirst()
- .orElseThrow(() -> new IllegalArgumentException(
- "Center point is not inside any of the rectangles!"));
+ RectangleWithTextSelectionLayout centerRectangle = null;
int startingOffset = 0;
- for (RectF rectangle : destinationRectangles) {
- if (rectangle.equals(centerRectangle)) {
+ int startingRectangleIndex = 0;
+ for (int index = 0; index < rectangleCount; ++index) {
+ final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+ destinationRectangles.get(index);
+ final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
+ if (contains(rectangle, start)) {
+ centerRectangle = rectangleWithTextSelectionLayout;
break;
}
startingOffset += rectangle.width();
+ ++startingRectangleIndex;
}
- startingOffset += start.x - centerRectangle.left;
+ if (centerRectangle == null) {
+ throw new IllegalArgumentException("Center point is not inside any of the rectangles!");
+ }
- final float centerRectangleHalfHeight = centerRectangle.height() / 2;
- final float startingOffsetLeft = startingOffset - centerRectangleHalfHeight;
- final float startingOffsetRight = startingOffset + centerRectangleHalfHeight;
+ startingOffset += start.x - centerRectangle.getRectangle().left;
final @RoundedRectangleShape.ExpansionDirection int[] expansionDirections =
generateDirections(centerRectangle, destinationRectangles);
final @RoundedRectangleShape.RectangleBorderType int[] rectangleBorderTypes =
- generateBorderTypes(destinationRectangles);
-
- int index = 0;
+ generateBorderTypes(rectangleCount);
- for (RectF rectangle : destinationRectangles) {
+ for (int index = 0; index < rectangleCount; ++index) {
+ final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+ destinationRectangles.get(index);
+ final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
final RoundedRectangleShape shape = new RoundedRectangleShape(
rectangle,
expansionDirections[index],
rectangleBorderTypes[index],
+ rectangleWithTextSelectionLayout.getTextSelectionLayout()
+ == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
mStrokeWidth);
cornerAnimators.add(createCornerAnimator(shape, updateListener));
shapes.add(shape);
- index++;
}
final RectangleList rectangleList = new RectangleList(shapes);
final ShapeDrawable shapeDrawable = new ShapeDrawable(rectangleList);
+ final float startingOffsetLeft;
+ final float startingOffsetRight;
+
+ final RoundedRectangleShape startingRectangleShape = shapes.get(startingRectangleIndex);
+ final float cornerRadius = startingRectangleShape.getCornerRadius();
+ if (startingRectangleShape.mRectangleBorderType
+ == RoundedRectangleShape.RectangleBorderType.FIT) {
+ switch (startingRectangleShape.mExpansionDirection) {
+ case RoundedRectangleShape.ExpansionDirection.LEFT:
+ startingOffsetLeft = startingOffsetRight = startingOffset - cornerRadius / 2;
+ break;
+ case RoundedRectangleShape.ExpansionDirection.RIGHT:
+ startingOffsetLeft = startingOffsetRight = startingOffset + cornerRadius / 2;
+ break;
+ case RoundedRectangleShape.ExpansionDirection.CENTER: // fall through
+ default:
+ startingOffsetLeft = startingOffset - cornerRadius / 2;
+ startingOffsetRight = startingOffset + cornerRadius / 2;
+ break;
+ }
+ } else {
+ startingOffsetLeft = startingOffsetRight = startingOffset;
+ }
+
final Paint paint = shapeDrawable.getPaint();
paint.setColor(mStrokeColor);
paint.setStyle(Paint.Style.STROKE);
@@ -511,7 +598,8 @@ final class SmartSelectSprite {
}
private static @RoundedRectangleShape.ExpansionDirection int[] generateDirections(
- final RectF centerRectangle, final List<RectF> rectangles) {
+ final RectangleWithTextSelectionLayout centerRectangle,
+ final List<RectangleWithTextSelectionLayout> rectangles) {
final @RoundedRectangleShape.ExpansionDirection int[] result = new int[rectangles.size()];
final int centerRectangleIndex = rectangles.indexOf(centerRectangle);
@@ -538,8 +626,8 @@ final class SmartSelectSprite {
}
private static @RoundedRectangleShape.RectangleBorderType int[] generateBorderTypes(
- final List<RectF> rectangles) {
- final @RoundedRectangleShape.RectangleBorderType int[] result = new int[rectangles.size()];
+ final int numberOfRectangles) {
+ final @RoundedRectangleShape.RectangleBorderType int[] result = new int[numberOfRectangles];
for (int i = 1; i < result.length - 1; ++i) {
result[i] = RoundedRectangleShape.RectangleBorderType.OVERSHOOT;
diff --git a/android/widget/Switch.java b/android/widget/Switch.java
index 2e1e9636..604575fa 100644
--- a/android/widget/Switch.java
+++ b/android/widget/Switch.java
@@ -248,10 +248,7 @@ public class Switch extends CompoundButton {
com.android.internal.R.styleable.Switch_switchPadding, 0);
mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false);
- // TODO: replace CUR_DEVELOPMENT with P once P is added to android.os.Build.VERSION_CODES.
- // STOPSHIP if the above TODO is not done.
- mUseFallbackLineSpacing =
- context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.CUR_DEVELOPMENT;
+ mUseFallbackLineSpacing = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.P;
ColorStateList thumbTintList = a.getColorStateList(
com.android.internal.R.styleable.Switch_thumbTint);
diff --git a/android/widget/TabWidget.java b/android/widget/TabWidget.java
index 05f7c0a1..f8b6837e 100644
--- a/android/widget/TabWidget.java
+++ b/android/widget/TabWidget.java
@@ -61,7 +61,10 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
// This value will be set to 0 as soon as the first tab is added to TabHost.
private int mSelectedTab = -1;
+ @Nullable
private Drawable mLeftStrip;
+
+ @Nullable
private Drawable mRightStrip;
private boolean mDrawBottomStrips = true;
@@ -374,23 +377,36 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
final Drawable leftStrip = mLeftStrip;
final Drawable rightStrip = mRightStrip;
- leftStrip.setState(selectedChild.getDrawableState());
- rightStrip.setState(selectedChild.getDrawableState());
+ if (leftStrip != null) {
+ leftStrip.setState(selectedChild.getDrawableState());
+ }
+ if (rightStrip != null) {
+ rightStrip.setState(selectedChild.getDrawableState());
+ }
if (mStripMoved) {
final Rect bounds = mBounds;
bounds.left = selectedChild.getLeft();
bounds.right = selectedChild.getRight();
final int myHeight = getHeight();
- leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
- myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
- rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
- Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
+ if (leftStrip != null) {
+ leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
+ myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
+ }
+ if (rightStrip != null) {
+ rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
+ Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()),
+ myHeight);
+ }
mStripMoved = false;
}
- leftStrip.draw(canvas);
- rightStrip.draw(canvas);
+ if (leftStrip != null) {
+ leftStrip.draw(canvas);
+ }
+ if (rightStrip != null) {
+ rightStrip.draw(canvas);
+ }
}
/**
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index efcc3a2f..24ae03c3 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -1256,9 +1256,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O;
- // TODO: replace CUR_DEVELOPMENT with P once P is added to android.os.Build.VERSION_CODES.
- // STOPSHIP if the above TODO is not done.
- mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.CUR_DEVELOPMENT;
+ mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P;
if (inputMethod != null) {
Class<?> c;
@@ -5549,7 +5547,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public final void setHint(CharSequence hint) {
setHintInternal(hint);
- if (isInputMethodTarget()) {
+ if (mEditor != null && isInputMethodTarget()) {
mEditor.reportExtractedText();
}
}
@@ -6283,7 +6281,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int horizontalPadding = getCompoundPaddingLeft();
final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
- if (mEditor.mCursorDrawable == null) {
+ if (mEditor.mDrawableForCursor == null) {
synchronized (TEMP_RECTF) {
/*
* The reason for this concern about the thickness of the
@@ -6310,7 +6308,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
(int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
}
} else {
- final Rect bounds = mEditor.mCursorDrawable.getBounds();
+ final Rect bounds = mEditor.mDrawableForCursor.getBounds();
invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
}
@@ -6362,8 +6360,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int bottom = mLayout.getLineBottom(lineEnd);
// mEditor can be null in case selection is set programmatically.
- if (invalidateCursor && mEditor != null && mEditor.mCursorDrawable != null) {
- final Rect bounds = mEditor.mCursorDrawable.getBounds();
+ if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
+ final Rect bounds = mEditor.mDrawableForCursor.getBounds();
top = Math.min(top, bounds.top);
bottom = Math.max(bottom, bounds.bottom);
}
diff --git a/android/widget/TextViewSetTextLocalePerfTest.java b/android/widget/TextViewSetTextLocalePerfTest.java
index 7fc5e4f8..e95676b2 100644
--- a/android/widget/TextViewSetTextLocalePerfTest.java
+++ b/android/widget/TextViewSetTextLocalePerfTest.java
@@ -16,27 +16,21 @@
package android.widget;
-import android.app.Activity;
-import android.os.Bundle;
-import android.perftests.utils.PerfStatusReporter;
-import android.util.Log;
-
import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.StubActivity;
import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
import android.support.test.rule.ActivityTestRule;
-import android.support.test.InstrumentationRegistry;
-
-import java.util.Locale;
-import java.util.Collection;
-import java.util.Arrays;
-import org.junit.Test;
import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
@LargeTest
@RunWith(Parameterized.class)