summaryrefslogtreecommitdiff
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
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
-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
-rw-r--r--benchmarks/ZipFileReadBenchmark.java90
-rw-r--r--com/android/commands/pm/Pm.java4
-rw-r--r--com/android/defcontainer/DefaultContainerService.java140
-rw-r--r--com/android/ims/ImsCallProfile.java55
-rw-r--r--com/android/internal/alsa/AlsaDevicesParser.java4
-rw-r--r--com/android/internal/app/ChooserActivity.java19
-rw-r--r--com/android/internal/app/NightDisplayController.java150
-rw-r--r--com/android/internal/app/ShutdownActivity.java7
-rw-r--r--com/android/internal/app/procstats/DumpUtils.java19
-rw-r--r--com/android/internal/app/procstats/ProcessState.java80
-rw-r--r--com/android/internal/app/procstats/ProcessStats.java42
-rw-r--r--com/android/internal/content/PackageHelper.java283
-rw-r--r--com/android/internal/os/BatteryStatsHelper.java3
-rw-r--r--com/android/internal/os/BatteryStatsImpl.java279
-rw-r--r--com/android/internal/os/Zygote.java5
-rw-r--r--com/android/internal/telephony/CallForwardInfo.java12
-rw-r--r--com/android/internal/telephony/CarrierKeyDownloadManager.java164
-rw-r--r--com/android/internal/telephony/CarrierServiceStateTracker.java9
-rw-r--r--com/android/internal/telephony/ClientWakelockTracker.java12
-rw-r--r--com/android/internal/telephony/Connection.java11
-rw-r--r--com/android/internal/telephony/DefaultPhoneNotifier.java3
-rw-r--r--com/android/internal/telephony/GsmCdmaPhone.java13
-rw-r--r--com/android/internal/telephony/InboundSmsHandler.java47
-rw-r--r--com/android/internal/telephony/Phone.java14
-rw-r--r--com/android/internal/telephony/RIL.java2
-rw-r--r--com/android/internal/telephony/ServiceStateTracker.java59
-rw-r--r--com/android/internal/telephony/cat/AppInterface.java1
-rw-r--r--com/android/internal/telephony/cat/CatService.java37
-rw-r--r--com/android/internal/telephony/cat/CommandParams.java9
-rw-r--r--com/android/internal/telephony/cat/CommandParamsFactory.java73
-rw-r--r--com/android/internal/telephony/cdma/SmsMessage.java2
-rw-r--r--com/android/internal/telephony/dataconnection/DcTracker.java25
-rw-r--r--com/android/internal/telephony/gsm/GsmSmsAddress.java8
-rw-r--r--com/android/internal/telephony/gsm/SmsMessage.java4
-rw-r--r--com/android/internal/telephony/imsphone/ImsPhone.java3
-rw-r--r--com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java9
-rw-r--r--com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java71
-rw-r--r--com/android/internal/telephony/uicc/AdnRecord.java15
-rw-r--r--com/android/internal/telephony/uicc/SIMRecords.java3
-rw-r--r--com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java107
-rw-r--r--com/android/internal/util/BitUtils.java4
-rw-r--r--com/android/internal/util/RingBuffer.java67
-rw-r--r--com/android/internal/widget/LinearLayoutManager.java4
-rw-r--r--com/android/internal/widget/LockPatternUtils.java19
-rw-r--r--com/android/internal/widget/Magnifier.java184
-rw-r--r--com/android/keyguard/KeyguardDisplayManager.java21
-rw-r--r--com/android/keyguard/KeyguardSimPinView.java6
-rw-r--r--com/android/keyguard/KeyguardSimPukView.java6
-rw-r--r--com/android/keyguard/KeyguardUpdateMonitor.java6
-rw-r--r--com/android/keyguard/ViewMediatorCallback.java5
-rw-r--r--com/android/layoutlib/bridge/Bridge.java655
-rw-r--r--com/android/layoutlib/bridge/android/BridgeContext.java79
-rw-r--r--com/android/layoutlib/bridge/bars/AppCompatActionBar.java103
-rw-r--r--com/android/layoutlib/bridge/impl/GcSnapshot.java69
-rw-r--r--com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--com/android/providers/settings/SettingsProvider.java20
-rw-r--r--com/android/server/BatteryService.java5
-rw-r--r--com/android/server/ConnectivityService.java60
-rw-r--r--com/android/server/DeviceIdleController.java204
-rw-r--r--com/android/server/DiskStatsService.java16
-rw-r--r--com/android/server/IpSecService.java160
-rw-r--r--com/android/server/LocationManagerService.java2
-rw-r--r--com/android/server/NetworkManagementService.java17
-rw-r--r--com/android/server/StorageManagerService.java1327
-rw-r--r--com/android/server/SystemServer.java87
-rw-r--r--com/android/server/accounts/AccountManagerService.java40
-rw-r--r--com/android/server/am/ActiveServices.java11
-rw-r--r--com/android/server/am/ActivityDisplay.java414
-rw-r--r--com/android/server/am/ActivityManagerService.java804
-rw-r--r--com/android/server/am/ActivityManagerShellCommand.java237
-rw-r--r--com/android/server/am/ActivityMetricsLogger.java6
-rw-r--r--com/android/server/am/ActivityRecord.java72
-rw-r--r--com/android/server/am/ActivityStack.java616
-rw-r--r--com/android/server/am/ActivityStackSupervisor.java1003
-rw-r--r--com/android/server/am/ActivityStarter.java354
-rw-r--r--com/android/server/am/AppErrors.java4
-rw-r--r--com/android/server/am/BatteryStatsService.java79
-rw-r--r--com/android/server/am/KeyguardController.java48
-rw-r--r--com/android/server/am/LockTaskController.java104
-rw-r--r--com/android/server/am/PendingIntentRecord.java2
-rw-r--r--com/android/server/am/PinnedActivityStack.java12
-rw-r--r--com/android/server/am/ProcessStatsService.java51
-rw-r--r--com/android/server/am/ServiceRecord.java5
-rw-r--r--com/android/server/am/TaskChangeNotificationController.java8
-rw-r--r--com/android/server/am/TaskPersister.java6
-rw-r--r--com/android/server/am/TaskRecord.java195
-rw-r--r--com/android/server/am/UserController.java1284
-rw-r--r--com/android/server/am/UserState.java4
-rw-r--r--com/android/server/appwidget/AppWidgetServiceImpl.java16
-rw-r--r--com/android/server/audio/AudioEventLogger.java18
-rw-r--r--com/android/server/audio/AudioService.java58
-rw-r--r--com/android/server/audio/AudioServiceEvents.java21
-rw-r--r--com/android/server/audio/MediaFocusControl.java39
-rw-r--r--com/android/server/audio/PlaybackActivityMonitor.java100
-rw-r--r--com/android/server/autofill/AutofillManagerService.java19
-rw-r--r--com/android/server/autofill/AutofillManagerServiceImpl.java55
-rw-r--r--com/android/server/autofill/Helper.java13
-rw-r--r--com/android/server/autofill/RemoteFillService.java5
-rw-r--r--com/android/server/autofill/Session.java311
-rw-r--r--com/android/server/autofill/ui/AutoFillUI.java66
-rw-r--r--com/android/server/autofill/ui/FillUi.java44
-rw-r--r--com/android/server/autofill/ui/SaveUi.java147
-rw-r--r--com/android/server/backup/BackupManagerConstants.java14
-rw-r--r--com/android/server/backup/BackupManagerService.java89
-rw-r--r--com/android/server/backup/BackupManagerServiceInterface.java2
-rw-r--r--com/android/server/backup/FullBackupJob.java4
-rw-r--r--com/android/server/backup/KeyValueAdbBackupEngine.java12
-rw-r--r--com/android/server/backup/KeyValueAdbRestoreEngine.java4
-rw-r--r--com/android/server/backup/KeyValueBackupJob.java4
-rw-r--r--com/android/server/backup/PackageManagerBackupAgent.java4
-rw-r--r--com/android/server/backup/ProcessedPackagesJournal.java6
-rw-r--r--com/android/server/backup/RefactoredBackupManagerService.java226
-rw-r--r--com/android/server/backup/Trampoline.java9
-rw-r--r--com/android/server/backup/TransportManager.java27
-rw-r--r--com/android/server/backup/fullbackup/PerformAdbBackupTask.java7
-rw-r--r--com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java17
-rw-r--r--com/android/server/backup/internal/PerformBackupTask.java15
-rw-r--r--com/android/server/backup/internal/RunInitializeReceiver.java22
-rw-r--r--com/android/server/backup/restore/PerformAdbRestoreTask.java3
-rw-r--r--com/android/server/backup/restore/PerformUnifiedRestoreTask.java13
-rw-r--r--com/android/server/backup/utils/AppBackupUtils.java29
-rw-r--r--com/android/server/connectivity/IpConnectivityEventBuilder.java2
-rw-r--r--com/android/server/connectivity/IpConnectivityMetrics.java49
-rw-r--r--com/android/server/connectivity/Nat464Xlat.java38
-rw-r--r--com/android/server/connectivity/NetdEventListenerService.java33
-rw-r--r--com/android/server/connectivity/tethering/OffloadController.java60
-rw-r--r--com/android/server/content/SyncManager.java47
-rw-r--r--com/android/server/content/SyncStorageEngine.java6
-rw-r--r--com/android/server/devicepolicy/DevicePolicyManagerService.java38
-rw-r--r--com/android/server/display/DisplayDeviceInfo.java6
-rw-r--r--com/android/server/display/LocalDisplayAdapter.java9
-rw-r--r--com/android/server/display/LogicalDisplay.java3
-rw-r--r--com/android/server/display/NightDisplayService.java101
-rw-r--r--com/android/server/display/VirtualDisplayAdapter.java5
-rw-r--r--com/android/server/fingerprint/FingerprintService.java163
-rw-r--r--com/android/server/fingerprint/InternalEnumerateClient.java9
-rw-r--r--com/android/server/job/JobSchedulerService.java44
-rw-r--r--com/android/server/job/JobSchedulerShellCommand.java44
-rw-r--r--com/android/server/locksettings/LockSettingsService.java18
-rw-r--r--com/android/server/locksettings/LockSettingsStrongAuth.java6
-rw-r--r--com/android/server/locksettings/SyntheticPasswordManager.java14
-rw-r--r--com/android/server/media/MediaRouterService.java10
-rw-r--r--com/android/server/media/MediaSessionRecord.java27
-rw-r--r--com/android/server/media/MediaSessionService.java4
-rw-r--r--com/android/server/net/NetworkPolicyManagerService.java32
-rw-r--r--com/android/server/notification/ConditionProviders.java5
-rw-r--r--com/android/server/notification/ImportanceExtractor.java2
-rw-r--r--com/android/server/notification/ManagedServices.java114
-rw-r--r--com/android/server/notification/NotificationAdjustmentExtractor.java3
-rw-r--r--com/android/server/notification/NotificationChannelExtractor.java2
-rw-r--r--com/android/server/notification/NotificationDelegate.java7
-rw-r--r--com/android/server/notification/NotificationManagerInternal.java2
-rw-r--r--com/android/server/notification/NotificationManagerService.java197
-rw-r--r--com/android/server/notification/NotificationRecord.java51
-rw-r--r--com/android/server/notification/PriorityExtractor.java2
-rw-r--r--com/android/server/notification/RankingConfig.java2
-rw-r--r--com/android/server/notification/RankingHelper.java75
-rw-r--r--com/android/server/notification/ScheduleCalendar.java3
-rw-r--r--com/android/server/notification/ZenModeHelper.java2
-rw-r--r--com/android/server/oemlock/OemLockService.java17
-rw-r--r--com/android/server/pm/BackgroundDexOptService.java6
-rw-r--r--com/android/server/pm/BasePermission.java105
-rw-r--r--com/android/server/pm/DefaultPermissionGrantPolicy.java1244
-rw-r--r--com/android/server/pm/DumpState.java95
-rw-r--r--com/android/server/pm/InstantAppRegistry.java7
-rw-r--r--com/android/server/pm/KeySetManagerService.java2
-rw-r--r--com/android/server/pm/PackageDexOptimizer.java52
-rw-r--r--com/android/server/pm/PackageInstallerService.java54
-rw-r--r--com/android/server/pm/PackageInstallerSession.java90
-rw-r--r--com/android/server/pm/PackageManagerService.java2606
-rw-r--r--com/android/server/pm/PackageManagerServiceCompilerMapping.java14
-rw-r--r--com/android/server/pm/PackageManagerServiceUtils.java77
-rw-r--r--com/android/server/pm/PackageManagerShellCommand.java4
-rw-r--r--com/android/server/pm/PackageSetting.java18
-rw-r--r--com/android/server/pm/PackageSettingBase.java9
-rw-r--r--com/android/server/pm/SettingBase.java2
-rw-r--r--com/android/server/pm/Settings.java238
-rw-r--r--com/android/server/pm/SharedUserSetting.java22
-rw-r--r--com/android/server/pm/ShortcutLauncher.java3
-rw-r--r--com/android/server/pm/ShortcutPackage.java7
-rw-r--r--com/android/server/pm/ShortcutService.java332
-rw-r--r--com/android/server/pm/ShortcutUser.java73
-rw-r--r--com/android/server/pm/UserManagerService.java16
-rw-r--r--com/android/server/pm/permission/BasePermission.java564
-rw-r--r--com/android/server/pm/permission/DefaultPermissionGrantPolicy.java1296
-rw-r--r--com/android/server/pm/permission/PermissionManagerInternal.java140
-rw-r--r--com/android/server/pm/permission/PermissionManagerService.java1081
-rw-r--r--com/android/server/pm/permission/PermissionSettings.java149
-rw-r--r--com/android/server/pm/permission/PermissionsState.java (renamed from com/android/server/pm/PermissionsState.java)22
-rw-r--r--com/android/server/policy/PhoneWindowManager.java339
-rw-r--r--com/android/server/policy/WindowOrientationListener.java8
-rw-r--r--com/android/server/policy/keyguard/KeyguardServiceDelegate.java46
-rw-r--r--com/android/server/power/PowerManagerService.java34
-rw-r--r--com/android/server/power/ShutdownThread.java3
-rw-r--r--com/android/server/stats/StatsCompanionService.java301
-rw-r--r--com/android/server/statusbar/StatusBarManagerService.java139
-rw-r--r--com/android/server/storage/AppCollector.java2
-rw-r--r--com/android/server/storage/DiskStatsFileLogger.java14
-rw-r--r--com/android/server/timezone/IntentHelper.java21
-rw-r--r--com/android/server/timezone/IntentHelperImpl.java45
-rw-r--r--com/android/server/timezone/PackageTracker.java58
-rw-r--r--com/android/server/timezone/PackageTrackerHelperImpl.java8
-rw-r--r--com/android/server/timezone/RulesManagerService.java36
-rw-r--r--com/android/server/timezone/TimeZoneUpdateIdler.java100
-rw-r--r--com/android/server/twilight/TwilightState.java23
-rw-r--r--com/android/server/usage/AppStandbyController.java987
-rw-r--r--com/android/server/usage/UsageStatsService.java907
-rw-r--r--com/android/server/usb/UsbAlsaManager.java6
-rw-r--r--com/android/server/utils/ManagedApplicationService.java343
-rw-r--r--com/android/server/utils/PriorityDump.java14
-rw-r--r--com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java5
-rw-r--r--com/android/server/vr/Vr2dDisplay.java3
-rw-r--r--com/android/server/vr/VrManagerInternal.java7
-rw-r--r--com/android/server/vr/VrManagerService.java270
-rw-r--r--com/android/server/webkit/SystemImpl.java4
-rw-r--r--com/android/server/wifi/NetworkListStoreData.java21
-rw-r--r--com/android/server/wifi/OpenNetworkNotifier.java55
-rw-r--r--com/android/server/wifi/VelocityBasedConnectedScore.java133
-rw-r--r--com/android/server/wifi/WifiBackupRestore.java5
-rw-r--r--com/android/server/wifi/WifiConfigManager.java16
-rw-r--r--com/android/server/wifi/WifiConnectivityManager.java20
-rw-r--r--com/android/server/wifi/WifiCountryCode.java21
-rw-r--r--com/android/server/wifi/WifiInjector.java18
-rw-r--r--com/android/server/wifi/WifiMetrics.java127
-rw-r--r--com/android/server/wifi/WifiNative.java4
-rw-r--r--com/android/server/wifi/WifiScoreReport.java20
-rw-r--r--com/android/server/wifi/WifiServiceImpl.java2
-rw-r--r--com/android/server/wifi/WifiStateMachine.java135
-rw-r--r--com/android/server/wifi/WifiVendorHal.java27
-rw-r--r--com/android/server/wifi/WificondControl.java9
-rw-r--r--com/android/server/wifi/aware/WifiAwareClientState.java42
-rw-r--r--com/android/server/wifi/aware/WifiAwareRttStateManager.java206
-rw-r--r--com/android/server/wifi/aware/WifiAwareServiceImpl.java41
-rw-r--r--com/android/server/wifi/aware/WifiAwareStateManager.java124
-rw-r--r--com/android/server/wifi/rtt/RttNative.java248
-rw-r--r--com/android/server/wifi/rtt/RttService.java67
-rw-r--r--com/android/server/wifi/rtt/RttServiceImpl.java399
-rw-r--r--com/android/server/wifi/scanner/ChannelHelper.java10
-rw-r--r--com/android/server/wifi/scanner/HalWifiScannerImpl.java5
-rw-r--r--com/android/server/wifi/scanner/WifiScannerImpl.java6
-rw-r--r--com/android/server/wifi/scanner/WifiScanningServiceImpl.java12
-rw-r--r--com/android/server/wifi/scanner/WificondScannerImpl.java314
-rw-r--r--com/android/server/wifi/util/KalmanFilter.java62
-rw-r--r--com/android/server/wm/AppWindowAnimator.java40
-rw-r--r--com/android/server/wm/AppWindowToken.java11
-rw-r--r--com/android/server/wm/ConfigurationContainer.java83
-rw-r--r--com/android/server/wm/DisplayContent.java86
-rw-r--r--com/android/server/wm/DockedStackDividerController.java29
-rw-r--r--com/android/server/wm/DragResizeMode.java15
-rw-r--r--com/android/server/wm/PinnedStackController.java49
-rw-r--r--com/android/server/wm/PinnedStackWindowController.java14
-rw-r--r--com/android/server/wm/RootWindowContainer.java24
-rw-r--r--com/android/server/wm/Session.java4
-rw-r--r--com/android/server/wm/StackWindowController.java5
-rw-r--r--com/android/server/wm/Task.java29
-rw-r--r--com/android/server/wm/TaskSnapshotController.java4
-rw-r--r--com/android/server/wm/TaskStack.java40
-rw-r--r--com/android/server/wm/WindowContainer.java21
-rw-r--r--com/android/server/wm/WindowLayersController.java32
-rw-r--r--com/android/server/wm/WindowManagerService.java53
-rw-r--r--com/android/server/wm/WindowState.java35
-rw-r--r--com/android/server/wm/WindowSurfacePlacer.java19
-rw-r--r--com/android/server/wm/WindowToken.java7
-rw-r--r--com/android/settingslib/applications/ApplicationsState.java34
-rw-r--r--com/android/settingslib/applications/StorageStatsSource.java2
-rw-r--r--com/android/settingslib/development/AbstractEnableAdbPreferenceController.java18
-rw-r--r--com/android/settingslib/development/AbstractLogdSizePreferenceController.java10
-rw-r--r--com/android/settingslib/development/AbstractLogpersistPreferenceController.java7
-rw-r--r--com/android/settingslib/development/DeveloperOptionsPreferenceController.java76
-rw-r--r--com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java64
-rw-r--r--com/android/settingslib/drawer/TileUtils.java70
-rw-r--r--com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java5
-rw-r--r--com/android/settingslib/suggestions/SuggestionParser.java2
-rw-r--r--com/android/settingslib/wifi/WifiTracker.java104
-rw-r--r--com/android/settingslib/wifi/WifiTrackerFactory.java11
-rw-r--r--com/android/settingslib/wrapper/PackageManagerWrapper.java59
-rw-r--r--com/android/setupwizardlib/test/util/DrawingTestActivity.java9
-rw-r--r--com/android/setupwizardlib/util/LinkAccessibilityHelper.java394
-rw-r--r--com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java (renamed from com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java)95
-rw-r--r--com/android/setupwizardlib/util/PartnerTest.java28
-rw-r--r--com/android/setupwizardlib/view/IllustrationVideoViewTest.java2
-rw-r--r--com/android/setupwizardlib/view/NavigationBarButton.java127
-rw-r--r--com/android/setupwizardlib/view/RichTextView.java49
-rw-r--r--com/android/systemui/BatteryMeterView.java6
-rw-r--r--com/android/systemui/assist/AssistManager.java6
-rw-r--r--com/android/systemui/doze/AlwaysOnDisplayPolicy.java100
-rw-r--r--com/android/systemui/doze/DozePauser.java7
-rw-r--r--com/android/systemui/doze/DozeScreenBrightness.java12
-rw-r--r--com/android/systemui/globalactions/GlobalActionsDialog.java24
-rw-r--r--com/android/systemui/keyguard/KeyguardViewMediator.java65
-rw-r--r--com/android/systemui/media/NotificationPlayer.java74
-rw-r--r--com/android/systemui/pip/phone/PipManager.java27
-rw-r--r--com/android/systemui/pip/phone/PipMediaController.java2
-rw-r--r--com/android/systemui/pip/phone/PipMenuActivityController.java11
-rw-r--r--com/android/systemui/pip/phone/PipMotionHelper.java22
-rw-r--r--com/android/systemui/pip/phone/PipNotificationController.java107
-rw-r--r--com/android/systemui/pip/phone/PipUtils.java17
-rw-r--r--com/android/systemui/pip/tv/PipManager.java17
-rw-r--r--com/android/systemui/qs/AlphaControlledSignalTileView.java86
-rw-r--r--com/android/systemui/qs/SignalTileView.java6
-rw-r--r--com/android/systemui/qs/SlashDrawable.java6
-rw-r--r--com/android/systemui/qs/tileimpl/QSIconViewImpl.java12
-rw-r--r--com/android/systemui/qs/tileimpl/SlashImageView.java18
-rw-r--r--com/android/systemui/qs/tiles/BluetoothTile.java25
-rw-r--r--com/android/systemui/qs/tiles/CellularTile.java2
-rw-r--r--com/android/systemui/qs/tiles/WifiTile.java4
-rw-r--r--com/android/systemui/recents/Recents.java10
-rw-r--r--com/android/systemui/recents/RecentsActivity.java3
-rw-r--r--com/android/systemui/recents/RecentsImpl.java25
-rw-r--r--com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java14
-rw-r--r--com/android/systemui/recents/events/activity/LaunchTaskEvent.java17
-rw-r--r--com/android/systemui/recents/misc/SystemServicesProxy.java145
-rw-r--r--com/android/systemui/recents/model/HighResThumbnailLoader.java2
-rw-r--r--com/android/systemui/recents/model/RecentsTaskLoadPlan.java7
-rw-r--r--com/android/systemui/recents/model/Task.java38
-rw-r--r--com/android/systemui/recents/model/TaskKeyCache.java2
-rw-r--r--com/android/systemui/recents/model/TaskStack.java22
-rw-r--r--com/android/systemui/recents/views/RecentsTransitionHelper.java54
-rw-r--r--com/android/systemui/recents/views/RecentsView.java61
-rw-r--r--com/android/systemui/recents/views/RecentsViewTouchHandler.java47
-rw-r--r--com/android/systemui/recents/views/TaskStackView.java39
-rw-r--r--com/android/systemui/recents/views/TaskView.java5
-rw-r--r--com/android/systemui/recents/views/TaskViewHeader.java19
-rw-r--r--com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java82
-rw-r--r--com/android/systemui/stackdivider/DividerView.java41
-rw-r--r--com/android/systemui/stackdivider/WindowManagerProxy.java30
-rw-r--r--com/android/systemui/statusbar/ExpandableNotificationRow.java19
-rw-r--r--com/android/systemui/statusbar/ExpandableView.java14
-rw-r--r--com/android/systemui/statusbar/KeyboardShortcuts.java35
-rw-r--r--com/android/systemui/statusbar/NotificationData.java38
-rw-r--r--com/android/systemui/statusbar/NotificationSnooze.java118
-rw-r--r--com/android/systemui/statusbar/SignalClusterView.java33
-rw-r--r--com/android/systemui/statusbar/StatusBarIconView.java33
-rw-r--r--com/android/systemui/statusbar/car/CarNavigationBarController.java29
-rw-r--r--com/android/systemui/statusbar/car/CarStatusBar.java9
-rw-r--r--com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java17
-rw-r--r--com/android/systemui/statusbar/notification/NotificationViewWrapper.java10
-rw-r--r--com/android/systemui/statusbar/phone/BarTransitions.java2
-rw-r--r--com/android/systemui/statusbar/phone/DozeScrimController.java2
-rw-r--r--com/android/systemui/statusbar/phone/LightBarController.java8
-rw-r--r--com/android/systemui/statusbar/phone/NavigationBarFragment.java2
-rw-r--r--com/android/systemui/statusbar/phone/NotificationIconAreaController.java26
-rw-r--r--com/android/systemui/statusbar/phone/NotificationPanelView.java5
-rw-r--r--com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java33
-rw-r--r--com/android/systemui/statusbar/phone/StatusBar.java1177
-rw-r--r--com/android/systemui/statusbar/phone/StatusBarIconController.java16
-rw-r--r--com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java11
-rw-r--r--com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java1
-rw-r--r--com/android/systemui/statusbar/policy/AccessPointControllerImpl.java15
-rw-r--r--com/android/systemui/statusbar/policy/CallbackHandler.java6
-rw-r--r--com/android/systemui/statusbar/policy/NetworkController.java2
-rw-r--r--com/android/systemui/statusbar/policy/NetworkControllerImpl.java41
-rw-r--r--com/android/systemui/util/Utils.java53
-rw-r--r--com/android/systemui/volume/ZenModePanel.java7
-rw-r--r--com/android/uiautomator/testrunner/UiAutomatorTestCase.java106
-rw-r--r--com/android/webview/nullwebview/NullWebViewFactoryProvider.java85
-rw-r--r--foo/bar/ComplexDao.java5
-rw-r--r--foo/bar/MultiPKeyEntity.java3
-rw-r--r--java/io/File.java73
-rw-r--r--java/io/RandomAccessFile.java24
-rw-r--r--java/lang/String.java128
-rw-r--r--java/lang/StringCoding.java414
-rw-r--r--java/lang/StringIndexOutOfBoundsException.java10
-rw-r--r--java/lang/invoke/CallSite.java350
-rw-r--r--java/lang/invoke/MethodHandle.java1347
-rw-r--r--java/lang/invoke/MethodHandles.java3379
-rw-r--r--java/lang/invoke/MethodType.java1205
-rw-r--r--java/lang/invoke/VarHandle.java2161
-rw-r--r--java/net/Inet6AddressImpl.java38
-rw-r--r--java/net/InetAddress.java709
-rw-r--r--java/net/InetAddressImpl.java10
-rw-r--r--java/net/InetSocketAddress.java12
-rw-r--r--java/net/InterfaceAddress.java2
-rw-r--r--java/net/MulticastSocket.java1
-rw-r--r--java/net/NetworkInterface.java63
-rw-r--r--java/net/PlainDatagramSocketImpl.java2
-rw-r--r--java/net/PlainSocketImpl.java9
-rw-r--r--java/net/PortUnreachableException.java1
-rw-r--r--java/net/ProtocolException.java1
-rw-r--r--java/net/ServerSocket.java6
-rw-r--r--java/net/URLConnection.java15
-rw-r--r--java/net/URLDecoder.java11
-rw-r--r--java/security/SecureRandom.java88
-rw-r--r--java/text/Bidi.java47
-rw-r--r--java/text/BreakIterator.java6
-rw-r--r--java/text/ChoiceFormat.java3
-rw-r--r--java/text/CollationElementIterator.java19
-rw-r--r--java/text/Collator.java13
-rw-r--r--java/text/DateFormatSymbols.java87
-rw-r--r--java/text/DecimalFormat.java305
-rw-r--r--java/text/DecimalFormatSymbols.java55
-rw-r--r--java/text/MessageFormat.java1
-rw-r--r--java/text/Normalizer.java37
-rw-r--r--java/text/NumberFormat.java8
-rw-r--r--java/text/RuleBasedCollator.java20
-rw-r--r--java/text/SimpleDateFormat.java64
-rw-r--r--java/time/format/DateTimeFormatterBuilder.java11
-rw-r--r--java/time/format/DateTimeTextProvider.java3
-rw-r--r--java/time/format/ZoneName.java5
-rw-r--r--java/util/zip/ZipFile.java4
-rw-r--r--org/apache/harmony/security/PrivateKeyImpl.java66
-rw-r--r--org/apache/harmony/security/PublicKeyImpl.java72
-rw-r--r--org/apache/harmony/security/provider/crypto/CryptoProvider.java42
-rw-r--r--org/apache/harmony/security/provider/crypto/SHA1Constants.java82
-rw-r--r--org/apache/harmony/security/provider/crypto/SHA1Impl.java246
-rw-r--r--org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java564
929 files changed, 54054 insertions, 23994 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)
diff --git a/benchmarks/ZipFileReadBenchmark.java b/benchmarks/ZipFileReadBenchmark.java
new file mode 100644
index 00000000..f6125a61
--- /dev/null
+++ b/benchmarks/ZipFileReadBenchmark.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package benchmarks;
+
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Param;
+import java.io.File;
+import java.io.InputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+
+public class ZipFileReadBenchmark {
+ private File file;
+ @Param({"1024", "16384", "65536"}) int readBufferSize;
+
+ @BeforeExperiment
+ protected void setUp() throws Exception {
+ System.setProperty("java.io.tmpdir", "/data/local/tmp");
+ file = File.createTempFile(getClass().getName(), ".zip");
+ writeEntries(new ZipOutputStream(new FileOutputStream(file)), 2, 1024*1024);
+ ZipFile zipFile = new ZipFile(file);
+ for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
+ ZipEntry zipEntry = e.nextElement();
+ }
+ zipFile.close();
+ }
+
+ /**
+ * Compresses the given number of files, each of the given size, into a .zip archive.
+ */
+ protected void writeEntries(ZipOutputStream out, int entryCount, long entrySize)
+ throws IOException {
+ byte[] writeBuffer = new byte[8192];
+ Random random = new Random();
+ try {
+ for (int entry = 0; entry < entryCount; ++entry) {
+ ZipEntry ze = new ZipEntry(Integer.toHexString(entry));
+ ze.setSize(entrySize);
+ out.putNextEntry(ze);
+
+ for (long i = 0; i < entrySize; i += writeBuffer.length) {
+ random.nextBytes(writeBuffer);
+ int byteCount = (int) Math.min(writeBuffer.length, entrySize - i);
+ out.write(writeBuffer, 0, byteCount);
+ }
+
+ out.closeEntry();
+ }
+ } finally {
+ out.close();
+ }
+ }
+
+ public void timeZipFileRead(int reps) throws Exception {
+ byte readBuffer[] = new byte[readBufferSize];
+ for (int i = 0; i < reps; ++i) {
+ ZipFile zipFile = new ZipFile(file);
+ for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) {
+ ZipEntry zipEntry = e.nextElement();
+ InputStream is = zipFile.getInputStream(zipEntry);
+ while (true) {
+ if (is.read(readBuffer, 0, readBuffer.length) < 0) {
+ break;
+ }
+ }
+ }
+ zipFile.close();
+ }
+ }
+}
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
index ad989dee..c5c38f53 100644
--- a/com/android/commands/pm/Pm.java
+++ b/com/android/commands/pm/Pm.java
@@ -416,7 +416,7 @@ public final class Pm {
PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
null, null);
params.sessionParams.setSize(
- PackageHelper.calculateInstalledSize(pkgLite, false,
+ PackageHelper.calculateInstalledSize(pkgLite,
params.sessionParams.abiOverride));
} catch (PackageParserException | IOException e) {
System.err.println("Error: Failed to parse APK file: " + e);
@@ -636,7 +636,7 @@ public final class Pm {
out = session.openWrite(splitName, 0, sizeBytes);
int total = 0;
- byte[] buffer = new byte[65536];
+ byte[] buffer = new byte[1024 * 1024];
int c;
while ((c = in.read(buffer)) != -1) {
total += c;
diff --git a/com/android/defcontainer/DefaultContainerService.java b/com/android/defcontainer/DefaultContainerService.java
index 3800e6f7..4a771ebd 100644
--- a/com/android/defcontainer/DefaultContainerService.java
+++ b/com/android/defcontainer/DefaultContainerService.java
@@ -16,8 +16,6 @@
package com.android.defcontainer;
-import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
-
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
@@ -31,21 +29,15 @@ import android.content.pm.PackageParser.PackageParserException;
import android.content.res.ObbInfo;
import android.content.res.ObbScanner;
import android.os.Binder;
-import android.os.Environment;
import android.os.Environment.UserEnvironment;
-import android.os.FileUtils;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructStatVfs;
import android.util.Slog;
import com.android.internal.app.IMediaContainerService;
-import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
import com.android.internal.os.IParcelFileDescriptorFactory;
import com.android.internal.util.ArrayUtils;
@@ -72,51 +64,6 @@ public class DefaultContainerService extends IntentService {
private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
/**
- * Creates a new container and copies package there.
- *
- * @param packagePath absolute path to the package to be copied. Can be
- * a single monolithic APK file or a cluster directory
- * containing one or more APKs.
- * @param containerId the id of the secure container that should be used
- * for creating a secure container into which the resource
- * will be copied.
- * @param key Refers to key used for encrypting the secure container
- * @return Returns the new cache path where the resource has been copied
- * into
- */
- @Override
- public String copyPackageToContainer(String packagePath, String containerId, String key,
- boolean isExternal, boolean isForwardLocked, String abiOverride) {
- if (packagePath == null || containerId == null) {
- return null;
- }
-
- if (isExternal) {
- // Make sure the sdcard is mounted.
- String status = Environment.getExternalStorageState();
- if (!status.equals(Environment.MEDIA_MOUNTED)) {
- Slog.w(TAG, "Make sure sdcard is mounted.");
- return null;
- }
- }
-
- PackageLite pkg = null;
- NativeLibraryHelper.Handle handle = null;
- try {
- final File packageFile = new File(packagePath);
- pkg = PackageParser.parsePackageLite(packageFile, 0);
- handle = NativeLibraryHelper.Handle.create(pkg);
- return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
- isForwardLocked, abiOverride);
- } catch (PackageParserException | IOException e) {
- Slog.w(TAG, "Failed to copy package at " + packagePath, e);
- return null;
- } finally {
- IoUtils.closeQuietly(handle);
- }
- }
-
- /**
* Copy package to the target location.
*
* @param packagePath absolute path to the package to be copied. Can be
@@ -153,7 +100,6 @@ public class DefaultContainerService extends IntentService {
public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
String abiOverride) {
final Context context = DefaultContainerService.this;
- final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
PackageInfoLite ret = new PackageInfoLite();
if (packagePath == null) {
@@ -167,7 +113,7 @@ public class DefaultContainerService extends IntentService {
final long sizeBytes;
try {
pkg = PackageParser.parsePackageLite(packageFile, 0);
- sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
+ sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
} catch (PackageParserException | IOException e) {
Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
@@ -230,13 +176,13 @@ public class DefaultContainerService extends IntentService {
* containing one or more APKs.
*/
@Override
- public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
- String abiOverride) throws RemoteException {
+ public long calculateInstalledSize(String packagePath, String abiOverride)
+ throws RemoteException {
final File packageFile = new File(packagePath);
final PackageParser.PackageLite pkg;
try {
pkg = PackageParser.parsePackageLite(packageFile, 0);
- return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
+ return PackageHelper.calculateInstalledSize(pkg, abiOverride);
} catch (PackageParserException | IOException e) {
Slog.w(TAG, "Failed to calculate installed size: " + e);
return Long.MAX_VALUE;
@@ -292,60 +238,6 @@ public class DefaultContainerService extends IntentService {
return mBinder;
}
- private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
- String newCid, String key, boolean isExternal, boolean isForwardLocked,
- String abiOverride) throws IOException {
-
- // Calculate container size, rounding up to nearest MB and adding an
- // extra MB for filesystem overhead
- final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle,
- isForwardLocked, abiOverride);
-
- // Create new container
- final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key,
- Process.myUid(), isExternal);
- if (newMountPath == null) {
- throw new IOException("Failed to create container " + newCid);
- }
- final File targetDir = new File(newMountPath);
-
- try {
- // Copy all APKs
- copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked);
- if (!ArrayUtils.isEmpty(pkg.splitNames)) {
- for (int i = 0; i < pkg.splitNames.length; i++) {
- copyFile(pkg.splitCodePaths[i], targetDir,
- "split_" + pkg.splitNames[i] + ".apk", isForwardLocked);
- }
- }
-
- // Extract native code
- final File libraryRoot = new File(targetDir, LIB_DIR_NAME);
- final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
- abiOverride);
- if (res != PackageManager.INSTALL_SUCCEEDED) {
- throw new IOException("Failed to extract native code, res=" + res);
- }
-
- if (!PackageHelper.finalizeSdDir(newCid)) {
- throw new IOException("Failed to finalize " + newCid);
- }
-
- if (PackageHelper.isContainerMounted(newCid)) {
- PackageHelper.unMountSdDir(newCid);
- }
-
- } catch (ErrnoException e) {
- PackageHelper.destroySdDir(newCid);
- throw e.rethrowAsIOException();
- } catch (IOException e) {
- PackageHelper.destroySdDir(newCid);
- throw e;
- }
-
- return newMountPath;
- }
-
private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
throws IOException, RemoteException {
copyFile(pkg.baseCodePath, target, "base.apk");
@@ -373,28 +265,4 @@ public class DefaultContainerService extends IntentService {
IoUtils.closeQuietly(in);
}
}
-
- private void copyFile(String sourcePath, File targetDir, String targetName,
- boolean isForwardLocked) throws IOException, ErrnoException {
- final File sourceFile = new File(sourcePath);
- final File targetFile = new File(targetDir, targetName);
-
- Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile);
- if (!FileUtils.copyFile(sourceFile, targetFile)) {
- throw new IOException("Failed to copy " + sourceFile + " to " + targetFile);
- }
-
- if (isForwardLocked) {
- final String publicTargetName = PackageHelper.replaceEnd(targetName,
- ".apk", ".zip");
- final File publicTargetFile = new File(targetDir, publicTargetName);
-
- PackageHelper.extractPublicFiles(sourceFile, publicTargetFile);
-
- Os.chmod(targetFile.getAbsolutePath(), 0640);
- Os.chmod(publicTargetFile.getAbsolutePath(), 0644);
- } else {
- Os.chmod(targetFile.getAbsolutePath(), 0644);
- }
- }
}
diff --git a/com/android/ims/ImsCallProfile.java b/com/android/ims/ImsCallProfile.java
index 36abfc9d..489c208a 100644
--- a/com/android/ims/ImsCallProfile.java
+++ b/com/android/ims/ImsCallProfile.java
@@ -19,7 +19,9 @@ package com.android.ims;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.telecom.VideoProfile;
+import android.util.Log;
import com.android.internal.telephony.PhoneConstants;
@@ -216,6 +218,29 @@ public class ImsCallProfile implements Parcelable {
public int mServiceType;
public int mCallType;
public int mRestrictCause = CALL_RESTRICT_CAUSE_NONE;
+
+ /**
+ * Extras associated with this {@link ImsCallProfile}.
+ * <p>
+ * Valid data types include:
+ * <ul>
+ * <li>{@link Integer} (and int)</li>
+ * <li>{@link Long} (and long)</li>
+ * <li>{@link Double} (and double)</li>
+ * <li>{@link String}</li>
+ * <li>{@code int[]}</li>
+ * <li>{@code long[]}</li>
+ * <li>{@code double[]}</li>
+ * <li>{@code String[]}</li>
+ * <li>{@link PersistableBundle}</li>
+ * <li>{@link Boolean} (and boolean)</li>
+ * <li>{@code boolean[]}</li>
+ * <li>Other {@link Parcelable} classes in the {@code android.*} namespace.</li>
+ * </ul>
+ * <p>
+ * Invalid types will be removed when the {@link ImsCallProfile} is parceled for transmit across
+ * a {@link android.os.Binder}.
+ */
public Bundle mCallExtras;
public ImsStreamMediaProfile mMediaProfile;
@@ -315,16 +340,17 @@ public class ImsCallProfile implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
+ Bundle filteredExtras = maybeCleanseExtras(mCallExtras);
out.writeInt(mServiceType);
out.writeInt(mCallType);
- out.writeParcelable(mCallExtras, 0);
+ out.writeBundle(filteredExtras);
out.writeParcelable(mMediaProfile, 0);
}
private void readFromParcel(Parcel in) {
mServiceType = in.readInt();
mCallType = in.readInt();
- mCallExtras = in.readParcelable(null);
+ mCallExtras = in.readBundle();
mMediaProfile = in.readParcelable(null);
}
@@ -465,6 +491,31 @@ public class ImsCallProfile implements Parcelable {
}
/**
+ * Cleanses a {@link Bundle} to ensure that it contains only data of type:
+ * 1. Primitive data types (e.g. int, bool, and other values determined by
+ * {@link android.os.PersistableBundle#isValidType(Object)}).
+ * 2. Other Bundles.
+ * 3. {@link Parcelable} objects in the {@code android.*} namespace.
+ * @param extras the source {@link Bundle}
+ * @return where all elements are valid types the source {@link Bundle} is returned unmodified,
+ * otherwise a copy of the {@link Bundle} with the invalid elements is returned.
+ */
+ private Bundle maybeCleanseExtras(Bundle extras) {
+ if (extras == null) {
+ return null;
+ }
+
+ int startSize = extras.size();
+ Bundle filtered = extras.filterValues();
+ int endSize = filtered.size();
+ if (startSize != endSize) {
+ Log.i(TAG, "maybeCleanseExtras: " + (startSize - endSize) + " extra values were "
+ + "removed - only primitive types and system parcelables are permitted.");
+ }
+ return filtered;
+ }
+
+ /**
* Determines if a video state is set in a video state bit-mask.
*
* @param videoState The video state bit mask.
diff --git a/com/android/internal/alsa/AlsaDevicesParser.java b/com/android/internal/alsa/AlsaDevicesParser.java
index 7cdd8970..6e3d5966 100644
--- a/com/android/internal/alsa/AlsaDevicesParser.java
+++ b/com/android/internal/alsa/AlsaDevicesParser.java
@@ -258,7 +258,7 @@ public class AlsaDevicesParser {
return line.charAt(kIndex_CardDeviceField) == '[';
}
- public void scan() {
+ public boolean scan() {
mDeviceRecords.clear();
File devicesFile = new File(kDevicesFilePath);
@@ -274,11 +274,13 @@ public class AlsaDevicesParser {
}
}
reader.close();
+ return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
+ return false;
}
//
diff --git a/com/android/internal/app/ChooserActivity.java b/com/android/internal/app/ChooserActivity.java
index 2cab009f..6e0ba341 100644
--- a/com/android/internal/app/ChooserActivity.java
+++ b/com/android/internal/app/ChooserActivity.java
@@ -100,7 +100,7 @@ public class ChooserActivity extends ResolverActivity {
private static final boolean DEBUG = false;
private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
- private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
+ private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
@@ -1450,11 +1450,16 @@ public class ChooserActivity extends ResolverActivity {
getFirstRowPosition(rowPosition + 1));
int serviceSpacing = holder.row.getContext().getResources()
.getDimensionPixelSize(R.dimen.chooser_service_spacing);
- int top = rowPosition == 0 ? serviceSpacing : 0;
- if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
- setVertPadding(holder, top, serviceSpacing);
+ if (rowPosition == 0 && nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+ // if the row is the only row for target service
+ setVertPadding(holder, 0, 0);
} else {
- setVertPadding(holder, top, 0);
+ int top = rowPosition == 0 ? serviceSpacing : 0;
+ if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+ setVertPadding(holder, top, serviceSpacing);
+ } else {
+ setVertPadding(holder, top, 0);
+ }
}
} else {
holder.row.setBackgroundColor(Color.TRANSPARENT);
@@ -1580,8 +1585,8 @@ public class ChooserActivity extends ResolverActivity {
} catch (RemoteException e) {
Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
mChooserActivity.unbindService(this);
- destroy();
mChooserActivity.mServiceConnections.remove(this);
+ destroy();
}
}
}
@@ -1597,7 +1602,6 @@ public class ChooserActivity extends ResolverActivity {
}
mChooserActivity.unbindService(this);
- destroy();
mChooserActivity.mServiceConnections.remove(this);
if (mChooserActivity.mServiceConnections.isEmpty()) {
mChooserActivity.mChooserHandler.removeMessages(
@@ -1605,6 +1609,7 @@ public class ChooserActivity extends ResolverActivity {
mChooserActivity.sendVoiceChoicesIfNeeded();
}
mConnectedComponent = null;
+ destroy();
}
}
diff --git a/com/android/internal/app/NightDisplayController.java b/com/android/internal/app/NightDisplayController.java
index 860c5c4c..7a1383c7 100644
--- a/com/android/internal/app/NightDisplayController.java
+++ b/com/android/internal/app/NightDisplayController.java
@@ -32,8 +32,12 @@ import com.android.internal.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Calendar;
-import java.util.Locale;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
/**
* Controller for managing Night display settings.
@@ -116,8 +120,9 @@ public final class NightDisplayController {
*/
public boolean setActivated(boolean activated) {
if (isActivated() != activated) {
- Secure.putLongForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, System.currentTimeMillis(),
+ Secure.putStringForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+ LocalDateTime.now().toString(),
mUserId);
}
return Secure.putIntForUser(mContext.getContentResolver(),
@@ -128,17 +133,22 @@ public final class NightDisplayController {
* Returns the time when Night display's activation state last changed, or {@code null} if it
* has never been changed.
*/
- public Calendar getLastActivatedTime() {
+ public LocalDateTime getLastActivatedTime() {
final ContentResolver cr = mContext.getContentResolver();
- final long lastActivatedTimeMillis = Secure.getLongForUser(
- cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1, mUserId);
- if (lastActivatedTimeMillis < 0) {
- return null;
+ final String lastActivatedTime = Secure.getStringForUser(
+ cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, mUserId);
+ if (lastActivatedTime != null) {
+ try {
+ return LocalDateTime.parse(lastActivatedTime);
+ } catch (DateTimeParseException ignored) {}
+ // Uses the old epoch time.
+ try {
+ return LocalDateTime.ofInstant(
+ Instant.ofEpochMilli(Long.parseLong(lastActivatedTime)),
+ ZoneId.systemDefault());
+ } catch (DateTimeException|NumberFormatException ignored) {}
}
-
- final Calendar lastActivatedTime = Calendar.getInstance();
- lastActivatedTime.setTimeInMillis(lastActivatedTimeMillis);
- return lastActivatedTime;
+ return null;
}
/**
@@ -183,8 +193,10 @@ public final class NightDisplayController {
}
if (getAutoMode() != autoMode) {
- Secure.putLongForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1L, mUserId);
+ Secure.putStringForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+ null,
+ mUserId);
}
return Secure.putIntForUser(mContext.getContentResolver(),
Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId);
@@ -206,7 +218,7 @@ public final class NightDisplayController {
R.integer.config_defaultNightDisplayCustomStartTime);
}
- return LocalTime.valueOf(startTimeValue);
+ return LocalTime.ofSecondOfDay(startTimeValue / 1000);
}
/**
@@ -221,7 +233,7 @@ public final class NightDisplayController {
throw new IllegalArgumentException("startTime cannot be null");
}
return Secure.putIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toMillis(), mUserId);
+ Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toSecondOfDay() * 1000, mUserId);
}
/**
@@ -240,7 +252,7 @@ public final class NightDisplayController {
R.integer.config_defaultNightDisplayCustomEndTime);
}
- return LocalTime.valueOf(endTimeValue);
+ return LocalTime.ofSecondOfDay(endTimeValue / 1000);
}
/**
@@ -255,7 +267,7 @@ public final class NightDisplayController {
throw new IllegalArgumentException("endTime cannot be null");
}
return Secure.putIntForUser(mContext.getContentResolver(),
- Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId);
+ Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toSecondOfDay() * 1000, mUserId);
}
/**
@@ -379,106 +391,6 @@ public final class NightDisplayController {
}
/**
- * A time without a time-zone or date.
- */
- public static class LocalTime {
-
- /**
- * The hour of the day from 0 - 23.
- */
- public final int hourOfDay;
- /**
- * The minute within the hour from 0 - 59.
- */
- public final int minute;
-
- public LocalTime(int hourOfDay, int minute) {
- if (hourOfDay < 0 || hourOfDay > 23) {
- throw new IllegalArgumentException("Invalid hourOfDay: " + hourOfDay);
- } else if (minute < 0 || minute > 59) {
- throw new IllegalArgumentException("Invalid minute: " + minute);
- }
-
- this.hourOfDay = hourOfDay;
- this.minute = minute;
- }
-
- /**
- * Returns the first date time corresponding to this local time that occurs before the
- * provided date time.
- *
- * @param time the date time to compare against
- * @return the prior date time corresponding to this local time
- */
- public Calendar getDateTimeBefore(Calendar time) {
- final Calendar c = Calendar.getInstance();
- c.set(Calendar.YEAR, time.get(Calendar.YEAR));
- c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR));
-
- c.set(Calendar.HOUR_OF_DAY, hourOfDay);
- c.set(Calendar.MINUTE, minute);
- c.set(Calendar.SECOND, 0);
- c.set(Calendar.MILLISECOND, 0);
-
- // Check if the local time has past, if so return the same time tomorrow.
- if (c.after(time)) {
- c.add(Calendar.DATE, -1);
- }
-
- return c;
- }
-
- /**
- * Returns the first date time corresponding to this local time that occurs after the
- * provided date time.
- *
- * @param time the date time to compare against
- * @return the next date time corresponding to this local time
- */
- public Calendar getDateTimeAfter(Calendar time) {
- final Calendar c = Calendar.getInstance();
- c.set(Calendar.YEAR, time.get(Calendar.YEAR));
- c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR));
-
- c.set(Calendar.HOUR_OF_DAY, hourOfDay);
- c.set(Calendar.MINUTE, minute);
- c.set(Calendar.SECOND, 0);
- c.set(Calendar.MILLISECOND, 0);
-
- // Check if the local time has past, if so return the same time tomorrow.
- if (c.before(time)) {
- c.add(Calendar.DATE, 1);
- }
-
- return c;
- }
-
- /**
- * Returns a local time corresponding the given number of milliseconds from midnight.
- *
- * @param millis the number of milliseconds from midnight
- * @return the corresponding local time
- */
- private static LocalTime valueOf(int millis) {
- final int hourOfDay = (millis / 3600000) % 24;
- final int minutes = (millis / 60000) % 60;
- return new LocalTime(hourOfDay, minutes);
- }
-
- /**
- * Returns the local time represented as milliseconds from midnight.
- */
- private int toMillis() {
- return hourOfDay * 3600000 + minute * 60000;
- }
-
- @Override
- public String toString() {
- return String.format(Locale.US, "%02d:%02d", hourOfDay, minute);
- }
- }
-
- /**
* Callback invoked whenever the Night display settings are changed.
*/
public interface Callback {
diff --git a/com/android/internal/app/ShutdownActivity.java b/com/android/internal/app/ShutdownActivity.java
index 745d28f3..f81e8383 100644
--- a/com/android/internal/app/ShutdownActivity.java
+++ b/com/android/internal/app/ShutdownActivity.java
@@ -41,6 +41,9 @@ public class ShutdownActivity extends Activity {
mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
mUserRequested = intent.getBooleanExtra(Intent.EXTRA_USER_REQUESTED_SHUTDOWN, false);
+ final String reason = mUserRequested
+ ? PowerManager.SHUTDOWN_USER_REQUESTED
+ : intent.getStringExtra(Intent.EXTRA_REASON);
Slog.i(TAG, "onCreate(): confirm=" + mConfirm);
Thread thr = new Thread("ShutdownActivity") {
@@ -52,9 +55,7 @@ public class ShutdownActivity extends Activity {
if (mReboot) {
pm.reboot(mConfirm, null, false);
} else {
- pm.shutdown(mConfirm,
- mUserRequested ? PowerManager.SHUTDOWN_USER_REQUESTED : null,
- false);
+ pm.shutdown(mConfirm, reason, false);
}
} catch (RemoteException e) {
}
diff --git a/com/android/internal/app/procstats/DumpUtils.java b/com/android/internal/app/procstats/DumpUtils.java
index ebedc89c..0bc8c483 100644
--- a/com/android/internal/app/procstats/DumpUtils.java
+++ b/com/android/internal/app/procstats/DumpUtils.java
@@ -29,6 +29,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import static com.android.internal.app.procstats.ProcessStats.*;
@@ -66,6 +67,8 @@ public final class DumpUtils {
"cch-activity", "cch-aclient", "cch-empty"
};
+ // State enum is defined in frameworks/base/core/proto/android/service/procstats.proto
+ // Update states must sync enum definition as well, the ordering must not be changed.
static final String[] ADJ_SCREEN_TAGS = new String[] {
"0", "1"
};
@@ -177,6 +180,13 @@ public final class DumpUtils {
printArrayEntry(pw, STATE_TAGS, state, 1);
}
+ public static void printProcStateTagProto(ProtoOutputStream proto, long screenId, long memId,
+ long stateId, int state) {
+ state = printProto(proto, screenId, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD * STATE_COUNT);
+ state = printProto(proto, memId, ADJ_MEM_TAGS, state, STATE_COUNT);
+ printProto(proto, stateId, STATE_TAGS, state, 1);
+ }
+
public static void printAdjTag(PrintWriter pw, int state) {
state = printArrayEntry(pw, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD);
printArrayEntry(pw, ADJ_MEM_TAGS, state, 1);
@@ -352,6 +362,15 @@ public final class DumpUtils {
return value - index*mod;
}
+ public static int printProto(ProtoOutputStream proto, long fieldId, String[] array, int value, int mod) {
+ int index = value/mod;
+ if (index >= 0 && index < array.length) {
+ // Valid state enum number starts at 1, 0 stands for unknown.
+ proto.write(fieldId, index + 1);
+ } // else enum default is always zero in proto3
+ return value - index*mod;
+ }
+
public static String collapseString(String pkgName, String itemName) {
if (itemName.startsWith(pkgName)) {
final int ITEMLEN = itemName.length();
diff --git a/com/android/internal/app/procstats/ProcessState.java b/com/android/internal/app/procstats/ProcessState.java
index e0a40536..7519fce4 100644
--- a/com/android/internal/app/procstats/ProcessState.java
+++ b/com/android/internal/app/procstats/ProcessState.java
@@ -21,6 +21,8 @@ import android.os.Parcelable;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.service.pm.PackageProto;
+import android.service.procstats.ProcessStatsProto;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -29,6 +31,8 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.app.procstats.ProcessStats.PackageState;
@@ -69,6 +73,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
public final class ProcessState {
@@ -1157,6 +1164,7 @@ public final class ProcessState {
}
}
+ @Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("ProcessState{").append(Integer.toHexString(System.identityHashCode(this)))
@@ -1167,4 +1175,76 @@ public final class ProcessState {
sb.append("}");
return sb.toString();
}
+
+ public void toProto(ProtoOutputStream proto, String procName, int uid, long now) {
+ proto.write(ProcessStatsProto.PROCESS, procName);
+ proto.write(ProcessStatsProto.UID, uid);
+ if (mNumExcessiveCpu > 0 || mNumCachedKill > 0 ) {
+ final long killToken = proto.start(ProcessStatsProto.KILL);
+ proto.write(ProcessStatsProto.Kill.CPU, mNumExcessiveCpu);
+ proto.write(ProcessStatsProto.Kill.CACHED, mNumCachedKill);
+ ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.Kill.CACHED_PSS,
+ mMinCachedKillPss, mAvgCachedKillPss, mMaxCachedKillPss);
+ proto.end(killToken);
+ }
+
+ // Group proc stats by type (screen state + mem state + process state)
+ Map<Integer, Long> durationByState = new HashMap<>();
+ boolean didCurState = false;
+ for (int i=0; i<mDurations.getKeyCount(); i++) {
+ final int key = mDurations.getKeyAt(i);
+ final int type = SparseMappingTable.getIdFromKey(key);
+ long time = mDurations.getValue(key);
+ if (mCurState == type) {
+ didCurState = true;
+ time += now - mStartTime;
+ }
+ durationByState.put(type, time);
+ }
+ if (!didCurState && mCurState != STATE_NOTHING) {
+ durationByState.put(mCurState, now - mStartTime);
+ }
+
+ for (int i=0; i<mPssTable.getKeyCount(); i++) {
+ final int key = mPssTable.getKeyAt(i);
+ final int type = SparseMappingTable.getIdFromKey(key);
+ if (!durationByState.containsKey(type)) {
+ // state without duration should not have stats!
+ continue;
+ }
+ final long stateToken = proto.start(ProcessStatsProto.STATES);
+ DumpUtils.printProcStateTagProto(proto,
+ ProcessStatsProto.State.SCREEN_STATE,
+ ProcessStatsProto.State.MEMORY_STATE,
+ ProcessStatsProto.State.PROCESS_STATE,
+ type);
+
+ long duration = durationByState.get(type);
+ durationByState.remove(type); // remove the key since it is already being dumped.
+ proto.write(ProcessStatsProto.State.DURATION_MS, duration);
+
+ proto.write(ProcessStatsProto.State.SAMPLE_SIZE, mPssTable.getValue(key, PSS_SAMPLE_COUNT));
+ ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.PSS,
+ mPssTable.getValue(key, PSS_MINIMUM),
+ mPssTable.getValue(key, PSS_AVERAGE),
+ mPssTable.getValue(key, PSS_MAXIMUM));
+ ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.USS,
+ mPssTable.getValue(key, PSS_USS_MINIMUM),
+ mPssTable.getValue(key, PSS_USS_AVERAGE),
+ mPssTable.getValue(key, PSS_USS_MAXIMUM));
+
+ proto.end(stateToken);
+ }
+
+ for (Map.Entry<Integer, Long> entry : durationByState.entrySet()) {
+ final long stateToken = proto.start(ProcessStatsProto.STATES);
+ DumpUtils.printProcStateTagProto(proto,
+ ProcessStatsProto.State.SCREEN_STATE,
+ ProcessStatsProto.State.MEMORY_STATE,
+ ProcessStatsProto.State.PROCESS_STATE,
+ entry.getKey());
+ proto.write(ProcessStatsProto.State.DURATION_MS, entry.getValue());
+ proto.end(stateToken);
+ }
+ }
}
diff --git a/com/android/internal/app/procstats/ProcessStats.java b/com/android/internal/app/procstats/ProcessStats.java
index 35b53c22..14f5e5b5 100644
--- a/com/android/internal/app/procstats/ProcessStats.java
+++ b/com/android/internal/app/procstats/ProcessStats.java
@@ -22,6 +22,7 @@ import android.os.Parcelable;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.service.procstats.ProcessStatsSectionProto;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -30,6 +31,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.procstats.DurationsTable;
@@ -1706,6 +1708,46 @@ public final class ProcessStats implements Parcelable {
}
}
+ public void toProto(ProtoOutputStream proto, long now) {
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+
+ proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
+ proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
+ mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
+ proto.write(ProcessStatsSectionProto.START_UPTIME_MS, mTimePeriodStartUptime);
+ proto.write(ProcessStatsSectionProto.END_UPTIME_MS, mTimePeriodEndUptime);
+ proto.write(ProcessStatsSectionProto.RUNTIME, mRuntime);
+ proto.write(ProcessStatsSectionProto.HAS_SWAPPED_PSS, mHasSwappedOutPss);
+ boolean partial = true;
+ if ((mFlags&FLAG_SHUTDOWN) != 0) {
+ proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SHUTDOWN);
+ partial = false;
+ }
+ if ((mFlags&FLAG_SYSPROPS) != 0) {
+ proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SYSPROPS);
+ partial = false;
+ }
+ if ((mFlags&FLAG_COMPLETE) != 0) {
+ proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_COMPLETE);
+ partial = false;
+ }
+ if (partial) {
+ proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_PARTIAL);
+ }
+
+ ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+ for (int ip=0; ip<procMap.size(); ip++) {
+ String procName = procMap.keyAt(ip);
+ SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ for (int iu=0; iu<uids.size(); iu++) {
+ final int uid = uids.keyAt(iu);
+ final ProcessState procState = uids.valueAt(iu);
+ final long processStateToken = proto.start(ProcessStatsSectionProto.PROCESS_STATS);
+ procState.toProto(proto, procName, uid, now);
+ proto.end(processStateToken);
+ }
+ }
+ }
final public static class ProcessStateHolder {
public final int appVersion;
diff --git a/com/android/internal/content/PackageHelper.java b/com/android/internal/content/PackageHelper.java
index e923223d..59a7995a 100644
--- a/com/android/internal/content/PackageHelper.java
+++ b/com/android/internal/content/PackageHelper.java
@@ -16,7 +16,6 @@
package com.android.internal.content;
-import static android.net.TrafficStats.MB_IN_BYTES;
import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
import android.content.Context;
@@ -27,13 +26,11 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageParser.PackageLite;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
-import android.os.storage.StorageResultCode;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.provider.Settings;
@@ -45,15 +42,9 @@ import com.android.internal.annotations.VisibleForTesting;
import libcore.io.IoUtils;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
/**
* Constants used internally between the PackageManager
@@ -72,7 +63,6 @@ public class PackageHelper {
public static final int RECOMMEND_FAILED_INVALID_URI = -6;
public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
- private static final boolean localLOGV = false;
private static final String TAG = "PackageHelper";
// App installation location settings values
public static final int APP_INSTALL_AUTO = 0;
@@ -91,259 +81,6 @@ public class PackageHelper {
}
}
- public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
- boolean isExternal) {
- // Round up to nearest MB, plus another MB for filesystem overhead
- final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
- try {
- IStorageManager storageManager = getStorageManager();
-
- if (localLOGV)
- Log.i(TAG, "Size of container " + sizeMb + " MB");
-
- int rc = storageManager.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
- isExternal);
- if (rc != StorageResultCode.OperationSucceeded) {
- Log.e(TAG, "Failed to create secure container " + cid);
- return null;
- }
- String cachePath = storageManager.getSecureContainerPath(cid);
- if (localLOGV) Log.i(TAG, "Created secure container " + cid +
- " at " + cachePath);
- return cachePath;
- } catch (RemoteException e) {
- Log.e(TAG, "StorageManagerService running?");
- }
- return null;
- }
-
- public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
- // Round up to nearest MB, plus another MB for filesystem overhead
- final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
- try {
- IStorageManager storageManager = getStorageManager();
- int rc = storageManager.resizeSecureContainer(cid, sizeMb, sdEncKey);
- if (rc == StorageResultCode.OperationSucceeded) {
- return true;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "StorageManagerService running?");
- }
- Log.e(TAG, "Failed to create secure container " + cid);
- return false;
- }
-
- public static String mountSdDir(String cid, String key, int ownerUid) {
- return mountSdDir(cid, key, ownerUid, true);
- }
-
- public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
- try {
- int rc = getStorageManager().mountSecureContainer(cid, key, ownerUid, readOnly);
- if (rc != StorageResultCode.OperationSucceeded) {
- Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
- return null;
- }
- return getStorageManager().getSecureContainerPath(cid);
- } catch (RemoteException e) {
- Log.e(TAG, "StorageManagerService running?");
- }
- return null;
- }
-
- public static boolean unMountSdDir(String cid) {
- try {
- int rc = getStorageManager().unmountSecureContainer(cid, true);
- if (rc != StorageResultCode.OperationSucceeded) {
- Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
- return false;
- }
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "StorageManagerService running?");
- }
- return false;
- }
-
- public static boolean renameSdDir(String oldId, String newId) {
- try {
- int rc = getStorageManager().renameSecureContainer(oldId, newId);
- if (rc != StorageResultCode.OperationSucceeded) {
- Log.e(TAG, "Failed to rename " + oldId + " to " +
- newId + "with rc " + rc);
- return false;
- }
- return true;
- } catch (RemoteException e) {
- Log.i(TAG, "Failed ot rename " + oldId + " to " + newId +
- " with exception : " + e);
- }
- return false;
- }
-
- public static String getSdDir(String cid) {
- try {
- return getStorageManager().getSecureContainerPath(cid);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get container path for " + cid +
- " with exception " + e);
- }
- return null;
- }
-
- public static String getSdFilesystem(String cid) {
- try {
- return getStorageManager().getSecureContainerFilesystemPath(cid);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get container path for " + cid +
- " with exception " + e);
- }
- return null;
- }
-
- public static boolean finalizeSdDir(String cid) {
- try {
- int rc = getStorageManager().finalizeSecureContainer(cid);
- if (rc != StorageResultCode.OperationSucceeded) {
- Log.i(TAG, "Failed to finalize container " + cid);
- return false;
- }
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to finalize container " + cid +
- " with exception " + e);
- }
- return false;
- }
-
- public static boolean destroySdDir(String cid) {
- try {
- if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
- int rc = getStorageManager().destroySecureContainer(cid, true);
- if (rc != StorageResultCode.OperationSucceeded) {
- Log.i(TAG, "Failed to destroy container " + cid);
- return false;
- }
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to destroy container " + cid +
- " with exception " + e);
- }
- return false;
- }
-
- public static String[] getSecureContainerList() {
- try {
- return getStorageManager().getSecureContainerList();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get secure container list with exception" +
- e);
- }
- return null;
- }
-
- public static boolean isContainerMounted(String cid) {
- try {
- return getStorageManager().isSecureContainerMounted(cid);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to find out if container " + cid + " mounted");
- }
- return false;
- }
-
- /**
- * Extract public files for the single given APK.
- */
- public static long extractPublicFiles(File apkFile, File publicZipFile)
- throws IOException {
- final FileOutputStream fstr;
- final ZipOutputStream publicZipOutStream;
-
- if (publicZipFile == null) {
- fstr = null;
- publicZipOutStream = null;
- } else {
- fstr = new FileOutputStream(publicZipFile);
- publicZipOutStream = new ZipOutputStream(fstr);
- Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
- }
-
- long size = 0L;
-
- try {
- final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
- try {
- // Copy manifest, resources.arsc and res directory to public zip
- for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
- final String zipEntryName = zipEntry.getName();
- if ("AndroidManifest.xml".equals(zipEntryName)
- || "resources.arsc".equals(zipEntryName)
- || zipEntryName.startsWith("res/")) {
- size += zipEntry.getSize();
- if (publicZipFile != null) {
- copyZipEntry(zipEntry, privateZip, publicZipOutStream);
- }
- }
- }
- } finally {
- try { privateZip.close(); } catch (IOException e) {}
- }
-
- if (publicZipFile != null) {
- publicZipOutStream.finish();
- publicZipOutStream.flush();
- FileUtils.sync(fstr);
- publicZipOutStream.close();
- FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
- | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
- }
- } finally {
- IoUtils.closeQuietly(publicZipOutStream);
- }
-
- return size;
- }
-
- private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
- ZipOutputStream outZipStream) throws IOException {
- byte[] buffer = new byte[4096];
- int num;
-
- ZipEntry newEntry;
- if (zipEntry.getMethod() == ZipEntry.STORED) {
- // Preserve the STORED method of the input entry.
- newEntry = new ZipEntry(zipEntry);
- } else {
- // Create a new entry so that the compressed len is recomputed.
- newEntry = new ZipEntry(zipEntry.getName());
- }
- outZipStream.putNextEntry(newEntry);
-
- final InputStream data = inZipFile.getInputStream(zipEntry);
- try {
- while ((num = data.read(buffer)) > 0) {
- outZipStream.write(buffer, 0, num);
- }
- outZipStream.flush();
- } finally {
- IoUtils.closeQuietly(data);
- }
- }
-
- public static boolean fixSdPermissions(String cid, int gid, String filename) {
- try {
- int rc = getStorageManager().fixPermissionsSecureContainer(cid, gid, filename);
- if (rc != StorageResultCode.OperationSucceeded) {
- Log.i(TAG, "Failed to fixperms container " + cid);
- return false;
- }
- return true;
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
- }
- return false;
- }
-
/**
* A group of external dependencies used in
* {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
@@ -638,29 +375,37 @@ public class PackageHelper {
return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
}
+ @Deprecated
public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
String abiOverride) throws IOException {
+ return calculateInstalledSize(pkg, abiOverride);
+ }
+
+ public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
+ throws IOException {
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(pkg);
- return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
+ return calculateInstalledSize(pkg, handle, abiOverride);
} finally {
IoUtils.closeQuietly(handle);
}
}
+ @Deprecated
+ public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
+ NativeLibraryHelper.Handle handle, String abiOverride) throws IOException {
+ return calculateInstalledSize(pkg, handle, abiOverride);
+ }
+
public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
- boolean isForwardLocked, String abiOverride) throws IOException {
+ String abiOverride) throws IOException {
long sizeBytes = 0;
// Include raw APKs, and possibly unpacked resources
for (String codePath : pkg.getAllCodePaths()) {
final File codeFile = new File(codePath);
sizeBytes += codeFile.length();
-
- if (isForwardLocked) {
- sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
- }
}
// Include all relevant native code
diff --git a/com/android/internal/os/BatteryStatsHelper.java b/com/android/internal/os/BatteryStatsHelper.java
index f085e290..15dc6f50 100644
--- a/com/android/internal/os/BatteryStatsHelper.java
+++ b/com/android/internal/os/BatteryStatsHelper.java
@@ -143,6 +143,9 @@ public class BatteryStatsHelper {
public static boolean checkWifiOnly(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
+ if (cm == null) {
+ return false;
+ }
return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
}
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index 0bd29815..36fd991c 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -119,7 +119,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 166 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 167 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS;
@@ -341,8 +341,8 @@ public class BatteryStatsImpl extends BatteryStats {
protected final TimeBase mOnBatteryTimeBase = new TimeBase();
// These are the objects that will want to do something when the device
- // is unplugged from power *and* the screen is off.
- final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
+ // is unplugged from power *and* the screen is off or doze.
+ protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
// Set to true when we want to distribute CPU across wakelocks for the next
// CPU update, even if we aren't currently running wake locks.
@@ -436,8 +436,12 @@ public class BatteryStatsImpl extends BatteryStats {
public boolean mRecordAllHistory;
boolean mNoAutoReset;
- int mScreenState = Display.STATE_UNKNOWN;
- StopwatchTimer mScreenOnTimer;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected int mScreenState = Display.STATE_UNKNOWN;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected StopwatchTimer mScreenOnTimer;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected StopwatchTimer mScreenDozeTimer;
int mScreenBrightnessBin = -1;
final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -583,12 +587,16 @@ public class BatteryStatsImpl extends BatteryStats {
int mHighDischargeAmountSinceCharge;
int mDischargeScreenOnUnplugLevel;
int mDischargeScreenOffUnplugLevel;
+ int mDischargeScreenDozeUnplugLevel;
int mDischargeAmountScreenOn;
int mDischargeAmountScreenOnSinceCharge;
int mDischargeAmountScreenOff;
int mDischargeAmountScreenOffSinceCharge;
+ int mDischargeAmountScreenDoze;
+ int mDischargeAmountScreenDozeSinceCharge;
private LongSamplingCounter mDischargeScreenOffCounter;
+ private LongSamplingCounter mDischargeScreenDozeCounter;
private LongSamplingCounter mDischargeCounter;
static final int MAX_LEVEL_STEPS = 200;
@@ -673,13 +681,18 @@ public class BatteryStatsImpl extends BatteryStats {
}
@Override
- public LongCounter getDischargeScreenOffCoulombCounter() {
- return mDischargeScreenOffCounter;
+ public long getMahDischarge(int which) {
+ return mDischargeCounter.getCountLocked(which);
+ }
+
+ @Override
+ public long getMahDischargeScreenOff(int which) {
+ return mDischargeScreenOffCounter.getCountLocked(which);
}
@Override
- public LongCounter getDischargeCoulombCounter() {
- return mDischargeCounter;
+ public long getMahDischargeScreenDoze(int which) {
+ return mDischargeScreenDozeCounter.getCountLocked(which);
}
@Override
@@ -3573,8 +3586,9 @@ public class BatteryStatsImpl extends BatteryStats {
mActiveHistoryStates2 = 0xffffffff;
}
- public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
+ public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime,
long realtime) {
+ final boolean screenOff = isScreenOff(screenState) || isScreenDoze(screenState);
final boolean updateOnBatteryTimeBase = unplugged != mOnBatteryTimeBase.isRunning();
final boolean updateOnBatteryScreenOffTimeBase =
(unplugged && screenOff) != mOnBatteryScreenOffTimeBase.isRunning();
@@ -3591,20 +3605,22 @@ public class BatteryStatsImpl extends BatteryStats {
updateRpmStatsLocked(); // if either OnBattery or OnBatteryScreenOff timebase changes.
}
if (DEBUG_ENERGY_CPU) {
- Slog.d(TAG, "Updating cpu time because screen is now " + (screenOff ? "off" : "on")
+ Slog.d(TAG, "Updating cpu time because screen is now "
+ + Display.stateToString(screenState)
+ " and battery is " + (unplugged ? "on" : "off"));
}
updateCpuTimeLocked();
mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
- mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
- for (int i = mUidStats.size() - 1; i >= 0; --i) {
- final Uid u = mUidStats.valueAt(i);
- if (updateOnBatteryTimeBase) {
- u.updateOnBatteryBgTimeBase(uptime, realtime);
+ if (updateOnBatteryTimeBase) {
+ for (int i = mUidStats.size() - 1; i >= 0; --i) {
+ mUidStats.valueAt(i).updateOnBatteryBgTimeBase(uptime, realtime);
}
- if (updateOnBatteryScreenOffTimeBase) {
- u.updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
+ }
+ if (updateOnBatteryScreenOffTimeBase) {
+ mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
+ for (int i = mUidStats.size() - 1; i >= 0; --i) {
+ mUidStats.valueAt(i).updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
}
}
}
@@ -3864,8 +3880,10 @@ public class BatteryStatsImpl extends BatteryStats {
}
public void setPretendScreenOff(boolean pretendScreenOff) {
- mPretendScreenOff = pretendScreenOff;
- noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+ if (mPretendScreenOff != pretendScreenOff) {
+ mPretendScreenOff = pretendScreenOff;
+ noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+ }
}
private String mInitialAcquireWakeName;
@@ -4195,54 +4213,58 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- if (state == Display.STATE_ON) {
- // Screen turning on.
- final long elapsedRealtime = mClocks.elapsedRealtime();
- final long uptime = mClocks.uptimeMillis();
+ final long elapsedRealtime = mClocks.elapsedRealtime();
+ final long uptime = mClocks.uptimeMillis();
+
+ boolean updateHistory = false;
+ if (isScreenDoze(state)) {
+ mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+ mScreenDozeTimer.startRunningLocked(elapsedRealtime);
+ updateHistory = true;
+ } else if (isScreenDoze(oldState)) {
+ mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
+ mScreenDozeTimer.stopRunningLocked(elapsedRealtime);
+ updateHistory = true;
+ }
+ if (isScreenOn(state)) {
mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtime, uptime);
mScreenOnTimer.startRunningLocked(elapsedRealtime);
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime);
}
-
- updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false,
- mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
-
- // Fake a wake lock, so we consider the device waked as long
- // as the screen is on.
- noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
- elapsedRealtime, uptime);
-
- // Update discharge amounts.
- if (mOnBatteryInternal) {
- updateDischargeScreenLevelsLocked(false, true);
- }
- } else if (oldState == Display.STATE_ON) {
- // Screen turning off or dozing.
- final long elapsedRealtime = mClocks.elapsedRealtime();
- final long uptime = mClocks.uptimeMillis();
+ updateHistory = true;
+ } else if (isScreenOn(oldState)) {
mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtime, uptime);
mScreenOnTimer.stopRunningLocked(elapsedRealtime);
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
}
-
+ updateHistory = true;
+ }
+ if (updateHistory) {
+ if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: "
+ + Display.stateToString(state));
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
+ if (isScreenOn(state)) {
+ updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
+ mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
+ // Fake a wake lock, so we consider the device waked as long as the screen is on.
+ noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
+ elapsedRealtime, uptime);
+ } else if (isScreenOn(oldState)) {
noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
elapsedRealtime, uptime);
-
- updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true,
+ updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
-
- // Update discharge amounts.
- if (mOnBatteryInternal) {
- updateDischargeScreenLevelsLocked(true, false);
- }
+ }
+ // Update discharge amounts.
+ if (mOnBatteryInternal) {
+ updateDischargeScreenLevelsLocked(oldState, state);
}
}
}
@@ -5391,6 +5413,14 @@ public class BatteryStatsImpl extends BatteryStats {
return mScreenOnTimer.getCountLocked(which);
}
+ @Override public long getScreenDozeTime(long elapsedRealtimeUs, int which) {
+ return mScreenDozeTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public int getScreenDozeCount(int which) {
+ return mScreenDozeTimer.getCountLocked(which);
+ }
+
@Override public long getScreenBrightnessTime(int brightnessBin,
long elapsedRealtimeUs, int which) {
return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
@@ -8829,6 +8859,7 @@ public class BatteryStatsImpl extends BatteryStats {
mHandler = new MyHandler(handler.getLooper());
mStartCount++;
mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
+ mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
mOnBatteryTimeBase);
@@ -8887,6 +8918,7 @@ public class BatteryStatsImpl extends BatteryStats {
mCameraOnTimer = new StopwatchTimer(mClocks, null, -13, null, mOnBatteryTimeBase);
mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
+ mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
mOnBattery = mOnBatteryInternal = false;
long uptime = mClocks.uptimeMillis() * 1000;
@@ -9430,8 +9462,16 @@ public class BatteryStatsImpl extends BatteryStats {
return mCharging;
}
- public boolean isScreenOn() {
- return mScreenState == Display.STATE_ON;
+ public boolean isScreenOn(int state) {
+ return state == Display.STATE_ON;
+ }
+
+ public boolean isScreenOff(int state) {
+ return state == Display.STATE_OFF;
+ }
+
+ public boolean isScreenDoze(int state) {
+ return state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND;
}
void initTimes(long uptime, long realtime) {
@@ -9451,9 +9491,12 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOnSinceCharge = 0;
mDischargeAmountScreenOff = 0;
mDischargeAmountScreenOffSinceCharge = 0;
+ mDischargeAmountScreenDoze = 0;
+ mDischargeAmountScreenDozeSinceCharge = 0;
mDischargeStepTracker.init();
mChargeStepTracker.init();
mDischargeScreenOffCounter.reset(false);
+ mDischargeScreenDozeCounter.reset(false);
mDischargeCounter.reset(false);
}
@@ -9471,15 +9514,22 @@ public class BatteryStatsImpl extends BatteryStats {
mOnBatteryTimeBase.reset(uptime, realtime);
mOnBatteryScreenOffTimeBase.reset(uptime, realtime);
if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
- if (mScreenState == Display.STATE_ON) {
+ if (isScreenOn(mScreenState)) {
mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenDozeUnplugLevel = 0;
+ mDischargeScreenOffUnplugLevel = 0;
+ } else if (isScreenDoze(mScreenState)) {
+ mDischargeScreenOnUnplugLevel = 0;
+ mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel;
mDischargeScreenOffUnplugLevel = 0;
} else {
mDischargeScreenOnUnplugLevel = 0;
+ mDischargeScreenDozeUnplugLevel = 0;
mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
}
mDischargeAmountScreenOn = 0;
mDischargeAmountScreenOff = 0;
+ mDischargeAmountScreenDoze = 0;
}
initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
}
@@ -9490,6 +9540,7 @@ public class BatteryStatsImpl extends BatteryStats {
mStartCount = 0;
initTimes(uptimeMillis * 1000, elapsedRealtimeMillis * 1000);
mScreenOnTimer.reset(false);
+ mScreenDozeTimer.reset(false);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i].reset(false);
}
@@ -9626,33 +9677,52 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) {
- if (oldScreenOn) {
+ void updateDischargeScreenLevelsLocked(int oldState, int newState) {
+ updateOldDischargeScreenLevelLocked(oldState);
+ updateNewDischargeScreenLevelLocked(newState);
+ }
+
+ private void updateOldDischargeScreenLevelLocked(int state) {
+ if (isScreenOn(state)) {
int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel;
if (diff > 0) {
mDischargeAmountScreenOn += diff;
mDischargeAmountScreenOnSinceCharge += diff;
}
- } else {
+ } else if (isScreenDoze(state)) {
+ int diff = mDischargeScreenDozeUnplugLevel - mDischargeCurrentLevel;
+ if (diff > 0) {
+ mDischargeAmountScreenDoze += diff;
+ mDischargeAmountScreenDozeSinceCharge += diff;
+ }
+ } else if (isScreenOff(state)){
int diff = mDischargeScreenOffUnplugLevel - mDischargeCurrentLevel;
if (diff > 0) {
mDischargeAmountScreenOff += diff;
mDischargeAmountScreenOffSinceCharge += diff;
}
}
- if (newScreenOn) {
+ }
+
+ private void updateNewDischargeScreenLevelLocked(int state) {
+ if (isScreenOn(state)) {
mDischargeScreenOnUnplugLevel = mDischargeCurrentLevel;
mDischargeScreenOffUnplugLevel = 0;
- } else {
+ mDischargeScreenDozeUnplugLevel = 0;
+ } else if (isScreenDoze(state)){
+ mDischargeScreenOnUnplugLevel = 0;
+ mDischargeScreenDozeUnplugLevel = mDischargeCurrentLevel;
+ mDischargeScreenOffUnplugLevel = 0;
+ } else if (isScreenOff(state)) {
mDischargeScreenOnUnplugLevel = 0;
+ mDischargeScreenDozeUnplugLevel = 0;
mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel;
}
}
public void pullPendingStateUpdatesLocked() {
if (mOnBatteryInternal) {
- final boolean screenOn = mScreenState == Display.STATE_ON;
- updateDischargeScreenLevelsLocked(screenOn, screenOn);
+ updateDischargeScreenLevelsLocked(mScreenState, mScreenState);
}
}
@@ -10785,8 +10855,8 @@ public class BatteryStatsImpl extends BatteryStats {
return false;
}
- void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
- final int oldStatus, final int level, final int chargeUAh) {
+ protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
+ final boolean onBattery, final int oldStatus, final int level, final int chargeUAh) {
boolean doWrite = false;
Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
m.arg1 = onBattery ? 1 : 0;
@@ -10794,7 +10864,7 @@ public class BatteryStatsImpl extends BatteryStats {
final long uptime = mSecUptime * 1000;
final long realtime = mSecRealtime * 1000;
- final boolean screenOn = mScreenState == Display.STATE_ON;
+ final int screenState = mScreenState;
if (onBattery) {
// We will reset our status if we are unplugging after the
// battery was last full, or the level is at 100, or
@@ -10870,16 +10940,23 @@ public class BatteryStatsImpl extends BatteryStats {
}
addHistoryRecordLocked(mSecRealtime, mSecUptime);
mDischargeCurrentLevel = mDischargeUnplugLevel = level;
- if (screenOn) {
+ if (isScreenOn(screenState)) {
mDischargeScreenOnUnplugLevel = level;
+ mDischargeScreenDozeUnplugLevel = 0;
+ mDischargeScreenOffUnplugLevel = 0;
+ } else if (isScreenDoze(screenState)) {
+ mDischargeScreenOnUnplugLevel = 0;
+ mDischargeScreenDozeUnplugLevel = level;
mDischargeScreenOffUnplugLevel = 0;
} else {
mDischargeScreenOnUnplugLevel = 0;
+ mDischargeScreenDozeUnplugLevel = 0;
mDischargeScreenOffUnplugLevel = level;
}
mDischargeAmountScreenOn = 0;
+ mDischargeAmountScreenDoze = 0;
mDischargeAmountScreenOff = 0;
- updateTimeBasesLocked(true, !screenOn, uptime, realtime);
+ updateTimeBasesLocked(true, screenState, uptime, realtime);
} else {
mLastChargingStateLevel = level;
mOnBattery = mOnBatteryInternal = false;
@@ -10894,8 +10971,8 @@ public class BatteryStatsImpl extends BatteryStats {
mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
}
- updateDischargeScreenLevelsLocked(screenOn, screenOn);
- updateTimeBasesLocked(false, !screenOn, uptime, realtime);
+ updateDischargeScreenLevelsLocked(screenState, screenState);
+ updateTimeBasesLocked(false, screenState, uptime, realtime);
mChargeStepTracker.init();
mLastChargeStepLevel = level;
mMaxChargeStepLevel = level;
@@ -11012,6 +11089,9 @@ public class BatteryStatsImpl extends BatteryStats {
final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
mDischargeCounter.addCountLocked(chargeDiff);
mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+ if (isScreenDoze(mScreenState)) {
+ mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11054,6 +11134,9 @@ public class BatteryStatsImpl extends BatteryStats {
final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
mDischargeCounter.addCountLocked(chargeDiff);
mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+ if (isScreenDoze(mScreenState)) {
+ mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
+ }
}
mHistoryCur.batteryChargeUAh = chargeUAh;
changed = true;
@@ -11362,10 +11445,11 @@ public class BatteryStatsImpl extends BatteryStats {
return dischargeAmount;
}
+ @Override
public int getDischargeAmountScreenOn() {
synchronized(this) {
int val = mDischargeAmountScreenOn;
- if (mOnBattery && mScreenState == Display.STATE_ON
+ if (mOnBattery && isScreenOn(mScreenState)
&& mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
}
@@ -11373,10 +11457,11 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ @Override
public int getDischargeAmountScreenOnSinceCharge() {
synchronized(this) {
int val = mDischargeAmountScreenOnSinceCharge;
- if (mOnBattery && mScreenState == Display.STATE_ON
+ if (mOnBattery && isScreenOn(mScreenState)
&& mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
}
@@ -11384,24 +11469,52 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ @Override
public int getDischargeAmountScreenOff() {
synchronized(this) {
int val = mDischargeAmountScreenOff;
- if (mOnBattery && mScreenState != Display.STATE_ON
+ if (mOnBattery && isScreenOff(mScreenState)
&& mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
}
- return val;
+ // For backward compatibility, doze discharge is counted into screen off.
+ return val + getDischargeAmountScreenDoze();
}
}
+ @Override
public int getDischargeAmountScreenOffSinceCharge() {
synchronized(this) {
int val = mDischargeAmountScreenOffSinceCharge;
- if (mOnBattery && mScreenState != Display.STATE_ON
+ if (mOnBattery && isScreenOff(mScreenState)
&& mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
}
+ // For backward compatibility, doze discharge is counted into screen off.
+ return val + getDischargeAmountScreenDozeSinceCharge();
+ }
+ }
+
+ @Override
+ public int getDischargeAmountScreenDoze() {
+ synchronized(this) {
+ int val = mDischargeAmountScreenDoze;
+ if (mOnBattery && isScreenDoze(mScreenState)
+ && mDischargeCurrentLevel < mDischargeScreenDozeUnplugLevel) {
+ val += mDischargeScreenDozeUnplugLevel-mDischargeCurrentLevel;
+ }
+ return val;
+ }
+ }
+
+ @Override
+ public int getDischargeAmountScreenDozeSinceCharge() {
+ synchronized(this) {
+ int val = mDischargeAmountScreenDozeSinceCharge;
+ if (mOnBattery && isScreenDoze(mScreenState)
+ && mDischargeCurrentLevel < mDischargeScreenDozeUnplugLevel) {
+ val += mDischargeScreenDozeUnplugLevel-mDischargeCurrentLevel;
+ }
return val;
}
}
@@ -11759,12 +11872,14 @@ public class BatteryStatsImpl extends BatteryStats {
mHighDischargeAmountSinceCharge = in.readInt();
mDischargeAmountScreenOnSinceCharge = in.readInt();
mDischargeAmountScreenOffSinceCharge = in.readInt();
+ mDischargeAmountScreenDozeSinceCharge = in.readInt();
mDischargeStepTracker.readFromParcel(in);
mChargeStepTracker.readFromParcel(in);
mDailyDischargeStepTracker.readFromParcel(in);
mDailyChargeStepTracker.readFromParcel(in);
mDischargeCounter.readSummaryFromParcelLocked(in);
mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
+ mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
int NPKG = in.readInt();
if (NPKG > 0) {
mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -11787,6 +11902,7 @@ public class BatteryStatsImpl extends BatteryStats {
mScreenState = Display.STATE_UNKNOWN;
mScreenOnTimer.readSummaryFromParcelLocked(in);
+ mScreenDozeTimer.readSummaryFromParcelLocked(in);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i].readSummaryFromParcelLocked(in);
}
@@ -12180,12 +12296,14 @@ public class BatteryStatsImpl extends BatteryStats {
out.writeInt(getHighDischargeAmountSinceCharge());
out.writeInt(getDischargeAmountScreenOnSinceCharge());
out.writeInt(getDischargeAmountScreenOffSinceCharge());
+ out.writeInt(getDischargeAmountScreenDozeSinceCharge());
mDischargeStepTracker.writeToParcel(out);
mChargeStepTracker.writeToParcel(out);
mDailyDischargeStepTracker.writeToParcel(out);
mDailyChargeStepTracker.writeToParcel(out);
mDischargeCounter.writeSummaryFromParcelLocked(out);
mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
+ mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
if (mDailyPackageChanges != null) {
final int NPKG = mDailyPackageChanges.size();
out.writeInt(NPKG);
@@ -12203,6 +12321,7 @@ public class BatteryStatsImpl extends BatteryStats {
out.writeLong(mNextMaxDailyDeadline);
mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ mScreenDozeTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
@@ -12635,6 +12754,7 @@ public class BatteryStatsImpl extends BatteryStats {
mScreenState = Display.STATE_UNKNOWN;
mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
+ mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
mOnBatteryTimeBase, in);
@@ -12728,10 +12848,13 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOnSinceCharge = in.readInt();
mDischargeAmountScreenOff = in.readInt();
mDischargeAmountScreenOffSinceCharge = in.readInt();
+ mDischargeAmountScreenDoze = in.readInt();
+ mDischargeAmountScreenDozeSinceCharge = in.readInt();
mDischargeStepTracker.readFromParcel(in);
mChargeStepTracker.readFromParcel(in);
mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
- mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
+ mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mLastWriteTime = in.readLong();
mRpmStats.clear();
@@ -12848,6 +12971,7 @@ public class BatteryStatsImpl extends BatteryStats {
mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
mScreenOnTimer.writeToParcel(out, uSecRealtime);
+ mScreenDozeTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
}
@@ -12910,10 +13034,13 @@ public class BatteryStatsImpl extends BatteryStats {
out.writeInt(mDischargeAmountScreenOnSinceCharge);
out.writeInt(mDischargeAmountScreenOff);
out.writeInt(mDischargeAmountScreenOffSinceCharge);
+ out.writeInt(mDischargeAmountScreenDoze);
+ out.writeInt(mDischargeAmountScreenDozeSinceCharge);
mDischargeStepTracker.writeToParcel(out);
mChargeStepTracker.writeToParcel(out);
mDischargeCounter.writeToParcel(out);
mDischargeScreenOffCounter.writeToParcel(out);
+ mDischargeScreenDozeCounter.writeToParcel(out);
out.writeLong(mLastWriteTime);
out.writeInt(mRpmStats.size());
@@ -13020,8 +13147,10 @@ public class BatteryStatsImpl extends BatteryStats {
pw.println("mOnBatteryScreenOffTimeBase:");
mOnBatteryScreenOffTimeBase.dump(pw, " ");
Printer pr = new PrintWriterPrinter(pw);
- pr.println("*** Screen timer:");
+ pr.println("*** Screen on timer:");
mScreenOnTimer.logState(pr, " ");
+ pr.println("*** Screen doze timer:");
+ mScreenDozeTimer.logState(pr, " ");
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
pr.println("*** Screen brightness #" + i + ":");
mScreenBrightnessTimer[i].logState(pr, " ");
diff --git a/com/android/internal/os/Zygote.java b/com/android/internal/os/Zygote.java
index 5ee0918c..cbc63cf8 100644
--- a/com/android/internal/os/Zygote.java
+++ b/com/android/internal/os/Zygote.java
@@ -49,6 +49,11 @@ public final class Zygote {
/** Make the code Java debuggable by turning off some optimizations. */
public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;
+ /** Turn off the verifier. */
+ public static final int DISABLE_VERIFIER = 1 << 9;
+ /** Only use oat files located in /system. Otherwise use dex/jar/apk . */
+ public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
+
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
/** Default external storage should be mounted. */
diff --git a/com/android/internal/telephony/CallForwardInfo.java b/com/android/internal/telephony/CallForwardInfo.java
index dccf3066..e40028fc 100644
--- a/com/android/internal/telephony/CallForwardInfo.java
+++ b/com/android/internal/telephony/CallForwardInfo.java
@@ -16,12 +16,16 @@
package com.android.internal.telephony;
+import android.telecom.Log;
+
/**
* See also RIL_CallForwardInfo in include/telephony/ril.h
*
* {@hide}
*/
public class CallForwardInfo {
+ private static final String TAG = "CallForwardInfo";
+
public int status; /*1 = active, 0 = not active */
public int reason; /* from TS 27.007 7.11 "reason" */
public int serviceClass; /* Saum of CommandsInterface.SERVICE_CLASS */
@@ -31,9 +35,9 @@ public class CallForwardInfo {
@Override
public String toString() {
- return super.toString() + (status == 0 ? " not active " : " active ")
- + " reason: " + reason
- + " serviceClass: " + serviceClass + " " + timeSeconds + " seconds";
-
+ return "[CallForwardInfo: status=" + (status == 0 ? " not active " : " active ")
+ + ", reason= " + reason
+ + ", serviceClass= " + serviceClass + ", timeSec= " + timeSeconds + " seconds"
+ + ", number=" + Log.pii(number) + "]";
}
}
diff --git a/com/android/internal/telephony/CarrierKeyDownloadManager.java b/com/android/internal/telephony/CarrierKeyDownloadManager.java
index bca337d8..606f7ffd 100644
--- a/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -16,8 +16,6 @@
package com.android.internal.telephony;
-import static android.preference.PreferenceManager.getDefaultSharedPreferences;
-
import android.app.AlarmManager;
import android.app.DownloadManager;
import android.app.PendingIntent;
@@ -34,22 +32,30 @@ import android.telephony.ImsiEncryptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.Base64;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.org.bouncycastle.util.io.pem.PemReader;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.Reader;
+import java.security.PublicKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.util.Date;
+import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+
/**
* This class contains logic to get Certificates and keep them current.
* The class will be instantiated by various Phone implementations.
@@ -68,16 +74,19 @@ public class CarrierKeyDownloadManager {
private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
"com.android.internal.telephony.carrier_key_download_alarm";
- private int mKeyAvailability = 0;
+ @VisibleForTesting
+ public int mKeyAvailability = 0;
public static final String MNC = "MNC";
public static final String MCC = "MCC";
private static final String SEPARATOR = ":";
- private static final String JSON_KEY = "key";
- private static final String JSON_TYPE = "type";
- private static final String JSON_IDENTIFIER = "identifier";
- private static final String JSON_EXPIRATION_DATE = "expiration-date";
+ private static final String JSON_CERTIFICATE = "certificate";
+ // This is a hack to accomodate Verizon. Verizon insists on using the public-key
+ // field to store the certificate. We'll just use which-ever is not null.
+ private static final String JSON_CERTIFICATE_ALTERNATE = "public-key";
+ private static final String JSON_TYPE = "key-type";
+ private static final String JSON_IDENTIFIER = "key-identifier";
private static final String JSON_CARRIER_KEYS = "carrier-keys";
private static final String JSON_TYPE_VALUE_WLAN = "WLAN";
private static final String JSON_TYPE_VALUE_EPDG = "EPDG";
@@ -89,7 +98,7 @@ public class CarrierKeyDownloadManager {
private final Phone mPhone;
private final Context mContext;
- private final DownloadManager mDownloadManager;
+ public final DownloadManager mDownloadManager;
private String mURL;
public CarrierKeyDownloadManager(Phone phone) {
@@ -173,14 +182,11 @@ public class CarrierKeyDownloadManager {
}
/**
- * this method resets the alarm. Starts by cleaning up the existing alarms.
- * We look at the earliest expiration date, and setup an alarms X days prior.
- * If the expiration date is in the past, we'll setup an alarm to run the next day. This
- * could happen if the download has failed.
+ * this method returns the date to be used to decide on when to start downloading the key.
+ * from the carrier.
**/
- private void resetRenewalAlarm() {
- cleanupRenewalAlarms();
- int slotId = mPhone.getPhoneId();
+ @VisibleForTesting
+ public long getExpirationDate() {
long minExpirationDate = Long.MAX_VALUE;
for (int key_type : CARRIER_KEY_TYPES) {
if (!isKeyEnabled(key_type)) {
@@ -204,6 +210,20 @@ public class CarrierKeyDownloadManager {
} else {
minExpirationDate = minExpirationDate - DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
}
+ return minExpirationDate;
+ }
+
+ /**
+ * this method resets the alarm. Starts by cleaning up the existing alarms.
+ * We look at the earliest expiration date, and setup an alarms X days prior.
+ * If the expiration date is in the past, we'll setup an alarm to run the next day. This
+ * could happen if the download has failed.
+ **/
+ @VisibleForTesting
+ public void resetRenewalAlarm() {
+ cleanupRenewalAlarms();
+ int slotId = mPhone.getPhoneId();
+ long minExpirationDate = getExpirationDate();
Log.d(LOG_TAG, "minExpirationDate: " + new Date(minExpirationDate));
final AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
Context.ALARM_SERVICE);
@@ -225,21 +245,30 @@ public class CarrierKeyDownloadManager {
}
/**
+ * Returns the sim operator.
+ **/
+ @VisibleForTesting
+ public String getSimOperator() {
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return telephonyManager.getSimOperator(mPhone.getSubId());
+ }
+
+ /**
* checks if the download was sent by this particular instance. We do this by including the
* slot id in the key. If no value is found, we know that the download was not for this
* instance of the phone.
**/
- private boolean isValidDownload(String mccMnc) {
+ @VisibleForTesting
+ public boolean isValidDownload(String mccMnc) {
String mccCurrent = "";
String mncCurrent = "";
String mccSource = "";
String mncSource = "";
- final TelephonyManager telephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
- String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
- if (TextUtils.isEmpty(networkOperator) || TextUtils.isEmpty(mccMnc)) {
- Log.e(LOG_TAG, "networkOperator or mcc/mnc is empty");
+ String simOperator = getSimOperator();
+ if (TextUtils.isEmpty(simOperator) || TextUtils.isEmpty(mccMnc)) {
+ Log.e(LOG_TAG, "simOperator or mcc/mnc is empty");
return false;
}
@@ -248,8 +277,8 @@ public class CarrierKeyDownloadManager {
mncSource = splitValue[1];
Log.d(LOG_TAG, "values from sharedPrefs mcc, mnc: " + mccSource + "," + mncSource);
- mccCurrent = networkOperator.substring(0, 3);
- mncCurrent = networkOperator.substring(3);
+ mccCurrent = simOperator.substring(0, 3);
+ mncCurrent = simOperator.substring(3);
Log.d(LOG_TAG, "using values for mcc, mnc: " + mccCurrent + "," + mncCurrent);
if (TextUtils.equals(mncSource, mncCurrent) && TextUtils.equals(mccSource, mccCurrent)) {
@@ -348,19 +377,20 @@ public class CarrierKeyDownloadManager {
* Converts the string into a json object to retreive the nodes. The Json should have 3 nodes,
* including the Carrier public key, the key type and the key identifier. Once the nodes have
* been extracted, they get persisted to the database. Sample:
- * "carrier-keys": [ { "key": "",
- * "type": WLAN,
- * "identifier": "",
- * "expiration-date": 1502577746000
+ * "carrier-keys": [ { "certificate": "",
+ * "key-type": "WLAN",
+ * "key-identifier": ""
* } ]
* @param jsonStr the json string.
- * @param mccMnc contains the mcc, mnc
+ * @param mccMnc contains the mcc, mnc.
*/
- private void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
+ @VisibleForTesting
+ public void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)) {
Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty");
return;
}
+ PemReader reader = null;
try {
String mcc = "";
String mnc = "";
@@ -369,10 +399,16 @@ public class CarrierKeyDownloadManager {
mnc = splitValue[1];
JSONObject jsonObj = new JSONObject(jsonStr);
JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS);
-
for (int i = 0; i < keys.length(); i++) {
JSONObject key = keys.getJSONObject(i);
- String carrierKey = key.getString(JSON_KEY);
+ // This is a hack to accomodate Verizon. Verizon insists on using the public-key
+ // field to store the certificate. We'll just use which-ever is not null.
+ String cert = null;
+ if (key.has(JSON_CERTIFICATE)) {
+ cert = key.getString(JSON_CERTIFICATE);
+ } else {
+ cert = key.getString(JSON_CERTIFICATE_ALTERNATE);
+ }
String typeString = key.getString(JSON_TYPE);
int type = UNINITIALIZED_KEY_TYPE;
if (typeString.equals(JSON_TYPE_VALUE_WLAN)) {
@@ -380,13 +416,27 @@ public class CarrierKeyDownloadManager {
} else if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
type = TelephonyManager.KEY_TYPE_EPDG;
}
- long expiration_date = key.getLong(JSON_EXPIRATION_DATE);
String identifier = key.getString(JSON_IDENTIFIER);
- savePublicKey(carrierKey, type, identifier, expiration_date,
- mcc, mnc);
+ ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes());
+ Reader fReader = new BufferedReader(new InputStreamReader(inStream));
+ reader = new PemReader(fReader);
+ Pair<PublicKey, Long> keyInfo =
+ getKeyInformation(reader.readPemObject().getContent());
+ reader.close();
+ savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc);
}
} catch (final JSONException e) {
Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
+ } catch (final Exception e) {
+ Log.e(LOG_TAG, "Exception getting certificate: " + e);
+ } finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (final Exception e) {
+ Log.e(LOG_TAG, "Exception getting certificate: " + e);
+ }
}
}
@@ -394,8 +444,8 @@ public class CarrierKeyDownloadManager {
* introspects the mKeyAvailability bitmask
* @return true if the digit at position k is 1, else false.
*/
-
- private boolean isKeyEnabled(int keyType) {
+ @VisibleForTesting
+ public boolean isKeyEnabled(int keyType) {
//since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
int returnValue = (mKeyAvailability >> (keyType - 1)) & 1;
return (returnValue == 1) ? true : false;
@@ -427,15 +477,13 @@ public class CarrierKeyDownloadManager {
private boolean downloadKey() {
Log.d(LOG_TAG, "starting download from: " + mURL);
- final TelephonyManager telephonyManager =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
String mcc = "";
String mnc = "";
- String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
+ String simOperator = getSimOperator();
- if (!TextUtils.isEmpty(networkOperator)) {
- mcc = networkOperator.substring(0, 3);
- mnc = networkOperator.substring(3);
+ if (!TextUtils.isEmpty(simOperator)) {
+ mcc = simOperator.substring(0, 3);
+ mnc = simOperator.substring(3);
Log.d(LOG_TAG, "using values for mcc, mnc: " + mcc + "," + mnc);
} else {
Log.e(LOG_TAG, "mcc, mnc: is empty");
@@ -461,11 +509,35 @@ public class CarrierKeyDownloadManager {
return true;
}
- private void savePublicKey(String key, int type, String identifier, long expirationDate,
+ /**
+ * Save the public key
+ * @param certificate certificate that contains the public key.
+ * @return Pair containing the Public Key and the expiration date.
+ **/
+ @VisibleForTesting
+ public static Pair<PublicKey, Long> getKeyInformation(byte[] certificate) throws Exception {
+ InputStream inStream = new ByteArrayInputStream(certificate);
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
+ Pair<PublicKey, Long> keyInformation =
+ new Pair(cert.getPublicKey(), cert.getNotAfter().getTime());
+ return keyInformation;
+ }
+
+ /**
+ * Save the public key
+ * @param publicKey public key.
+ * @param type key-type.
+ * @param identifier which is an opaque string.
+ * @param expirationDate expiration date of the key.
+ * @param mcc
+ * @param mnc
+ **/
+ @VisibleForTesting
+ public void savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate,
String mcc, String mnc) {
- byte[] keyBytes = Base64.decode(key.getBytes(), Base64.DEFAULT);
ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc, type, identifier,
- keyBytes, new Date(expirationDate));
+ publicKey, new Date(expirationDate));
mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
}
}
diff --git a/com/android/internal/telephony/CarrierServiceStateTracker.java b/com/android/internal/telephony/CarrierServiceStateTracker.java
index 8df201e5..77a39eb9 100644
--- a/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -79,13 +79,8 @@ public class CarrierServiceStateTracker extends Handler {
switch (msg.what) {
case CARRIER_EVENT_VOICE_REGISTRATION:
case CARRIER_EVENT_DATA_REGISTRATION:
- handleConfigChanges();
- break;
case CARRIER_EVENT_VOICE_DEREGISTRATION:
case CARRIER_EVENT_DATA_DEREGISTRATION:
- if (isRadioOffOrAirplaneMode()) {
- break;
- }
handleConfigChanges();
break;
case NOTIFICATION_EMERGENCY_NETWORK:
@@ -317,8 +312,8 @@ public class CarrierServiceStateTracker extends Handler {
Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
+ "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
+ "," + mSST.isRadioOn());
- if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered()
- || isGlobalMode()) {
+ if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() || isGlobalMode()
+ || isRadioOffOrAirplaneMode()) {
return false;
}
return true;
diff --git a/com/android/internal/telephony/ClientWakelockTracker.java b/com/android/internal/telephony/ClientWakelockTracker.java
index 5bec60ba..fa71e769 100644
--- a/com/android/internal/telephony/ClientWakelockTracker.java
+++ b/com/android/internal/telephony/ClientWakelockTracker.java
@@ -18,10 +18,10 @@ package com.android.internal.telephony;
import android.os.SystemClock;
import android.telephony.ClientRequestStats;
-import android.telephony.Rlog;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -119,13 +119,13 @@ public class ClientWakelockTracker {
return false;
}
- void dumpClientRequestTracker() {
- Rlog.d(RIL.RILJ_LOG_TAG, "-------mClients---------------");
+ void dumpClientRequestTracker(PrintWriter pw) {
+ pw.println("-------mClients---------------");
synchronized (mClients) {
for (String key : mClients.keySet()) {
- Rlog.d(RIL.RILJ_LOG_TAG, "Client : " + key);
- Rlog.d(RIL.RILJ_LOG_TAG, mClients.get(key).toString());
+ pw.println("Client : " + key);
+ pw.println(mClients.get(key).toString());
}
}
}
-} \ No newline at end of file
+}
diff --git a/com/android/internal/telephony/Connection.java b/com/android/internal/telephony/Connection.java
index 245f76ce..8c54a31b 100644
--- a/com/android/internal/telephony/Connection.java
+++ b/com/android/internal/telephony/Connection.java
@@ -34,6 +34,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
* {@hide}
*/
public abstract class Connection {
+ private static final String TAG = "Connection";
public interface PostDialListener {
void onPostDialWait();
@@ -836,6 +837,16 @@ public abstract class Connection {
public void setConnectionExtras(Bundle extras) {
if (extras != null) {
mExtras = new Bundle(extras);
+
+ int previousCount = mExtras.size();
+ // Prevent vendors from passing in extras other than primitive types and android API
+ // parcelables.
+ mExtras = mExtras.filterValues();
+ int filteredCount = mExtras.size();
+ if (filteredCount != previousCount) {
+ Rlog.i(TAG, "setConnectionExtras: filtering " + (previousCount - filteredCount)
+ + " invalid extras.");
+ }
} else {
mExtras = null;
}
diff --git a/com/android/internal/telephony/DefaultPhoneNotifier.java b/com/android/internal/telephony/DefaultPhoneNotifier.java
index c13e5408..98c0a32e 100644
--- a/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -125,6 +125,9 @@ public class DefaultPhoneNotifier implements PhoneNotifier {
int subId = sender.getSubId();
try {
if (mRegistry != null) {
+ Rlog.d(LOG_TAG, "notifyCallForwardingChanged: subId=" + subId + ", isCFActive="
+ + sender.getCallForwardingIndicator());
+
mRegistry.notifyCallForwardingChangedForSubscriber(subId,
sender.getCallForwardingIndicator());
}
diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java
index d95d0183..ad078d67 100644
--- a/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/com/android/internal/telephony/GsmCdmaPhone.java
@@ -286,7 +286,7 @@ public class GsmCdmaPhone extends Phone {
tm.setPhoneType(getPhoneId(), PhoneConstants.PHONE_TYPE_GSM);
mIccCardProxy.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
} else {
- mCdmaSubscriptionSource = CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
+ mCdmaSubscriptionSource = mCdmaSSM.getCdmaSubscriptionSource();
// This is needed to handle phone process crashes
mIsPhoneInEcmState = getInEcmMode();
if (mIsPhoneInEcmState) {
@@ -505,7 +505,7 @@ public class GsmCdmaPhone extends Phone {
ret = PhoneConstants.DataState.DISCONNECTED;
} else if (mSST.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE
- && (isPhoneTypeCdma() ||
+ && (isPhoneTypeCdma() || isPhoneTypeCdmaLte() ||
(isPhoneTypeGsm() && !apnType.equals(PhoneConstants.APN_TYPE_EMERGENCY)))) {
// If we're out of service, open TCP sockets may still work
// but no data will flow
@@ -1063,7 +1063,7 @@ public class GsmCdmaPhone extends Phone {
boolean alwaysTryImsForEmergencyCarrierConfig = configManager.getConfigForSubId(getSubId())
.getBoolean(CarrierConfigManager.KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL);
- boolean imsUseEnabled = isImsUseEnabled()
+ boolean useImsForCall = isImsUseEnabled()
&& imsPhone != null
&& (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
(imsPhone.isVideoEnabled() && VideoProfile.isVideo(videoState)))
@@ -1083,7 +1083,7 @@ public class GsmCdmaPhone extends Phone {
boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
if (DBG) {
- logd("imsUseEnabled=" + imsUseEnabled
+ logd("useImsForCall=" + useImsForCall
+ ", useImsForEmergency=" + useImsForEmergency
+ ", useImsForUt=" + useImsForUt
+ ", isUt=" + isUt
@@ -1100,13 +1100,13 @@ public class GsmCdmaPhone extends Phone {
Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mContext);
- if ((imsUseEnabled && (!isUt || useImsForUt)) || useImsForEmergency) {
+ if ((useImsForCall && !isUt) || (isUt && useImsForUt) || useImsForEmergency) {
try {
if (DBG) logd("Trying IMS PS call");
return imsPhone.dial(dialString, uusInfo, videoState, intentExtras);
} catch (CallStateException e) {
if (DBG) logd("IMS PS call exception " + e +
- "imsUseEnabled =" + imsUseEnabled + ", imsPhone =" + imsPhone);
+ "useImsForCall =" + useImsForCall + ", imsPhone =" + imsPhone);
// Do not throw a CallStateException and instead fall back to Circuit switch
// for emergency calls and MMI codes.
if (Phone.CS_FALLBACK.equals(e.getMessage()) || isEmergency) {
@@ -2549,6 +2549,7 @@ public class GsmCdmaPhone extends Phone {
private void processIccRecordEvents(int eventCode) {
switch (eventCode) {
case IccRecords.EVENT_CFI:
+ logi("processIccRecordEvents: EVENT_CFI");
notifyCallForwardingIndicator();
break;
}
diff --git a/com/android/internal/telephony/InboundSmsHandler.java b/com/android/internal/telephony/InboundSmsHandler.java
index 59195f83..391de500 100644
--- a/com/android/internal/telephony/InboundSmsHandler.java
+++ b/com/android/internal/telephony/InboundSmsHandler.java
@@ -158,6 +158,17 @@ public abstract class InboundSmsHandler extends StateMachine {
/** New SMS received as an AsyncResult. */
public static final int EVENT_INJECT_SMS = 8;
+ /** Update tracker object; used only in waiting state */
+ private static final int EVENT_UPDATE_TRACKER = 9;
+
+ /** Timeout in case state machine is stuck in a state for too long; used only in waiting
+ * state */
+ private static final int EVENT_STATE_TIMEOUT = 10;
+
+ /** Timeout duration for EVENT_STATE_TIMEOUT */
+ @VisibleForTesting
+ public static final int STATE_TIMEOUT = 30000;
+
/** Wakelock release delay when returning to idle state. */
private static final int WAKELOCK_TIMEOUT = 3000;
@@ -450,6 +461,7 @@ public abstract class InboundSmsHandler extends StateMachine {
// if any broadcasts were sent, transition to waiting state
InboundSmsTracker inboundSmsTracker = (InboundSmsTracker) msg.obj;
if (processMessagePart(inboundSmsTracker)) {
+ sendMessage(EVENT_UPDATE_TRACKER, inboundSmsTracker);
transitionTo(mWaitingState);
} else {
// if event is sent from SmsBroadcastUndelivered.broadcastSms(), and
@@ -493,18 +505,41 @@ public abstract class InboundSmsHandler extends StateMachine {
* {@link IdleState} after any deferred {@link #EVENT_BROADCAST_SMS} messages are handled.
*/
private class WaitingState extends State {
+ private InboundSmsTracker mTracker;
+
+ @Override
+ public void enter() {
+ if (DBG) log("entering Waiting state");
+ mTracker = null;
+ sendMessageDelayed(EVENT_STATE_TIMEOUT, STATE_TIMEOUT);
+ }
+
@Override
public void exit() {
if (DBG) log("exiting Waiting state");
// Before moving to idle state, set wakelock timeout to WAKE_LOCK_TIMEOUT milliseconds
// to give any receivers time to take their own wake locks
setWakeLockTimeout(WAKELOCK_TIMEOUT);
+ if (VDBG) {
+ if (hasMessages(EVENT_STATE_TIMEOUT)) {
+ log("exiting Waiting state: removing EVENT_STATE_TIMEOUT from message queue");
+ }
+ if (hasMessages(EVENT_UPDATE_TRACKER)) {
+ log("exiting Waiting state: removing EVENT_UPDATE_TRACKER from message queue");
+ }
+ }
+ removeMessages(EVENT_STATE_TIMEOUT);
+ removeMessages(EVENT_UPDATE_TRACKER);
}
@Override
public boolean processMessage(Message msg) {
log("WaitingState.processMessage:" + msg.what);
switch (msg.what) {
+ case EVENT_UPDATE_TRACKER:
+ mTracker = (InboundSmsTracker) msg.obj;
+ return HANDLED;
+
case EVENT_BROADCAST_SMS:
// defer until the current broadcast completes
deferMessage(msg);
@@ -520,6 +555,18 @@ public abstract class InboundSmsHandler extends StateMachine {
// not ready to return to idle; ignore
return HANDLED;
+ case EVENT_STATE_TIMEOUT:
+ // stuck in WaitingState for too long; drop the message and exit this state
+ if (mTracker != null) {
+ log("WaitingState.processMessage: EVENT_STATE_TIMEOUT; dropping message");
+ dropSms(new SmsBroadcastReceiver(mTracker));
+ } else {
+ log("WaitingState.processMessage: EVENT_STATE_TIMEOUT; mTracker is null "
+ + "- sending EVENT_BROADCAST_COMPLETE");
+ sendMessage(EVENT_BROADCAST_COMPLETE);
+ }
+ return HANDLED;
+
default:
// parent state handles the other message types
return NOT_HANDLED;
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index 28e45565..6acc8743 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -56,6 +56,7 @@ import android.telephony.SignalStrength;
import android.telephony.SubscriptionManager;
import android.telephony.VoLteServiceState;
import android.text.TextUtils;
+import android.util.Log;
import com.android.ims.ImsCall;
import com.android.ims.ImsConfig;
@@ -226,6 +227,9 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
// Key used to read/write "disable DNS server check" pref (used for testing)
private static final String DNS_SERVER_CHECK_DISABLED_KEY = "dns_server_check_disabled_key";
+ // Integer used to let the calling application know that the we are ignoring auto mode switch.
+ private static final int ALREADY_IN_AUTO_SELECTION = 1;
+
/**
* This method is invoked when the Phone exits Emergency Callback Mode.
*/
@@ -1205,6 +1209,11 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
mCi.setNetworkSelectionModeAutomatic(msg);
} else {
Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - already auto, ignoring");
+ // let the calling application know that the we are ignoring automatic mode switch.
+ if (nsm.message != null) {
+ nsm.message.arg1 = ALREADY_IN_AUTO_SELECTION;
+ }
+
ar.userObj = nsm;
handleSetSelectNetwork(ar);
}
@@ -1789,7 +1798,7 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
int status = enable ? IccRecords.CALL_FORWARDING_STATUS_ENABLED :
IccRecords.CALL_FORWARDING_STATUS_DISABLED;
int subId = getSubId();
- Rlog.d(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
+ Rlog.i(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
" in pref " + CF_STATUS + subId);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -1831,6 +1840,9 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
if (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_UNKNOWN) {
callForwardingIndicator = getCallForwardingIndicatorFromSharedPref();
}
+ Rlog.v(LOG_TAG, "getCallForwardingIndicator: iccForwardingFlag=" + (r != null
+ ? r.getVoiceCallForwardingFlag() : "null") + ", sharedPrefFlag="
+ + getCallForwardingIndicatorFromSharedPref());
return (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_ENABLED);
}
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 8bb2125d..84c2b659 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -4774,7 +4774,7 @@ public class RIL extends BaseCommands implements CommandsInterface {
}
pw.println(" mLastNITZTimeInfo=" + Arrays.toString(mLastNITZTimeInfo));
pw.println(" mTestingEmergencyCall=" + mTestingEmergencyCall.get());
- mClientWakelockTracker.dumpClientRequestTracker();
+ mClientWakelockTracker.dumpClientRequestTracker(pw);
}
public List<ClientRequestStats> getClientRequestStats() {
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index b3794402..c34cbb29 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -210,6 +210,7 @@ public class ServiceStateTracker extends Handler {
protected static final int EVENT_ALL_DATA_DISCONNECTED = 49;
protected static final int EVENT_PHONE_TYPE_SWITCHED = 50;
protected static final int EVENT_RADIO_POWER_FROM_CARRIER = 51;
+ protected static final int EVENT_SIM_NOT_INSERTED = 52;
protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
@@ -354,6 +355,14 @@ public class ServiceStateTracker extends Handler {
}
// update voicemail count and notify message waiting changed
mPhone.updateVoiceMail();
+
+ // cancel notifications if we see SIM_NOT_INSERTED (This happens on bootup before
+ // the SIM is first detected and then subsequently on SIM removals)
+ if (mSubscriptionController.getSlotIndex(subId)
+ == SubscriptionManager.SIM_NOT_INSERTED) {
+ // this is handled on the main thread to mitigate racing with setNotification().
+ sendMessage(obtainMessage(EVENT_SIM_NOT_INSERTED));
+ }
}
}
};
@@ -446,12 +455,15 @@ public class ServiceStateTracker extends Handler {
public static final int CS_NORMAL_ENABLED = 1005; // Access Control blocks normal voice/sms service
public static final int CS_EMERGENCY_ENABLED = 1006; // Access Control blocks emergency call service
public static final int CS_REJECT_CAUSE_ENABLED = 2001; // Notify MM rejection cause
- public static final int CS_REJECT_CAUSE_DISABLED = 2002; // Cancel MM rejection cause
/** Notification id. */
public static final int PS_NOTIFICATION = 888; // Id to update and cancel PS restricted
public static final int CS_NOTIFICATION = 999; // Id to update and cancel CS restricted
public static final int CS_REJECT_CAUSE_NOTIFICATION = 111; // Id to update and cancel MM
// rejection cause
+
+ /** To identify whether EVENT_SIM_READY is received or not */
+ private boolean mIsSimReady = false;
+
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1064,6 +1076,11 @@ public class ServiceStateTracker extends Handler {
case EVENT_ICC_CHANGED:
onUpdateIccAvailability();
+ if (mUiccApplcation != null
+ && mUiccApplcation.getState() != AppState.APPSTATE_READY) {
+ mIsSimReady = false;
+ updateSpnDisplay();
+ }
break;
case EVENT_GET_CELL_INFO_LIST: {
@@ -1121,6 +1138,7 @@ public class ServiceStateTracker extends Handler {
// Reset the mPreviousSubId so we treat a SIM power bounce
// as a first boot. See b/19194287
mOnSubscriptionsChangedListener.mPreviousSubId.set(-1);
+ mIsSimReady = true;
pollState();
// Signal strength polling stops when radio is off
queueNextSignalStrengthPoll();
@@ -1298,6 +1316,14 @@ public class ServiceStateTracker extends Handler {
}
break;
+ case EVENT_SIM_NOT_INSERTED:
+ if (DBG) log("EVENT_SIM_NOT_INSERTED");
+ cancelAllNotifications();
+ mMdn = null;
+ mMin = null;
+ mIsMinInfoReady = false;
+ break;
+
case EVENT_ALL_DATA_DISCONNECTED:
int dds = SubscriptionManager.getDefaultDataSubscriptionId();
ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this);
@@ -2222,7 +2248,12 @@ public class ServiceStateTracker extends Handler {
if (combinedRegState == ServiceState.STATE_OUT_OF_SERVICE
|| combinedRegState == ServiceState.STATE_EMERGENCY_ONLY) {
showPlmn = true;
- if (mEmergencyOnly) {
+
+ // Force display no service
+ final boolean forceDisplayNoService = mPhone.getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_display_no_service_when_sim_unready)
+ && !mIsSimReady;
+ if (mEmergencyOnly && !forceDisplayNoService) {
// No service but emergency call allowed
plmn = Resources.getSystem().
getText(com.android.internal.R.string.emergency_calls_only).toString();
@@ -2825,7 +2856,7 @@ public class ServiceStateTracker extends Handler {
}
if (hasRejectCauseChanged) {
- setNotification(mRejectCode == 0 ? CS_REJECT_CAUSE_DISABLED : CS_REJECT_CAUSE_ENABLED);
+ setNotification(CS_REJECT_CAUSE_ENABLED);
}
if (hasChanged) {
@@ -3833,6 +3864,18 @@ public class ServiceStateTracker extends Handler {
}
/**
+ * Cancels all notifications posted to NotificationManager. These notifications for restricted
+ * state and rejection cause for cs registration are no longer valid after the SIM has been
+ * removed.
+ */
+ private void cancelAllNotifications() {
+ if (DBG) log("setNotification: cancelAllNotifications");
+ NotificationManager notificationManager = (NotificationManager)
+ mPhone.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+
+ /**
* Post a notification to NotificationManager for restricted state and
* rejection cause for cs registration
*
@@ -3907,17 +3950,14 @@ public class ServiceStateTracker extends Handler {
notificationId = CS_REJECT_CAUSE_NOTIFICATION;
int resId = selectResourceForRejectCode(mRejectCode);
if (0 == resId) {
- // cancel notification because current reject code is not handled.
- notifyType = CS_REJECT_CAUSE_DISABLED;
+ loge("setNotification: mRejectCode=" + mRejectCode + " is not handled.");
+ return;
} else {
icon = com.android.internal.R.drawable.stat_notify_mmcc_indication_icn;
title = Resources.getSystem().getString(resId);
details = null;
}
break;
- case CS_REJECT_CAUSE_DISABLED:
- notificationId = CS_REJECT_CAUSE_NOTIFICATION;
- break;
}
if (DBG) {
@@ -3941,8 +3981,7 @@ public class ServiceStateTracker extends Handler {
NotificationManager notificationManager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
- if (notifyType == PS_DISABLED || notifyType == CS_DISABLED
- || notifyType == CS_REJECT_CAUSE_DISABLED) {
+ if (notifyType == PS_DISABLED || notifyType == CS_DISABLED) {
// cancel previous post notification
notificationManager.cancel(notificationId);
} else {
diff --git a/com/android/internal/telephony/cat/AppInterface.java b/com/android/internal/telephony/cat/AppInterface.java
index c78b7f8c..1f2d3a06 100644
--- a/com/android/internal/telephony/cat/AppInterface.java
+++ b/com/android/internal/telephony/cat/AppInterface.java
@@ -84,6 +84,7 @@ public interface AppInterface {
SET_UP_MENU(0x25),
SET_UP_CALL(0x10),
PROVIDE_LOCAL_INFORMATION(0x26),
+ LANGUAGE_NOTIFICATION(0x35),
OPEN_CHANNEL(0x40),
CLOSE_CHANNEL(0x41),
RECEIVE_DATA(0x42),
diff --git a/com/android/internal/telephony/cat/CatService.java b/com/android/internal/telephony/cat/CatService.java
index a242de43..cd7a7561 100644
--- a/com/android/internal/telephony/cat/CatService.java
+++ b/com/android/internal/telephony/cat/CatService.java
@@ -16,15 +16,21 @@
package com.android.internal.telephony.cat;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.backup.BackupManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.content.res.Resources.NotFoundException;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.LocaleList;
import android.os.Message;
+import android.os.RemoteException;
import android.os.SystemProperties;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -450,6 +456,18 @@ public class CatService extends Handler implements AppInterface {
((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString();
}
break;
+ case LANGUAGE_NOTIFICATION:
+ String language = ((LanguageParams) cmdParams).mLanguage;
+ ResultCode result = ResultCode.OK;
+ if (language != null && language.length() > 0) {
+ try {
+ changeLanguage(language);
+ } catch (RemoteException e) {
+ result = ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS;
+ }
+ }
+ sendTerminalResponse(cmdParams.mCmdDet, result, false, 0, null);
+ return;
case OPEN_CHANNEL:
case CLOSE_CHANNEL:
case RECEIVE_DATA:
@@ -881,8 +899,9 @@ public class CatService extends Handler implements AppInterface {
// This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true).
intent.putExtra(AppInterface.CARD_STATUS, cardPresent);
intent.setComponent(AppInterface.getDefaultSTKApplication());
+ intent.putExtra("SLOT_ID", mSlotId);
CatLog.d(this, "Sending Card Status: "
- + cardState + " " + "cardPresent: " + cardPresent);
+ + cardState + " " + "cardPresent: " + cardPresent + "SLOT_ID: " + mSlotId);
mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
}
@@ -1006,6 +1025,13 @@ public class CatService extends Handler implements AppInterface {
}
break;
case LAUNCH_BROWSER:
+ if (resMsg.mResCode == ResultCode.LAUNCH_BROWSER_ERROR) {
+ // Additional info for Default URL unavailable.
+ resMsg.setAdditionalInfo(0x04);
+ } else {
+ resMsg.mIncludeAdditionalInfo = false;
+ resMsg.mAdditionalInfo = 0;
+ }
break;
// 3GPP TS.102.223: Open Channel alpha confirmation should not send TR
case OPEN_CHANNEL:
@@ -1121,4 +1147,13 @@ public class CatService extends Handler implements AppInterface {
mCmdIf.reportStkServiceIsRunning(null);
}
}
+
+ private void changeLanguage(String language) throws RemoteException {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ Configuration config = am.getConfiguration();
+ config.setLocales(new LocaleList(new Locale(language), LocaleList.getDefault()));
+ config.userSetLocale = true;
+ am.updatePersistentConfiguration(config);
+ BackupManager.dataChanged("com.android.providers.settings");
+ }
}
diff --git a/com/android/internal/telephony/cat/CommandParams.java b/com/android/internal/telephony/cat/CommandParams.java
index 7dfedab8..59cd4148 100644
--- a/com/android/internal/telephony/cat/CommandParams.java
+++ b/com/android/internal/telephony/cat/CommandParams.java
@@ -150,6 +150,15 @@ class CallSetupParams extends CommandParams {
}
}
+class LanguageParams extends CommandParams {
+ String mLanguage;
+
+ LanguageParams(CommandDetails cmdDet, String lang) {
+ super(cmdDet);
+ mLanguage = lang;
+ }
+}
+
class SelectItemParams extends CommandParams {
Menu mMenu = null;
boolean mLoadTitleIcon = false;
diff --git a/com/android/internal/telephony/cat/CommandParamsFactory.java b/com/android/internal/telephony/cat/CommandParamsFactory.java
index 3dd53376..eb928885 100644
--- a/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -19,12 +19,15 @@ package com.android.internal.telephony.cat;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
+import android.text.TextUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.uicc.IccFileHandler;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
+
import static com.android.internal.telephony.cat.CatCmdMessage.
SetupEventListConstants.USER_ACTIVITY_EVENT;
import static com.android.internal.telephony.cat.CatCmdMessage.
@@ -47,6 +50,8 @@ class CommandParamsFactory extends Handler {
private int mIconLoadState = LOAD_NO_ICON;
private RilMessageDecoder mCaller = null;
private boolean mloadIcon = false;
+ private String mSavedLanguage;
+ private String mRequestedLanguage;
// constants
static final int MSG_ID_LOAD_ICON_DONE = 1;
@@ -66,6 +71,10 @@ class CommandParamsFactory extends Handler {
static final int DTTZ_SETTING = 0x03;
static final int LANGUAGE_SETTING = 0x04;
+ // Command Qualifier value for language notification command
+ static final int NON_SPECIFIC_LANGUAGE = 0x00;
+ static final int SPECIFIC_LANGUAGE = 0x01;
+
// As per TS 102.223 Annex C, Structure of CAT communications,
// the APDU length can be max 255 bytes. This leaves only 239 bytes for user
// input string. CMD details TLV + Device IDs TLV + Result TLV + Other
@@ -203,6 +212,9 @@ class CommandParamsFactory extends Handler {
case PROVIDE_LOCAL_INFORMATION:
cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
break;
+ case LANGUAGE_NOTIFICATION:
+ cmdPending = processLanguageNotification(cmdDet, ctlvs);
+ break;
case OPEN_CHANNEL:
case CLOSE_CHANNEL:
case RECEIVE_DATA:
@@ -1014,6 +1026,67 @@ class CommandParamsFactory extends Handler {
return false;
}
+ /**
+ * Processes LANGUAGE_NOTIFICATION proactive command from the SIM card.
+ *
+ * The SPECIFIC_LANGUAGE notification sets the specified language.
+ * The NON_SPECIFIC_LANGUAGE notification restores the last specifically set language.
+ *
+ * @param cmdDet Command Details object retrieved from the proactive command object
+ * @param ctlvs List of ComprehensionTlv objects following Command Details
+ * object and Device Identities object within the proactive command
+ * @return false. This function always returns false meaning that the command
+ * processing is not pending and additional asynchronous processing
+ * is not required.
+ */
+ private boolean processLanguageNotification(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)
+ throws ResultException {
+ CatLog.d(this, "process Language Notification");
+
+ String desiredLanguage = null;
+ String currentLanguage = Locale.getDefault().getLanguage();
+ switch (cmdDet.commandQualifier) {
+ case NON_SPECIFIC_LANGUAGE:
+ if (!TextUtils.isEmpty(mSavedLanguage) && (!TextUtils.isEmpty(mRequestedLanguage)
+ && mRequestedLanguage.equals(currentLanguage))) {
+ CatLog.d(this, "Non-specific language notification changes the language "
+ + "setting back to " + mSavedLanguage);
+ desiredLanguage = mSavedLanguage;
+ }
+
+ mSavedLanguage = null;
+ mRequestedLanguage = null;
+ break;
+ case SPECIFIC_LANGUAGE:
+ ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.LANGUAGE, ctlvs);
+ if (ctlv != null) {
+ int valueLen = ctlv.getLength();
+ if (valueLen != 2) {
+ throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+ }
+
+ byte[] rawValue = ctlv.getRawValue();
+ int valueIndex = ctlv.getValueIndex();
+ desiredLanguage = GsmAlphabet.gsm8BitUnpackedToString(rawValue, valueIndex, 2);
+
+ if (TextUtils.isEmpty(mSavedLanguage) || (!TextUtils.isEmpty(mRequestedLanguage)
+ && !mRequestedLanguage.equals(currentLanguage))) {
+ mSavedLanguage = currentLanguage;
+ }
+ mRequestedLanguage = desiredLanguage;
+ CatLog.d(this, "Specific language notification changes the language setting to "
+ + mRequestedLanguage);
+ }
+ break;
+ default:
+ CatLog.d(this, "LN[" + cmdDet.commandQualifier + "] Command Not Supported");
+ break;
+ }
+
+ mCmdParams = new LanguageParams(cmdDet, desiredLanguage);
+ return false;
+ }
+
private boolean processBIPClient(CommandDetails cmdDet,
List<ComprehensionTlv> ctlvs) throws ResultException {
AppInterface.CommandType commandType =
diff --git a/com/android/internal/telephony/cdma/SmsMessage.java b/com/android/internal/telephony/cdma/SmsMessage.java
index 629173df..7a53ef63 100644
--- a/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/com/android/internal/telephony/cdma/SmsMessage.java
@@ -161,7 +161,7 @@ public class SmsMessage extends SmsMessageBase {
// Second byte is the MSG_LEN, length of the message
// See 3GPP2 C.S0023 3.4.27
- int size = data[1];
+ int size = data[1] & 0xFF;
// Note: Data may include trailing FF's. That's OK; message
// should still parse correctly.
diff --git a/com/android/internal/telephony/dataconnection/DcTracker.java b/com/android/internal/telephony/dataconnection/DcTracker.java
index f9b00178..fb756cd9 100644
--- a/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -149,8 +149,6 @@ public class DcTracker extends Handler {
private static final boolean DATA_STALL_SUSPECTED = true;
private static final boolean DATA_STALL_NOT_SUSPECTED = false;
- private String RADIO_RESET_PROPERTY = "gsm.radioreset";
-
private static final String INTENT_RECONNECT_ALARM =
"com.android.internal.telephony.data-reconnect";
private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type";
@@ -2246,7 +2244,7 @@ public class DcTracker extends Handler {
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// Get current sub id.
- int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+ int subId = mPhone.getSubId();
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
if (DBG) {
@@ -4546,13 +4544,11 @@ public class DcTracker extends Handler {
public static final int CLEANUP = 1;
public static final int REREGISTER = 2;
public static final int RADIO_RESTART = 3;
- public static final int RADIO_RESTART_WITH_PROP = 4;
private static boolean isAggressiveRecovery(int value) {
return ((value == RecoveryAction.CLEANUP) ||
(value == RecoveryAction.REREGISTER) ||
- (value == RecoveryAction.RADIO_RESTART) ||
- (value == RecoveryAction.RADIO_RESTART_WITH_PROP));
+ (value == RecoveryAction.RADIO_RESTART));
}
}
@@ -4598,23 +4594,6 @@ public class DcTracker extends Handler {
EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
mSentSinceLastRecv);
if (DBG) log("restarting radio");
- putRecoveryAction(RecoveryAction.RADIO_RESTART_WITH_PROP);
- restartRadio();
- break;
- case RecoveryAction.RADIO_RESTART_WITH_PROP:
- // This is in case radio restart has not recovered the data.
- // It will set an additional "gsm.radioreset" property to tell
- // RIL or system to take further action.
- // The implementation of hard reset recovery action is up to OEM product.
- // Once RADIO_RESET property is consumed, it is expected to set back
- // to false by RIL.
- EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP, -1);
- if (DBG) log("restarting radio with gsm.radioreset to true");
- SystemProperties.set(RADIO_RESET_PROPERTY, "true");
- // give 1 sec so property change can be notified.
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {}
restartRadio();
putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
break;
diff --git a/com/android/internal/telephony/gsm/GsmSmsAddress.java b/com/android/internal/telephony/gsm/GsmSmsAddress.java
index 2fbf7ed5..bd8c83e6 100644
--- a/com/android/internal/telephony/gsm/GsmSmsAddress.java
+++ b/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -17,6 +17,7 @@
package com.android.internal.telephony.gsm;
import android.telephony.PhoneNumberUtils;
+
import java.text.ParseException;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsAddress;
@@ -71,8 +72,11 @@ public class GsmSmsAddress extends SmsAddress {
// Make sure the final unused BCD digit is 0xf
origBytes[length - 1] |= 0xf0;
}
- address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
- OFFSET_TOA, length - OFFSET_TOA);
+ address = PhoneNumberUtils.calledPartyBCDToString(
+ origBytes,
+ OFFSET_TOA,
+ length - OFFSET_TOA,
+ PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
// And restore origBytes
origBytes[length - 1] = lastByte;
diff --git a/com/android/internal/telephony/gsm/SmsMessage.java b/com/android/internal/telephony/gsm/SmsMessage.java
index d4098d94..1ca19e01 100644
--- a/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/com/android/internal/telephony/gsm/SmsMessage.java
@@ -535,8 +535,8 @@ public class SmsMessage extends SmsMessageBase {
} else {
// SC address
try {
- ret = PhoneNumberUtils
- .calledPartyBCDToString(mPdu, mCur, len);
+ ret = PhoneNumberUtils.calledPartyBCDToString(
+ mPdu, mCur, len, PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
} catch (RuntimeException tr) {
Rlog.d(LOG_TAG, "invalid SC address: ", tr);
ret = null;
diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java
index 45dc0b27..03d83dfc 100644
--- a/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -1038,6 +1038,9 @@ public class ImsPhone extends ImsPhoneBase {
break;
case ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE:
error = CommandException.Error.RADIO_NOT_AVAILABLE;
+ break;
+ case ImsReasonInfo.CODE_FDN_BLOCKED:
+ error = CommandException.Error.FDN_CHECK_FAILURE;
default:
break;
}
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index ab30878b..f837b563 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -2566,7 +2566,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
&& targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
&& targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
if (isHandoverFromWifi && imsCall.isVideoCall()) {
- if (mNotifyHandoverVideoFromWifiToLTE) {
+ if (mNotifyHandoverVideoFromWifiToLTE && mIsDataEnabled) {
log("onCallHandover :: notifying of WIFI to LTE handover.");
conn.onConnectionEvent(
TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
@@ -2575,7 +2575,7 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
if (!mIsDataEnabled && mIsViLteDataMetered) {
// Call was downgraded from WIFI to LTE and data is metered; downgrade the
// call now.
- downgradeVideoCall(ImsReasonInfo.CODE_DATA_DISABLED, conn);
+ downgradeVideoCall(ImsReasonInfo.CODE_WIFI_LOST, conn);
}
}
} else {
@@ -3535,8 +3535,9 @@ public class ImsPhoneCallTracker extends CallTracker implements ImsPullCall {
// If the carrier supports downgrading to voice, then we can simply issue a
// downgrade to voice instead of terminating the call.
modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
- } else if (mSupportPauseVideo) {
- // The carrier supports video pause signalling, so pause the video.
+ } else if (mSupportPauseVideo && reasonCode != ImsReasonInfo.CODE_WIFI_LOST) {
+ // The carrier supports video pause signalling, so pause the video if we didn't just
+ // lose wifi; in that case just disconnect.
mShouldUpdateImsConfigOnDisconnect = true;
conn.pauseVideo(VideoPauseTracker.SOURCE_DATA_ENABLED);
} else {
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 4e3957ed..9c99055c 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -40,6 +40,7 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import com.android.ims.ImsException;
+import com.android.ims.ImsReasonInfo;
import com.android.ims.ImsSsInfo;
import com.android.ims.ImsUtInterface;
import com.android.internal.telephony.CallForwardInfo;
@@ -1172,6 +1173,14 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode {
}
private CharSequence getErrorMessage(AsyncResult ar) {
+ if (ar.exception instanceof CommandException) {
+ CommandException.Error err = ((CommandException) (ar.exception)).getCommandError();
+ if (err == CommandException.Error.FDN_CHECK_FAILURE) {
+ Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
+ return mContext.getText(com.android.internal.R.string.mmiFdnError);
+ }
+ }
+
return mContext.getText(com.android.internal.R.string.mmiError);
}
@@ -1216,18 +1225,15 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode {
if (err.getCommandError() == CommandException.Error.PASSWORD_INCORRECT) {
sb.append(mContext.getText(
com.android.internal.R.string.passwordIncorrect));
+ } else if (err.getCommandError() == CommandException.Error.FDN_CHECK_FAILURE) {
+ sb.append(mContext.getText(com.android.internal.R.string.mmiFdnError));
} else if (err.getMessage() != null) {
sb.append(err.getMessage());
} else {
sb.append(mContext.getText(com.android.internal.R.string.mmiError));
}
- } else {
- ImsException error = (ImsException) ar.exception;
- if (error.getMessage() != null) {
- sb.append(error.getMessage());
- } else {
- sb.append(getErrorMessage(ar));
- }
+ } else if (ar.exception instanceof ImsException) {
+ sb.append(getImsErrorMessage(ar));
}
} else if (isActivate()) {
mState = State.COMPLETE;
@@ -1360,12 +1366,7 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode {
mState = State.FAILED;
if (ar.exception instanceof ImsException) {
- ImsException error = (ImsException) ar.exception;
- if (error.getMessage() != null) {
- sb.append(error.getMessage());
- } else {
- sb.append(getErrorMessage(ar));
- }
+ sb.append(getImsErrorMessage(ar));
}
else {
sb.append(getErrorMessage(ar));
@@ -1421,21 +1422,14 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
+ mState = State.FAILED;
if (ar.exception != null) {
- mState = State.FAILED;
-
if (ar.exception instanceof ImsException) {
- ImsException error = (ImsException) ar.exception;
- if (error.getMessage() != null) {
- sb.append(error.getMessage());
- } else {
- sb.append(getErrorMessage(ar));
- }
+ sb.append(getImsErrorMessage(ar));
} else {
sb.append(getErrorMessage(ar));
}
} else {
- mState = State.FAILED;
ImsSsInfo ssInfo = null;
if (ar.result instanceof Bundle) {
Rlog.d(LOG_TAG, "onSuppSvcQueryComplete: Received CLIP/COLP/COLR Response.");
@@ -1486,12 +1480,7 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode {
mState = State.FAILED;
if (ar.exception instanceof ImsException) {
- ImsException error = (ImsException) ar.exception;
- if (error.getMessage() != null) {
- sb.append(error.getMessage());
- } else {
- sb.append(getErrorMessage(ar));
- }
+ sb.append(getImsErrorMessage(ar));
} else {
sb.append(getErrorMessage(ar));
}
@@ -1525,14 +1514,8 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode {
mState = State.FAILED;
if (ar.exception != null) {
-
if (ar.exception instanceof ImsException) {
- ImsException error = (ImsException) ar.exception;
- if (error.getMessage() != null) {
- sb.append(error.getMessage());
- } else {
- sb.append(getErrorMessage(ar));
- }
+ sb.append(getImsErrorMessage(ar));
}
} else {
Bundle ssInfo = (Bundle) ar.result;
@@ -1623,12 +1606,7 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode {
mState = State.FAILED;
if (ar.exception instanceof ImsException) {
- ImsException error = (ImsException) ar.exception;
- if (error.getMessage() != null) {
- sb.append(error.getMessage());
- } else {
- sb.append(getErrorMessage(ar));
- }
+ sb.append(getImsErrorMessage(ar));
} else {
sb.append(getErrorMessage(ar));
}
@@ -1676,6 +1654,17 @@ public final class ImsPhoneMmiCode extends Handler implements MmiCode {
return sb;
}
+ private CharSequence getImsErrorMessage(AsyncResult ar) {
+ ImsException error = (ImsException) ar.exception;
+ if (error.getCode() == ImsReasonInfo.CODE_FDN_BLOCKED) {
+ return mContext.getText(com.android.internal.R.string.mmiFdnError);
+ } else if (error.getMessage() != null) {
+ return error.getMessage();
+ } else {
+ return getErrorMessage(ar);
+ }
+ }
+
@Override
public ResultReceiver getUssdCallbackReceiver() {
return this.mCallbackReceiver;
diff --git a/com/android/internal/telephony/uicc/AdnRecord.java b/com/android/internal/telephony/uicc/AdnRecord.java
index 203236ca..4414cafb 100644
--- a/com/android/internal/telephony/uicc/AdnRecord.java
+++ b/com/android/internal/telephony/uicc/AdnRecord.java
@@ -19,8 +19,8 @@ package com.android.internal.telephony.uicc;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
import android.telephony.Rlog;
+import android.text.TextUtils;
import com.android.internal.telephony.GsmAlphabet;
@@ -248,7 +248,8 @@ public class AdnRecord implements Parcelable {
Rlog.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset);
return null;
} else {
- bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(mNumber);
+ bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+ mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
System.arraycopy(bcdNumber, 0, adnString,
footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
@@ -289,7 +290,10 @@ public class AdnRecord implements Parcelable {
}
mNumber += PhoneNumberUtils.calledPartyBCDFragmentToString(
- extRecord, 2, 0xff & extRecord[1]);
+ extRecord,
+ 2,
+ 0xff & extRecord[1],
+ PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
// We don't support ext record chaining.
@@ -327,7 +331,10 @@ public class AdnRecord implements Parcelable {
// the ME (see note 2)."
mNumber = PhoneNumberUtils.calledPartyBCDToString(
- record, footerOffset + 1, numberLength);
+ record,
+ footerOffset + 1,
+ numberLength,
+ PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
mExtRecord = 0xff & record[record.length - 1];
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index 724b4781..dad1ee2b 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -563,7 +563,8 @@ public class SIMRecords extends IccRecords {
// Spec reference for EF_CFIS contents, TS 51.011 section 10.3.46.
if (enable && !TextUtils.isEmpty(dialNumber)) {
logv("EF_CFIS: updating cf number, " + Rlog.pii(LOG_TAG, dialNumber));
- byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(dialNumber);
+ byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+ dialNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
System.arraycopy(bcdNumber, 0, mEfCfis, CFIS_TON_NPI_OFFSET, bcdNumber.length);
diff --git a/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index e50f40c0..bfa458b8 100644
--- a/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -54,7 +54,10 @@ public class UiccCarrierPrivilegeRules extends Handler {
private static final String LOG_TAG = "UiccCarrierPrivilegeRules";
private static final boolean DBG = false;
- private static final String AID = "A00000015141434C00";
+ private static final String ARAM_AID = "A00000015141434C00";
+ private static final String ARAD_AID = "A00000015144414300";
+ private static final int ARAM = 1;
+ private static final int ARAD = 0;
private static final int CLA = 0x80;
private static final int COMMAND = 0xCA;
private static final int P1 = 0xFF;
@@ -184,18 +187,21 @@ public class UiccCarrierPrivilegeRules extends Handler {
private String mStatusMessage; // Only used for debugging.
private int mChannelId; // Channel Id for communicating with UICC.
private int mRetryCount; // Number of retries for open logical channel.
+ private boolean mCheckedRules = false; // Flag that used to mark whether get rules from ARA-D.
+ private int mAIDInUse; // Message component to identify which AID is currently in-use.
private final Runnable mRetryRunnable = new Runnable() {
@Override
public void run() {
- openChannel();
+ openChannel(mAIDInUse);
}
};
- private void openChannel() {
+ private void openChannel(int aidId) {
// Send open logical channel request.
+ String aid = (aidId == ARAD) ? ARAD_AID : ARAM_AID;
int p2 = 0x00;
- mUiccCard.iccOpenLogicalChannel(AID, p2, /* supported p2 value */
- obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, null));
+ mUiccCard.iccOpenLogicalChannel(aid, p2, /* supported p2 value */
+ obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, 0, aidId, null));
}
public UiccCarrierPrivilegeRules(UiccCard uiccCard, Message loadedCallback) {
@@ -207,7 +213,9 @@ public class UiccCarrierPrivilegeRules extends Handler {
mRules = "";
mAccessRules = new ArrayList<>();
- openChannel();
+ // Open logical channel with ARA_D.
+ mAIDInUse = ARAD;
+ openChannel(mAIDInUse);
}
/**
@@ -391,6 +399,7 @@ public class UiccCarrierPrivilegeRules extends Handler {
@Override
public void handleMessage(Message msg) {
AsyncResult ar;
+ mAIDInUse = msg.arg2; // 0 means ARA-D and 1 means ARA-M.
switch (msg.what) {
@@ -400,23 +409,34 @@ public class UiccCarrierPrivilegeRules extends Handler {
if (ar.exception == null && ar.result != null) {
mChannelId = ((int[]) ar.result)[0];
mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3,
- DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
- mChannelId));
+ DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId,
+ mAIDInUse));
} else {
// MISSING_RESOURCE could be due to logical channels temporarily unavailable,
// so we retry up to MAX_RETRY times, with an interval of RETRY_INTERVAL_MS.
if (ar.exception instanceof CommandException && mRetryCount < MAX_RETRY
&& ((CommandException) (ar.exception)).getCommandError()
- == CommandException.Error.MISSING_RESOURCE) {
+ == CommandException.Error.MISSING_RESOURCE) {
mRetryCount++;
removeCallbacks(mRetryRunnable);
postDelayed(mRetryRunnable, RETRY_INTERVAL_MS);
} else {
- // if rules cannot be read from ARA applet,
- // fallback to PKCS15-based ARF.
- log("No ARA, try ARF next.");
- mUiccPkcs15 = new UiccPkcs15(mUiccCard,
- obtainMessage(EVENT_PKCS15_READ_DONE));
+ if (mAIDInUse == ARAD) {
+ // Open logical channel with ARA_M.
+ mRules = "";
+ openChannel(1);
+ }
+ if (mAIDInUse == ARAM) {
+ if (mCheckedRules) {
+ updateState(STATE_LOADED, "Success!");
+ } else {
+ // if rules cannot be read from both ARA_D and ARA_M applet,
+ // fallback to PKCS15-based ARF.
+ log("No ARA, try ARF next.");
+ mUiccPkcs15 = new UiccPkcs15(mUiccCard,
+ obtainMessage(EVENT_PKCS15_READ_DONE));
+ }
+ }
}
}
break;
@@ -432,34 +452,49 @@ public class UiccCarrierPrivilegeRules extends Handler {
mRules += IccUtils.bytesToHexString(response.payload)
.toUpperCase(Locale.US);
if (isDataComplete()) {
- mAccessRules = parseRules(mRules);
- updateState(STATE_LOADED, "Success!");
+ mAccessRules.addAll(parseRules(mRules));
+ if (mAIDInUse == ARAD) {
+ mCheckedRules = true;
+ } else {
+ updateState(STATE_LOADED, "Success!");
+ }
} else {
mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND,
P1, P2_EXTENDED_DATA, P3, DATA,
obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
- mChannelId));
+ mChannelId, mAIDInUse));
break;
}
} catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
- updateState(STATE_ERROR, "Error parsing rules: " + ex);
+ if (mAIDInUse == ARAM) {
+ updateState(STATE_ERROR, "Error parsing rules: " + ex);
+ }
}
} else {
- String errorMsg = "Invalid response: payload=" + response.payload
- + " sw1=" + response.sw1 + " sw2=" + response.sw2;
- updateState(STATE_ERROR, errorMsg);
+ if (mAIDInUse == ARAM) {
+ String errorMsg = "Invalid response: payload=" + response.payload
+ + " sw1=" + response.sw1 + " sw2=" + response.sw2;
+ updateState(STATE_ERROR, errorMsg);
+ }
}
} else {
- updateState(STATE_ERROR, "Error reading value from SIM.");
+ if (mAIDInUse == ARAM) {
+ updateState(STATE_ERROR, "Error reading value from SIM.");
+ }
}
mUiccCard.iccCloseLogicalChannel(mChannelId, obtainMessage(
- EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
+ EVENT_CLOSE_LOGICAL_CHANNEL_DONE, 0, mAIDInUse));
mChannelId = -1;
break;
case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
log("EVENT_CLOSE_LOGICAL_CHANNEL_DONE");
+ if (mAIDInUse == ARAD) {
+ // Close logical channel with ARA_D and then open logical channel with ARA_M.
+ mRules = "";
+ openChannel(1);
+ }
break;
case EVENT_PKCS15_READ_DONE:
@@ -492,7 +527,7 @@ public class UiccCarrierPrivilegeRules extends Handler {
String lengthBytes = allRules.parseLength(mRules);
log("isDataComplete lengthBytes: " + lengthBytes);
if (mRules.length() == TAG_ALL_REF_AR_DO.length() + lengthBytes.length() +
- allRules.length) {
+ allRules.length) {
log("isDataComplete yes");
return true;
} else {
@@ -522,7 +557,7 @@ public class UiccCarrierPrivilegeRules extends Handler {
if (accessRule != null) {
accessRules.add(accessRule);
} else {
- Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
+ Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
}
}
return accessRules;
@@ -644,15 +679,15 @@ public class UiccCarrierPrivilegeRules extends Handler {
* Converts state into human readable format.
*/
private String getStateString(int state) {
- switch (state) {
- case STATE_LOADING:
- return "STATE_LOADING";
- case STATE_LOADED:
- return "STATE_LOADED";
- case STATE_ERROR:
- return "STATE_ERROR";
- default:
- return "UNKNOWN";
- }
+ switch (state) {
+ case STATE_LOADING:
+ return "STATE_LOADING";
+ case STATE_LOADED:
+ return "STATE_LOADED";
+ case STATE_ERROR:
+ return "STATE_ERROR";
+ default:
+ return "UNKNOWN";
+ }
}
-}
+} \ No newline at end of file
diff --git a/com/android/internal/util/BitUtils.java b/com/android/internal/util/BitUtils.java
index 28f12ebf..ba80aeae 100644
--- a/com/android/internal/util/BitUtils.java
+++ b/com/android/internal/util/BitUtils.java
@@ -93,6 +93,10 @@ public final class BitUtils {
return s & 0xffff;
}
+ public static int uint16(byte hi, byte lo) {
+ return ((hi & 0xff) << 8) | (lo & 0xff);
+ }
+
public static long uint32(int i) {
return i & 0xffffffffL;
}
diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java
new file mode 100644
index 00000000..ad84353f
--- /dev/null
+++ b/com/android/internal/util/RingBuffer.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.internal.util;
+
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * A simple ring buffer structure with bounded capacity backed by an array.
+ * Events can always be added at the logical end of the buffer. If the buffer is
+ * full, oldest events are dropped when new events are added.
+ * {@hide}
+ */
+public class RingBuffer<T> {
+
+ // Array for storing events.
+ private final T[] mBuffer;
+ // Cursor keeping track of the logical end of the array. This cursor never
+ // wraps and instead keeps track of the total number of append() operations.
+ private long mCursor = 0;
+
+ public RingBuffer(Class<T> c, int capacity) {
+ checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
+ // Java cannot create generic arrays without a runtime hint.
+ mBuffer = (T[]) Array.newInstance(c, capacity);
+ }
+
+ public int size() {
+ return (int) Math.min(mBuffer.length, (long) mCursor);
+ }
+
+ public void append(T t) {
+ mBuffer[indexOf(mCursor++)] = t;
+ }
+
+ public T[] toArray() {
+ // Only generic way to create a T[] from another T[]
+ T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
+ // Reverse iteration from youngest event to oldest event.
+ long inCursor = mCursor - 1;
+ int outIdx = out.length - 1;
+ while (outIdx >= 0) {
+ out[outIdx--] = (T) mBuffer[indexOf(inCursor--)];
+ }
+ return out;
+ }
+
+ private int indexOf(long cursor) {
+ return (int) Math.abs(cursor % mBuffer.length);
+ }
+}
diff --git a/com/android/internal/widget/LinearLayoutManager.java b/com/android/internal/widget/LinearLayoutManager.java
index d82c7466..0000a74f 100644
--- a/com/android/internal/widget/LinearLayoutManager.java
+++ b/com/android/internal/widget/LinearLayoutManager.java
@@ -168,10 +168,6 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements
/**
* Constructor used when layout manager is set in XML by RecyclerView attribute
* "layoutManager". Defaults to vertical orientation.
- *
- * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
- * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
- * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
*/
public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
diff --git a/com/android/internal/widget/LockPatternUtils.java b/com/android/internal/widget/LockPatternUtils.java
index b8ef82b8..4be6b28a 100644
--- a/com/android/internal/widget/LockPatternUtils.java
+++ b/com/android/internal/widget/LockPatternUtils.java
@@ -303,7 +303,7 @@ public class LockPatternUtils {
}
public void reportFailedPasswordAttempt(int userId) {
- if (userId == USER_FRP && frpCredentialEnabled()) {
+ if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
return;
}
getDevicePolicyManager().reportFailedPasswordAttempt(userId);
@@ -311,7 +311,7 @@ public class LockPatternUtils {
}
public void reportSuccessfulPasswordAttempt(int userId) {
- if (userId == USER_FRP && frpCredentialEnabled()) {
+ if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
return;
}
getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
@@ -319,21 +319,21 @@ public class LockPatternUtils {
}
public void reportPasswordLockout(int timeoutMs, int userId) {
- if (userId == USER_FRP && frpCredentialEnabled()) {
+ if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
return;
}
getTrustManager().reportUnlockLockout(timeoutMs, userId);
}
public int getCurrentFailedPasswordAttempts(int userId) {
- if (userId == USER_FRP && frpCredentialEnabled()) {
+ if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
return 0;
}
return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
}
public int getMaximumFailedPasswordsForWipe(int userId) {
- if (userId == USER_FRP && frpCredentialEnabled()) {
+ if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
return 0;
}
return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
@@ -1774,11 +1774,12 @@ public class LockPatternUtils {
return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
}
- public static boolean userOwnsFrpCredential(UserInfo info) {
- return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled();
+ public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
+ return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
}
- public static boolean frpCredentialEnabled() {
- return FRP_CREDENTIAL_ENABLED;
+ public static boolean frpCredentialEnabled(Context context) {
+ return FRP_CREDENTIAL_ENABLED && context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
}
}
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
new file mode 100644
index 00000000..86e7b38a
--- /dev/null
+++ b/com/android/internal/widget/Magnifier.java
@@ -0,0 +1,184 @@
+/*
+ * 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 com.android.internal.widget;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.PixelCopy;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Android magnifier widget. Can be used by any view which is attached to window.
+ */
+public final class Magnifier {
+ private static final String LOG_TAG = "magnifier";
+ // The view for which this magnifier is attached.
+ private final View mView;
+ // The window containing the magnifier.
+ private final PopupWindow mWindow;
+ // The center coordinates of the window containing the magnifier.
+ private final Point mWindowCoords = new Point();
+ // The width of the window containing the magnifier.
+ private final int mWindowWidth;
+ // The height of the window containing the magnifier.
+ private final int mWindowHeight;
+ // The bitmap used to display the contents of the magnifier.
+ private final Bitmap mBitmap;
+ // The center coordinates of the content that is to be magnified.
+ private final Point mCenterZoomCoords = new Point();
+ // The callback of the pixel copy request will be invoked on this Handler when
+ // the copy is finished.
+ private final Handler mPixelCopyHandler = Handler.getMain();
+
+ /**
+ * Initializes a magnifier.
+ *
+ * @param view the view for which this magnifier is attached
+ */
+ @UiThread
+ public Magnifier(@NonNull View view) {
+ mView = Preconditions.checkNotNull(view);
+ final Context context = mView.getContext();
+ final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
+ mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
+ mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
+ final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
+
+ mWindow = new PopupWindow(context);
+ mWindow.setContentView(content);
+ mWindow.setWidth(mWindowWidth);
+ mWindow.setHeight(mWindowHeight);
+ mWindow.setElevation(elevation);
+ mWindow.setTouchable(false);
+ mWindow.setBackgroundDrawable(null);
+
+ mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888);
+ getImageView().setImageBitmap(mBitmap);
+ }
+
+ /**
+ * Shows the magnifier on the screen.
+ *
+ * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source
+ * @param centerYOnScreen vertical coordinate of the center point of the magnifier source
+ * @param scale the scale at which the magnifier zooms on the source content
+ */
+ public void show(@FloatRange(from=0) float centerXOnScreen,
+ @FloatRange(from=0) float centerYOnScreen,
+ @FloatRange(from=1, to=10) float scale) {
+ maybeResizeBitmap(scale);
+ configureCoordinates(centerXOnScreen, centerYOnScreen);
+ performPixelCopy();
+
+ if (mWindow.isShowing()) {
+ mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
+ mWindow.getHeight());
+ } else {
+ mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
+ mWindowCoords.x, mWindowCoords.y);
+ }
+ }
+
+ /**
+ * Dismisses the magnifier from the screen.
+ */
+ public void dismiss() {
+ mWindow.dismiss();
+ }
+
+ /**
+ * @return the height of the magnifier window.
+ */
+ public int getHeight() {
+ return mWindowHeight;
+ }
+
+ /**
+ * @return the width of the magnifier window.
+ */
+ public int getWidth() {
+ return mWindowWidth;
+ }
+
+ private void maybeResizeBitmap(float scale) {
+ final int bitmapWidth = (int) (mWindowWidth / scale);
+ final int bitmapHeight = (int) (mWindowHeight / scale);
+ if (mBitmap.getWidth() != bitmapWidth || mBitmap.getHeight() != bitmapHeight) {
+ mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+ getImageView().setImageBitmap(mBitmap);
+ }
+ }
+
+ private void configureCoordinates(float posXOnScreen, float posYOnScreen) {
+ mCenterZoomCoords.x = (int) posXOnScreen;
+ mCenterZoomCoords.y = (int) posYOnScreen;
+
+ final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
+ R.dimen.magnifier_offset);
+ final int availableTopSpace = (mCenterZoomCoords.y - mWindowHeight / 2)
+ - verticalMagnifierOffset - (mBitmap.getHeight() / 2);
+
+ mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
+ mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2
+ + verticalMagnifierOffset * (availableTopSpace > 0 ? -1 : 1);
+ }
+
+ private void performPixelCopy() {
+ int startX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
+ // Clamp startX value to avoid distorting the rendering of the magnifier content.
+ if (startX < 0) {
+ startX = 0;
+ } else if (startX + mBitmap.getWidth() > mView.getWidth()) {
+ startX = mView.getWidth() - mBitmap.getWidth();
+ }
+
+ final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+ final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
+
+ if (viewRootImpl != null && viewRootImpl.mSurface != null
+ && viewRootImpl.mSurface.isValid()) {
+ PixelCopy.request(
+ viewRootImpl.mSurface,
+ new Rect(startX, startY, startX + mBitmap.getWidth(),
+ startY + mBitmap.getHeight()),
+ mBitmap,
+ result -> getImageView().invalidate(),
+ mPixelCopyHandler);
+ } else {
+ Log.d(LOG_TAG, "Could not perform PixelCopy request");
+ }
+ }
+
+ private ImageView getImageView() {
+ return mWindow.getContentView().findViewById(R.id.magnifier_image);
+ }
+}
diff --git a/com/android/keyguard/KeyguardDisplayManager.java b/com/android/keyguard/KeyguardDisplayManager.java
index 8de1d317..2bc0e45c 100644
--- a/com/android/keyguard/KeyguardDisplayManager.java
+++ b/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,6 +15,8 @@
*/
package com.android.keyguard;
+import static android.view.Display.INVALID_DISPLAY;
+
import android.app.Presentation;
import android.content.Context;
import android.content.DialogInterface;
@@ -28,16 +30,21 @@ import android.view.Display;
import android.view.View;
import android.view.WindowManager;
+// TODO(multi-display): Support multiple external displays
public class KeyguardDisplayManager {
protected static final String TAG = "KeyguardDisplayManager";
private static boolean DEBUG = KeyguardConstants.DEBUG;
+
+ private final ViewMediatorCallback mCallback;
+ private final MediaRouter mMediaRouter;
+ private final Context mContext;
+
Presentation mPresentation;
- private MediaRouter mMediaRouter;
- private Context mContext;
private boolean mShowing;
- public KeyguardDisplayManager(Context context) {
+ public KeyguardDisplayManager(Context context, ViewMediatorCallback callback) {
mContext = context;
+ mCallback = callback;
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
}
@@ -90,6 +97,7 @@ public class KeyguardDisplayManager {
};
protected void updateDisplays(boolean showing) {
+ Presentation originalPresentation = mPresentation;
if (showing) {
MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
@@ -121,6 +129,13 @@ public class KeyguardDisplayManager {
mPresentation = null;
}
}
+
+ // mPresentation is only updated when the display changes
+ if (mPresentation != originalPresentation) {
+ final int displayId = mPresentation != null
+ ? mPresentation.getDisplay().getDisplayId() : INVALID_DISPLAY;
+ mCallback.onSecondaryDisplayShowingChanged(displayId);
+ }
}
private final static class KeyguardPresentation extends Presentation {
diff --git a/com/android/keyguard/KeyguardSimPinView.java b/com/android/keyguard/KeyguardSimPinView.java
index 7225ba99..432b4061 100644
--- a/com/android/keyguard/KeyguardSimPinView.java
+++ b/com/android/keyguard/KeyguardSimPinView.java
@@ -66,7 +66,11 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
// again when the PUK locked SIM is re-entered.
case ABSENT: {
KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
- mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ // onSimStateChanged callback can fire when the SIM PIN lock is not currently
+ // active and mCallback is null.
+ if (mCallback != null) {
+ mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ }
break;
}
default:
diff --git a/com/android/keyguard/KeyguardSimPukView.java b/com/android/keyguard/KeyguardSimPukView.java
index 171cf236..7f79008b 100644
--- a/com/android/keyguard/KeyguardSimPukView.java
+++ b/com/android/keyguard/KeyguardSimPukView.java
@@ -72,7 +72,11 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
// move into the READY state and the PUK lock keyguard should be removed.
case READY: {
KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
- mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ // mCallback can be null if onSimStateChanged callback is called when keyguard
+ // isn't active.
+ if (mCallback != null) {
+ mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ }
break;
}
default:
diff --git a/com/android/keyguard/KeyguardUpdateMonitor.java b/com/android/keyguard/KeyguardUpdateMonitor.java
index d95402cd..d83a6c60 100644
--- a/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
import static android.os.BatteryManager.BATTERY_STATUS_FULL;
@@ -452,6 +454,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
*/
public void setKeyguardGoingAway(boolean goingAway) {
mKeyguardGoingAway = goingAway;
+ updateFingerprintListeningState();
}
/**
@@ -1069,6 +1072,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
cb.onDreamingStateChanged(mIsDreaming);
}
}
+ updateFingerprintListeningState();
}
/**
@@ -1772,7 +1776,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
public void onTaskStackChangedBackground() {
try {
ActivityManager.StackInfo info = ActivityManager.getService().getStackInfo(
- ActivityManager.StackId.ASSISTANT_STACK_ID);
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
if (info == null) {
return;
}
diff --git a/com/android/keyguard/ViewMediatorCallback.java b/com/android/keyguard/ViewMediatorCallback.java
index 327d2189..b194de43 100644
--- a/com/android/keyguard/ViewMediatorCallback.java
+++ b/com/android/keyguard/ViewMediatorCallback.java
@@ -88,4 +88,9 @@ public interface ViewMediatorCallback {
* {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
*/
int getBouncerPromptReason();
+
+ /**
+ * Invoked when the secondary display showing a keyguard window changes.
+ */
+ void onSecondaryDisplayShowingChanged(int displayId);
}
diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java
index 5dca8e7f..0cfc1811 100644
--- a/com/android/layoutlib/bridge/Bridge.java
+++ b/com/android/layoutlib/bridge/Bridge.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -14,659 +14,62 @@
* limitations under the License.
*/
-package com.android.layoutlib.bridge;
-
-import com.android.ide.common.rendering.api.Capability;
-import com.android.ide.common.rendering.api.DrawableParams;
-import com.android.ide.common.rendering.api.Features;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderSession;
+package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.Result.Status;
import com.android.ide.common.rendering.api.SessionParams;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.RenderDrawable;
-import com.android.layoutlib.bridge.impl.RenderSessionImpl;
-import com.android.layoutlib.bridge.util.DynamicIdMap;
-import com.android.ninepatch.NinePatchChunk;
-import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.create.MethodAdapter;
-import com.android.tools.layoutlib.create.OverrideMethod;
-import com.android.util.Pair;
-
-import android.annotation.NonNull;
-import android.content.res.BridgeAssetManager;
-import android.graphics.Bitmap;
-import android.graphics.FontFamily_Delegate;
-import android.graphics.Typeface;
-import android.graphics.Typeface_Delegate;
-import android.icu.util.ULocale;
-import android.os.Looper;
-import android.os.Looper_Accessor;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-
-import java.io.File;
-import java.lang.ref.SoftReference;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.concurrent.locks.ReentrantLock;
-import libcore.io.MemoryMappedFile_Delegate;
-
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
/**
- * Main entry point of the LayoutLib Bridge.
- * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
- * {@link #createSession(SessionParams)}
+ * Legacy Bridge used in the SDK version of layoutlib
*/
public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
+ private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported";
+ private static final Result NOT_SUPPORTED_RESULT =
+ Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED);
+ private static BufferedImage sImage;
- private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
+ private static class BridgeRenderSession extends RenderSession {
- public static class StaticMethodNotImplementedException extends RuntimeException {
- private static final long serialVersionUID = 1L;
+ @Override
+ public synchronized BufferedImage getImage() {
+ if (sImage == null) {
+ sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = sImage.createGraphics();
+ g.clearRect(0, 0, 500, 500);
+ g.drawString(SDK_NOT_SUPPORTED, 20, 20);
+ g.dispose();
+ }
- public StaticMethodNotImplementedException(String msg) {
- super(msg);
+ return sImage;
}
- }
-
- /**
- * Lock to ensure only one rendering/inflating happens at a time.
- * This is due to some singleton in the Android framework.
- */
- private final static ReentrantLock sLock = new ReentrantLock();
-
- /**
- * Maps from id to resource type/name. This is for com.android.internal.R
- */
- @SuppressWarnings("deprecation")
- private final static Map<Integer, Pair<ResourceType, String>> sRMap = new HashMap<>();
- /**
- * Reverse map compared to sRMap, resource type -> (resource name -> id).
- * This is for com.android.internal.R.
- */
- private final static Map<ResourceType, Map<String, Integer>> sRevRMap = new EnumMap<>(ResourceType.class);
-
- // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
- // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
- // collision which should be fine.
- private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
- private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
-
- private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
- new WeakHashMap<>();
- private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
- new WeakHashMap<>();
-
- private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>();
- private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
- new HashMap<>();
-
- private static Map<String, Map<String, Integer>> sEnumValueMap;
- private static Map<String, String> sPlatformProperties;
-
- /**
- * A default log than prints to stdout/stderr.
- */
- private final static LayoutLog sDefaultLog = new LayoutLog() {
@Override
- public void error(String tag, String message, Object data) {
- System.err.println(message);
+ public Result render(long timeout, boolean forceMeasure) {
+ return NOT_SUPPORTED_RESULT;
}
@Override
- public void error(String tag, String message, Throwable throwable, Object data) {
- System.err.println(message);
+ public Result measure(long timeout) {
+ return NOT_SUPPORTED_RESULT;
}
@Override
- public void warning(String tag, String message, Object data) {
- System.out.println(message);
- }
- };
-
- /**
- * Current log.
- */
- private static LayoutLog sCurrentLog = sDefaultLog;
-
- private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR;
-
- @Override
- public int getApiLevel() {
- return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
- }
-
- @SuppressWarnings("deprecation")
- @Override
- @Deprecated
- public EnumSet<Capability> getCapabilities() {
- // The Capability class is deprecated and frozen. All Capabilities enumerated there are
- // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf()
- return EnumSet.allOf(Capability.class);
- }
-
- @Override
- public boolean supports(int feature) {
- return feature <= LAST_SUPPORTED_FEATURE;
- }
-
- @Override
- public boolean init(Map<String,String> platformProperties,
- File fontLocation,
- Map<String, Map<String, Integer>> enumValueMap,
- LayoutLog log) {
- sPlatformProperties = platformProperties;
- sEnumValueMap = enumValueMap;
-
- BridgeAssetManager.initSystem();
-
- // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
- // on static (native) methods which prints the signature on the console and
- // throws an exception.
- // This is useful when testing the rendering in ADT to identify static native
- // methods that are ignored -- layoutlib_create makes them returns 0/false/null
- // which is generally OK yet might be a problem, so this is how you'd find out.
- //
- // Currently layoutlib_create only overrides static native method.
- // Static non-natives are not overridden and thus do not get here.
- final String debug = System.getenv("DEBUG_LAYOUT");
- if (debug != null && !debug.equals("0") && !debug.equals("false")) {
-
- OverrideMethod.setDefaultListener(new MethodAdapter() {
- @Override
- public void onInvokeV(String signature, boolean isNative, Object caller) {
- sDefaultLog.error(null, "Missing Stub: " + signature +
- (isNative ? " (native)" : ""), null /*data*/);
-
- if (debug.equalsIgnoreCase("throw")) {
- // Throwing this exception doesn't seem that useful. It breaks
- // the layout editor yet doesn't display anything meaningful to the
- // user. Having the error in the console is just as useful. We'll
- // throw it only if the environment variable is "throw" or "THROW".
- throw new StaticMethodNotImplementedException(signature);
- }
- }
- });
- }
-
- // load the fonts.
- FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath());
- MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile());
-
- // now parse com.android.internal.R (and only this one as android.R is a subset of
- // the internal version), and put the content in the maps.
- try {
- Class<?> r = com.android.internal.R.class;
- // Parse the styleable class first, since it may contribute to attr values.
- parseStyleable();
-
- for (Class<?> inner : r.getDeclaredClasses()) {
- if (inner == com.android.internal.R.styleable.class) {
- // Already handled the styleable case. Not skipping attr, as there may be attrs
- // that are not referenced from styleables.
- continue;
- }
- String resTypeName = inner.getSimpleName();
- ResourceType resType = ResourceType.getEnum(resTypeName);
- if (resType != null) {
- Map<String, Integer> fullMap = null;
- switch (resType) {
- case ATTR:
- fullMap = sRevRMap.get(ResourceType.ATTR);
- break;
- case STRING:
- case STYLE:
- // Slightly less than thousand entries in each.
- fullMap = new HashMap<>(1280);
- // no break.
- default:
- if (fullMap == null) {
- fullMap = new HashMap<>();
- }
- sRevRMap.put(resType, fullMap);
- }
-
- for (Field f : inner.getDeclaredFields()) {
- // only process static final fields. Since the final attribute may have
- // been altered by layoutlib_create, we only check static
- if (!isValidRField(f)) {
- continue;
- }
- Class<?> type = f.getType();
- if (!type.isArray()) {
- Integer value = (Integer) f.get(null);
- //noinspection deprecation
- sRMap.put(value, Pair.of(resType, f.getName()));
- fullMap.put(f.getName(), value);
- }
- }
- }
- }
- } catch (Exception throwable) {
- if (log != null) {
- log.error(LayoutLog.TAG_BROKEN,
- "Failed to load com.android.internal.R from the layout library jar",
- throwable, null);
- }
- return false;
+ public Result getResult() {
+ return NOT_SUPPORTED_RESULT;
}
-
- return true;
- }
-
- /**
- * Tests if the field is pubic, static and one of int or int[].
- */
- private static boolean isValidRField(Field field) {
- int modifiers = field.getModifiers();
- boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
- Class<?> type = field.getType();
- return isAcceptable && type == int.class ||
- (type.isArray() && type.getComponentType() == int.class);
-
}
- private static void parseStyleable() throws Exception {
- // R.attr doesn't contain all the needed values. There are too many resources in the
- // framework for all to be in the R class. Only the ones specified manually in
- // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr
- // values, we try and find them from the styleables.
-
- // There were 1500 elements in this map at M timeframe.
- Map<String, Integer> revRAttrMap = new HashMap<>(2048);
- sRevRMap.put(ResourceType.ATTR, revRAttrMap);
- // There were 2000 elements in this map at M timeframe.
- Map<String, Integer> revRStyleableMap = new HashMap<>(3072);
- sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap);
- Class<?> c = com.android.internal.R.styleable.class;
- Field[] fields = c.getDeclaredFields();
- // Sort the fields to bring all arrays to the beginning, so that indices into the array are
- // able to refer back to the arrays (i.e. no forward references).
- Arrays.sort(fields, (o1, o2) -> {
- if (o1 == o2) {
- return 0;
- }
- Class<?> t1 = o1.getType();
- Class<?> t2 = o2.getType();
- if (t1.isArray() && !t2.isArray()) {
- return -1;
- } else if (t2.isArray() && !t1.isArray()) {
- return 1;
- }
- return o1.getName().compareTo(o2.getName());
- });
- Map<String, int[]> styleables = new HashMap<>();
- for (Field field : fields) {
- if (!isValidRField(field)) {
- // Only consider public static fields that are int or int[].
- // Don't check the final flag as it may have been modified by layoutlib_create.
- continue;
- }
- String name = field.getName();
- if (field.getType().isArray()) {
- int[] styleableValue = (int[]) field.get(null);
- styleables.put(name, styleableValue);
- continue;
- }
- // Not an array.
- String arrayName = name;
- int[] arrayValue = null;
- int index;
- while ((index = arrayName.lastIndexOf('_')) >= 0) {
- // Find the name of the corresponding styleable.
- // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity
- // are mapped to LinearLayout_Layout and not to LinearLayout.
- arrayName = arrayName.substring(0, index);
- arrayValue = styleables.get(arrayName);
- if (arrayValue != null) {
- break;
- }
- }
- index = (Integer) field.get(null);
- if (arrayValue != null) {
- String attrName = name.substring(arrayName.length() + 1);
- int attrValue = arrayValue[index];
- //noinspection deprecation
- sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName));
- revRAttrMap.put(attrName, attrValue);
- }
- //noinspection deprecation
- sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name));
- revRStyleableMap.put(name, index);
- }
- }
@Override
- public boolean dispose() {
- BridgeAssetManager.clearSystem();
-
- // dispose of the default typeface.
- Typeface_Delegate.resetDefaults();
- Typeface.sDynamicTypefaceCache.evictAll();
- sProject9PatchCache.clear();
- sProjectBitmapCache.clear();
-
- return true;
- }
-
- /**
- * Starts a layout session by inflating and rendering it. The method returns a
- * {@link RenderSession} on which further actions can be taken.
- * <p/>
- * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE},
- * this method will only inflate the layout but will NOT render it.
- * @param params the {@link SessionParams} object with all the information necessary to create
- * the scene.
- * @return a new {@link RenderSession} object that contains the result of the layout.
- * @since 5
- */
- @Override
public RenderSession createSession(SessionParams params) {
- try {
- Result lastResult;
- RenderSessionImpl scene = new RenderSessionImpl(params);
- try {
- prepareThread();
- lastResult = scene.init(params.getTimeout());
- if (lastResult.isSuccess()) {
- lastResult = scene.inflate();
-
- boolean doNotRenderOnCreate = Boolean.TRUE.equals(
- params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE));
- if (lastResult.isSuccess() && !doNotRenderOnCreate) {
- lastResult = scene.render(true /*freshRender*/);
- }
- }
- } finally {
- scene.release();
- cleanupThread();
- }
-
- return new BridgeRenderSession(scene, lastResult);
- } catch (Throwable t) {
- // get the real cause of the exception.
- Throwable t2 = t;
- while (t2.getCause() != null) {
- t2 = t2.getCause();
- }
- return new BridgeRenderSession(null,
- ERROR_UNKNOWN.createResult(t2.getMessage(), t));
- }
- }
-
- @Override
- public Result renderDrawable(DrawableParams params) {
- try {
- Result lastResult;
- RenderDrawable action = new RenderDrawable(params);
- try {
- prepareThread();
- lastResult = action.init(params.getTimeout());
- if (lastResult.isSuccess()) {
- lastResult = action.render();
- }
- } finally {
- action.release();
- cleanupThread();
- }
-
- return lastResult;
- } catch (Throwable t) {
- // get the real cause of the exception.
- Throwable t2 = t;
- while (t2.getCause() != null) {
- t2 = t.getCause();
- }
- return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
- }
+ return new BridgeRenderSession();
}
@Override
- public void clearCaches(Object projectKey) {
- if (projectKey != null) {
- sProjectBitmapCache.remove(projectKey);
- sProject9PatchCache.remove(projectKey);
- }
- }
-
- @Override
- public Result getViewParent(Object viewObject) {
- if (viewObject instanceof View) {
- return Status.SUCCESS.createResult(((View)viewObject).getParent());
- }
-
- throw new IllegalArgumentException("viewObject is not a View");
- }
-
- @Override
- public Result getViewIndex(Object viewObject) {
- if (viewObject instanceof View) {
- View view = (View) viewObject;
- ViewParent parentView = view.getParent();
-
- if (parentView instanceof ViewGroup) {
- Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
- }
-
- return Status.SUCCESS.createResult();
- }
-
- throw new IllegalArgumentException("viewObject is not a View");
- }
-
- @Override
- public boolean isRtl(String locale) {
- return isLocaleRtl(locale);
- }
-
- public static boolean isLocaleRtl(String locale) {
- if (locale == null) {
- locale = "";
- }
- ULocale uLocale = new ULocale(locale);
- return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL);
- }
-
- /**
- * Returns the lock for the bridge
- */
- public static ReentrantLock getLock() {
- return sLock;
- }
-
- /**
- * Prepares the current thread for rendering.
- *
- * Note that while this can be called several time, the first call to {@link #cleanupThread()}
- * will do the clean-up, and make the thread unable to do further scene actions.
- */
- public synchronized static void prepareThread() {
- // we need to make sure the Looper has been initialized for this thread.
- // this is required for View that creates Handler objects.
- if (Looper.myLooper() == null) {
- Looper.prepareMainLooper();
- }
- }
-
- /**
- * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
- * <p>
- * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
- * call to this will prevent the thread from doing further scene actions
- */
- public synchronized static void cleanupThread() {
- // clean up the looper
- Looper_Accessor.cleanupThread();
- }
-
- public static LayoutLog getLog() {
- return sCurrentLog;
- }
-
- public static void setLog(LayoutLog log) {
- // check only the thread currently owning the lock can do this.
- if (!sLock.isHeldByCurrentThread()) {
- throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
- }
-
- if (log != null) {
- sCurrentLog = log;
- } else {
- sCurrentLog = sDefaultLog;
- }
- }
-
- /**
- * Returns details of a framework resource from its integer value.
- * @param value the integer value
- * @return a Pair containing the resource type and name, or null if the id
- * does not match any resource.
- */
- @SuppressWarnings("deprecation")
- public static Pair<ResourceType, String> resolveResourceId(int value) {
- Pair<ResourceType, String> pair = sRMap.get(value);
- if (pair == null) {
- pair = sDynamicIds.resolveId(value);
- }
- return pair;
- }
-
- /**
- * Returns the integer id of a framework resource, from a given resource type and resource name.
- * <p/>
- * If no resource is found, it creates a dynamic id for the resource.
- *
- * @param type the type of the resource
- * @param name the name of the resource.
- *
- * @return an {@link Integer} containing the resource id.
- */
- @NonNull
- public static Integer getResourceId(ResourceType type, String name) {
- Map<String, Integer> map = sRevRMap.get(type);
- Integer value = null;
- if (map != null) {
- value = map.get(name);
- }
-
- return value == null ? sDynamicIds.getId(type, name) : value;
-
- }
-
- /**
- * Returns the list of possible enums for a given attribute name.
- */
- public static Map<String, Integer> getEnumValues(String attributeName) {
- if (sEnumValueMap != null) {
- return sEnumValueMap.get(attributeName);
- }
-
- return null;
- }
-
- /**
- * Returns the platform build properties.
- */
- public static Map<String, String> getPlatformProperties() {
- return sPlatformProperties;
- }
-
- /**
- * Returns the bitmap for a specific path, from a specific project cache, or from the
- * framework cache.
- * @param value the path of the bitmap
- * @param projectKey the key of the project, or null to query the framework cache.
- * @return the cached Bitmap or null if not found.
- */
- public static Bitmap getCachedBitmap(String value, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
- if (map != null) {
- SoftReference<Bitmap> ref = map.get(value);
- if (ref != null) {
- return ref.get();
- }
- }
- } else {
- SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
- if (ref != null) {
- return ref.get();
- }
- }
-
- return null;
- }
-
- /**
- * Sets a bitmap in a project cache or in the framework cache.
- * @param value the path of the bitmap
- * @param bmp the Bitmap object
- * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
- */
- public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<Bitmap>> map =
- sProjectBitmapCache.computeIfAbsent(projectKey, k -> new HashMap<>());
-
- map.put(value, new SoftReference<>(bmp));
- } else {
- sFrameworkBitmapCache.put(value, new SoftReference<>(bmp));
- }
- }
-
- /**
- * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
- * framework cache.
- * @param value the path of the 9 patch
- * @param projectKey the key of the project, or null to query the framework cache.
- * @return the cached 9 patch or null if not found.
- */
- public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
-
- if (map != null) {
- SoftReference<NinePatchChunk> ref = map.get(value);
- if (ref != null) {
- return ref.get();
- }
- }
- } else {
- SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
- if (ref != null) {
- return ref.get();
- }
- }
-
- return null;
- }
-
- /**
- * Sets a 9 patch chunk in a project cache or in the framework cache.
- * @param value the path of the 9 patch
- * @param ninePatch the 9 patch object
- * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
- */
- public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
- if (projectKey != null) {
- Map<String, SoftReference<NinePatchChunk>> map =
- sProject9PatchCache.computeIfAbsent(projectKey, k -> new HashMap<>());
-
- map.put(value, new SoftReference<>(ninePatch));
- } else {
- sFramework9PatchCache.put(value, new SoftReference<>(ninePatch));
- }
+ public int getApiLevel() {
+ return 0;
}
}
diff --git a/com/android/layoutlib/bridge/android/BridgeContext.java b/com/android/layoutlib/bridge/android/BridgeContext.java
index 4c6c9d48..4a75be98 100644
--- a/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -40,7 +40,6 @@ import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Notification;
import android.app.SystemServiceRegistry_Accessor;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -86,7 +85,6 @@ import android.util.TypedValue;
import android.view.BridgeInflater;
import android.view.Display;
import android.view.DisplayAdjustments;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -610,45 +608,35 @@ public class BridgeContext extends Context {
@Override
public Object getSystemService(String service) {
- if (LAYOUT_INFLATER_SERVICE.equals(service)) {
- return mBridgeInflater;
- }
-
- if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
- // we need to return a valid service to avoid NPE
- return TextServicesManager.getInstance();
- }
+ switch (service) {
+ case LAYOUT_INFLATER_SERVICE:
+ return mBridgeInflater;
- if (WINDOW_SERVICE.equals(service)) {
- return mWindowManager;
- }
-
- // needed by SearchView
- if (INPUT_METHOD_SERVICE.equals(service)) {
- return null;
- }
+ case TEXT_SERVICES_MANAGER_SERVICE:
+ // we need to return a valid service to avoid NPE
+ return TextServicesManager.getInstance();
- if (POWER_SERVICE.equals(service)) {
- return new PowerManager(this, new BridgePowerManager(), new Handler());
- }
+ case WINDOW_SERVICE:
+ return mWindowManager;
- if (DISPLAY_SERVICE.equals(service)) {
- return mDisplayManager;
- }
+ case POWER_SERVICE:
+ return new PowerManager(this, new BridgePowerManager(), new Handler());
- if (ACCESSIBILITY_SERVICE.equals(service)) {
- return AccessibilityManager.getInstance(this);
- }
+ case DISPLAY_SERVICE:
+ return mDisplayManager;
- if (AUTOFILL_MANAGER_SERVICE.equals(service)) {
- return null;
- }
+ case ACCESSIBILITY_SERVICE:
+ return AccessibilityManager.getInstance(this);
- if (AUDIO_SERVICE.equals(service)) {
- return null;
+ case INPUT_METHOD_SERVICE: // needed by SearchView
+ case AUTOFILL_MANAGER_SERVICE:
+ case AUDIO_SERVICE:
+ case TEXT_CLASSIFICATION_SERVICE:
+ return null;
+ default:
+ assert false : "Unsupported Service: " + service;
}
- assert false : "Unsupported Service: " + service;
return null;
}
@@ -657,13 +645,13 @@ public class BridgeContext extends Context {
return SystemServiceRegistry_Accessor.getSystemServiceName(serviceClass);
}
- @Override
- public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
- return obtainStyledAttributes(0, attrs);
- }
- @Override
- public final BridgeTypedArray obtainStyledAttributes(int resId, int[] attrs)
+ /**
+ * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+ * original Context the chance to override the theme when needed.
+ */
+ @Nullable
+ public final BridgeTypedArray internalObtainStyledAttributes(int resId, int[] attrs)
throws Resources.NotFoundException {
StyleResourceValue style = null;
// get the StyleResourceValue based on the resId;
@@ -715,13 +703,12 @@ public class BridgeContext extends Context {
return typeArrayAndPropertiesPair.getFirst();
}
- @Override
- public final BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
- return obtainStyledAttributes(set, attrs, 0, 0);
- }
-
- @Override
- public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
+ /**
+ * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+ * original Context the chance to override the theme when needed.
+ */
+ @Nullable
+ public BridgeTypedArray internalObtainStyledAttributes(@Nullable AttributeSet set, int[] attrs,
int defStyleAttr, int defStyleRes) {
PropertiesMap defaultPropMap = null;
diff --git a/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index cdcf0ea1..bc77685e 100644
--- a/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -26,6 +26,7 @@ import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,11 +34,18 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
import android.view.View;
import android.widget.FrameLayout;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.List;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.resources.ResourceType.MENU;
/**
@@ -50,6 +58,7 @@ public class AppCompatActionBar extends BridgeActionBar {
private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
// This is used on v23.1.1 and later.
private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
+
private Class<?> mWindowActionBarClass;
/**
@@ -90,6 +99,7 @@ public class AppCompatActionBar extends BridgeActionBar {
constructorParams, constructorArgs);
mWindowActionBarClass = mWindowDecorActionBar == null ? null :
mWindowDecorActionBar.getClass();
+ inflateMenus();
setupActionBar();
} catch (Exception e) {
Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
@@ -165,6 +175,51 @@ public class AppCompatActionBar extends BridgeActionBar {
}
}
+ private void inflateMenus() {
+ List<String> menuNames = getCallBack().getMenuIdNames();
+ if (menuNames.isEmpty()) {
+ return;
+ }
+
+ if (menuNames.size() > 1) {
+ // Supporting multiple menus means that we would need to instantiate our own supportlib
+ // MenuInflater instances using reflection
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Support Toolbar does not currently support multiple menus in the preview.",
+ null, null, null);
+ }
+
+ String name = menuNames.get(0);
+ int id;
+ if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
+ // Framework menu.
+ name = name.substring(ANDROID_NS_NAME_PREFIX.length());
+ id = mBridgeContext.getFrameworkResourceValue(MENU, name, -1);
+ } else {
+ // Project menu.
+ id = mBridgeContext.getProjectResourceValue(MENU, name, -1);
+ }
+ if (id < 1) {
+ return;
+ }
+ // Get toolbar decorator
+ Object mDecorToolbar = getFieldValue(mWindowDecorActionBar, "mDecorToolbar");
+ if (mDecorToolbar == null) {
+ return;
+ }
+
+ Class<?> mDecorToolbarClass = mDecorToolbar.getClass();
+ Context themedContext = (Context)invoke(
+ getMethod(mWindowActionBarClass, "getThemedContext"),
+ mWindowDecorActionBar);
+ MenuInflater inflater = new MenuInflater(themedContext);
+ Menu menuBuilder = (Menu)invoke(getMethod(mDecorToolbarClass, "getMenu"), mDecorToolbar);
+ inflater.inflate(id, menuBuilder);
+
+ // Set the actual menu
+ invoke(findMethod(mDecorToolbarClass, "setMenu"), mDecorToolbar, menuBuilder, null);
+ }
+
@Override
public void createMenuPopup() {
// it's hard to add menus to appcompat's actionbar, since it'll use a lot of reflection.
@@ -181,13 +236,53 @@ public class AppCompatActionBar extends BridgeActionBar {
return null;
}
+ /**
+ * Same as getMethod but doesn't require the parameterTypes. This allows us to call methods
+ * without having to get all the types for the parameters when we do not need them
+ */
@Nullable
- private static Object invoke(Method method, Object owner, Object... args) {
+ private static Method findMethod(@Nullable Class<?> owner, @NotNull String name) {
+ if (owner == null) {
+ return null;
+ }
+ for (Method method : owner.getMethods()) {
+ if (name.equals(method.getName())) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static Object getFieldValue(@Nullable Object instance, @NotNull String name) {
+ if (instance == null) {
+ return null;
+ }
+
+ Class<?> instanceClass = instance.getClass();
try {
- return method == null ? null : method.invoke(owner, args);
- } catch (InvocationTargetException e) {
+ Field field = instanceClass.getDeclaredField(name);
+ boolean accesible = field.isAccessible();
+ if (!accesible) {
+ field.setAccessible(true);
+ }
+ try {
+ return field.get(instance);
+ } finally {
+ field.setAccessible(accesible);
+ }
+ } catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
- } catch (IllegalAccessException e) {
+ }
+ return null;
+ }
+
+ @Nullable
+ private static Object invoke(@Nullable Method method, Object owner, Object... args) {
+ try {
+ return method == null ? null : method.invoke(owner, args);
+ } catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
diff --git a/com/android/layoutlib/bridge/impl/GcSnapshot.java b/com/android/layoutlib/bridge/impl/GcSnapshot.java
index 3ad859c6..7526e090 100644
--- a/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -19,7 +19,6 @@ package com.android.layoutlib.bridge.impl;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
-import android.annotation.NonNull;
import android.graphics.Bitmap_Delegate;
import android.graphics.Canvas;
import android.graphics.ColorFilter_Delegate;
@@ -40,13 +39,11 @@ import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
-import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
-import java.lang.ref.SoftReference;
import java.util.ArrayList;
/**
@@ -69,7 +66,7 @@ public class GcSnapshot {
private final int mFlags;
/** list of layers. The first item in the list is always the */
- private final ArrayList<Layer> mLayers = new ArrayList<>();
+ private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
/** temp transform in case transformation are set before a Graphics2D exists */
private AffineTransform mTransform = null;
@@ -85,13 +82,6 @@ public class GcSnapshot {
private final Layer mLocalLayer;
private final Paint_Delegate mLocalLayerPaint;
private final Rect mLayerBounds;
- /**
- * Cached buffer to be used for tinting operations. This buffer is usually used many times
- * and there is no need to creating it every time.
- */
- private SoftReference<BufferedImage> mCachedLayerBuffer = new SoftReference<>(null);
- private Rectangle2D.Float mCachedClipRect = new Rectangle2D.Float();
-
public interface Drawable {
void draw(Graphics2D graphics, Paint_Delegate paint);
@@ -310,11 +300,12 @@ public class GcSnapshot {
Layer baseLayer = mLayers.get(0);
// create the image for the layer
- BufferedImage layerImage =
- baseLayer.getGraphics().getDeviceConfiguration().createCompatibleImage(
- baseLayer.getImage().getWidth(), baseLayer.getImage().getHeight(),
- (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
- Transparency.TRANSLUCENT : Transparency.OPAQUE);
+ BufferedImage layerImage = new BufferedImage(
+ baseLayer.getImage().getWidth(),
+ baseLayer.getImage().getHeight(),
+ (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
+ BufferedImage.TYPE_INT_ARGB :
+ BufferedImage.TYPE_INT_RGB);
// create a graphics for it so that drawing can be done.
Graphics2D layerGraphics = layerImage.createGraphics();
@@ -361,8 +352,6 @@ public class GcSnapshot {
}
public void dispose() {
- mCachedLayerBuffer.clear();
-
for (Layer layer : mLayers) {
layer.getGraphics().dispose();
}
@@ -538,12 +527,7 @@ public class GcSnapshot {
}
public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
- if (mCachedClipRect == null) {
- mCachedClipRect = new Rectangle2D.Float(left, top, right - left, bottom - top);
- } else {
- mCachedClipRect.setRect(left, top, right - left, bottom - top);
- }
- return clip(mCachedClipRect, regionOp);
+ return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
}
/**
@@ -656,16 +640,17 @@ public class GcSnapshot {
height = layer.getImage().getHeight();
}
- // get a Graphics2D object configured with the drawing parameters, but no shader.
- Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
- true /*compositeOnly*/, forceMode);
-
- // Create or re-use a temporary image to which the color filter will be applied.
- BufferedImage image = getTemporaryBuffer(configuredGraphics, width, height);
+ // Create a temporary image to which the color filter will be applied.
+ BufferedImage image = new BufferedImage(width, height,
+ BufferedImage.TYPE_INT_ARGB);
Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
// Configure the Graphics2D object with drawing parameters and shader.
- Graphics2D imageGraphics = createCustomGraphics(imageBaseGraphics, paint, compositeOnly,
+ Graphics2D imageGraphics = createCustomGraphics(
+ imageBaseGraphics, paint, compositeOnly,
AlphaComposite.SRC_OVER);
+ // get a Graphics2D object configured with the drawing parameters, but no shader.
+ Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
+ true /*compositeOnly*/, forceMode);
try {
// The main draw operation.
// We translate the operation to take into account that the rendering does not
@@ -692,28 +677,6 @@ public class GcSnapshot {
}
}
- /**
- * Returns a temporary buffer sized width * height and configured with the given
- * {@link Graphics2D} device configuration.
- */
- @NonNull
- private BufferedImage getTemporaryBuffer(@NonNull Graphics2D configuredGraphics,
- int width, int height) {
- BufferedImage cachedImage = mCachedLayerBuffer.get();
- if (cachedImage == null ||
- width > cachedImage.getWidth() || height > cachedImage.getHeight() ||
- !configuredGraphics.getDeviceConfiguration().getColorModel()
- .isCompatibleSampleModel(cachedImage.getSampleModel())) {
- // The current cached image is not valid or does not exist
- cachedImage = configuredGraphics.getDeviceConfiguration()
- .createCompatibleImage(width, height);
- mCachedLayerBuffer = new SoftReference<>(cachedImage);
- } else {
- cachedImage = cachedImage.getSubimage(0, 0, width, height);
- }
- return cachedImage;
- }
-
private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
Layer layer) {
try {
diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java
index ec6f8319..67fb4d9f 100644
--- a/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -720,9 +720,6 @@ class SettingsProtoDumpUtil {
Settings.Global.DEVICE_IDLE_CONSTANTS,
GlobalSettingsProto.DEVICE_IDLE_CONSTANTS);
dumpSetting(s, p,
- Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH,
- GlobalSettingsProto.DEVICE_IDLE_CONSTANTS_WATCH);
- dumpSetting(s, p,
Settings.Global.APP_IDLE_CONSTANTS,
GlobalSettingsProto.APP_IDLE_CONSTANTS);
dumpSetting(s, p,
diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java
index a463db6f..36f9b840 100644
--- a/com/android/providers/settings/SettingsProvider.java
+++ b/com/android/providers/settings/SettingsProvider.java
@@ -2896,7 +2896,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 149;
+ private static final int SETTINGS_VERSION = 150;
private final int mUserId;
@@ -3470,9 +3470,25 @@ public class SettingsProvider extends ContentProvider {
true, SettingsState.SYSTEM_PACKAGE_NAME);
}
}
-
currentVersion = 149;
}
+
+ if (currentVersion == 149) {
+ // Version 150: Set a default value for mobile data always on
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ final Setting currentSetting = globalSettings.getSettingLocked(
+ Settings.Global.MOBILE_DATA_ALWAYS_ON);
+ if (currentSetting.isNull()) {
+ globalSettings.insertSettingLocked(
+ Settings.Global.MOBILE_DATA_ALWAYS_ON,
+ getContext().getResources().getBoolean(
+ R.bool.def_mobile_data_always_on) ? "1" : "0",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 150;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index 83bd9ebd..5106c8d7 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -20,6 +20,7 @@ import android.app.ActivityManagerInternal;
import android.database.ContentObserver;
import android.os.BatteryStats;
+import android.os.PowerManager;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
@@ -291,6 +292,8 @@ public final class BatteryService extends SystemService {
if (mActivityManagerInternal.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+ intent.putExtra(Intent.EXTRA_REASON,
+ PowerManager.SHUTDOWN_LOW_BATTERY);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
}
@@ -310,6 +313,8 @@ public final class BatteryService extends SystemService {
if (mActivityManagerInternal.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+ intent.putExtra(Intent.EXTRA_REASON,
+ PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
}
diff --git a/com/android/server/ConnectivityService.java b/com/android/server/ConnectivityService.java
index bfe50404..348c7997 100644
--- a/com/android/server/ConnectivityService.java
+++ b/com/android/server/ConnectivityService.java
@@ -19,6 +19,7 @@ package com.android.server;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -90,6 +91,7 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -128,7 +130,6 @@ import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
-import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.NetworkMonitor;
@@ -781,6 +782,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
mNetworksDefined++; // used only in the log() statement below.
}
+ // Do the same for Ethernet, since it's often not specified in the configs, although many
+ // devices can use it via USB host adapters.
+ if (mNetConfigs[TYPE_ETHERNET] == null && hasService(Context.ETHERNET_SERVICE)) {
+ mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
+ mNetworksDefined++;
+ }
+
if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
mProtectedNetworks = new ArrayList<Integer>();
@@ -2205,7 +2213,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// A network factory has connected. Send it all current NetworkRequests.
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.request.isListen()) continue;
- NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+ NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
(nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
}
@@ -2282,9 +2290,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Remove all previously satisfied requests.
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest request = nai.requestAt(i);
- NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
+ NetworkAgentInfo currentNetwork = getNetworkForRequest(request.requestId);
if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
- mNetworkForRequestId.remove(request.requestId);
+ clearNetworkForRequest(request.requestId);
sendUpdatedScoreToFactories(request, 0);
}
}
@@ -2360,7 +2368,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
rematchAllNetworksAndRequests(null, 0);
- if (nri.request.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
+ if (nri.request.isRequest() && getNetworkForRequest(nri.request.requestId) == null) {
sendUpdatedScoreToFactories(nri.request, 0);
}
}
@@ -2415,7 +2423,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// 2. Unvalidated WiFi will not be reaped when validated cellular
// is currently satisfying the request. This is desirable when
// WiFi ends up validating and out scoring cellular.
- mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
+ getNetworkForRequest(nri.request.requestId).getCurrentScore() <
nai.getCurrentScoreAsValidated())) {
return false;
}
@@ -2442,7 +2450,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (mNetworkRequests.get(nri.request) == null) {
return;
}
- if (mNetworkForRequestId.get(nri.request.requestId) != null) {
+ if (getNetworkForRequest(nri.request.requestId) != null) {
return;
}
if (VDBG || (DBG && nri.request.isRequest())) {
@@ -2482,7 +2490,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mNetworkRequestInfoLogs.log("RELEASE " + nri);
if (nri.request.isRequest()) {
boolean wasKept = false;
- NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+ NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
if (nai != null) {
boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
nai.removeRequest(nri.request.requestId);
@@ -2499,7 +2507,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} else {
wasKept = true;
}
- mNetworkForRequestId.remove(nri.request.requestId);
+ clearNetworkForRequest(nri.request.requestId);
if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
// Went from foreground to background.
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -4296,7 +4304,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
* and the are the highest scored network available.
* the are keyed off the Requests requestId.
*/
- // TODO: Yikes, this is accessed on multiple threads: add synchronization.
+ // NOTE: Accessed on multiple threads, must be synchronized on itself.
+ @GuardedBy("mNetworkForRequestId")
private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
new SparseArray<NetworkAgentInfo>();
@@ -4326,8 +4335,26 @@ public class ConnectivityService extends IConnectivityManager.Stub
// priority networks like Wi-Fi are active.
private final NetworkRequest mDefaultMobileDataRequest;
+ private NetworkAgentInfo getNetworkForRequest(int requestId) {
+ synchronized (mNetworkForRequestId) {
+ return mNetworkForRequestId.get(requestId);
+ }
+ }
+
+ private void clearNetworkForRequest(int requestId) {
+ synchronized (mNetworkForRequestId) {
+ mNetworkForRequestId.remove(requestId);
+ }
+ }
+
+ private void setNetworkForRequest(int requestId, NetworkAgentInfo nai) {
+ synchronized (mNetworkForRequestId) {
+ mNetworkForRequestId.put(requestId, nai);
+ }
+ }
+
private NetworkAgentInfo getDefaultNetwork() {
- return mNetworkForRequestId.get(mDefaultRequest.requestId);
+ return getNetworkForRequest(mDefaultRequest.requestId);
}
private boolean isDefaultNetwork(NetworkAgentInfo nai) {
@@ -4881,7 +4908,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// requests or not, and doesn't affect the network's score.
if (nri.request.isListen()) continue;
- final NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ final NetworkAgentInfo currentNetwork = getNetworkForRequest(nri.request.requestId);
final boolean satisfies = newNetwork.satisfies(nri.request);
if (newNetwork == currentNetwork && satisfies) {
if (VDBG) {
@@ -4913,7 +4940,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (VDBG) log(" accepting network in place of null");
}
newNetwork.unlingerRequest(nri.request);
- mNetworkForRequestId.put(nri.request.requestId, newNetwork);
+ setNetworkForRequest(nri.request.requestId, newNetwork);
if (!newNetwork.addRequest(nri.request)) {
Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request);
}
@@ -4947,7 +4974,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
newNetwork.removeRequest(nri.request.requestId);
if (currentNetwork == newNetwork) {
- mNetworkForRequestId.remove(nri.request.requestId);
+ clearNetworkForRequest(nri.request.requestId);
sendUpdatedScoreToFactories(nri.request, 0);
} else {
Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
@@ -5522,6 +5549,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
return new WakeupMessage(c, h, s, cmd, 0, 0, obj);
}
+ @VisibleForTesting
+ public boolean hasService(String name) {
+ return ServiceManager.checkService(name) != null;
+ }
+
private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
int newNetid = NETID_UNSET;
int prevNetid = NETID_UNSET;
diff --git a/com/android/server/DeviceIdleController.java b/com/android/server/DeviceIdleController.java
index abbc89e4..2d9baf61 100644
--- a/com/android/server/DeviceIdleController.java
+++ b/com/android/server/DeviceIdleController.java
@@ -307,6 +307,12 @@ public class DeviceIdleController extends SystemService
*/
private int[] mTempWhitelistAppIdArray = new int[0];
+ /**
+ * Apps in the system whitelist that have been taken out (probably because the user wanted to).
+ * They can be restored back by calling restoreAppToSystemWhitelist(String).
+ */
+ private ArrayMap<String, Integer> mRemovedFromSystemWhitelistApps = new ArrayMap<>();
+
private static final int EVENT_NULL = 0;
private static final int EVENT_NORMAL = 1;
private static final int EVENT_LIGHT_IDLE = 2;
@@ -760,17 +766,15 @@ public class DeviceIdleController extends SystemService
public long NOTIFICATION_WHITELIST_DURATION;
private final ContentResolver mResolver;
- private final boolean mHasWatch;
+ private final boolean mSmallBatteryDevice;
private final KeyValueListParser mParser = new KeyValueListParser(',');
public Constants(Handler handler, ContentResolver resolver) {
super(handler);
mResolver = resolver;
- mHasWatch = getContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_WATCH);
- mResolver.registerContentObserver(Settings.Global.getUriFor(
- mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
- : Settings.Global.DEVICE_IDLE_CONSTANTS),
+ mSmallBatteryDevice = ActivityManager.isSmallBatteryDevice();
+ mResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_IDLE_CONSTANTS),
false, this);
updateConstants();
}
@@ -784,8 +788,7 @@ public class DeviceIdleController extends SystemService
synchronized (DeviceIdleController.this) {
try {
mParser.setString(Settings.Global.getString(mResolver,
- mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
- : Settings.Global.DEVICE_IDLE_CONSTANTS));
+ Settings.Global.DEVICE_IDLE_CONSTANTS));
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
@@ -815,7 +818,7 @@ public class DeviceIdleController extends SystemService
MIN_DEEP_MAINTENANCE_TIME = mParser.getLong(
KEY_MIN_DEEP_MAINTENANCE_TIME,
!COMPRESS_TIME ? 30 * 1000L : 5 * 1000L);
- long inactiveTimeoutDefault = (mHasWatch ? 15 : 30) * 60 * 1000L;
+ long inactiveTimeoutDefault = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
!COMPRESS_TIME ? inactiveTimeoutDefault : (inactiveTimeoutDefault / 10));
SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
@@ -825,7 +828,7 @@ public class DeviceIdleController extends SystemService
LOCATION_ACCURACY = mParser.getFloat(KEY_LOCATION_ACCURACY, 20);
MOTION_INACTIVE_TIMEOUT = mParser.getLong(KEY_MOTION_INACTIVE_TIMEOUT,
!COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L);
- long idleAfterInactiveTimeout = (mHasWatch ? 15 : 30) * 60 * 1000L;
+ long idleAfterInactiveTimeout = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
!COMPRESS_TIME ? idleAfterInactiveTimeout
: (idleAfterInactiveTimeout / 10));
@@ -1162,6 +1165,38 @@ public class DeviceIdleController extends SystemService
}
}
+ @Override public void removeSystemPowerWhitelistApp(String name) {
+ if (DEBUG) {
+ Slog.d(TAG, "removeAppFromSystemWhitelist(name = " + name + ")");
+ }
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ null);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ removeSystemPowerWhitelistAppInternal(name);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override public void restoreSystemPowerWhitelistApp(String name) {
+ if (DEBUG) {
+ Slog.d(TAG, "restoreAppToSystemWhitelist(name = " + name + ")");
+ }
+ getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+ null);
+ long ident = Binder.clearCallingIdentity();
+ try {
+ restoreSystemPowerWhitelistAppInternal(name);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ public String[] getRemovedSystemPowerWhitelistApps() {
+ return getRemovedSystemPowerWhitelistAppsInternal();
+ }
+
@Override public String[] getSystemPowerWhitelistExceptIdle() {
return getSystemPowerWhitelistExceptIdleInternal();
}
@@ -1504,6 +1539,42 @@ public class DeviceIdleController extends SystemService
}
}
+ void resetSystemPowerWhitelistInternal() {
+ synchronized (this) {
+ mPowerSaveWhitelistApps.putAll(mRemovedFromSystemWhitelistApps);
+ mRemovedFromSystemWhitelistApps.clear();
+ reportPowerSaveWhitelistChangedLocked();
+ updateWhitelistAppIdsLocked();
+ writeConfigFileLocked();
+ }
+ }
+
+ public boolean restoreSystemPowerWhitelistAppInternal(String name) {
+ synchronized (this) {
+ if (!mRemovedFromSystemWhitelistApps.containsKey(name)) {
+ return false;
+ }
+ mPowerSaveWhitelistApps.put(name, mRemovedFromSystemWhitelistApps.remove(name));
+ reportPowerSaveWhitelistChangedLocked();
+ updateWhitelistAppIdsLocked();
+ writeConfigFileLocked();
+ return true;
+ }
+ }
+
+ public boolean removeSystemPowerWhitelistAppInternal(String name) {
+ synchronized (this) {
+ if (!mPowerSaveWhitelistApps.containsKey(name)) {
+ return false;
+ }
+ mRemovedFromSystemWhitelistApps.put(name, mPowerSaveWhitelistApps.remove(name));
+ reportPowerSaveWhitelistChangedLocked();
+ updateWhitelistAppIdsLocked();
+ writeConfigFileLocked();
+ return true;
+ }
+ }
+
public boolean addPowerSaveWhitelistExceptIdleInternal(String name) {
synchronized (this) {
try {
@@ -1565,6 +1636,17 @@ public class DeviceIdleController extends SystemService
}
}
+ public String[] getRemovedSystemPowerWhitelistAppsInternal() {
+ synchronized (this) {
+ int size = mRemovedFromSystemWhitelistApps.size();
+ final String[] apps = new String[size];
+ for (int i = 0; i < size; i++) {
+ apps[i] = mRemovedFromSystemWhitelistApps.keyAt(i);
+ }
+ return apps;
+ }
+ }
+
public String[] getUserPowerWhitelistInternal() {
synchronized (this) {
int size = mPowerSaveWhitelistUserApps.size();
@@ -2481,21 +2563,31 @@ public class DeviceIdleController extends SystemService
}
String tagName = parser.getName();
- if (tagName.equals("wl")) {
- String name = parser.getAttributeValue(null, "n");
- if (name != null) {
- try {
- ApplicationInfo ai = pm.getApplicationInfo(name,
- PackageManager.MATCH_ANY_USER);
- mPowerSaveWhitelistUserApps.put(ai.packageName,
- UserHandle.getAppId(ai.uid));
- } catch (PackageManager.NameNotFoundException e) {
+ switch (tagName) {
+ case "wl":
+ String name = parser.getAttributeValue(null, "n");
+ if (name != null) {
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(name,
+ PackageManager.MATCH_ANY_USER);
+ mPowerSaveWhitelistUserApps.put(ai.packageName,
+ UserHandle.getAppId(ai.uid));
+ } catch (PackageManager.NameNotFoundException e) {
+ }
}
- }
- } else {
- Slog.w(TAG, "Unknown element under <config>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
+ break;
+ case "un-wl":
+ final String packageName = parser.getAttributeValue(null, "n");
+ if (mPowerSaveWhitelistApps.containsKey(packageName)) {
+ mRemovedFromSystemWhitelistApps.put(packageName,
+ mPowerSaveWhitelistApps.remove(packageName));
+ }
+ break;
+ default:
+ Slog.w(TAG, "Unknown element under <config>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ break;
}
}
@@ -2556,6 +2648,11 @@ public class DeviceIdleController extends SystemService
out.attribute(null, "n", name);
out.endTag(null, "wl");
}
+ for (int i = 0; i < mRemovedFromSystemWhitelistApps.size(); i++) {
+ out.startTag(null, "un-wl");
+ out.attribute(null, "n", mRemovedFromSystemWhitelistApps.keyAt(i));
+ out.endTag(null, "un-wl");
+ }
out.endTag(null, "config");
out.endDocument();
}
@@ -2584,6 +2681,13 @@ public class DeviceIdleController extends SystemService
pw.println(" Print currently whitelisted apps.");
pw.println(" whitelist [package ...]");
pw.println(" Add (prefix with +) or remove (prefix with -) packages.");
+ pw.println(" sys-whitelist [package ...|reset]");
+ pw.println(" Prefix the package with '-' to remove it from the system whitelist or '+'"
+ + " to put it back in the system whitelist.");
+ pw.println(" Note that only packages that were"
+ + " earlier removed from the system whitelist can be added back.");
+ pw.println(" reset will reset the whitelist to the original state");
+ pw.println(" Prints the system whitelist if no arguments are specified");
pw.println(" except-idle-whitelist [package ...|reset]");
pw.println(" Prefix the package with '+' to add it to whitelist or "
+ "'=' to check if it is already whitelisted");
@@ -2944,6 +3048,50 @@ public class DeviceIdleController extends SystemService
} finally {
Binder.restoreCallingIdentity(token);
}
+ } else if ("sys-whitelist".equals(cmd)) {
+ String arg = shell.getNextArg();
+ if (arg != null) {
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.DEVICE_POWER, null);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if ("reset".equals(arg)) {
+ resetSystemPowerWhitelistInternal();
+ } else {
+ do {
+ if (arg.length() < 1
+ || (arg.charAt(0) != '-' && arg.charAt(0) != '+')) {
+ pw.println("Package must be prefixed with + or - " + arg);
+ return -1;
+ }
+ final char op = arg.charAt(0);
+ final String pkg = arg.substring(1);
+ switch (op) {
+ case '+':
+ if (restoreSystemPowerWhitelistAppInternal(pkg)) {
+ pw.println("Restored " + pkg);
+ }
+ break;
+ case '-':
+ if (removeSystemPowerWhitelistAppInternal(pkg)) {
+ pw.println("Removed " + pkg);
+ }
+ break;
+ }
+ } while ((arg = shell.getNextArg()) != null);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ synchronized (this) {
+ for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
+ pw.print(mPowerSaveWhitelistApps.keyAt(j));
+ pw.print(",");
+ pw.println(mPowerSaveWhitelistApps.valueAt(j));
+ }
+ }
+ }
} else {
return shell.handleDefaultCommands(cmd);
}
@@ -3027,6 +3175,14 @@ public class DeviceIdleController extends SystemService
pw.println(mPowerSaveWhitelistApps.keyAt(i));
}
}
+ size = mRemovedFromSystemWhitelistApps.size();
+ if (size > 0) {
+ pw.println(" Removed from whitelist system apps:");
+ for (int i = 0; i < size; i++) {
+ pw.print(" ");
+ pw.println(mRemovedFromSystemWhitelistApps.keyAt(i));
+ }
+ }
size = mPowerSaveWhitelistUserApps.size();
if (size > 0) {
pw.println(" Whitelist user apps:");
diff --git a/com/android/server/DiskStatsService.java b/com/android/server/DiskStatsService.java
index 800081e5..2d2c6b0b 100644
--- a/com/android/server/DiskStatsService.java
+++ b/com/android/server/DiskStatsService.java
@@ -202,6 +202,8 @@ public class DiskStatsService extends Binder {
JSONObject json = new JSONObject(jsonString);
pw.print("App Size: ");
pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+ pw.print("App Data Size: ");
+ pw.println(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
pw.print("App Cache Size: ");
pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
pw.print("Photos Size: ");
@@ -220,6 +222,8 @@ public class DiskStatsService extends Binder {
pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
pw.print("App Sizes: ");
pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
+ pw.print("App Data Sizes: ");
+ pw.println(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY));
pw.print("Cache Sizes: ");
pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
} catch (IOException | JSONException e) {
@@ -235,6 +239,8 @@ public class DiskStatsService extends Binder {
proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE,
json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+ proto.write(DiskStatsCachedValuesProto.AGG_APPS_DATA_SIZE,
+ json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE,
json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE,
@@ -252,22 +258,26 @@ public class DiskStatsService extends Binder {
JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+ JSONArray appDataSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY);
JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
final int len = packageNamesArray.length();
- if (len == appSizesArray.length() && len == cacheSizesArray.length()) {
+ if (len == appSizesArray.length()
+ && len == appDataSizesArray.length()
+ && len == cacheSizesArray.length()) {
for (int i = 0; i < len; i++) {
long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES);
proto.write(DiskStatsAppSizesProto.PACKAGE_NAME,
packageNamesArray.getString(i));
proto.write(DiskStatsAppSizesProto.APP_SIZE, appSizesArray.getLong(i));
+ proto.write(DiskStatsAppSizesProto.APP_DATA_SIZE, appDataSizesArray.getLong(i));
proto.write(DiskStatsAppSizesProto.CACHE_SIZE, cacheSizesArray.getLong(i));
proto.end(packageToken);
}
} else {
- Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray and cacheSizesArray "
- + "are not the same");
+ Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray, appDataSizesArray "
+ + " and cacheSizesArray are not the same");
}
proto.end(cachedValuesToken);
diff --git a/com/android/server/IpSecService.java b/com/android/server/IpSecService.java
index 30568313..2e1f142a 100644
--- a/com/android/server/IpSecService.java
+++ b/com/android/server/IpSecService.java
@@ -33,6 +33,7 @@ import android.net.IpSecSpiResponse;
import android.net.IpSecTransform;
import android.net.IpSecTransformResponse;
import android.net.IpSecUdpEncapResponse;
+import android.net.NetworkUtils;
import android.net.util.NetdService;
import android.os.Binder;
import android.os.IBinder;
@@ -42,11 +43,14 @@ import android.os.ServiceSpecificException;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -54,6 +58,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.atomic.AtomicInteger;
+
import libcore.io.IoUtils;
/** @hide */
@@ -252,7 +257,11 @@ public class IpSecService extends IIpSecService.Stub {
return (mReferenceCount.get() > 0);
}
- public void checkOwnerOrSystemAndThrow() {
+ /**
+ * Ensures that the caller is either the owner of this resource or has the system UID and
+ * throws a SecurityException otherwise.
+ */
+ public void checkOwnerOrSystem() {
if (uid != Binder.getCallingUid()
&& android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
throw new SecurityException("Only the owner may access managed resources!");
@@ -335,12 +344,12 @@ public class IpSecService extends IIpSecService.Stub {
private class ManagedResourceArray<T extends ManagedResource> {
SparseArray<T> mArray = new SparseArray<>();
- T get(int key) {
+ T getAndCheckOwner(int key) {
T val = mArray.get(key);
// The value should never be null unless the resource doesn't exist
// (since we do not allow null resources to be added).
if (val != null) {
- val.checkOwnerOrSystemAndThrow();
+ val.checkOwnerOrSystem();
}
return val;
}
@@ -405,12 +414,8 @@ public class IpSecService extends IIpSecService.Stub {
.ipSecDeleteSecurityAssociation(
mResourceId,
direction,
- (mConfig.getLocalAddress() != null)
- ? mConfig.getLocalAddress().getHostAddress()
- : "",
- (mConfig.getRemoteAddress() != null)
- ? mConfig.getRemoteAddress().getHostAddress()
- : "",
+ mConfig.getLocalAddress(),
+ mConfig.getRemoteAddress(),
spi);
} catch (ServiceSpecificException e) {
// FIXME: get the error code and throw is at an IOException from Errno Exception
@@ -638,11 +643,45 @@ public class IpSecService extends IIpSecService.Stub {
}
}
+ /**
+ * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
+ * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
+ */
+ private static void checkInetAddress(String inetAddress) {
+ if (TextUtils.isEmpty(inetAddress)) {
+ throw new IllegalArgumentException("Unspecified address");
+ }
+
+ InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
+
+ if (checkAddr.isAnyLocalAddress()) {
+ throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
+ }
+ }
+
+ /**
+ * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
+ * DIRECTION_IN or DIRECTION_OUT
+ */
+ private static void checkDirection(int direction) {
+ switch (direction) {
+ case IpSecTransform.DIRECTION_OUT:
+ case IpSecTransform.DIRECTION_IN:
+ return;
+ }
+ throw new IllegalArgumentException("Invalid Direction: " + direction);
+ }
+
@Override
/** Get a new SPI and maintain the reservation in the system server */
public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
int direction, String remoteAddress, int requestedSpi, IBinder binder)
throws RemoteException {
+ checkDirection(direction);
+ checkInetAddress(remoteAddress);
+ /* requestedSpi can be anything in the int range, so no check is needed. */
+ checkNotNull(binder, "Null Binder passed to reserveSecurityParameterIndex");
+
int resourceId = mNextResourceId.getAndIncrement();
int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
@@ -651,9 +690,7 @@ public class IpSecService extends IIpSecService.Stub {
try {
if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).spi.isAvailable()) {
return new IpSecSpiResponse(
- IpSecManager.Status.RESOURCE_UNAVAILABLE,
- INVALID_RESOURCE_ID,
- spi);
+ IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
}
spi =
mSrvConfig
@@ -686,7 +723,7 @@ public class IpSecService extends IIpSecService.Stub {
throws RemoteException {
// We want to non-destructively get so that we can check credentials before removing
// this from the records.
- T record = resArray.get(resourceId);
+ T record = resArray.getAndCheckOwner(resourceId);
if (record == null) {
throw new IllegalArgumentException(
@@ -751,6 +788,8 @@ public class IpSecService extends IIpSecService.Stub {
throw new IllegalArgumentException(
"Specified port number must be a valid non-reserved UDP port");
}
+ checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
+
int resourceId = mNextResourceId.getAndIncrement();
FileDescriptor sockFd = null;
try {
@@ -792,6 +831,68 @@ public class IpSecService extends IIpSecService.Stub {
}
/**
+ * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
+ * IllegalArgumentException if they are not.
+ */
+ private void checkIpSecConfig(IpSecConfig config) {
+ if (config.getLocalAddress() == null) {
+ throw new IllegalArgumentException("Invalid null Local InetAddress");
+ }
+
+ if (config.getRemoteAddress() == null) {
+ throw new IllegalArgumentException("Invalid null Remote InetAddress");
+ }
+
+ switch (config.getMode()) {
+ case IpSecTransform.MODE_TRANSPORT:
+ if (!config.getLocalAddress().isEmpty()) {
+ throw new IllegalArgumentException("Non-empty Local Address");
+ }
+ // Must be valid, and not a wildcard
+ checkInetAddress(config.getRemoteAddress());
+ break;
+ case IpSecTransform.MODE_TUNNEL:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid IpSecTransform.mode: " + config.getMode());
+ }
+
+ switch (config.getEncapType()) {
+ case IpSecTransform.ENCAP_NONE:
+ break;
+ case IpSecTransform.ENCAP_ESPINUDP:
+ case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
+ if (mUdpSocketRecords.getAndCheckOwner(
+ config.getEncapSocketResourceId()) == null) {
+ throw new IllegalStateException(
+ "No Encapsulation socket for Resource Id: "
+ + config.getEncapSocketResourceId());
+ }
+
+ int port = config.getEncapRemotePort();
+ if (port <= 0 || port > 0xFFFF) {
+ throw new IllegalArgumentException("Invalid remote UDP port: " + port);
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
+ }
+
+ for (int direction : DIRECTIONS) {
+ IpSecAlgorithm crypt = config.getEncryption(direction);
+ IpSecAlgorithm auth = config.getAuthentication(direction);
+ if (crypt == null && auth == null) {
+ throw new IllegalArgumentException("Encryption and Authentication are both null");
+ }
+
+ if (mSpiRecords.getAndCheckOwner(config.getSpiResourceId(direction)) == null) {
+ throw new IllegalStateException("No SPI for specified Resource Id");
+ }
+ }
+ }
+
+ /**
* Create a transport mode transform, which represent two security associations (one in each
* direction) in the kernel. The transform will be cached by the system server and must be freed
* when no longer needed. It is possible to free one, deleting the SA from underneath sockets
@@ -801,17 +902,19 @@ public class IpSecService extends IIpSecService.Stub {
@Override
public synchronized IpSecTransformResponse createTransportModeTransform(
IpSecConfig c, IBinder binder) throws RemoteException {
+ checkIpSecConfig(c);
+ checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
int resourceId = mNextResourceId.getAndIncrement();
if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).transform.isAvailable()) {
return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
}
SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
- // TODO: Basic input validation here since it's coming over the Binder
+
int encapType, encapLocalPort = 0, encapRemotePort = 0;
UdpSocketRecord socketRecord = null;
encapType = c.getEncapType();
if (encapType != IpSecTransform.ENCAP_NONE) {
- socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
+ socketRecord = mUdpSocketRecords.getAndCheckOwner(c.getEncapSocketResourceId());
encapLocalPort = socketRecord.getPort();
encapRemotePort = c.getEncapRemotePort();
}
@@ -820,23 +923,18 @@ public class IpSecService extends IIpSecService.Stub {
IpSecAlgorithm auth = c.getAuthentication(direction);
IpSecAlgorithm crypt = c.getEncryption(direction);
- spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
+ spis[direction] = mSpiRecords.getAndCheckOwner(c.getSpiResourceId(direction));
int spi = spis[direction].getSpi();
try {
- mSrvConfig.getNetdInstance()
+ mSrvConfig
+ .getNetdInstance()
.ipSecAddSecurityAssociation(
resourceId,
c.getMode(),
direction,
- (c.getLocalAddress() != null)
- ? c.getLocalAddress().getHostAddress()
- : "",
- (c.getRemoteAddress() != null)
- ? c.getRemoteAddress().getHostAddress()
- : "",
- (c.getNetwork() != null)
- ? c.getNetwork().getNetworkHandle()
- : 0,
+ c.getLocalAddress(),
+ c.getRemoteAddress(),
+ (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
spi,
(auth != null) ? auth.getName() : "",
(auth != null) ? auth.getKey() : null,
@@ -879,7 +977,7 @@ public class IpSecService extends IIpSecService.Stub {
// Synchronize liberally here because we are using ManagedResources in this block
TransformRecord info;
// FIXME: this code should be factored out into a security check + getter
- info = mTransformRecords.get(resourceId);
+ info = mTransformRecords.getAndCheckOwner(resourceId);
if (info == null) {
throw new IllegalArgumentException("Transform " + resourceId + " is not active");
@@ -899,12 +997,8 @@ public class IpSecService extends IIpSecService.Stub {
socket.getFileDescriptor(),
resourceId,
direction,
- (c.getLocalAddress() != null)
- ? c.getLocalAddress().getHostAddress()
- : "",
- (c.getRemoteAddress() != null)
- ? c.getRemoteAddress().getHostAddress()
- : "",
+ c.getLocalAddress(),
+ c.getRemoteAddress(),
info.getSpiRecord(direction).getSpi());
}
} catch (ServiceSpecificException e) {
diff --git a/com/android/server/LocationManagerService.java b/com/android/server/LocationManagerService.java
index 340d672d..0fd59eaa 100644
--- a/com/android/server/LocationManagerService.java
+++ b/com/android/server/LocationManagerService.java
@@ -18,7 +18,6 @@ package com.android.server;
import android.app.ActivityManager;
import android.annotation.NonNull;
-import android.content.pm.PackageManagerInternal;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.content.PackageMonitor;
@@ -56,6 +55,7 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
diff --git a/com/android/server/NetworkManagementService.java b/com/android/server/NetworkManagementService.java
index 2f95aa2c..ba3afc31 100644
--- a/com/android/server/NetworkManagementService.java
+++ b/com/android/server/NetworkManagementService.java
@@ -1140,17 +1140,6 @@ public class NetworkManagementService extends INetworkManagementService.Stub
}
@Override
- public void setInterfaceIpv6NdOffload(String iface, boolean enable) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute(
- "interface", "ipv6ndoffload", iface, (enable ? "enable" : "disable"));
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
public void addRoute(int netId, RouteInfo route) {
modifyRoute("add", "" + netId, route);
}
@@ -1991,8 +1980,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub
final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
+ final boolean useTls = false;
+ final String tlsHostname = "";
+ final String[] tlsFingerprints = new String[0];
try {
- mNetdService.setResolverConfiguration(netId, servers, domainStrs, params);
+ mNetdService.setResolverConfiguration(netId, servers, domainStrs, params,
+ useTls, tlsHostname, tlsFingerprints);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index c0fcfd07..55391b3e 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -111,8 +111,6 @@ import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.NativeDaemonConnector.Command;
-import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.pm.PackageManagerService;
import com.android.server.storage.AppFuseBridge;
@@ -138,7 +136,6 @@ import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -161,8 +158,7 @@ import javax.crypto.spec.PBEKeySpec;
* watch for and manage dynamically added storage, such as SD cards and USB mass
* storage. Also decides how storage should be presented to users on the device.
*/
-class StorageManagerService extends IStorageManager.Stub
- implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
+class StorageManagerService extends IStorageManager.Stub implements Watchdog.Monitor {
// Static direct instance pointer for the tightly-coupled idle service to use
static StorageManagerService sSelf = null;
@@ -206,18 +202,12 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- /** Flag to enable binder-based interface to vold */
- private static final boolean ENABLE_BINDER = true;
-
private static final boolean DEBUG_EVENTS = false;
private static final boolean DEBUG_OBB = false;
// Disable this since it messes up long-running cryptfs operations.
private static final boolean WATCHDOG_ENABLE = false;
- /** Flag to enable ASECs */
- private static final boolean ASEC_ENABLE = false;
-
/**
* Our goal is for all Android devices to be usable as development devices,
* which includes the new Direct Boot mode added in N. For devices that
@@ -232,66 +222,9 @@ class StorageManagerService extends IStorageManager.Stub
private static final String TAG_STORAGE_BENCHMARK = "storage_benchmark";
private static final String TAG_STORAGE_TRIM = "storage_trim";
- private static final String VOLD_TAG = "VoldConnector";
- private static final String CRYPTD_TAG = "CryptdConnector";
-
- /** Maximum number of ASEC containers allowed to be mounted. */
- private static final int MAX_CONTAINERS = 250;
-
/** Magic value sent by MoveTask.cpp */
private static final int MOVE_STATUS_COPY_FINISHED = 82;
- /*
- * Internal vold response code constants
- */
- class VoldResponseCode {
- /*
- * 100 series - Requestion action was initiated; expect another reply
- * before proceeding with a new command.
- */
- public static final int VolumeListResult = 110;
- public static final int AsecListResult = 111;
- public static final int StorageUsersListResult = 112;
- public static final int CryptfsGetfieldResult = 113;
-
- /*
- * 200 series - Requestion action has been successfully completed.
- */
- public static final int ShareStatusResult = 210;
- public static final int AsecPathResult = 211;
- public static final int ShareEnabledResult = 212;
-
- /*
- * 400 series - Command was accepted, but the requested action
- * did not take place.
- */
- public static final int OpFailedNoMedia = 401;
- public static final int OpFailedMediaBlank = 402;
- public static final int OpFailedMediaCorrupt = 403;
- public static final int OpFailedVolNotMounted = 404;
- public static final int OpFailedStorageBusy = 405;
- public static final int OpFailedStorageNotFound = 406;
-
- /*
- * 600 series - Unsolicited broadcasts.
- */
- public static final int DISK_CREATED = 640;
- public static final int DISK_SIZE_CHANGED = 641;
- public static final int DISK_LABEL_CHANGED = 642;
- public static final int DISK_SCANNED = 643;
- public static final int DISK_SYS_PATH_CHANGED = 644;
- public static final int DISK_DESTROYED = 649;
-
- public static final int VOLUME_CREATED = 650;
- public static final int VOLUME_STATE_CHANGED = 651;
- public static final int VOLUME_FS_TYPE_CHANGED = 652;
- public static final int VOLUME_FS_UUID_CHANGED = 653;
- public static final int VOLUME_FS_LABEL_CHANGED = 654;
- public static final int VOLUME_PATH_CHANGED = 655;
- public static final int VOLUME_INTERNAL_PATH_CHANGED = 656;
- public static final int VOLUME_DESTROYED = 659;
- }
-
private static final int VERSION_INIT = 1;
private static final int VERSION_ADD_PRIMARY = 2;
private static final int VERSION_FIX_PRIMARY = 3;
@@ -453,17 +386,6 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- private static String escapeNull(String arg) {
- if (TextUtils.isEmpty(arg)) {
- return "!";
- } else {
- if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
- throw new IllegalArgumentException(arg);
- }
- return arg;
- }
- }
-
/** List of crypto types.
* These must match CRYPT_TYPE_XXX in cryptfs.h AND their
* corresponding commands in CommandListener.cpp */
@@ -472,12 +394,6 @@ class StorageManagerService extends IStorageManager.Stub
private final Context mContext;
- private final NativeDaemonConnector mConnector;
- private final NativeDaemonConnector mCryptConnector;
-
- private final Thread mConnectorThread;
- private final Thread mCryptConnectorThread;
-
private volatile IVold mVold;
private volatile boolean mSystemReady = false;
@@ -489,20 +405,6 @@ class StorageManagerService extends IStorageManager.Stub
private final Callbacks mCallbacks;
private final LockPatternUtils mLockPatternUtils;
- // Two connectors - mConnector & mCryptConnector
- private final CountDownLatch mConnectedSignal = new CountDownLatch(2);
- private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
-
- private final Object mUnmountLock = new Object();
- @GuardedBy("mUnmountLock")
- private CountDownLatch mUnmountSignal;
-
- /**
- * Private hash of currently mounted secure containers.
- * Used as a lock in methods to manipulate secure containers.
- */
- final private HashSet<String> mAsecMountSet = new HashSet<String>();
-
/**
* The size of the crypto algorithm key in bits for OBB files. Currently
* Twofish is used which takes 128-bit keys.
@@ -616,7 +518,7 @@ class StorageManagerService extends IStorageManager.Stub
if (DEBUG_OBB)
Slog.i(TAG, "onServiceDisconnected");
}
- };
+ }
// Used in the ObbActionHandler
private IMediaContainerService mContainerService = null;
@@ -655,13 +557,6 @@ class StorageManagerService extends IStorageManager.Stub
break;
}
case H_FSTRIM: {
- if (!isReady()) {
- Slog.i(TAG, "fstrim requested, but no daemon connection yet; trying again");
- sendMessageDelayed(obtainMessage(H_FSTRIM, msg.obj),
- DateUtils.SECOND_IN_MILLIS);
- break;
- }
-
Slog.i(TAG, "Running fstrim idle maintenance");
// Remember when we kicked it off
@@ -687,12 +582,8 @@ class StorageManagerService extends IStorageManager.Stub
final IStorageShutdownObserver obs = (IStorageShutdownObserver) msg.obj;
boolean success = false;
try {
- if (ENABLE_BINDER) {
- mVold.shutdown();
- success = true;
- } else {
- success = mConnector.execute("volume", "shutdown").isClassOk();
- }
+ mVold.shutdown();
+ success = true;
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -711,12 +602,7 @@ class StorageManagerService extends IStorageManager.Stub
break;
}
try {
- if (ENABLE_BINDER) {
- mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
- } else {
- mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
- vol.mountUserId);
- }
+ mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -778,11 +664,7 @@ class StorageManagerService extends IStorageManager.Stub
if (Intent.ACTION_USER_ADDED.equals(action)) {
final UserManager um = mContext.getSystemService(UserManager.class);
final int userSerialNumber = um.getUserSerialNumber(userId);
- if (ENABLE_BINDER) {
- mVold.onUserAdded(userId, userSerialNumber);
- } else {
- mConnector.execute("volume", "user_added", userId, userSerialNumber);
- }
+ mVold.onUserAdded(userId, userSerialNumber);
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
synchronized (mVolumes) {
final int size = mVolumes.size();
@@ -794,11 +676,7 @@ class StorageManagerService extends IStorageManager.Stub
}
}
}
- if (ENABLE_BINDER) {
- mVold.onUserRemoved(userId);
- } else {
- mConnector.execute("volume", "user_removed", userId);
- }
+ mVold.onUserRemoved(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -806,22 +684,6 @@ class StorageManagerService extends IStorageManager.Stub
}
};
- @Override
- public void waitForAsecScan() {
- waitForLatch(mAsecsScanned, "mAsecsScanned");
- }
-
- private void waitForReady() {
- waitForLatch(mConnectedSignal, "mConnectedSignal");
- }
-
- private void waitForLatch(CountDownLatch latch, String condition) {
- try {
- waitForLatch(latch, condition, -1);
- } catch (TimeoutException ignored) {
- }
- }
-
private void waitForLatch(CountDownLatch latch, String condition, long timeoutMillis)
throws TimeoutException {
final long startMillis = SystemClock.elapsedRealtime();
@@ -843,14 +705,6 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- private boolean isReady() {
- try {
- return mConnectedSignal.await(0, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- return false;
- }
- }
-
private void handleSystemReady() {
initIfReadyAndConnected();
resetIfReadyAndConnected();
@@ -917,19 +771,10 @@ class StorageManagerService extends IStorageManager.Stub
for (UserInfo user : users) {
try {
if (initLocked) {
- if (ENABLE_BINDER) {
- mVold.lockUserKey(user.id);
- } else {
- mCryptConnector.execute("cryptfs", "lock_user_key", user.id);
- }
+ mVold.lockUserKey(user.id);
} else {
- if (ENABLE_BINDER) {
- mVold.unlockUserKey(user.id, user.serialNumber, encodeBytes(null),
- encodeBytes(null));
- } else {
- mCryptConnector.execute("cryptfs", "unlock_user_key", user.id,
- user.serialNumber, "!", "!");
- }
+ mVold.unlockUserKey(user.id, user.serialNumber, encodeBytes(null),
+ encodeBytes(null));
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -956,26 +801,14 @@ class StorageManagerService extends IStorageManager.Stub
}
try {
- if (ENABLE_BINDER) {
- mVold.reset();
- } else {
- mConnector.execute("volume", "reset");
- }
+ mVold.reset();
// Tell vold about all existing and started users
for (UserInfo user : users) {
- if (ENABLE_BINDER) {
- mVold.onUserAdded(user.id, user.serialNumber);
- } else {
- mConnector.execute("volume", "user_added", user.id, user.serialNumber);
- }
+ mVold.onUserAdded(user.id, user.serialNumber);
}
for (int userId : systemUnlockedUsers) {
- if (ENABLE_BINDER) {
- mVold.onUserStarted(userId);
- } else {
- mConnector.execute("volume", "user_started", userId);
- }
+ mVold.onUserStarted(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -990,11 +823,7 @@ class StorageManagerService extends IStorageManager.Stub
// staging area is ready so it's ready for zygote-forked apps to
// bind mount against.
try {
- if (ENABLE_BINDER) {
- mVold.onUserStarted(userId);
- } else {
- mConnector.execute("volume", "user_started", userId);
- }
+ mVold.onUserStarted(userId);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -1020,11 +849,7 @@ class StorageManagerService extends IStorageManager.Stub
Slog.d(TAG, "onCleanupUser " + userId);
try {
- if (ENABLE_BINDER) {
- mVold.onUserStopped(userId);
- } else {
- mConnector.execute("volume", "user_stopped", userId);
- }
+ mVold.onUserStopped(userId);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -1050,10 +875,6 @@ class StorageManagerService extends IStorageManager.Stub
return mLastMaintenance;
}
- /**
- * Callback from NativeDaemonConnector
- */
- @Override
public void onDaemonConnected() {
mDaemonConnected = true;
mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget();
@@ -1063,29 +884,11 @@ class StorageManagerService extends IStorageManager.Stub
initIfReadyAndConnected();
resetIfReadyAndConnected();
- /*
- * Now that we've done our initialization, release
- * the hounds!
- */
- mConnectedSignal.countDown();
- if (mConnectedSignal.getCount() != 0) {
- // More daemons need to connect
- return;
- }
-
// On an encrypted device we can't see system properties yet, so pull
// the system locale out of the mount service.
if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
copyLocaleFromMountService();
}
-
- // Let package manager load internal ASECs.
- if (ASEC_ENABLE) {
- mPms.scanAvailableAsecs();
- }
-
- // Notify people waiting for ASECs to be scanned that it's done.
- mAsecsScanned.countDown();
}
private void copyLocaleFromMountService() {
@@ -1114,148 +917,6 @@ class StorageManagerService extends IStorageManager.Stub
SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
}
- /**
- * Callback from NativeDaemonConnector
- */
- @Override
- public boolean onCheckHoldWakeLock(int code) {
- return false;
- }
-
- /**
- * Callback from NativeDaemonConnector
- */
- @Override
- public boolean onEvent(int code, String raw, String[] cooked) {
- synchronized (mLock) {
- try {
- return onEventLocked(code, raw, cooked);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- }
- }
-
- private boolean onEventLocked(int code, String raw, String[] cooked) throws RemoteException {
- switch (code) {
- case VoldResponseCode.DISK_CREATED: {
- if (cooked.length != 3) break;
- final String diskId = cooked[1];
- final int flags = Integer.parseInt(cooked[2]);
- mListener.onDiskCreated(diskId, flags);
- break;
- }
- case VoldResponseCode.DISK_SIZE_CHANGED: {
- if (cooked.length != 3) break;
- final DiskInfo disk = mDisks.get(cooked[1]);
- if (disk != null) {
- disk.size = Long.parseLong(cooked[2]);
- }
- break;
- }
- case VoldResponseCode.DISK_LABEL_CHANGED: {
- final DiskInfo disk = mDisks.get(cooked[1]);
- if (disk != null) {
- final StringBuilder builder = new StringBuilder();
- for (int i = 2; i < cooked.length; i++) {
- builder.append(cooked[i]).append(' ');
- }
- disk.label = builder.toString().trim();
- }
- break;
- }
- case VoldResponseCode.DISK_SCANNED: {
- if (cooked.length != 2) break;
- final String diskId = cooked[1];
- mListener.onDiskScanned(diskId);
- break;
- }
- case VoldResponseCode.DISK_SYS_PATH_CHANGED: {
- if (cooked.length != 3) break;
- final DiskInfo disk = mDisks.get(cooked[1]);
- if (disk != null) {
- disk.sysPath = cooked[2];
- }
- break;
- }
- case VoldResponseCode.DISK_DESTROYED: {
- if (cooked.length != 2) break;
- final String diskId = cooked[1];
- mListener.onDiskDestroyed(diskId);
- break;
- }
-
- case VoldResponseCode.VOLUME_CREATED: {
- final String volId = cooked[1];
- final int type = Integer.parseInt(cooked[2]);
- final String diskId = TextUtils.nullIfEmpty(cooked[3]);
- final String partGuid = TextUtils.nullIfEmpty(cooked[4]);
- mListener.onVolumeCreated(volId, type, diskId, partGuid);
- break;
- }
- case VoldResponseCode.VOLUME_STATE_CHANGED: {
- if (cooked.length != 3) break;
- final String volId = cooked[1];
- final int state = Integer.parseInt(cooked[2]);
- mListener.onVolumeStateChanged(volId, state);
- break;
- }
- case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: {
- if (cooked.length != 3) break;
- final VolumeInfo vol = mVolumes.get(cooked[1]);
- if (vol != null) {
- vol.fsType = cooked[2];
- }
- break;
- }
- case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
- if (cooked.length != 3) break;
- final VolumeInfo vol = mVolumes.get(cooked[1]);
- if (vol != null) {
- vol.fsUuid = cooked[2];
- }
- break;
- }
- case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
- final VolumeInfo vol = mVolumes.get(cooked[1]);
- if (vol != null) {
- final StringBuilder builder = new StringBuilder();
- for (int i = 2; i < cooked.length; i++) {
- builder.append(cooked[i]).append(' ');
- }
- vol.fsLabel = builder.toString().trim();
- }
- // TODO: notify listeners that label changed
- break;
- }
- case VoldResponseCode.VOLUME_PATH_CHANGED: {
- if (cooked.length != 3) break;
- final String volId = cooked[1];
- final String path = cooked[2];
- mListener.onVolumePathChanged(volId, path);
- break;
- }
- case VoldResponseCode.VOLUME_INTERNAL_PATH_CHANGED: {
- if (cooked.length != 3) break;
- final String volId = cooked[1];
- final String internalPath = cooked[2];
- mListener.onVolumeInternalPathChanged(volId, internalPath);
- break;
- }
- case VoldResponseCode.VOLUME_DESTROYED: {
- if (cooked.length != 2) break;
- final String volId = cooked[1];
- mListener.onVolumeDestroyed(volId);
- break;
- }
- default: {
- Slog.d(TAG, "Unhandled vold event " + code);
- }
- }
-
- return true;
- }
-
private final IVoldListener mListener = new IVoldListener.Stub() {
@Override
public void onDiskCreated(String diskId, int flags) {
@@ -1654,24 +1315,6 @@ class StorageManagerService extends IStorageManager.Stub
LocalServices.addService(StorageManagerInternal.class, mStorageManagerInternal);
- /*
- * Create the connection to vold with a maximum queue of twice the
- * amount of containers we'd ever expect to have. This keeps an
- * "asec list" from blocking a thread repeatedly.
- */
-
- mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
- null);
- mConnector.setDebug(true);
- mConnector.setWarnIfHeld(mLock);
- mConnectorThread = new Thread(mConnector, VOLD_TAG);
-
- // Reuse parameters from first connector since they are tested and safe
- mCryptConnector = new NativeDaemonConnector(this, "cryptd",
- MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null);
- mCryptConnector.setDebug(true);
- mCryptConnectorThread = new Thread(mCryptConnector, CRYPTD_TAG);
-
final IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_ADDED);
userFilter.addAction(Intent.ACTION_USER_REMOVED);
@@ -1689,8 +1332,6 @@ class StorageManagerService extends IStorageManager.Stub
private void start() {
connect();
- mConnectorThread.start();
- mCryptConnectorThread.start();
}
private void connect() {
@@ -1713,6 +1354,7 @@ class StorageManagerService extends IStorageManager.Stub
mVold = IVold.Stub.asInterface(binder);
try {
mVold.setListener(mListener);
+ onDaemonConnected();
return;
} catch (RemoteException e) {
Slog.w(TAG, "vold listener rejected; trying again", e);
@@ -1864,62 +1506,15 @@ class StorageManagerService extends IStorageManager.Stub
}
@Override
- public boolean isUsbMassStorageConnected() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setUsbMassStorageEnabled(boolean enable) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isUsbMassStorageEnabled() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getVolumeState(String mountPoint) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isExternalStorageEmulated() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int mountVolume(String path) {
- mount(findVolumeIdForPathOrThrow(path));
- return 0;
- }
-
- @Override
- public void unmountVolume(String path, boolean force, boolean removeEncryption) {
- unmount(findVolumeIdForPathOrThrow(path));
- }
-
- @Override
- public int formatVolume(String path) {
- format(findVolumeIdForPathOrThrow(path));
- return 0;
- }
-
- @Override
public void mount(String volId) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
final VolumeInfo vol = findVolumeByIdOrThrow(volId);
if (isMountDisallowed(vol)) {
throw new SecurityException("Mounting " + volId + " restricted by policy");
}
try {
- if (ENABLE_BINDER) {
- mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
- } else {
- mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
- }
+ mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -1928,31 +1523,10 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void unmount(String volId) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
final VolumeInfo vol = findVolumeByIdOrThrow(volId);
-
- // TODO: expand PMS to know about multiple volumes
- if (vol.isPrimaryPhysical()) {
- final long ident = Binder.clearCallingIdentity();
- try {
- synchronized (mUnmountLock) {
- mUnmountSignal = new CountDownLatch(1);
- mPms.updateExternalMediaStatus(false, true);
- waitForLatch(mUnmountSignal, "mUnmountSignal");
- mUnmountSignal = null;
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
try {
- if (ENABLE_BINDER) {
- mVold.unmount(vol.id);
- } else {
- mConnector.execute("volume", "unmount", vol.id);
- }
+ mVold.unmount(vol.id);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -1961,15 +1535,10 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void format(String volId) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
- waitForReady();
final VolumeInfo vol = findVolumeByIdOrThrow(volId);
try {
- if (ENABLE_BINDER) {
- mVold.format(vol.id, "auto");
- } else {
- mConnector.execute("volume", "format", vol.id, "auto");
- }
+ mVold.format(vol.id, "auto");
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -1978,7 +1547,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public long benchmark(String volId) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
- waitForReady();
// TODO: refactor for callers to provide a listener
try {
@@ -2022,15 +1590,10 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void partitionPublic(String diskId) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
- waitForReady();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
try {
- if (ENABLE_BINDER) {
- mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
- } else {
- mConnector.execute("volume", "partition", diskId, "public");
- }
+ mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -2041,15 +1604,10 @@ class StorageManagerService extends IStorageManager.Stub
public void partitionPrivate(String diskId) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
enforceAdminUser();
- waitForReady();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
try {
- if (ENABLE_BINDER) {
- mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
- } else {
- mConnector.execute("volume", "partition", diskId, "private");
- }
+ mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -2060,15 +1618,10 @@ class StorageManagerService extends IStorageManager.Stub
public void partitionMixed(String diskId, int ratio) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
enforceAdminUser();
- waitForReady();
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
try {
- if (ENABLE_BINDER) {
- mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
- } else {
- mConnector.execute("volume", "partition", diskId, "mixed", ratio);
- }
+ mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -2078,7 +1631,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void setVolumeNickname(String fsUuid, String nickname) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
Preconditions.checkNotNull(fsUuid);
synchronized (mLock) {
@@ -2092,7 +1644,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void setVolumeUserFlags(String fsUuid, int flags, int mask) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
Preconditions.checkNotNull(fsUuid);
synchronized (mLock) {
@@ -2106,7 +1657,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void forgetVolume(String fsUuid) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
Preconditions.checkNotNull(fsUuid);
@@ -2131,7 +1681,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void forgetAllVolumes() {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
synchronized (mLock) {
for (int i = 0; i < mRecords.size(); i++) {
@@ -2155,11 +1704,7 @@ class StorageManagerService extends IStorageManager.Stub
private void forgetPartition(String partGuid) {
try {
- if (ENABLE_BINDER) {
- mVold.forgetPartition(partGuid);
- } else {
- mConnector.execute("volume", "forget_partition", partGuid);
- }
+ mVold.forgetPartition(partGuid);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -2168,14 +1713,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void fstrim(int flags) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
- waitForReady();
-
- String cmd;
- if ((flags & StorageManager.FSTRIM_FLAG_DEEP) != 0) {
- cmd = "dodtrim";
- } else {
- cmd = "dotrim";
- }
try {
mVold.fstrim(flags, new IVoldTaskListener.Stub() {
@@ -2212,27 +1749,8 @@ class StorageManagerService extends IStorageManager.Stub
}
private void remountUidExternalStorage(int uid, int mode) {
- waitForReady();
-
- String modeName = "none";
- switch (mode) {
- case Zygote.MOUNT_EXTERNAL_DEFAULT: {
- modeName = "default";
- } break;
- case Zygote.MOUNT_EXTERNAL_READ: {
- modeName = "read";
- } break;
- case Zygote.MOUNT_EXTERNAL_WRITE: {
- modeName = "write";
- } break;
- }
-
try {
- if (ENABLE_BINDER) {
- mVold.remountUid(uid, mode);
- } else {
- mConnector.execute("volume", "remount_uid", uid, modeName);
- }
+ mVold.remountUid(uid, mode);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -2241,7 +1759,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void setDebugFlags(int flags, int mask) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
if (!EMULATE_FBE_SUPPORTED) {
@@ -2331,7 +1848,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
final VolumeInfo from;
final VolumeInfo to;
@@ -2403,33 +1919,6 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- @Override
- public int[] getStorageUsers(String path) {
- enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
- waitForReady();
- try {
- final String[] r = NativeDaemonEvent.filterMessageList(
- mConnector.executeForList("storage", "users", path),
- VoldResponseCode.StorageUsersListResult);
-
- // FMT: <pid> <process name>
- int[] data = new int[r.length];
- for (int i = 0; i < r.length; i++) {
- String[] tok = r[i].split(" ");
- try {
- data[i] = Integer.parseInt(tok[0]);
- } catch (NumberFormatException nfe) {
- Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
- return new int[0];
- }
- }
- return data;
- } catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to retrieve storage users list", e);
- return new int[0];
- }
- }
-
private void warnOnNotMounted() {
synchronized (mLock) {
for (int i = 0; i < mVolumes.size(); i++) {
@@ -2444,304 +1933,6 @@ class StorageManagerService extends IStorageManager.Stub
Slog.w(TAG, "No primary storage mounted!");
}
- public String[] getSecureContainerList() {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_ACCESS);
- waitForReady();
- warnOnNotMounted();
-
- try {
- return NativeDaemonEvent.filterMessageList(
- mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
- } catch (NativeDaemonConnectorException e) {
- return new String[0];
- }
- }
-
- public int createSecureContainer(String id, int sizeMb, String fstype, String key,
- int ownerUid, boolean external) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_CREATE);
- waitForReady();
- warnOnNotMounted();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
- ownerUid, external ? "1" : "0");
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mAsecMountSet) {
- mAsecMountSet.add(id);
- }
- }
- return rc;
- }
-
- @Override
- public int resizeSecureContainer(String id, int sizeMb, String key) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_CREATE);
- waitForReady();
- warnOnNotMounted();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "resize", id, sizeMb, new SensitiveArg(key));
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- return rc;
- }
-
- public int finalizeSecureContainer(String id) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_CREATE);
- warnOnNotMounted();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "finalize", id);
- /*
- * Finalization does a remount, so no need
- * to update mAsecMountSet
- */
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- return rc;
- }
-
- public int fixPermissionsSecureContainer(String id, int gid, String filename) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_CREATE);
- warnOnNotMounted();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "fixperms", id, gid, filename);
- /*
- * Fix permissions does a remount, so no need to update
- * mAsecMountSet
- */
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- return rc;
- }
-
- public int destroySecureContainer(String id, boolean force) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_DESTROY);
- waitForReady();
- warnOnNotMounted();
-
- /*
- * Force a GC to make sure AssetManagers in other threads of the
- * system_server are cleaned up. We have to do this since AssetManager
- * instances are kept as a WeakReference and it's possible we have files
- * open on the external storage.
- */
- Runtime.getRuntime().gc();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- final Command cmd = new Command("asec", "destroy", id);
- if (force) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedStorageBusy;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mAsecMountSet) {
- if (mAsecMountSet.contains(id)) {
- mAsecMountSet.remove(id);
- }
- }
- }
-
- return rc;
- }
-
- public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
- waitForReady();
- warnOnNotMounted();
-
- synchronized (mAsecMountSet) {
- if (mAsecMountSet.contains(id)) {
- return StorageResultCode.OperationFailedStorageMounted;
- }
- }
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid,
- readOnly ? "ro" : "rw");
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code != VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mAsecMountSet) {
- mAsecMountSet.add(id);
- }
- }
- return rc;
- }
-
- public int unmountSecureContainer(String id, boolean force) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
- waitForReady();
- warnOnNotMounted();
-
- synchronized (mAsecMountSet) {
- if (!mAsecMountSet.contains(id)) {
- return StorageResultCode.OperationFailedStorageNotMounted;
- }
- }
-
- /*
- * Force a GC to make sure AssetManagers in other threads of the
- * system_server are cleaned up. We have to do this since AssetManager
- * instances are kept as a WeakReference and it's possible we have files
- * open on the external storage.
- */
- Runtime.getRuntime().gc();
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- final Command cmd = new Command("asec", "unmount", id);
- if (force) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedStorageBusy;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
- }
-
- if (rc == StorageResultCode.OperationSucceeded) {
- synchronized (mAsecMountSet) {
- mAsecMountSet.remove(id);
- }
- }
- return rc;
- }
-
- public boolean isSecureContainerMounted(String id) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_ACCESS);
- waitForReady();
- warnOnNotMounted();
-
- synchronized (mAsecMountSet) {
- return mAsecMountSet.contains(id);
- }
- }
-
- public int renameSecureContainer(String oldId, String newId) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_RENAME);
- waitForReady();
- warnOnNotMounted();
-
- synchronized (mAsecMountSet) {
- /*
- * Because a mounted container has active internal state which cannot be
- * changed while active, we must ensure both ids are not currently mounted.
- */
- if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
- return StorageResultCode.OperationFailedStorageMounted;
- }
- }
-
- int rc = StorageResultCode.OperationSucceeded;
- try {
- mConnector.execute("asec", "rename", oldId, newId);
- } catch (NativeDaemonConnectorException e) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
-
- return rc;
- }
-
- public String getSecureContainerPath(String id) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_ACCESS);
- waitForReady();
- warnOnNotMounted();
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("asec", "path", id);
- event.checkCode(VoldResponseCode.AsecPathResult);
- return event.getMessage();
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageNotFound) {
- Slog.i(TAG, String.format("Container '%s' not found", id));
- return null;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- }
-
- public String getSecureContainerFilesystemPath(String id) {
- if (!ASEC_ENABLE) throw new UnsupportedOperationException();
- enforcePermission(android.Manifest.permission.ASEC_ACCESS);
- waitForReady();
- warnOnNotMounted();
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("asec", "fspath", id);
- event.checkCode(VoldResponseCode.AsecPathResult);
- return event.getMessage();
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageNotFound) {
- Slog.i(TAG, String.format("Container '%s' not found", id));
- return null;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
- }
-
- @Override
- public void finishMediaUpdate() {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("no permission to call finishMediaUpdate()");
- }
- if (mUnmountSignal != null) {
- mUnmountSignal.countDown();
- } else {
- Slog.w(TAG, "Odd, nobody asked to unmount?");
- }
- }
-
private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
if (callerUid == android.os.Process.SYSTEM_UID) {
return true;
@@ -2762,10 +1953,10 @@ class StorageManagerService extends IStorageManager.Stub
return callerUid == packageUid;
}
+ @Override
public String getMountedObbPath(String rawPath) {
Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
- waitForReady();
warnOnNotMounted();
final ObbState state;
@@ -2777,23 +1968,7 @@ class StorageManagerService extends IStorageManager.Stub
return null;
}
- if (ENABLE_BINDER) {
- return findVolumeByIdOrThrow(state.volId).getPath().getAbsolutePath();
- }
-
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("obb", "path", state.canonicalPath);
- event.checkCode(VoldResponseCode.AsecPathResult);
- return event.getMessage();
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageNotFound) {
- return null;
- } else {
- throw new IllegalStateException(String.format("Unexpected response code %d", code));
- }
- }
+ return findVolumeByIdOrThrow(state.volId).getPath().getAbsolutePath();
}
@Override
@@ -2850,28 +2025,10 @@ class StorageManagerService extends IStorageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
- waitForReady();
-
- if (ENABLE_BINDER) {
- try {
- return mVold.fdeComplete();
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
- }
- }
-
- final NativeDaemonEvent event;
try {
- event = mCryptConnector.execute("cryptfs", "cryptocomplete");
- return Integer.parseInt(event.getMessage());
- } catch (NumberFormatException e) {
- // Bad result - unexpected.
- Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
- return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
- } catch (NativeDaemonConnectorException e) {
- // Something bad happened.
- Slog.w(TAG, "Error in communicating with cryptfs in validating");
+ return mVold.fdeComplete();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
}
}
@@ -2881,8 +2038,6 @@ class StorageManagerService extends IStorageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
- waitForReady();
-
if (TextUtils.isEmpty(password)) {
throw new IllegalArgumentException("password cannot be empty");
}
@@ -2891,55 +2046,27 @@ class StorageManagerService extends IStorageManager.Stub
Slog.i(TAG, "decrypting storage...");
}
- if (ENABLE_BINDER) {
- try {
- mVold.fdeCheckPassword(password);
- mHandler.postDelayed(() -> {
- try {
- mVold.fdeRestart();
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- }
- }, DateUtils.SECOND_IN_MILLIS);
- return 0;
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
- }
- }
-
- final NativeDaemonEvent event;
try {
- event = mCryptConnector.execute("cryptfs", "checkpw", new SensitiveArg(password));
-
- final int code = Integer.parseInt(event.getMessage());
- if (code == 0) {
- // Decrypt was successful. Post a delayed message before restarting in order
- // to let the UI to clear itself
- mHandler.postDelayed(new Runnable() {
- public void run() {
- try {
- mCryptConnector.execute("cryptfs", "restart");
- } catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "problem executing in background", e);
- }
- }
- }, 1000); // 1 second
- }
-
- return code;
- } catch (NativeDaemonConnectorException e) {
- // Decryption failed
- return e.getCode();
+ mVold.fdeCheckPassword(password);
+ mHandler.postDelayed(() -> {
+ try {
+ mVold.fdeRestart();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }, DateUtils.SECOND_IN_MILLIS);
+ return 0;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
}
}
+ @Override
public int encryptStorage(int type, String password) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
- waitForReady();
-
if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
password = "";
} else if (TextUtils.isEmpty(password)) {
@@ -2951,17 +2078,7 @@ class StorageManagerService extends IStorageManager.Stub
}
try {
- if (ENABLE_BINDER) {
- mVold.fdeEnable(type, password, IVold.ENCRYPTION_FLAG_IN_PLACE);
- } else {
- if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
- mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
- CRYPTO_TYPES[type]);
- } else {
- mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
- CRYPTO_TYPES[type], new SensitiveArg(password));
- }
- }
+ mVold.fdeEnable(type, password, IVold.ENCRYPTION_FLAG_IN_PLACE);
} catch (Exception e) {
Slog.wtf(TAG, e);
return -1;
@@ -2974,12 +2091,11 @@ class StorageManagerService extends IStorageManager.Stub
* @param type One of the CRYPTO_TYPE_XXX consts defined in StorageManager.
* @param password The password to set.
*/
+ @Override
public int changeEncryptionPassword(int type, String password) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
- waitForReady();
-
if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
password = "";
} else if (TextUtils.isEmpty(password)) {
@@ -2990,23 +2106,12 @@ class StorageManagerService extends IStorageManager.Stub
Slog.i(TAG, "changing encryption password...");
}
- if (ENABLE_BINDER) {
- try {
- mVold.fdeChangePassword(type, password);
- return 0;
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return -1;
- }
- }
-
try {
- NativeDaemonEvent event = mCryptConnector.execute("cryptfs", "changepw", CRYPTO_TYPES[type],
- new SensitiveArg(password));
- return Integer.parseInt(event.getMessage());
- } catch (NativeDaemonConnectorException e) {
- // Encryption failed
- return e.getCode();
+ mVold.fdeChangePassword(type, password);
+ return 0;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return -1;
}
}
@@ -3027,30 +2132,16 @@ class StorageManagerService extends IStorageManager.Stub
throw new IllegalArgumentException("password cannot be empty");
}
- waitForReady();
-
if (DEBUG_EVENTS) {
Slog.i(TAG, "validating encryption password...");
}
- if (ENABLE_BINDER) {
- try {
- mVold.fdeVerifyPassword(password);
- return 0;
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return -1;
- }
- }
-
- final NativeDaemonEvent event;
try {
- event = mCryptConnector.execute("cryptfs", "verifypw", new SensitiveArg(password));
- Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
- return Integer.parseInt(event.getMessage());
- } catch (NativeDaemonConnectorException e) {
- // Encryption failed
- return e.getCode();
+ mVold.fdeVerifyPassword(password);
+ return 0;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return -1;
}
}
@@ -3063,28 +2154,11 @@ class StorageManagerService extends IStorageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
- waitForReady();
-
- if (ENABLE_BINDER) {
- try {
- return mVold.fdeGetPasswordType();
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return -1;
- }
- }
-
- final NativeDaemonEvent event;
try {
- event = mCryptConnector.execute("cryptfs", "getpwtype");
- for (int i = 0; i < CRYPTO_TYPES.length; ++i) {
- if (CRYPTO_TYPES[i].equals(event.getMessage()))
- return i;
- }
-
- throw new IllegalStateException("unexpected return from cryptfs");
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
+ return mVold.fdeGetPasswordType();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return -1;
}
}
@@ -3098,23 +2172,12 @@ class StorageManagerService extends IStorageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
- waitForReady();
-
- if (ENABLE_BINDER) {
- try {
- mVold.fdeSetField(field, contents);
- return;
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return;
- }
- }
-
- final NativeDaemonEvent event;
try {
- event = mCryptConnector.execute("cryptfs", "setfield", field, contents);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
+ mVold.fdeSetField(field, contents);
+ return;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return;
}
}
@@ -3128,29 +2191,11 @@ class StorageManagerService extends IStorageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
- waitForReady();
-
- if (ENABLE_BINDER) {
- try {
- return mVold.fdeGetField(field);
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return null;
- }
- }
-
- final NativeDaemonEvent event;
try {
- final String[] contents = NativeDaemonEvent.filterMessageList(
- mCryptConnector.executeForList("cryptfs", "getfield", field),
- VoldResponseCode.CryptfsGetfieldResult);
- String result = new String();
- for (String content : contents) {
- result += content;
- }
- return result;
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
+ return mVold.fdeGetField(field);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return null;
}
}
@@ -3163,23 +2208,11 @@ class StorageManagerService extends IStorageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"no permission to access the crypt keeper");
- waitForReady();
-
- if (ENABLE_BINDER) {
- try {
- return mVold.isConvertibleToFbe();
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return false;
- }
- }
-
- final NativeDaemonEvent event;
try {
- event = mCryptConnector.execute("cryptfs", "isConvertibleToFBE");
- return Integer.parseInt(event.getMessage()) != 0;
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
+ return mVold.isConvertibleToFbe();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return false;
}
}
@@ -3188,31 +2221,10 @@ class StorageManagerService extends IStorageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"only keyguard can retrieve password");
- if (!isReady()) {
- return new String();
- }
-
- if (ENABLE_BINDER) {
- try {
- return mVold.fdeGetPassword();
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return null;
- }
- }
-
- final NativeDaemonEvent event;
try {
- event = mCryptConnector.execute("cryptfs", "getpw");
- if ("-1".equals(event.getMessage())) {
- // -1 equals no password
- return null;
- }
- return event.getMessage();
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Invalid response to getPassword");
+ return mVold.fdeGetPassword();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
return null;
}
}
@@ -3222,40 +2234,21 @@ class StorageManagerService extends IStorageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
"only keyguard can clear password");
- if (!isReady()) {
- return;
- }
-
- if (ENABLE_BINDER) {
- try {
- mVold.fdeClearPassword();
- return;
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return;
- }
- }
-
- final NativeDaemonEvent event;
try {
- event = mCryptConnector.execute("cryptfs", "clearpw");
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
+ mVold.fdeClearPassword();
+ return;
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ return;
}
}
@Override
public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
try {
- if (ENABLE_BINDER) {
- mVold.createUserKey(userId, serialNumber, ephemeral);
- } else {
- mCryptConnector.execute("cryptfs", "create_user_key", userId, serialNumber,
- ephemeral ? 1 : 0);
- }
+ mVold.createUserKey(userId, serialNumber, ephemeral);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3264,14 +2257,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void destroyUserKey(int userId) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
try {
- if (ENABLE_BINDER) {
- mVold.destroyUserKey(userId);
- } else {
- mCryptConnector.execute("cryptfs", "destroy_user_key", userId);
- }
+ mVold.destroyUserKey(userId);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3295,16 +2283,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
try {
- if (ENABLE_BINDER) {
- mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
- } else {
- mCryptConnector.execute("cryptfs", "add_user_key_auth", userId, serialNumber,
- new SensitiveArg(encodeBytes(token)),
- new SensitiveArg(encodeBytes(secret)));
- }
+ mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3316,14 +2297,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void fixateNewestUserKeyAuth(int userId) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
try {
- if (ENABLE_BINDER) {
- mVold.fixateNewestUserKeyAuth(userId);
- } else {
- mCryptConnector.execute("cryptfs", "fixate_newest_user_key_auth", userId);
- }
+ mVold.fixateNewestUserKeyAuth(userId);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3332,7 +2308,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
// When a user has secure lock screen, require secret to actually unlock.
@@ -3342,14 +2317,8 @@ class StorageManagerService extends IStorageManager.Stub
}
try {
- if (ENABLE_BINDER) {
- mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
- encodeBytes(secret));
- } else {
- mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber,
- new SensitiveArg(encodeBytes(token)),
- new SensitiveArg(encodeBytes(secret)));
- }
+ mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
+ encodeBytes(secret));
} catch (Exception e) {
Slog.wtf(TAG, e);
return;
@@ -3369,14 +2338,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void lockUserKey(int userId) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
try {
- if (ENABLE_BINDER) {
- mVold.lockUserKey(userId);
- } else {
- mCryptConnector.execute("cryptfs", "lock_user_key", userId);
- }
+ mVold.lockUserKey(userId);
} catch (Exception e) {
Slog.wtf(TAG, e);
return;
@@ -3397,15 +2361,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
try {
- if (ENABLE_BINDER) {
- mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
- } else {
- mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
- userId, serialNumber, flags);
- }
+ mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3414,15 +2372,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void destroyUserStorage(String volumeUuid, int userId, int flags) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
try {
- if (ENABLE_BINDER) {
- mVold.destroyUserStorage(volumeUuid, userId, flags);
- } else {
- mCryptConnector.execute("cryptfs", "destroy_user_storage", escapeNull(volumeUuid),
- userId, flags);
- }
+ mVold.destroyUserStorage(volumeUuid, userId, flags);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3431,14 +2383,9 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void secdiscard(String path) {
enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
- waitForReady();
try {
- if (ENABLE_BINDER) {
- mVold.secdiscard(path);
- } else {
- mCryptConnector.execute("cryptfs", "secdiscard", escapeNull(path));
- }
+ mVold.secdiscard(path);
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -3453,33 +2400,18 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public ParcelFileDescriptor open() throws NativeDaemonConnectorException {
- if (ENABLE_BINDER) {
- try {
- return new ParcelFileDescriptor(
- mVold.mountAppFuse(uid, Process.myPid(), mountId));
- } catch (Exception e) {
- throw new NativeDaemonConnectorException("Failed to mount", e);
- }
- } else {
- final NativeDaemonEvent event = mConnector.execute(
- "appfuse", "mount", uid, Process.myPid(), mountId);
- opened = true;
- if (event.getFileDescriptors() == null ||
- event.getFileDescriptors().length == 0) {
- throw new NativeDaemonConnectorException("Cannot obtain device FD");
- }
- return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
+ try {
+ return new ParcelFileDescriptor(
+ mVold.mountAppFuse(uid, Process.myPid(), mountId));
+ } catch (Exception e) {
+ throw new NativeDaemonConnectorException("Failed to mount", e);
}
}
@Override
public void close() throws Exception {
if (opened) {
- if (ENABLE_BINDER) {
- mVold.unmountAppFuse(uid, Process.myPid(), mountId);
- } else {
- mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
- }
+ mVold.unmountAppFuse(uid, Process.myPid(), mountId);
opened = false;
}
}
@@ -3568,11 +2500,7 @@ class StorageManagerService extends IStorageManager.Stub
}
try {
- if (ENABLE_BINDER) {
- mVold.mkdirs(appPath);
- } else {
- mConnector.execute("volume", "mkdirs", appPath);
- }
+ mVold.mkdirs(appPath);
return 0;
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -4124,7 +3052,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void handleExecute() throws IOException, RemoteException {
- waitForReady();
warnOnNotMounted();
final ObbInfo obbInfo = getObbInfo();
@@ -4174,19 +3101,9 @@ class StorageManagerService extends IStorageManager.Stub
int rc = StorageResultCode.OperationSucceeded;
try {
- if (ENABLE_BINDER) {
- mObbState.volId = mVold.createObb(mObbState.canonicalPath, binderKey,
- mObbState.ownerGid);
- mVold.mount(mObbState.volId, 0, -1);
- } else {
- mConnector.execute("obb", "mount", mObbState.canonicalPath,
- new SensitiveArg(hashedKey), mObbState.ownerGid);
- }
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code != VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedInternalError;
- }
+ mObbState.volId = mVold.createObb(mObbState.canonicalPath, binderKey,
+ mObbState.ownerGid);
+ mVold.mount(mObbState.volId, 0, -1);
} catch (Exception e) {
Slog.w(TAG, e);
rc = StorageResultCode.OperationFailedInternalError;
@@ -4233,7 +3150,6 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void handleExecute() throws IOException {
- waitForReady();
warnOnNotMounted();
final ObbState existingState;
@@ -4255,27 +3171,9 @@ class StorageManagerService extends IStorageManager.Stub
int rc = StorageResultCode.OperationSucceeded;
try {
- if (ENABLE_BINDER) {
- mVold.unmount(mObbState.volId);
- mVold.destroyObb(mObbState.volId);
- mObbState.volId = null;
- } else {
- final Command cmd = new Command("obb", "unmount", mObbState.canonicalPath);
- if (mForceUnmount) {
- cmd.appendArg("force");
- }
- mConnector.execute(cmd);
- }
- } catch (NativeDaemonConnectorException e) {
- int code = e.getCode();
- if (code == VoldResponseCode.OpFailedStorageBusy) {
- rc = StorageResultCode.OperationFailedStorageBusy;
- } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
- // If it's not mounted then we've already won.
- rc = StorageResultCode.OperationSucceeded;
- } else {
- rc = StorageResultCode.OperationFailedInternalError;
- }
+ mVold.unmount(mObbState.volId);
+ mVold.destroyObb(mObbState.volId);
+ mObbState.volId = null;
} catch (Exception e) {
Slog.w(TAG, e);
rc = StorageResultCode.OperationFailedInternalError;
@@ -4506,18 +3404,6 @@ class StorageManagerService extends IStorageManager.Stub
}
pw.println();
- pw.println("mConnector:");
- pw.increaseIndent();
- mConnector.dump(fd, pw, args);
- pw.decreaseIndent();
-
- pw.println();
- pw.println("mCryptConnector:");
- pw.increaseIndent();
- mCryptConnector.dump(fd, pw, args);
- pw.decreaseIndent();
-
- pw.println();
pw.print("Last maintenance: ");
pw.println(TimeUtils.formatForLogging(mLastMaintenance));
}
@@ -4525,11 +3411,10 @@ class StorageManagerService extends IStorageManager.Stub
/** {@inheritDoc} */
@Override
public void monitor() {
- if (mConnector != null) {
- mConnector.monitor();
- }
- if (mCryptConnector != null) {
- mCryptConnector.monitor();
+ try {
+ mVold.monitor();
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
}
}
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index 57271fa1..92cbd3d5 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -103,6 +103,7 @@ import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.stats.StatsCompanionService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
@@ -151,7 +152,7 @@ public final class SystemServer {
* them from the build system somehow.
*/
private static final String BACKUP_MANAGER_SERVICE_CLASS =
- "com.android.server.backup.BackupManagerService$Lifecycle";
+ "com.android.server.backup.RefactoredBackupManagerService$Lifecycle";
private static final String APPWIDGET_SERVICE_CLASS =
"com.android.server.appwidget.AppWidgetService";
private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
@@ -667,9 +668,11 @@ public final class SystemServer {
traceEnd();
// Tracks whether the updatable WebView is in a ready state and watches for update installs.
- traceBeginAndSlog("StartWebViewUpdateService");
- mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
- traceEnd();
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+ traceBeginAndSlog("StartWebViewUpdateService");
+ mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
+ traceEnd();
+ }
}
/**
@@ -681,6 +684,7 @@ public final class SystemServer {
VibratorService vibrator = null;
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
+ IpSecService ipSecService = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
@@ -1030,6 +1034,15 @@ public final class SystemServer {
reportWtf("starting NetworkManagement Service", e);
}
traceEnd();
+
+ traceBeginAndSlog("StartIpSecService");
+ try {
+ ipSecService = IpSecService.create(context);
+ ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+ } catch (Throwable e) {
+ reportWtf("starting IpSec Service", e);
+ }
+ traceEnd();
}
if (!disableNonCoreServices && !disableTextServices) {
@@ -1080,6 +1093,14 @@ public final class SystemServer {
traceBeginAndSlog("StartWifiRtt");
mSystemServiceManager.startService("com.android.server.wifi.RttService");
traceEnd();
+
+ if (context.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WIFI_RTT)) {
+ traceBeginAndSlog("StartRttService");
+ mSystemServiceManager.startService(
+ "com.android.server.wifi.rtt.RttService");
+ traceEnd();
+ }
}
if (context.getPackageManager().hasSystemFeature(
@@ -1087,8 +1108,6 @@ public final class SystemServer {
traceBeginAndSlog("StartWifiAware");
mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
traceEnd();
- } else {
- Slog.i(TAG, "No Wi-Fi Aware Service (Aware support Not Present)");
}
if (context.getPackageManager().hasSystemFeature(
@@ -1146,20 +1165,6 @@ public final class SystemServer {
traceEnd();
}
- /*
- * StorageManagerService has a few dependencies: Notification Manager and
- * AppWidget Provider. Make sure StorageManagerService is completely started
- * first before continuing.
- */
- if (storageManager != null && !mOnlyCore) {
- traceBeginAndSlog("WaitForAsecScan");
- try {
- storageManager.waitForAsecScan();
- } catch (RemoteException ignored) {
- }
- traceEnd();
- }
-
traceBeginAndSlog("StartNotificationManager");
mSystemServiceManager.startService(NotificationManagerService.class);
SystemNotificationChannels.createAll(context);
@@ -1209,18 +1214,6 @@ public final class SystemServer {
traceEnd();
}
- // timezone.RulesManagerService will prevent a device starting up if the chain of trust
- // required for safe time zone updates might be broken. RuleManagerService cannot do
- // this check when mOnlyCore == true, so we don't enable the service in this case.
- final boolean startRulesManagerService =
- !mOnlyCore && context.getResources().getBoolean(
- R.bool.config_enableUpdateableTimeZoneRules);
- if (startRulesManagerService) {
- traceBeginAndSlog("StartTimeZoneRulesManagerService");
- mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
- traceEnd();
- }
-
traceBeginAndSlog("StartAudioService");
mSystemServiceManager.startService(AudioService.Lifecycle.class);
traceEnd();
@@ -1361,6 +1354,19 @@ public final class SystemServer {
}
traceEnd();
+ // timezone.RulesManagerService will prevent a device starting up if the chain of trust
+ // required for safe time zone updates might be broken. RuleManagerService cannot do
+ // this check when mOnlyCore == true, so we don't enable the service in this case.
+ // This service requires that JobSchedulerService is already started when it starts.
+ final boolean startRulesManagerService =
+ !mOnlyCore && context.getResources().getBoolean(
+ R.bool.config_enableUpdateableTimeZoneRules);
+ if (startRulesManagerService) {
+ traceBeginAndSlog("StartTimeZoneRulesManagerService");
+ mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
+ traceEnd();
+ }
+
if (!disableNetwork && !disableNetworkTime) {
traceBeginAndSlog("StartNetworkTimeUpdateService");
try {
@@ -1536,6 +1542,11 @@ public final class SystemServer {
traceEnd();
}
+ // Statsd helper
+ traceBeginAndSlog("StartStatsCompanionService");
+ mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+ traceEnd();
+
// Before things start rolling, be sure we have decided whether
// we are in safe mode.
final boolean safeMode = wm.detectSafeMode();
@@ -1661,6 +1672,7 @@ public final class SystemServer {
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final MediaRouterService mediaRouterF = mediaRouter;
final MmsServiceBroker mmsServiceF = mmsService;
+ final IpSecService ipSecServiceF = ipSecService;
final WindowManagerService windowManagerF = wm;
// We now tell the activity manager it is okay to run third party
@@ -1683,10 +1695,10 @@ public final class SystemServer {
traceEnd();
// No dependency on Webview preparation in system server. But this should
- // be completed before allowring 3rd party
+ // be completed before allowing 3rd party
final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
Future<?> webviewPrep = null;
- if (!mOnlyCore) {
+ if (!mOnlyCore && mWebViewUpdateService != null) {
webviewPrep = SystemServerInitThreadPool.get().submit(() -> {
Slog.i(TAG, WEBVIEW_PREPARATION);
TimingsTraceLog traceLog = new TimingsTraceLog(
@@ -1731,6 +1743,13 @@ public final class SystemServer {
.networkScoreAndNetworkManagementServiceReady();
}
traceEnd();
+ traceBeginAndSlog("MakeIpSecServiceReady");
+ try {
+ if (ipSecServiceF != null) ipSecServiceF.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making IpSec Service ready", e);
+ }
+ traceEnd();
traceBeginAndSlog("MakeNetworkStatsServiceReady");
try {
if (networkStatsF != null) networkStatsF.systemReady();
diff --git a/com/android/server/accounts/AccountManagerService.java b/com/android/server/accounts/AccountManagerService.java
index 8ae592f7..4e15e5d9 100644
--- a/com/android/server/accounts/AccountManagerService.java
+++ b/com/android/server/accounts/AccountManagerService.java
@@ -2969,9 +2969,13 @@ public class AccountManagerService
* have users launching arbitrary activities by tricking users to
* interact with malicious notifications.
*/
- checkKeyIntent(
+ if (!checkKeyIntent(
Binder.getCallingUid(),
- intent);
+ intent)) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "invalid intent in bundle returned");
+ return;
+ }
doNotification(
mAccounts,
account,
@@ -3366,9 +3370,13 @@ public class AccountManagerService
Intent intent = null;
if (result != null
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
- checkKeyIntent(
+ if (!checkKeyIntent(
Binder.getCallingUid(),
- intent);
+ intent)) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "invalid intent in bundle returned");
+ return;
+ }
}
IAccountManagerResponse response;
if (mExpectActivityLaunch && result != null
@@ -4716,9 +4724,7 @@ public class AccountManagerService
* into launching arbitrary intents on the device via by tricking to click authenticator
* supplied entries in the system Settings app.
*/
- protected void checkKeyIntent(
- int authUid,
- Intent intent) throws SecurityException {
+ protected boolean checkKeyIntent(int authUid, Intent intent) {
intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
@@ -4727,6 +4733,9 @@ public class AccountManagerService
try {
PackageManager pm = mContext.getPackageManager();
ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+ if (resolveInfo == null) {
+ return false;
+ }
ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
int targetUid = targetActivityInfo.applicationInfo.uid;
if (!isExportedSystemActivity(targetActivityInfo)
@@ -4736,9 +4745,10 @@ public class AccountManagerService
String activityName = targetActivityInfo.name;
String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "
+ "does not share a signature with the supplying authenticator (%s).";
- throw new SecurityException(
- String.format(tmpl, activityName, pkgName, mAccountType));
+ Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType));
+ return false;
}
+ return true;
} finally {
Binder.restoreCallingIdentity(bid);
}
@@ -4888,9 +4898,13 @@ public class AccountManagerService
}
if (result != null
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
- checkKeyIntent(
+ if (!checkKeyIntent(
Binder.getCallingUid(),
- intent);
+ intent)) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "invalid intent in bundle returned");
+ return;
+ }
}
if (result != null
&& !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
@@ -5285,7 +5299,7 @@ public class AccountManagerService
== PackageManager.PERMISSION_GRANTED) {
// Checks runtime permission revocation.
final int opCode = AppOpsManager.permissionToOpCode(perm);
- if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+ if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow(
opCode, uid, packageName) == AppOpsManager.MODE_ALLOWED) {
return true;
}
@@ -5306,7 +5320,7 @@ public class AccountManagerService
Log.v(TAG, " caller uid " + callingUid + " has " + perm);
}
final int opCode = AppOpsManager.permissionToOpCode(perm);
- if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+ if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow(
opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) {
return true;
}
diff --git a/com/android/server/am/ActiveServices.java b/com/android/server/am/ActiveServices.java
index e0cde72f..2131731d 100644
--- a/com/android/server/am/ActiveServices.java
+++ b/com/android/server/am/ActiveServices.java
@@ -2162,6 +2162,15 @@ public final class ActiveServices {
}
}
+ if (r.fgRequired) {
+ if (DEBUG_FOREGROUND_SERVICE) {
+ Slog.v(TAG, "Whitelisting " + UserHandle.formatUid(r.appInfo.uid)
+ + " for fg-service launch");
+ }
+ mAm.tempWhitelistUidLocked(r.appInfo.uid,
+ SERVICE_START_FOREGROUND_TIMEOUT, "fg-service-launch");
+ }
+
if (!mPendingServices.contains(r)) {
mPendingServices.add(r);
}
@@ -3141,7 +3150,7 @@ public final class ActiveServices {
sr.userId, sr.crashCount, sr.shortName, app.pid);
bringDownServiceLocked(sr);
} else if (!allowRestart
- || !mAm.mUserController.isUserRunningLocked(sr.userId, 0)) {
+ || !mAm.mUserController.isUserRunning(sr.userId, 0)) {
bringDownServiceLocked(sr);
} else {
boolean canceled = scheduleServiceRestartLocked(sr, true);
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
new file mode 100644
index 00000000..8bcbfbef
--- /dev/null
+++ b/com/android/server/am/ActivityDisplay.java
@@ -0,0 +1,414 @@
+/*
+ * 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 com.android.server.am;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.getStackIdForWindowingMode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+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;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.proto.ActivityDisplayProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
+import static com.android.server.am.proto.ActivityDisplayProto.ID;
+
+import android.app.ActivityManagerInternal;
+import android.app.WindowConfiguration;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.ConfigurationContainer;
+
+import java.util.ArrayList;
+
+/**
+ * Exactly one of these classes per Display in the system. Capable of holding zero or more
+ * attached {@link ActivityStack}s.
+ */
+class ActivityDisplay extends ConfigurationContainer {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM;
+ private static final String TAG_STACK = TAG + POSTFIX_STACK;
+
+ static final int POSITION_TOP = Integer.MAX_VALUE;
+ static final int POSITION_BOTTOM = Integer.MIN_VALUE;
+
+ private ActivityStackSupervisor mSupervisor;
+ /** Actual Display this object tracks. */
+ int mDisplayId;
+ Display mDisplay;
+
+ /** All of the stacks on this display. Order matters, topmost stack is in front of all other
+ * stacks, bottommost behind. Accessed directly by ActivityManager package classes */
+ final ArrayList<ActivityStack> mStacks = new ArrayList<>();
+
+ /** Array of all UIDs that are present on the display. */
+ private IntArray mDisplayAccessUIDs = new IntArray();
+
+ /** All tokens used to put activities on this stack to sleep (including mOffToken) */
+ final ArrayList<ActivityManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
+ /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
+ ActivityManagerInternal.SleepToken mOffToken;
+
+ private boolean mSleeping;
+
+ ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
+ mSupervisor = supervisor;
+ mDisplayId = displayId;
+ final Display display = supervisor.mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ throw new IllegalStateException("Display does not exist displayId=" + displayId);
+ }
+ mDisplay = display;
+ }
+
+ void addChild(ActivityStack stack, int position) {
+ if (position == POSITION_BOTTOM) {
+ position = 0;
+ } else if (position == POSITION_TOP) {
+ position = mStacks.size();
+ }
+ if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack
+ + " to displayId=" + mDisplayId + " position=" + position);
+ positionChildAt(stack, position);
+ mSupervisor.mService.updateSleepIfNeededLocked();
+ }
+
+ void removeChild(ActivityStack stack) {
+ if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack
+ + " from displayId=" + mDisplayId);
+ mStacks.remove(stack);
+ mSupervisor.mService.updateSleepIfNeededLocked();
+ }
+
+ void positionChildAtTop(ActivityStack stack) {
+ positionChildAt(stack, mStacks.size());
+ }
+
+ void positionChildAtBottom(ActivityStack stack) {
+ positionChildAt(stack, 0);
+ }
+
+ private void positionChildAt(ActivityStack stack, int position) {
+ mStacks.remove(stack);
+ mStacks.add(getTopInsertPosition(stack, position), stack);
+ }
+
+ private int getTopInsertPosition(ActivityStack stack, int candidatePosition) {
+ int position = mStacks.size();
+ if (position > 0) {
+ final ActivityStack topStack = mStacks.get(position - 1);
+ if (topStack.getWindowConfiguration().isAlwaysOnTop() && topStack != stack) {
+ // If the top stack is always on top, we move this stack just below it.
+ position--;
+ }
+ }
+ return Math.min(position, candidatePosition);
+ }
+
+ <T extends ActivityStack> T getStack(int stackId) {
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack stack = mStacks.get(i);
+ if (stack.mStackId == stackId) {
+ return (T) stack;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the topmost stack on the display that is compatible with the input windowing mode and
+ * activity type. {@code null} means no compatible stack on the display.
+ * @see ConfigurationContainer#isCompatible(int, int)
+ */
+ <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack stack = mStacks.get(i);
+ // TODO: Should undefined windowing and activity type be compatible with standard type?
+ if (stack.isCompatible(windowingMode, activityType)) {
+ return (T) stack;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @see #getStack(int, int)
+ * @see #createStack(int, int, boolean)
+ */
+ <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType,
+ boolean onTop) {
+ T stack = getStack(windowingMode, activityType);
+ if (stack != null) {
+ return stack;
+ }
+ return createStack(windowingMode, activityType, onTop);
+ }
+
+ /**
+ * Creates a stack matching the input windowing mode and activity type on this display.
+ * @param windowingMode The windowing mode the stack should be created in. If
+ * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
+ * be created in {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
+ * @param activityType The activityType the stack should be created in. If
+ * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
+ * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+ * @param onTop If true the stack will be created at the top of the display, else at the bottom.
+ * @return The newly created stack.
+ */
+ <T extends ActivityStack> T createStack(int windowingMode, int activityType, boolean onTop) {
+
+ if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+ // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
+ // anything else should be passing it in anyways...
+ activityType = ACTIVITY_TYPE_STANDARD;
+ }
+
+ if (activityType != ACTIVITY_TYPE_STANDARD) {
+ // For now there can be only one stack of a particular non-standard activity type on a
+ // display. So, get that ignoring whatever windowing mode it is currently in.
+ T stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
+ if (stack != null) {
+ throw new IllegalArgumentException("Stack=" + stack + " of activityType="
+ + activityType + " already on display=" + this + ". Can't have multiple.");
+ }
+ }
+
+ final ActivityManagerService service = mSupervisor.mService;
+ if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
+ service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
+ service.mSupportsPictureInPicture, activityType)) {
+ throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
+ + windowingMode);
+ }
+
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ // TODO: Should be okay to have stacks with with undefined windowing mode long term, but
+ // have to set them to something for now due to logic that depending on them.
+ windowingMode = WINDOWING_MODE_FULLSCREEN;
+ }
+
+ final boolean inSplitScreenMode = hasSplitScreenStack();
+ if (!inSplitScreenMode
+ && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+ // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+ // trying to launch in split-screen secondary.
+ windowingMode = WINDOWING_MODE_FULLSCREEN;
+ } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+ && WindowConfiguration.supportSplitScreenWindowingMode(
+ windowingMode, activityType)) {
+ windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ }
+
+ int stackId = INVALID_STACK_ID;
+ if (mDisplayId == DEFAULT_DISPLAY && (activityType == ACTIVITY_TYPE_STANDARD
+ || activityType == ACTIVITY_TYPE_UNDEFINED)) {
+ // TODO: Will be removed once we are no longer using static stack ids.
+ stackId = getStackIdForWindowingMode(windowingMode);
+ if (stackId == INVALID_STACK_ID) {
+ // Whatever...put in fullscreen stack for now.
+ stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+ }
+ final T stack = getStack(stackId);
+ if (stack != null) {
+ return stack;
+ }
+ }
+
+ if (stackId == INVALID_STACK_ID) {
+ stackId = mSupervisor.getNextStackId();
+ }
+
+ final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
+
+ if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ // Make sure recents stack exist when creating a dock stack as it normally need to be on
+ // the other side of the docked stack and we make visibility decisions based on that.
+ // TODO: Not sure if this is needed after we change to calculate visibility based on
+ // stack z-order vs. id.
+ getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
+ }
+
+ return stack;
+ }
+
+ @VisibleForTesting
+ <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
+ int stackId, boolean onTop) {
+ switch (windowingMode) {
+ case WINDOWING_MODE_PINNED:
+ return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
+ default:
+ return (T) new ActivityStack(
+ this, stackId, mSupervisor, windowingMode, activityType, onTop);
+ }
+ }
+
+ /**
+ * Removes stacks in the input windowing modes from the system if they are of activity type
+ * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+ */
+ void removeStacksInWindowingModes(int... windowingModes) {
+ if (windowingModes == null || windowingModes.length == 0) {
+ return;
+ }
+
+ for (int j = windowingModes.length - 1 ; j >= 0; --j) {
+ final int windowingMode = windowingModes[j];
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack stack = mStacks.get(i);
+ if (!stack.isActivityTypeStandardOrUndefined()) {
+ continue;
+ }
+ if (stack.getWindowingMode() != windowingMode) {
+ continue;
+ }
+ mSupervisor.removeStackLocked(stack.mStackId);
+ }
+ }
+ }
+
+ void removeStacksWithActivityTypes(int... activityTypes) {
+ if (activityTypes == null || activityTypes.length == 0) {
+ return;
+ }
+
+ for (int j = activityTypes.length - 1 ; j >= 0; --j) {
+ final int activityType = activityTypes[j];
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack stack = mStacks.get(i);
+ if (stack.getActivityType() == activityType) {
+ mSupervisor.removeStackLocked(stack.mStackId);
+ }
+ }
+ }
+ }
+
+ /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
+ int getTopVisibleStackActivityType(int excludeWindowingMode) {
+ for (int i = mStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack stack = mStacks.get(i);
+ if (stack.getWindowingMode() == excludeWindowingMode) {
+ continue;
+ }
+ if (stack.shouldBeVisible(null /* starting */)) {
+ return stack.getActivityType();
+ }
+ }
+ return ACTIVITY_TYPE_UNDEFINED;
+ }
+
+ ActivityStack getSplitScreenStack() {
+ return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+ }
+
+ boolean hasSplitScreenStack() {
+ return getSplitScreenStack() != null;
+ }
+
+ PinnedActivityStack getPinnedStack() {
+ return getStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+ }
+
+ boolean hasPinnedStack() {
+ return getPinnedStack() != null;
+ }
+
+ @Override
+ public String toString() {
+ return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
+ }
+
+ @Override
+ protected int getChildCount() {
+ return mStacks.size();
+ }
+
+ @Override
+ protected ConfigurationContainer getChildAt(int index) {
+ return mStacks.get(index);
+ }
+
+ @Override
+ protected ConfigurationContainer getParent() {
+ return mSupervisor;
+ }
+
+ boolean isPrivate() {
+ return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
+ }
+
+ boolean isUidPresent(int uid) {
+ for (ActivityStack stack : mStacks) {
+ if (stack.isUidPresent(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Update and get all UIDs that are present on the display and have access to it. */
+ IntArray getPresentUIDs() {
+ mDisplayAccessUIDs.clear();
+ for (ActivityStack stack : mStacks) {
+ stack.getPresentUIDs(mDisplayAccessUIDs);
+ }
+ return mDisplayAccessUIDs;
+ }
+
+ boolean shouldDestroyContentOnRemove() {
+ return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
+ }
+
+ boolean shouldSleep() {
+ return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
+ && (mSupervisor.mService.mRunningVoice == null);
+ }
+
+ boolean isSleeping() {
+ return mSleeping;
+ }
+
+ void setIsSleeping(boolean asleep) {
+ mSleeping = asleep;
+ }
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ proto.write(ID, mDisplayId);
+ for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = mStacks.get(stackNdx);
+ stack.writeToProto(proto, STACKS);
+ }
+ proto.end(token);
+ }
+}
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index 02eb3b43..e6fe6204 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -33,6 +33,14 @@ import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
+import static android.app.ActivityManager.StackId.isStaticStack;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
@@ -145,7 +153,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IMMERSIVE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKSCREEN;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -163,10 +170,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_URI_PERMISSION;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_ONLY;
import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
@@ -177,8 +182,8 @@ import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
+import static com.android.server.am.proto.ActivityManagerServiceProto.ACTIVITIES;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
-import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_RELAUNCH;
import static com.android.server.wm.AppTransition.TRANSIT_NONE;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
@@ -259,8 +264,8 @@ import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.PathPermission;
import android.content.pm.PermissionInfo;
@@ -349,6 +354,7 @@ import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -402,6 +408,7 @@ import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
+import com.android.server.utils.PriorityDump;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.PinnedStackWindowController;
import com.android.server.wm.WindowManagerService;
@@ -682,11 +689,6 @@ public class ActivityManagerService extends IActivityManager.Stub
ActivityInfo mLastAddedTaskActivity;
/**
- * List of packages whitelisted by DevicePolicyManager for locktask. Indexed by userId.
- */
- SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
-
- /**
* The package name of the DeviceOwner. This package is not permitted to have its data cleared.
*/
String mDeviceOwnerName;
@@ -712,9 +714,45 @@ public class ActivityManagerService extends IActivityManager.Stub
@VisibleForTesting
long mWaitForNetworkTimeoutMs;
+ /**
+ * Helper class which parses out priority arguments and dumps sections according to their
+ * priority. If priority arguments are omitted, function calls the legacy dump command.
+ */
+ private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+ @Override
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ doDump(fd, pw, new String[] {"activities"});
+ }
+
+ @Override
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ doDump(fd, pw, new String[] {"settings"});
+ doDump(fd, pw, new String[] {"intents"});
+ doDump(fd, pw, new String[] {"broadcasts"});
+ doDump(fd, pw, new String[] {"providers"});
+ doDump(fd, pw, new String[] {"permissions"});
+ doDump(fd, pw, new String[] {"services"});
+ doDump(fd, pw, new String[] {"recents"});
+ doDump(fd, pw, new String[] {"lastanr"});
+ doDump(fd, pw, new String[] {"starter"});
+ if (mAssociations.size() > 0) {
+ doDump(fd, pw, new String[] {"associations"});
+ }
+ doDump(fd, pw, new String[] {"processes"});
+ doDump(fd, pw, new String[] {"-v", "all"});
+ doDump(fd, pw, new String[] {"service", "all"});
+ doDump(fd, pw, new String[] {"provider", "all"});
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ doDump(fd, pw, args);
+ }
+ };
+
public boolean canShowErrorDialogs() {
return mShowDialogs && !mSleeping && !mShuttingDown
- && !mKeyguardController.isKeyguardShowing()
+ && !mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)
&& !(UserManager.isDeviceInDemoMode(mContext)
&& mUserController.getCurrentUser().isDemo());
}
@@ -1653,45 +1691,34 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int DISPATCH_PROCESSES_CHANGED_UI_MSG = 31;
static final int DISPATCH_PROCESS_DIED_UI_MSG = 32;
static final int REPORT_MEM_USAGE_MSG = 33;
- static final int REPORT_USER_SWITCH_MSG = 34;
- static final int CONTINUE_USER_SWITCH_MSG = 35;
- static final int USER_SWITCH_TIMEOUT_MSG = 36;
static final int IMMERSIVE_MODE_LOCK_MSG = 37;
static final int PERSIST_URI_GRANTS_MSG = 38;
static final int REQUEST_ALL_PSS_MSG = 39;
- static final int START_PROFILES_MSG = 40;
static final int UPDATE_TIME_PREFERENCE_MSG = 41;
- static final int SYSTEM_USER_START_MSG = 42;
- static final int SYSTEM_USER_CURRENT_MSG = 43;
static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
static final int FINISH_BOOTING_MSG = 45;
- static final int START_USER_SWITCH_UI_MSG = 46;
static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47;
static final int DISMISS_DIALOG_UI_MSG = 48;
static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 49;
static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 50;
static final int DELETE_DUMPHEAP_MSG = 51;
- static final int FOREGROUND_PROFILE_CHANGED_MSG = 52;
static final int DISPATCH_UIDS_CHANGED_UI_MSG = 53;
static final int REPORT_TIME_TRACKER_MSG = 54;
- static final int REPORT_USER_SWITCH_COMPLETE_MSG = 55;
static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 56;
static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 57;
static final int IDLE_UIDS_MSG = 58;
- static final int SYSTEM_USER_UNLOCK_MSG = 59;
static final int LOG_STACK_STATE = 60;
static final int VR_MODE_CHANGE_MSG = 61;
static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62;
static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
- static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 64;
static final int NOTIFY_VR_SLEEPING_MSG = 65;
static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
static final int DISPATCH_PENDING_INTENT_CANCEL_MSG = 67;
static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
- static final int USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 71;
- static final int START_USER_SWITCH_FG_MSG = 712;
+ static final int TOP_APP_KILLED_BY_LMK_MSG = 73;
+ static final int NOTIFY_VR_KEYGUARD_MSG = 74;
static final int FIRST_ACTIVITY_STACK_MSG = 100;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1904,10 +1931,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
break;
}
- case START_USER_SWITCH_UI_MSG: {
- mUserController.showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
- break;
- }
case DISMISS_DIALOG_UI_MSG: {
final Dialog d = (Dialog) msg.obj;
d.dismiss();
@@ -1923,6 +1946,17 @@ public class ActivityManagerService extends IActivityManager.Stub
dispatchProcessDied(pid, uid);
break;
}
+ case TOP_APP_KILLED_BY_LMK_MSG: {
+ final String appName = (String) msg.obj;
+ final AlertDialog d = new BaseErrorDialog(mUiContext);
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+ d.setTitle(mUiContext.getText(R.string.top_app_killed_title));
+ d.setMessage(mUiContext.getString(R.string.top_app_killed_message, appName));
+ d.setButton(DialogInterface.BUTTON_POSITIVE, mUiContext.getText(R.string.close),
+ obtainMessage(DISMISS_DIALOG_UI_MSG, d));
+ d.show();
+ break;
+ }
case DISPATCH_UIDS_CHANGED_UI_MSG: {
dispatchUidsChanged();
} break;
@@ -2136,26 +2170,6 @@ public class ActivityManagerService extends IActivityManager.Stub
thread.start();
break;
}
- case START_USER_SWITCH_FG_MSG: {
- mUserController.startUserInForeground(msg.arg1);
- break;
- }
- case REPORT_USER_SWITCH_MSG: {
- mUserController.dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
- break;
- }
- case CONTINUE_USER_SWITCH_MSG: {
- mUserController.continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
- break;
- }
- case USER_SWITCH_TIMEOUT_MSG: {
- mUserController.timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
- break;
- }
- case USER_SWITCH_CALLBACKS_TIMEOUT_MSG: {
- mUserController.timeoutUserSwitchCallbacks(msg.arg1, msg.arg2);
- break;
- }
case IMMERSIVE_MODE_LOCK_MSG: {
final boolean nextState = (msg.arg1 != 0);
if (mUpdateLock.isHeld() != nextState) {
@@ -2180,12 +2194,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
break;
}
- case START_PROFILES_MSG: {
- synchronized (ActivityManagerService.this) {
- mUserController.startProfilesLocked();
- }
- break;
- }
case UPDATE_TIME_PREFERENCE_MSG: {
// The user's time format preference might have changed.
// For convenience we re-use the Intent extra values.
@@ -2204,35 +2212,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
break;
}
- case SYSTEM_USER_START_MSG: {
- mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
- Integer.toString(msg.arg1), msg.arg1);
- mSystemServiceManager.startUser(msg.arg1);
- break;
- }
- case SYSTEM_USER_UNLOCK_MSG: {
- final int userId = msg.arg1;
- mSystemServiceManager.unlockUser(userId);
- synchronized (ActivityManagerService.this) {
- mRecentTasks.loadUserRecentsLocked(userId);
- }
- if (userId == UserHandle.USER_SYSTEM) {
- startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- }
- installEncryptionUnawareProviders(userId);
- mUserController.finishUserUnlocked((UserState) msg.obj);
- break;
- }
- case SYSTEM_USER_CURRENT_MSG: {
- mBatteryStatsService.noteEvent(
- BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
- Integer.toString(msg.arg2), msg.arg2);
- mBatteryStatsService.noteEvent(
- BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
- Integer.toString(msg.arg1), msg.arg1);
- mSystemServiceManager.switchUser(msg.arg1);
- break;
- }
case ENTER_ANIMATION_COMPLETE_MSG: {
synchronized (ActivityManagerService.this) {
ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
@@ -2372,19 +2351,10 @@ public class ActivityManagerService extends IActivityManager.Stub
mMemWatchDumpUid = -1;
}
} break;
- case FOREGROUND_PROFILE_CHANGED_MSG: {
- mUserController.dispatchForegroundProfileChanged(msg.arg1);
- } break;
case REPORT_TIME_TRACKER_MSG: {
AppTimeTracker tracker = (AppTimeTracker)msg.obj;
tracker.deliverResult(mContext);
} break;
- case REPORT_USER_SWITCH_COMPLETE_MSG: {
- mUserController.dispatchUserSwitchComplete(msg.arg1);
- } break;
- case REPORT_LOCKED_BOOT_COMPLETE_MSG: {
- mUserController.dispatchLockedBootComplete(msg.arg1);
- } break;
case SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG: {
IUiAutomationConnection connection = (IUiAutomationConnection) msg.obj;
try {
@@ -2409,17 +2379,16 @@ public class ActivityManagerService extends IActivityManager.Stub
if (disableNonVrUi) {
// If we are in a VR mode where Picture-in-Picture mode is unsupported,
// then remove the pinned stack.
- final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
- PINNED_STACK_ID);
- if (pinnedStack != null) {
- mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
- }
+ mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
}
}
} break;
case NOTIFY_VR_SLEEPING_MSG: {
notifyVrManagerOfSleepState(msg.arg1 != 0);
} break;
+ case NOTIFY_VR_KEYGUARD_MSG: {
+ notifyVrManagerOfKeyguardState(msg.arg1 != 0);
+ } break;
case HANDLE_TRUST_STORAGE_UPDATE_MSG: {
synchronized (ActivityManagerService.this) {
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
@@ -2568,10 +2537,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
public void setWindowManager(WindowManagerService wm) {
- mWindowManager = wm;
- mStackSupervisor.setWindowManager(wm);
- mActivityStarter.setWindowManager(wm);
- mLockTaskController.setWindowManager(wm);
+ synchronized (this) {
+ mWindowManager = wm;
+ mStackSupervisor.setWindowManager(wm);
+ mActivityStarter.setWindowManager(wm);
+ mLockTaskController.setWindowManager(wm);
+ }
}
public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) {
@@ -2589,6 +2560,14 @@ public class ActivityManagerService extends IActivityManager.Stub
static class MemBinder extends Binder {
ActivityManagerService mActivityManagerService;
+ private final PriorityDump.PriorityDumper mPriorityDumper =
+ new PriorityDump.PriorityDumper() {
+ @Override
+ public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, false, null);
+ }
+ };
+
MemBinder(ActivityManagerService activityManagerService) {
mActivityManagerService = activityManagerService;
}
@@ -2597,7 +2576,7 @@ public class ActivityManagerService extends IActivityManager.Stub
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
"meminfo", pw)) return;
- mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, false, null);
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
}
@@ -2631,19 +2610,27 @@ public class ActivityManagerService extends IActivityManager.Stub
static class CpuBinder extends Binder {
ActivityManagerService mActivityManagerService;
+ private final PriorityDump.PriorityDumper mPriorityDumper =
+ new PriorityDump.PriorityDumper() {
+ @Override
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+ "cpuinfo", pw)) return;
+ synchronized (mActivityManagerService.mProcessCpuTracker) {
+ pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad());
+ pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState(
+ SystemClock.uptimeMillis()));
+ }
+ }
+ };
+
CpuBinder(ActivityManagerService activityManagerService) {
mActivityManagerService = activityManagerService;
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
- "cpuinfo", pw)) return;
- synchronized (mActivityManagerService.mProcessCpuTracker) {
- pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad());
- pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState(
- SystemClock.uptimeMillis()));
- }
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
}
@@ -3152,9 +3139,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (mLastResumedActivity != null && r.userId != mLastResumedActivity.userId) {
- mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
- mHandler.obtainMessage(
- FOREGROUND_PROFILE_CHANGED_MSG, r.userId, 0).sendToTarget();
+ mUserController.sendForegroundProfileChanged(r.userId);
}
mLastResumedActivity = r;
@@ -3259,7 +3244,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (r.requestedVrComponent != null && r.getStackId() >= FIRST_DYNAMIC_STACK_ID) {
Slog.i(TAG, "Moving " + r.shortComponentName + " from stack " + r.getStackId()
+ " to main stack for VR");
- moveTaskToStack(r.getTask().taskId, FULLSCREEN_WORKSPACE_STACK_ID, true /* toTop */);
+ setTaskWindowingMode(r.getTask().taskId,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
}
mHandler.sendMessage(
mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r));
@@ -3278,6 +3264,19 @@ public class ActivityManagerService extends IActivityManager.Stub
vrService.onSleepStateChanged(isSleeping);
}
+ private void sendNotifyVrManagerOfKeyguardState(boolean isShowing) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(NOTIFY_VR_KEYGUARD_MSG, isShowing ? 1 : 0, 0));
+ }
+
+ private void notifyVrManagerOfKeyguardState(boolean isShowing) {
+ final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
+ if (vrService == null) {
+ return;
+ }
+ vrService.onKeyguardStateChanged(isShowing);
+ }
+
final void showAskCompatModeDialogLocked(ActivityRecord r) {
Message msg = Message.obtain();
msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
@@ -3874,6 +3873,12 @@ public class ActivityManagerService extends IActivityManager.Stub
mNativeDebuggingApp = null;
}
+ if (app.info.isPrivilegedApp() &&
+ !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+ runtimeFlags |= Zygote.DISABLE_VERIFIER;
+ runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
+ }
+
String invokeWith = null;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
@@ -4156,15 +4161,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- void enforceShellRestriction(String restriction, int userHandle) {
- if (Binder.getCallingUid() == SHELL_UID) {
- if (userHandle < 0 || mUserController.hasUserRestriction(restriction, userHandle)) {
- throw new SecurityException("Shell does not have permission to access user "
- + userHandle);
- }
- }
- }
-
@Override
public int getFrontActivityScreenCompatMode() {
enforceNotIsolatedCaller("getFrontActivityScreenCompatMode");
@@ -5432,6 +5428,7 @@ public class ActivityManagerService extends IActivityManager.Stub
boolean doLowMem = app.instr == null;
boolean doOomAdj = doLowMem;
if (!app.killedByAm) {
+ maybeNotifyTopAppKilled(app);
Slog.i(TAG, "Process " + app.processName + " (pid " + pid + ") has died: "
+ ProcessList.makeOomAdjString(app.setAdj)
+ ProcessList.makeProcStateString(app.setProcState));
@@ -5465,6 +5462,23 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ /** Show system error dialog when a top app is killed by LMK */
+ void maybeNotifyTopAppKilled(ProcessRecord app) {
+ if (!shouldNotifyTopAppKilled(app)) {
+ return;
+ }
+
+ Message msg = mHandler.obtainMessage(TOP_APP_KILLED_BY_LMK_MSG);
+ msg.obj = mContext.getPackageManager().getApplicationLabel(app.info);
+ mUiHandler.sendMessage(msg);
+ }
+
+ /** Only show notification when the top app is killed on low ram devices */
+ private boolean shouldNotifyTopAppKilled(ProcessRecord app) {
+ return app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
+ ActivityManager.isLowRamDeviceStatic();
+ }
+
/**
* If a stack trace dump file is configured, dump process stack traces.
* @param clearTraces causes the dump file to be erased prior to the new
@@ -6188,7 +6202,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, "Failed trying to unstop package "
+ packageName + ": " + e);
}
- if (mUserController.isUserRunningLocked(user, 0)) {
+ if (mUserController.isUserRunning(user, 0)) {
forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
finishForceStopPackageLocked(packageName, pkgUid);
}
@@ -7346,33 +7360,32 @@ public class ActivityManagerService extends IActivityManager.Stub
startProcessLocked(procs.get(ip), "on-hold", null);
}
}
+ if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
+ return;
+ }
+ // Start looking for apps that are abusing wake locks.
+ Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
+ mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+ // Tell anyone interested that we are done booting!
+ SystemProperties.set("sys.boot_completed", "1");
- if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
- // Start looking for apps that are abusing wake locks.
- Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
- mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
- // Tell anyone interested that we are done booting!
- SystemProperties.set("sys.boot_completed", "1");
-
- // And trigger dev.bootcomplete if we are not showing encryption progress
- if (!"trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))
+ // And trigger dev.bootcomplete if we are not showing encryption progress
+ if (!"trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))
|| "".equals(SystemProperties.get("vold.encrypt_progress"))) {
- SystemProperties.set("dev.bootcomplete", "1");
- }
- mUserController.sendBootCompletedLocked(
- new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser) {
- synchronized (ActivityManagerService.this) {
- requestPssAllProcsLocked(SystemClock.uptimeMillis(),
- true, false);
- }
- }
- });
- scheduleStartProfilesLocked();
+ SystemProperties.set("dev.bootcomplete", "1");
}
+ mUserController.sendBootCompleted(
+ new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser) {
+ synchronized (ActivityManagerService.this) {
+ requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+ }
+ }
+ });
+ mUserController.scheduleStartProfiles();
}
}
@@ -8112,7 +8125,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint());
mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
true /* moveHomeStackToFront */, "enterPictureInPictureMode");
- final PinnedActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID);
+ final PinnedActivityStack stack = r.getStack();
stack.setPictureInPictureAspectRatio(aspectRatio);
stack.setPictureInPictureActions(actions);
@@ -8227,11 +8240,6 @@ public class ActivityManagerService extends IActivityManager.Stub
+ ": Current activity does not support picture-in-picture.");
}
- if (!StackId.isAllowedToEnterPictureInPicture(r.getStack().getStackId())) {
- throw new IllegalStateException(caller
- + ": Activities on the home, assistant, or recents stack not supported");
- }
-
if (params.hasSetAspectRatio()
&& !mWindowManager.isValidPictureInPictureAspectRatio(r.getStack().mDisplayId,
params.getAspectRatio())) {
@@ -9869,8 +9877,9 @@ public class ActivityManagerService extends IActivityManager.Stub
if (tr.mBounds != null) {
rti.bounds = new Rect(tr.mBounds);
}
- rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreen();
+ rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
rti.resizeMode = tr.mResizeMode;
+ rti.configuration.setTo(tr.getConfiguration());
ActivityRecord base = null;
ActivityRecord top = null;
@@ -10048,7 +10057,7 @@ public class ActivityManagerService extends IActivityManager.Stub
enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
"getTaskDescription()");
final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id,
- MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr != null) {
return tr.lastTaskDescription;
}
@@ -10161,7 +10170,7 @@ public class ActivityManagerService extends IActivityManager.Stub
public void setTaskResizeable(int taskId, int resizeableMode) {
synchronized (this) {
final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
- taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+ taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (task == null) {
Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found");
return;
@@ -10188,21 +10197,23 @@ public class ActivityManagerService extends IActivityManager.Stub
// - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
// that task to freeform
// - otherwise the task is not moved
- int stackId = task.getStackId();
+ ActivityStack stack = task.getStack();
if (!task.getWindowConfiguration().canResizeTask()) {
throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
}
- if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
- stackId = FULLSCREEN_WORKSPACE_STACK_ID;
- } else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
- stackId = FREEFORM_WORKSPACE_STACK_ID;
+ if (bounds == null && stack.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ stack = stack.getDisplay().getOrCreateStack(
+ WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
+ } else if (bounds != null && stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ stack = stack.getDisplay().getOrCreateStack(
+ WINDOWING_MODE_FREEFORM, stack.getActivityType(), ON_TOP);
}
// Reparent the task to the right stack if necessary
boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
- if (stackId != task.getStackId()) {
+ if (stack != task.getStack()) {
// Defer resume until the task is resized below
- task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+ task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
DEFER_RESUME, "resizeTask");
preserveWindow = false;
}
@@ -10224,7 +10235,7 @@ public class ActivityManagerService extends IActivityManager.Stub
try {
synchronized (this) {
final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
- MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (task == null) {
Slog.w(TAG, "getTaskBounds: taskId=" + taskId + " not found");
return rect;
@@ -10256,7 +10267,7 @@ public class ActivityManagerService extends IActivityManager.Stub
try {
synchronized (this) {
final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
- MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+ MATCH_TASK_IN_STACKS_ONLY);
if (task == null) {
Slog.w(TAG, "cancelTaskWindowTransition: taskId=" + taskId + " not found");
return;
@@ -10275,7 +10286,7 @@ public class ActivityManagerService extends IActivityManager.Stub
try {
synchronized (this) {
final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
- MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+ MATCH_TASK_IN_STACKS_ONLY);
if (task == null) {
Slog.w(TAG, "cancelTaskThumbnailTransition: taskId=" + taskId + " not found");
return;
@@ -10295,7 +10306,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final TaskRecord task;
synchronized (this) {
task = mStackSupervisor.anyTaskForIdLocked(taskId,
- MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (task == null) {
Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
return null;
@@ -10375,14 +10386,45 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void removeStack(int stackId) {
enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
- if (StackId.isHomeOrRecentsStack(stackId)) {
- throw new IllegalArgumentException("Removing home or recents stack is not allowed.");
+ synchronized (this) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ final ActivityStack stack = mStackSupervisor.getStack(stackId);
+ if (stack != null && !stack.isActivityTypeStandardOrUndefined()) {
+ throw new IllegalArgumentException(
+ "Removing non-standard stack is not allowed.");
+ }
+ mStackSupervisor.removeStackLocked(stackId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
+ }
+ /**
+ * Removes stacks in the input windowing modes from the system if they are of activity type
+ * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+ */
+ @Override
+ public void removeStacksInWindowingModes(int[] windowingModes) {
+ enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksInWindowingModes()");
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
- mStackSupervisor.removeStackLocked(stackId);
+ mStackSupervisor.removeStacksInWindowingModes(windowingModes);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ @Override
+ public void removeStacksWithActivityTypes(int[] activityTypes) {
+ enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksWithActivityTypes()");
+ synchronized (this) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mStackSupervisor.removeStacksWithActivityTypes(activityTypes);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10532,13 +10574,15 @@ public class ActivityManagerService extends IActivityManager.Stub
public int createStackOnDisplay(int displayId) throws RemoteException {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "createStackOnDisplay()");
synchronized (this) {
- final int stackId = mStackSupervisor.getNextStackId();
- final ActivityStack stack =
- mStackSupervisor.createStackOnDisplay(stackId, displayId, true /*onTop*/);
- if (stack == null) {
+ final ActivityDisplay display =
+ mStackSupervisor.getActivityDisplayOrCreateLocked(displayId);
+ if (display == null) {
return INVALID_STACK_ID;
}
- return stack.mStackId;
+ // TODO(multi-display): Have the caller pass in the windowing mode and activity type.
+ final ActivityStack stack = display.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/);
+ return (stack != null) ? stack.mStackId : INVALID_STACK_ID;
}
}
@@ -10570,8 +10614,12 @@ public class ActivityManagerService extends IActivityManager.Stub
"exitFreeformMode: You can only go fullscreen from freeform.");
}
+ final ActivityStack fullscreenStack = stack.getDisplay().getOrCreateStack(
+ WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
+
if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
- r.getTask().reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+ // TODO: Should just change windowing mode vs. re-parenting...
+ r.getTask().reparent(fullscreenStack, ON_TOP,
REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "exitFreeformMode");
} finally {
Binder.restoreCallingIdentity(ident);
@@ -10580,29 +10628,35 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
- if (StackId.isHomeOrRecentsStack(stackId)) {
- throw new IllegalArgumentException(
- "moveTaskToStack: Attempt to move task " + taskId + " to stack " + stackId);
- }
+ public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
+ enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
synchronized (this) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
if (task == null) {
- Slog.w(TAG, "moveTaskToStack: No task for id=" + taskId);
+ Slog.w(TAG, "setTaskWindowingMode: No task for id=" + taskId);
return;
}
- if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
- + " to stackId=" + stackId + " toTop=" + toTop);
- if (stackId == DOCKED_STACK_ID) {
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "setTaskWindowingMode: moving task=" + taskId
+ + " to windowingMode=" + windowingMode + " toTop=" + toTop);
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
null /* initialBounds */);
}
- task.reparent(stackId, toTop,
- REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "moveTaskToStack");
+
+ if (!task.isActivityTypeStandardOrUndefined()) {
+ throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move task "
+ + taskId + " to non-standard windowin mode=" + windowingMode);
+ }
+ final ActivityDisplay display = task.getStack().getDisplay();
+ final ActivityStack stack = display.getOrCreateStack(windowingMode,
+ task.getStack().getActivityType(), toTop);
+ // TODO: We should just change the windowing mode for the task vs. creating and
+ // moving it to a stack.
+ task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+ "moveTaskToStack");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10610,49 +10664,42 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void swapDockedAndFullscreenStack() throws RemoteException {
- enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "swapDockedAndFullscreenStack()");
+ public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+ enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
- final ActivityStack fullscreenStack = mStackSupervisor.getStack(
- FULLSCREEN_WORKSPACE_STACK_ID);
- final TaskRecord topTask = fullscreenStack != null ? fullscreenStack.topTask()
- : null;
- final ActivityStack dockedStack = mStackSupervisor.getStack(DOCKED_STACK_ID);
- final ArrayList<TaskRecord> tasks = dockedStack != null ? dockedStack.getAllTasks()
- : null;
- if (topTask == null || tasks == null || tasks.size() == 0) {
- Slog.w(TAG,
- "Unable to swap tasks, either docked or fullscreen stack is empty.");
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ Slog.w(TAG, "moveTaskToStack: No task for id=" + taskId);
return;
}
- // TODO: App transition
- mWindowManager.prepareAppTransition(TRANSIT_ACTIVITY_RELAUNCH, false);
-
- // Defer the resume until we move all the docked tasks to the fullscreen stack below
- topTask.reparent(DOCKED_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
- DEFER_RESUME, "swapDockedAndFullscreenStack - DOCKED_STACK");
- final int size = tasks.size();
- for (int i = 0; i < size; i++) {
- final int id = tasks.get(i).taskId;
- if (id == topTask.taskId) {
- continue;
- }
-
- // Defer the resume until after all the tasks have been moved
- tasks.get(i).reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
- REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, DEFER_RESUME,
- "swapDockedAndFullscreenStack - FULLSCREEN_STACK");
+ if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
+ + " to stackId=" + stackId + " toTop=" + toTop);
+ if (stackId == DOCKED_STACK_ID) {
+ mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
+ null /* initialBounds */);
}
- // Because we deferred the resume to avoid conflicts with stack switches while
- // resuming, we need to do it after all the tasks are moved.
- mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
- mStackSupervisor.resumeFocusedStackTopActivityLocked();
-
- mWindowManager.executeAppTransition();
+ ActivityStack stack = mStackSupervisor.getStack(stackId);
+ if (stack == null) {
+ if (!isStaticStack(stackId)) {
+ throw new IllegalStateException(
+ "moveTaskToStack: No stack for stackId=" + stackId);
+ }
+ final ActivityDisplay display = task.getStack().getDisplay();
+ final int windowingMode =
+ getWindowingModeForStackId(stackId, display.hasSplitScreenStack());
+ stack = display.getOrCreateStack(windowingMode,
+ task.getStack().getActivityType(), toTop);
+ }
+ if (!stack.isActivityTypeStandardOrUndefined()) {
+ throw new IllegalArgumentException("moveTaskToStack: Attempt to move task "
+ + taskId + " to stack " + stackId);
+ }
+ task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+ "moveTaskToStack");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10685,13 +10732,18 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
return false;
}
-
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
+ " to createMode=" + createMode + " toTop=" + toTop);
mWindowManager.setDockedStackCreateState(createMode, initialBounds);
+ final ActivityDisplay display = task.getStack().getDisplay();
+ final ActivityStack stack = display.getOrCreateStack(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, task.getStack().getActivityType(),
+ toTop);
+
// Defer resuming until we move the home stack to the front below
- final boolean moved = task.reparent(DOCKED_STACK_ID, toTop,
+ // TODO: Should just change windowing mode vs. re-parenting...
+ final boolean moved = task.reparent(stack, toTop,
REPARENT_KEEP_STACK_AT_FRONT, animate, !DEFER_RESUME,
"moveTaskToDockedStack");
if (moved) {
@@ -10705,6 +10757,66 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
+ * Dismisses split-screen multi-window mode.
+ * @param toTop If true the current primary split-screen stack will be placed or left on top.
+ */
+ @Override
+ public void dismissSplitScreenMode(boolean toTop) {
+ enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ final ActivityStack stack =
+ mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+ if (toTop) {
+ mStackSupervisor.resizeStackLocked(stack.mStackId, null /* destBounds */,
+ null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+ true /* preserveWindows */, true /* allowResizeInDockedMode */,
+ !DEFER_RESUME);
+ } else {
+ mStackSupervisor.moveTasksToFullscreenStackLocked(stack, false /* onTop */);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Dismisses Pip
+ * @param animate True if the dismissal should be animated.
+ * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
+ * default animation duration should be used.
+ */
+ @Override
+ public void dismissPip(boolean animate, int animationDuration) {
+ enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ final PinnedActivityStack stack =
+ mStackSupervisor.getDefaultDisplay().getPinnedStack();
+
+ if (stack == null) {
+ return;
+ }
+ if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ throw new IllegalArgumentException("Stack: " + stack
+ + " doesn't support animated resize.");
+ }
+ if (animate) {
+ stack.animateResizePinnedStack(null /* sourceHintBounds */,
+ null /* destBounds */, animationDuration, false /* fromFullscreen */);
+ } else {
+ mStackSupervisor.moveTasksToFullscreenStackLocked(stack, true /* onTop */);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
* Moves the top activity in the input stackId to the pinned stack.
*
* @param stackId Id of stack to move the top activity to pinned stack.
@@ -10739,17 +10851,16 @@ public class ActivityManagerService extends IActivityManager.Stub
try {
synchronized (this) {
if (animate) {
- if (stackId == PINNED_STACK_ID) {
- final PinnedActivityStack pinnedStack =
- mStackSupervisor.getStack(PINNED_STACK_ID);
- if (pinnedStack != null) {
- pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */,
- destBounds, animationDuration, false /* fromFullscreen */);
- }
- } else {
+ final PinnedActivityStack stack = mStackSupervisor.getStack(stackId);
+ if (stack == null) {
+ return;
+ }
+ if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
throw new IllegalArgumentException("Stack: " + stackId
+ " doesn't support animated resize.");
}
+ stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
+ animationDuration, false /* fromFullscreen */);
} else {
mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */,
null /* tempTaskInsetBounds */, preserveWindows,
@@ -10801,11 +10912,6 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void positionTaskInStack(int taskId, int stackId, int position) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "positionTaskInStack()");
- if (StackId.isHomeOrRecentsStack(stackId)) {
- throw new IllegalArgumentException(
- "positionTaskInStack: Attempt to change the position of task "
- + taskId + " in/to home/recents stack");
- }
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
@@ -10817,8 +10923,16 @@ public class ActivityManagerService extends IActivityManager.Stub
+ taskId);
}
- final ActivityStack stack = mStackSupervisor.getStack(stackId, CREATE_IF_NEEDED,
- !ON_TOP);
+ final ActivityStack stack = mStackSupervisor.getStack(stackId);
+
+ if (stack == null) {
+ throw new IllegalArgumentException("positionTaskInStack: no stack for id="
+ + stackId);
+ }
+ if (!stack.isActivityTypeStandardOrUndefined()) {
+ throw new IllegalArgumentException("positionTaskInStack: Attempt to change"
+ + " the position of task " + taskId + " in/to non-standard stack");
+ }
// TODO: Have the callers of this API call a separate reparent method if that is
// what they intended to do vs. having this method also do reparenting.
@@ -10827,8 +10941,8 @@ public class ActivityManagerService extends IActivityManager.Stub
stack.positionChildAt(task, position);
} else {
// Reparent to new stack.
- task.reparent(stackId, position, REPARENT_LEAVE_STACK_IN_PLACE,
- !ANIMATE, !DEFER_RESUME, "positionTaskInStack");
+ task.reparent(stack, position, REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE,
+ !DEFER_RESUME, "positionTaskInStack");
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -10850,12 +10964,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public StackInfo getStackInfo(int stackId) {
+ public StackInfo getStackInfo(int windowingMode, int activityType) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
long ident = Binder.clearCallingIdentity();
try {
synchronized (this) {
- return mStackSupervisor.getStackInfoLocked(stackId);
+ return mStackSupervisor.getStackInfo(windowingMode, activityType);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -10882,7 +10996,6 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void updateLockTaskPackages(int userId, String[] packages) {
- // TODO: move this into LockTaskController
final int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != SYSTEM_UID) {
enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
@@ -10891,8 +11004,7 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized (this) {
if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":" +
Arrays.toString(packages));
- mLockTaskPackages.put(userId, packages);
- mLockTaskController.onLockTaskPackagesUpdated();
+ mLockTaskController.updateLockTaskPackages(userId, packages);
}
}
@@ -10908,11 +11020,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
// When a task is locked, dismiss the pinned stack if it exists
- final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
- PINNED_STACK_ID);
- if (pinnedStack != null) {
- mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
- }
+ mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
// isAppPinning is used to distinguish between locked and pinned mode, as pinned mode
// is initiated by system after the pinning request was shown and locked mode is initiated
@@ -11139,7 +11247,7 @@ public class ActivityManagerService extends IActivityManager.Stub
boolean checkedGrants = false;
if (checkUser) {
// Looking for cross-user grants before enforcing the typical cross-users permissions
- int tmpTargetUserId = mUserController.unsafeConvertIncomingUserLocked(userId);
+ int tmpTargetUserId = mUserController.unsafeConvertIncomingUser(userId);
if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
return null;
@@ -11527,7 +11635,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Make sure that the user who owns this provider is running. If not,
// we don't want to allow it to run.
- if (!mUserController.isUserRunningLocked(userId, 0)) {
+ if (!mUserController.isUserRunning(userId, 0)) {
Slog.w(TAG, "Unable to launch app "
+ cpi.applicationInfo.packageName + "/"
+ cpi.applicationInfo.uid + " for provider "
@@ -12073,7 +12181,7 @@ public class ActivityManagerService extends IActivityManager.Stub
//mUsageStatsService.monitorPackages();
}
- private void startPersistentApps(int matchFlags) {
+ void startPersistentApps(int matchFlags) {
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;
synchronized (this) {
@@ -12094,7 +12202,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* When a user is unlocked, we need to install encryption-unaware providers
* belonging to any running apps.
*/
- private void installEncryptionUnawareProviders(int userId) {
+ void installEncryptionUnawareProviders(int userId) {
// We're only interested in providers that are encryption unaware, and
// we don't care about uninstalled apps, since there's no way they're
// running at this point.
@@ -12157,9 +12265,7 @@ public class ActivityManagerService extends IActivityManager.Stub
int callingPid = Binder.getCallingPid();
long ident = 0;
boolean clearedIdentity = false;
- synchronized (this) {
- userId = mUserController.unsafeConvertIncomingUserLocked(userId);
- }
+ userId = mUserController.unsafeConvertIncomingUser(userId);
if (canClearIdentity(callingPid, callingUid, userId)) {
clearedIdentity = true;
ident = Binder.clearCallingIdentity();
@@ -12399,19 +12505,14 @@ public class ActivityManagerService extends IActivityManager.Stub
void onWakefulnessChanged(int wakefulness) {
synchronized(this) {
+ boolean wasAwake = mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
+ boolean isAwake = wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
mWakefulness = wakefulness;
- // Also update state in a special way for running foreground services UI.
- switch (mWakefulness) {
- case PowerManagerInternal.WAKEFULNESS_ASLEEP:
- case PowerManagerInternal.WAKEFULNESS_DREAMING:
- case PowerManagerInternal.WAKEFULNESS_DOZING:
- mServices.updateScreenStateLocked(false /* screenOn */);
- break;
- case PowerManagerInternal.WAKEFULNESS_AWAKE:
- default:
- mServices.updateScreenStateLocked(true /* screenOn */);
- break;
+ if (wasAwake != isAwake) {
+ // Also update state in a special way for running foreground services UI.
+ mServices.updateScreenStateLocked(isAwake);
+ sendNotifyVrManagerOfSleepState(!isAwake);
}
}
}
@@ -12447,7 +12548,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
mStackSupervisor.applySleepTokensLocked(true /* applyToStacks */);
if (wasSleeping) {
- sendNotifyVrManagerOfSleepState(false);
updateOomAdjLocked();
}
} else if (!mSleeping && shouldSleep) {
@@ -12457,7 +12557,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
mStackSupervisor.goingToSleepLocked();
- sendNotifyVrManagerOfSleepState(true);
updateOomAdjLocked();
}
}
@@ -12551,7 +12650,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void setLockScreenShown(boolean showing) {
+ public void setLockScreenShown(boolean showing, int secondaryDisplayShowing) {
if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
@@ -12561,11 +12660,12 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized(this) {
long ident = Binder.clearCallingIdentity();
try {
- mKeyguardController.setKeyguardShown(showing);
+ mKeyguardController.setKeyguardShown(showing, secondaryDisplayShowing);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+ sendNotifyVrManagerOfKeyguardState(showing);
}
@Override
@@ -12584,7 +12684,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (mUserController.shouldConfirmCredentials(userId)) {
if (mKeyguardController.isKeyguardLocked()) {
// Showing launcher to avoid user entering credential twice.
- final int currentUserId = mUserController.getCurrentUserIdLocked();
+ final int currentUserId = mUserController.getCurrentUserId();
startHomeActivityLocked(currentUserId, "notifyLockedProfile");
}
mStackSupervisor.lockAllProfileTasks(userId);
@@ -13982,10 +14082,10 @@ public class ActivityManagerService extends IActivityManager.Stub
mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
|| Settings.Global.getInt(
resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
- final boolean supportsPictureInPicture =
- mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
final boolean supportsMultiWindow = ActivityManager.supportsMultiWindow(mContext);
+ final boolean supportsPictureInPicture = supportsMultiWindow &&
+ mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
final boolean supportsSplitScreenMultiWindow =
ActivityManager.supportsSplitScreenMultiWindow(mContext);
final boolean supportsMultiDisplay = mContext.getPackageManager()
@@ -14164,9 +14264,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
retrieveSettings();
- final int currentUserId;
+ final int currentUserId = mUserController.getCurrentUserId();
synchronized (this) {
- currentUserId = mUserController.getCurrentUserIdLocked();
readGrantedUriPermissionsLocked();
}
@@ -14245,7 +14344,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Binder.restoreCallingIdentity(ident);
}
mStackSupervisor.resumeFocusedStackTopActivityLocked();
- mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
+ mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
traceLog.traceEnd(); // ActivityManagerStartApps
traceLog.traceEnd(); // PhaseActivityManagerReady
}
@@ -14597,6 +14696,9 @@ public class ActivityManagerService extends IActivityManager.Stub
}
sb.append("\n");
}
+ if (process.info.isInstantApp()) {
+ sb.append("Instant-App: true\n");
+ }
}
}
@@ -14943,6 +15045,13 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ }
+
+ /**
+ * Wrapper function to print out debug data filtered by specified arguments.
+ */
+ private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
boolean dumpAll = false;
@@ -14951,6 +15060,7 @@ public class ActivityManagerService extends IActivityManager.Stub
boolean dumpCheckinFormat = false;
boolean dumpVisibleStacksOnly = false;
boolean dumpFocusedStackOnly = false;
+ boolean useProto = false;
String dumpPackage = null;
int opti = 0;
@@ -14984,12 +15094,26 @@ public class ActivityManagerService extends IActivityManager.Stub
} else if ("-h".equals(opt)) {
ActivityManagerShellCommand.dumpHelp(pw, true);
return;
+ } else if ("--proto".equals(opt)) {
+ useProto = true;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
}
}
long origId = Binder.clearCallingIdentity();
+
+ if (useProto) {
+ //TODO: Options when dumping proto
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ synchronized (this) {
+ writeActivitiesToProtoLocked(proto);
+ }
+ proto.flush();
+ Binder.restoreCallingIdentity(origId);
+ return;
+ }
+
boolean more = false;
// Is the caller requesting to dump a particular piece of data?
if (opti < args.length) {
@@ -15333,6 +15457,10 @@ public class ActivityManagerService extends IActivityManager.Stub
Binder.restoreCallingIdentity(origId);
}
+ private void writeActivitiesToProtoLocked(ProtoOutputStream proto) {
+ mStackSupervisor.writeToProto(proto, ACTIVITIES);
+ }
+
private void dumpLastANRLocked(PrintWriter pw) {
pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
if (mLastANRState == null) {
@@ -19047,7 +19175,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// If not, we will just skip it. Make an exception for shutdown broadcasts
// and upgrade steps.
- if (userId != UserHandle.USER_ALL && !mUserController.isUserRunningLocked(userId, 0)) {
+ if (userId != UserHandle.USER_ALL && !mUserController.isUserRunning(userId, 0)) {
if ((callingUid != SYSTEM_UID
|| (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
&& !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
@@ -19466,7 +19594,7 @@ public class ActivityManagerService extends IActivityManager.Stub
int[] users;
if (userId == UserHandle.USER_ALL) {
// Caller wants broadcast to go to all started users.
- users = mUserController.getStartedUserArrayLocked();
+ users = mUserController.getStartedUserArray();
} else {
// Caller wants broadcast to go to one specific user.
users = new int[] {userId};
@@ -20092,12 +20220,20 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public int getFocusedStackId() throws RemoteException {
- ActivityStack focusedStack = getFocusedStack();
- if (focusedStack != null) {
- return focusedStack.getStackId();
+ public StackInfo getFocusedStackInfo() throws RemoteException {
+ enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ ActivityStack focusedStack = getFocusedStack();
+ if (focusedStack != null) {
+ return mStackSupervisor.getStackInfo(focusedStack.mStackId);
+ }
+ return null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- return -1;
}
public Configuration getConfiguration() {
@@ -20123,15 +20259,20 @@ public class ActivityManagerService extends IActivityManager.Stub
* activity and clearing the task at the same time.
*/
@Override
+ // TODO: API should just be about changing windowing modes...
public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
- if (StackId.isHomeOrRecentsStack(fromStackId)) {
- throw new IllegalArgumentException("You can't move tasks from the home/recents stack.");
- }
synchronized (this) {
final long origId = Binder.clearCallingIdentity();
try {
- mStackSupervisor.moveTasksToFullscreenStackLocked(fromStackId, onTop);
+ final ActivityStack stack = mStackSupervisor.getStack(fromStackId);
+ if (stack != null){
+ if (!stack.isActivityTypeStandardOrUndefined()) {
+ throw new IllegalArgumentException(
+ "You can't move tasks from non-standard stacks.");
+ }
+ mStackSupervisor.moveTasksToFullscreenStackLocked(stack, onTop);
+ }
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -20229,7 +20370,7 @@ public class ActivityManagerService extends IActivityManager.Stub
void updateUserConfigurationLocked() {
final Configuration configuration = new Configuration(getGlobalConfiguration());
- final int currentUserId = mUserController.getCurrentUserIdLocked();
+ final int currentUserId = mUserController.getCurrentUserId();
Settings.System.adjustConfigurationForUser(mContext.getContentResolver(), configuration,
currentUserId, Settings.System.canWrite(mContext));
updateConfigurationLocked(configuration, null /* starting */, false /* initLocale */,
@@ -20340,7 +20481,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
// TODO(multi-display): Update UsageEvents#Event to include displayId.
mUsageStatsService.reportConfigurationChange(mTempConfig,
- mUserController.getCurrentUserIdLocked());
+ mUserController.getCurrentUserId());
// TODO: If our config changes, should we auto dismiss any currently showing dialogs?
mShowDialogs = shouldShowDialogs(mTempConfig);
@@ -22271,7 +22412,7 @@ public class ActivityManagerService extends IActivityManager.Stub
String authority) {
if (app == null) return;
if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- UserState userState = mUserController.getStartedUserStateLocked(app.userId);
+ UserState userState = mUserController.getStartedUserState(app.userId);
if (userState == null) return;
final long now = SystemClock.elapsedRealtime();
Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -23566,54 +23707,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public boolean switchUser(final int targetUserId) {
- enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
- int currentUserId;
- UserInfo targetUserInfo;
- synchronized (this) {
- currentUserId = mUserController.getCurrentUserIdLocked();
- targetUserInfo = mUserController.getUserInfo(targetUserId);
- if (targetUserId == currentUserId) {
- Slog.i(TAG, "user #" + targetUserId + " is already the current user");
- return true;
- }
- if (targetUserInfo == null) {
- Slog.w(TAG, "No user info for user #" + targetUserId);
- return false;
- }
- if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mContext)) {
- Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
- + " when device is in demo mode");
- return false;
- }
- if (!targetUserInfo.supportsSwitchTo()) {
- Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
- return false;
- }
- if (targetUserInfo.isManagedProfile()) {
- Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
- return false;
- }
- mUserController.setTargetUserIdLocked(targetUserId);
- }
- if (mUserController.mUserSwitchUiEnabled) {
- UserInfo currentUserInfo = mUserController.getUserInfo(currentUserId);
- Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
- mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
- mUiHandler.sendMessage(mHandler.obtainMessage(
- START_USER_SWITCH_UI_MSG, userNames));
- } else {
- mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(
- START_USER_SWITCH_FG_MSG, targetUserId, 0));
- }
- return true;
- }
-
- void scheduleStartProfilesLocked() {
- if (!mHandler.hasMessages(START_PROFILES_MSG)) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
- DateUtils.SECOND_IN_MILLIS);
- }
+ return mUserController.switchUser(targetUserId);
}
@Override
@@ -23627,10 +23721,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
String getStartedUserState(int userId) {
- synchronized (this) {
- final UserState userState = mUserController.getStartedUserStateLocked(userId);
- return UserState.stateToString(userState.state);
- }
+ final UserState userState = mUserController.getStartedUserState(userId);
+ return UserState.stateToString(userState.state);
}
@Override
@@ -23645,9 +23737,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
- synchronized (this) {
- return mUserController.isUserRunningLocked(userId, flags);
- }
+ return mUserController.isUserRunning(userId, flags);
}
@Override
@@ -23661,9 +23751,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
- synchronized (this) {
- return mUserController.getStartedUserArrayLocked();
- }
+ return mUserController.getStartedUserArray();
}
@Override
@@ -23684,9 +23772,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
public boolean isUserStopped(int userId) {
- synchronized (this) {
- return mUserController.getStartedUserStateLocked(userId) == null;
- }
+ return mUserController.getStartedUserState(userId) == null;
}
ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
@@ -24025,7 +24111,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void notifyKeyguardTrustedChanged() {
synchronized (ActivityManagerService.this) {
- if (mKeyguardController.isKeyguardShowing()) {
+ if (mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
}
@@ -24274,7 +24360,7 @@ public class ActivityManagerService extends IActivityManager.Stub
permission.INTERACT_ACROSS_USERS_FULL, "getLastResumedActivityUserId()");
synchronized (this) {
if (mLastResumedActivity == null) {
- return mUserController.getCurrentUserIdLocked();
+ return mUserController.getCurrentUserId();
}
return mLastResumedActivity.userId;
}
@@ -24481,7 +24567,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
try {
final ApplicationInfo ai = AppGlobals.getPackageManager()
- .getApplicationInfo(packageName, 0 /*flags*/, app.userId);
+ .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
if (ai != null) {
app.thread.scheduleApplicationInfoChanged(ai);
}
diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java
index 6901d2de..4c934232 100644
--- a/com/android/server/am/ActivityManagerShellCommand.java
+++ b/com/android/server/am/ActivityManagerShellCommand.java
@@ -75,6 +75,9 @@ import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityManager.RESIZE_MODE_USER;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
@@ -115,7 +118,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
private boolean mStreaming; // Streaming the profiling output to a file.
private String mAgent; // Agent to attach on startup.
private int mDisplayId;
- private int mStackId;
+ private int mWindowingMode;
+ private int mActivityType;
private int mTaskId;
private boolean mIsTaskOverlay;
@@ -271,7 +275,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
mStreaming = false;
mUserId = defUser;
mDisplayId = INVALID_DISPLAY;
- mStackId = INVALID_STACK_ID;
+ mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ mActivityType = ACTIVITY_TYPE_UNDEFINED;
mTaskId = INVALID_TASK_ID;
mIsTaskOverlay = false;
@@ -308,8 +313,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
mReceiverPermission = getNextArgRequired();
} else if (opt.equals("--display")) {
mDisplayId = Integer.parseInt(getNextArgRequired());
- } else if (opt.equals("--stack")) {
- mStackId = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("--windowingMode")) {
+ mWindowingMode = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("--activityType")) {
+ mActivityType = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--task")) {
mTaskId = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--task-overlay")) {
@@ -396,9 +403,17 @@ final class ActivityManagerShellCommand extends ShellCommand {
options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mDisplayId);
}
- if (mStackId != INVALID_STACK_ID) {
- options = ActivityOptions.makeBasic();
- options.setLaunchStackId(mStackId);
+ if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ options.setLaunchWindowingMode(mWindowingMode);
+ }
+ if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ options.setLaunchActivityType(mActivityType);
}
if (mTaskId != INVALID_TASK_ID) {
options = ActivityOptions.makeBasic();
@@ -2099,9 +2114,9 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
int runStackInfo(PrintWriter pw) throws RemoteException {
- String stackIdStr = getNextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- ActivityManager.StackInfo info = mInterface.getStackInfo(stackId);
+ int windowingMode = Integer.parseInt(getNextArgRequired());
+ int activityType = Integer.parseInt(getNextArgRequired());
+ ActivityManager.StackInfo info = mInterface.getStackInfo(windowingMode, activityType);
pw.println(info);
return 0;
}
@@ -2135,7 +2150,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
final String delayStr = getNextArg();
final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
- ActivityManager.StackInfo info = mInterface.getStackInfo(DOCKED_STACK_ID);
+ ActivityManager.StackInfo info = mInterface.getStackInfo(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
if (info == null) {
err.println("Docked stack doesn't exist");
return -1;
@@ -2238,10 +2254,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runTaskResizeable(pw);
} else if (op.equals("resize")) {
return runTaskResize(pw);
- } else if (op.equals("drag-task-test")) {
- return runTaskDragTaskTest(pw);
- } else if (op.equals("size-task-test")) {
- return runTaskSizeTaskTest(pw);
} else if (op.equals("focus")) {
return runTaskFocus(pw);
} else {
@@ -2294,58 +2306,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
}
- int runTaskDragTaskTest(PrintWriter pw) throws RemoteException {
- final int taskId = Integer.parseInt(getNextArgRequired());
- final int stepSize = Integer.parseInt(getNextArgRequired());
- final String delayStr = getNextArg();
- final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
- final ActivityManager.StackInfo stackInfo;
- Rect taskBounds;
- stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
- taskBounds = mInterface.getTaskBounds(taskId);
- final Rect stackBounds = stackInfo.bounds;
- int travelRight = stackBounds.width() - taskBounds.width();
- int travelLeft = -travelRight;
- int travelDown = stackBounds.height() - taskBounds.height();
- int travelUp = -travelDown;
- int passes = 0;
-
- // We do 2 passes to get back to the original location of the task.
- while (passes < 2) {
- // Move right
- pw.println("Moving right...");
- pw.flush();
- travelRight = moveTask(taskId, taskBounds, stackBounds, stepSize,
- travelRight, MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
- pw.println("Still need to travel right by " + travelRight);
-
- // Move down
- pw.println("Moving down...");
- pw.flush();
- travelDown = moveTask(taskId, taskBounds, stackBounds, stepSize,
- travelDown, MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
- pw.println("Still need to travel down by " + travelDown);
-
- // Move left
- pw.println("Moving left...");
- pw.flush();
- travelLeft = moveTask(taskId, taskBounds, stackBounds, stepSize,
- travelLeft, !MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
- pw.println("Still need to travel left by " + travelLeft);
-
- // Move up
- pw.println("Moving up...");
- pw.flush();
- travelUp = moveTask(taskId, taskBounds, stackBounds, stepSize,
- travelUp, !MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
- pw.println("Still need to travel up by " + travelUp);
-
- taskBounds = mInterface.getTaskBounds(taskId);
- passes++;
- }
- return 0;
- }
-
int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize,
int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms)
throws RemoteException {
@@ -2408,133 +2368,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
return stepSize;
}
- int runTaskSizeTaskTest(PrintWriter pw) throws RemoteException {
- final int taskId = Integer.parseInt(getNextArgRequired());
- final int stepSize = Integer.parseInt(getNextArgRequired());
- final String delayStr = getNextArg();
- final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
- final ActivityManager.StackInfo stackInfo;
- final Rect initialTaskBounds;
- stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
- initialTaskBounds = mInterface.getTaskBounds(taskId);
- final Rect stackBounds = stackInfo.bounds;
- stackBounds.inset(STACK_BOUNDS_INSET, STACK_BOUNDS_INSET);
- final Rect currentTaskBounds = new Rect(initialTaskBounds);
-
- // Size by top-left
- pw.println("Growing top-left");
- pw.flush();
- do {
- currentTaskBounds.top -= getStepSize(
- currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
- currentTaskBounds.left -= getStepSize(
- currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (stackBounds.top < currentTaskBounds.top
- || stackBounds.left < currentTaskBounds.left);
-
- // Back to original size
- pw.println("Shrinking top-left");
- pw.flush();
- do {
- currentTaskBounds.top += getStepSize(
- currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
- currentTaskBounds.left += getStepSize(
- currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (initialTaskBounds.top > currentTaskBounds.top
- || initialTaskBounds.left > currentTaskBounds.left);
-
- // Size by top-right
- pw.println("Growing top-right");
- pw.flush();
- do {
- currentTaskBounds.top -= getStepSize(
- currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
- currentTaskBounds.right += getStepSize(
- currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (stackBounds.top < currentTaskBounds.top
- || stackBounds.right > currentTaskBounds.right);
-
- // Back to original size
- pw.println("Shrinking top-right");
- pw.flush();
- do {
- currentTaskBounds.top += getStepSize(
- currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
- currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
- stepSize, GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (initialTaskBounds.top > currentTaskBounds.top
- || initialTaskBounds.right < currentTaskBounds.right);
-
- // Size by bottom-left
- pw.println("Growing bottom-left");
- pw.flush();
- do {
- currentTaskBounds.bottom += getStepSize(
- currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
- currentTaskBounds.left -= getStepSize(
- currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (stackBounds.bottom > currentTaskBounds.bottom
- || stackBounds.left < currentTaskBounds.left);
-
- // Back to original size
- pw.println("Shrinking bottom-left");
- pw.flush();
- do {
- currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
- initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
- currentTaskBounds.left += getStepSize(
- currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (initialTaskBounds.bottom < currentTaskBounds.bottom
- || initialTaskBounds.left > currentTaskBounds.left);
-
- // Size by bottom-right
- pw.println("Growing bottom-right");
- pw.flush();
- do {
- currentTaskBounds.bottom += getStepSize(
- currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
- currentTaskBounds.right += getStepSize(
- currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (stackBounds.bottom > currentTaskBounds.bottom
- || stackBounds.right > currentTaskBounds.right);
-
- // Back to original size
- pw.println("Shrinking bottom-right");
- pw.flush();
- do {
- currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
- initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
- currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
- stepSize, GREATER_THAN_TARGET);
-
- taskResize(taskId, currentTaskBounds, delay_ms, true);
- } while (initialTaskBounds.bottom < currentTaskBounds.bottom
- || initialTaskBounds.right < currentTaskBounds.right);
- return 0;
- }
-
int runTaskFocus(PrintWriter pw) throws RemoteException {
final int taskId = Integer.parseInt(getNextArgRequired());
pw.println("Setting focus to task " + taskId);
@@ -2661,6 +2494,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" -p: limit output to given package.");
pw.println(" --checkin: output checkin format, resetting data.");
pw.println(" --C: output checkin format, not resetting data.");
+ pw.println(" --proto: output dump in protocol buffer format.");
} else {
pw.println("Activity manager (activity) commands:");
pw.println(" help");
@@ -2685,7 +2519,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" --track-allocation: enable tracking of object allocations");
pw.println(" --user <USER_ID> | current: Specify which user to run as; if not");
pw.println(" specified then run as the current user.");
- pw.println(" --stack <STACK_ID>: Specify into which stack should the activity be put.");
+ pw.println(" --windowingMode <WINDOWING_MODE>: The windowing mode to launch the activity into.");
+ pw.println(" --activityType <ACTIVITY_TYPE>: The activity type to launch the activity as.");
pw.println(" start-service [--user <USER_ID> | current] <INTENT>");
pw.println(" Start a Service. Options are:");
pw.println(" --user <USER_ID> | current: Specify which user to run as; if not");
@@ -2864,8 +2699,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Place <TASK_ID> in <STACK_ID> at <POSITION>");
pw.println(" list");
pw.println(" List all of the activity stacks and their sizes.");
- pw.println(" info <STACK_ID>");
- pw.println(" Display the information about activity stack <STACK_ID>.");
+ pw.println(" info <WINDOWING_MODE> <ACTIVITY_TYPE>");
+ pw.println(" Display the information about activity stack in <WINDOWING_MODE> and <ACTIVITY_TYPE>.");
pw.println(" remove <STACK_ID>");
pw.println(" Remove stack <STACK_ID>.");
pw.println(" task [COMMAND] [...]: sub-commands for operating on activity tasks.");
@@ -2883,14 +2718,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Makes sure <TASK_ID> is in a stack with the specified bounds.");
pw.println(" Forces the task to be resizeable and creates a stack if no existing stack");
pw.println(" has the specified bounds.");
- pw.println(" drag-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
- pw.println(" Test command for dragging/moving <TASK_ID> by");
- pw.println(" <STEP_SIZE> increments around the screen applying the optional [DELAY_MS]");
- pw.println(" between each step.");
- pw.println(" size-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
- pw.println(" Test command for sizing <TASK_ID> by <STEP_SIZE>");
- pw.println(" increments within the screen applying the optional [DELAY_MS] between");
- pw.println(" each step.");
pw.println(" update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]");
pw.println(" Update the ApplicationInfo objects of the listed packages for <USER_ID>");
pw.println(" without restarting any processes.");
diff --git a/com/android/server/am/ActivityMetricsLogger.java b/com/android/server/am/ActivityMetricsLogger.java
index 0c8321d5..fdcb8c69 100644
--- a/com/android/server/am/ActivityMetricsLogger.java
+++ b/com/android/server/am/ActivityMetricsLogger.java
@@ -2,12 +2,9 @@ package com.android.server.am;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
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_SECONDARY;
@@ -32,13 +29,10 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import android.app.ActivityManager.StackId;
import android.content.Context;
import android.metrics.LogMaker;
import android.os.SystemClock;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 0ccb45f7..7b0b942a 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -17,8 +17,6 @@
package com.android.server.am;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
@@ -36,7 +34,6 @@ import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
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.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.content.Intent.ACTION_MAIN;
@@ -89,10 +86,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SAVED_STATE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SCREENSHOTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_THUMBNAILS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -107,7 +102,6 @@ import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
import static com.android.server.am.ActivityStack.LAUNCH_TICK;
import static com.android.server.am.ActivityStack.LAUNCH_TICK_MSG;
import static com.android.server.am.ActivityStack.PAUSE_TIMEOUT_MSG;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
import static com.android.server.am.ActivityStack.STOP_TIMEOUT_MSG;
import static com.android.server.am.EventLogTags.AM_ACTIVITY_FULLY_DRAWN_TIME;
import static com.android.server.am.EventLogTags.AM_ACTIVITY_LAUNCH_TIME;
@@ -116,6 +110,16 @@ import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY;
import static com.android.server.am.TaskPersister.DEBUG;
import static com.android.server.am.TaskPersister.IMAGE_EXTENSION;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.proto.ActivityRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityRecordProto.FRONT_OF_TASK;
+import static com.android.server.am.proto.ActivityRecordProto.IDENTIFIER;
+import static com.android.server.am.proto.ActivityRecordProto.PROC_ID;
+import static com.android.server.am.proto.ActivityRecordProto.STATE;
+import static com.android.server.am.proto.ActivityRecordProto.VISIBLE;
+import static com.android.server.wm.proto.IdentifierProto.HASH_CODE;
+import static com.android.server.wm.proto.IdentifierProto.TITLE;
+import static com.android.server.wm.proto.IdentifierProto.USER_ID;
+
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -153,6 +157,7 @@ import android.util.Log;
import android.util.MergedConfiguration;
import android.util.Slog;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import android.view.AppTransitionAnimationSpec;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IApplicationToken;
@@ -1038,7 +1043,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
}
} else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
activityType = ACTIVITY_TYPE_RECENTS;
- } else if (options != null && options.getLaunchStackId() == ASSISTANT_STACK_ID
+ } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
&& canLaunchAssistActivity(launchedFromPackage)) {
activityType = ACTIVITY_TYPE_ASSISTANT;
}
@@ -1062,6 +1067,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
return getStack() != null ? getStack().mStackId : INVALID_STACK_ID;
}
+ ActivityDisplay getDisplay() {
+ final ActivityStack stack = getStack();
+ return stack != null ? stack.getDisplay() : null;
+ }
+
boolean changeWindowTranslucency(boolean toOpaque) {
if (fullscreen == toOpaque) {
return false;
@@ -1127,10 +1137,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
* @return whether this activity supports split-screen multi-window and can be put in the docked
* stack.
*/
- boolean supportsSplitScreen() {
+ @Override
+ public boolean supportsSplitScreenWindowingMode() {
// An activity can not be docked even if it is considered resizeable because it only
// supports picture-in-picture mode but has a non-resizeable resizeMode
- return service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
+ return super.supportsSplitScreenWindowingMode()
+ && service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
}
/**
@@ -1157,8 +1169,15 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
* can be put a secondary screen.
*/
boolean canBeLaunchedOnDisplay(int displayId) {
+ final TaskRecord task = getTask();
+
+ // The resizeability of an Activity's parent task takes precendence over the ActivityInfo.
+ // This allows for a non resizable activity to be launched into a resizeable task.
+ final boolean resizeable =
+ task != null ? task.isResizeable() : supportsResizeableMultiWindow();
+
return service.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
- supportsResizeableMultiWindow(), launchedFromPid, launchedFromUid, info);
+ resizeable, launchedFromPid, launchedFromUid, info);
}
/**
@@ -1184,7 +1203,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
boolean isKeyguardLocked = service.isKeyguardLocked();
boolean isCurrentAppLocked = service.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
- boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
+ final ActivityDisplay display = getDisplay();
+ boolean hasPinnedStack = display != null && display.hasPinnedStack();
// Don't return early if !isNotLocked, since we want to throw an exception if the activity
// is in an incorrect state
boolean isNotLockedOrOnKeyguard = !isKeyguardLocked && !isCurrentAppLocked;
@@ -1530,8 +1550,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) {
// On devices that support leanback only (Android TV), Recents activity can only be
// visible if the home stack is the focused stack or we are in split-screen mode.
- isVisible = mStackSupervisor.getStack(DOCKED_STACK_ID) != null
- || mStackSupervisor.isFocusedStack(getStack());
+ final ActivityDisplay display = getDisplay();
+ boolean hasSplitScreenStack = display != null && display.hasSplitScreenStack();
+ isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack());
}
return isVisible;
@@ -1934,7 +1955,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
return (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0
|| (mStackSupervisor.isCurrentProfileLocked(userId)
- && service.mUserController.isUserRunningLocked(userId, 0 /* flags */));
+ && service.mUserController.isUserRunning(userId, 0 /* flags */));
}
/**
@@ -2298,7 +2319,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
// be visible based on the stack, task, and lockscreen state and use that here instead. The
// method should be based on the logic in ActivityStack.ensureActivitiesVisibleLocked().
// Skip updating configuration for activity is a stack that shouldn't be visible.
- if (stack.shouldBeVisible(null /* starting */) == STACK_INVISIBLE) {
+ if (!stack.shouldBeVisible(null /* starting */)) {
if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Skipping config check invisible stack: " + this);
return true;
@@ -2770,4 +2791,25 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
stringName = sb.toString();
return toString();
}
+
+ void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(HASH_CODE, System.identityHashCode(this));
+ proto.write(USER_ID, userId);
+ proto.write(TITLE, intent.getComponent().flattenToShortString());
+ proto.end(token);
+ }
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ writeIdentifierToProto(proto, IDENTIFIER);
+ proto.write(STATE, state.toString());
+ proto.write(VISIBLE, visible);
+ proto.write(FRONT_OF_TASK, frontOfTask);
+ if (app != null) {
+ proto.write(PROC_ID, app.pid);
+ }
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index a6a702fb..1940ca2b 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -16,19 +16,19 @@
package com.android.server.am;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.getActivityTypeForStackId;
-import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+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;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
@@ -37,6 +37,8 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
+import static com.android.server.am.ActivityDisplay.POSITION_TOP;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_APP;
@@ -74,10 +76,16 @@ import static com.android.server.am.ActivityStack.ActivityState.PAUSED;
import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
-import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
+import static com.android.server.am.proto.ActivityStackProto.BOUNDS;
+import static com.android.server.am.proto.ActivityStackProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityStackProto.DISPLAY_ID;
+import static com.android.server.am.proto.ActivityStackProto.FULLSCREEN;
+import static com.android.server.am.proto.ActivityStackProto.ID;
+import static com.android.server.am.proto.ActivityStackProto.RESUMED_ACTIVITY;
+import static com.android.server.am.proto.ActivityStackProto.TASKS;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_NONE;
@@ -86,6 +94,7 @@ import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN_BEHIND;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
+
import static java.lang.Integer.MAX_VALUE;
import android.app.Activity;
@@ -101,7 +110,6 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
-import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
@@ -122,6 +130,7 @@ import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
@@ -231,11 +240,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
DESTROYED
}
- // Stack is not considered visible.
- static final int STACK_INVISIBLE = 0;
- // Stack is considered visible
- static final int STACK_VISIBLE = 1;
-
@VisibleForTesting
/* The various modes for the method {@link #removeTask}. */
// Task is being completely removed from all stacks in the system.
@@ -258,7 +262,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
final ActivityManagerService mService;
private final WindowManagerService mWindowManager;
T mWindowContainerController;
- private final RecentTasks mRecentTasks;
/**
* The back history of all previous (and possibly still
@@ -341,9 +344,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
int mCurrentUser;
final int mStackId;
- /** The other stacks, in order, on the attached display. Updated at attach/detach time. */
- // TODO: This list doesn't belong here...
- ArrayList<ActivityStack> mStacks;
/** The attached Display's unique identifier, or -1 if detached */
int mDisplayId;
@@ -452,49 +452,35 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return count;
}
- ActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
- ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
+ ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+ int windowingMode, int activityType, boolean onTop) {
mStackSupervisor = supervisor;
mService = supervisor.mService;
mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
mWindowManager = mService.mWindowManager;
mStackId = stackId;
- mCurrentUser = mService.mUserController.getCurrentUserIdLocked();
- mRecentTasks = recentTasks;
+ mCurrentUser = mService.mUserController.getCurrentUserId();
mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
? new LaunchingTaskPositioner() : null;
mTmpRect2.setEmpty();
- updateOverrideConfiguration();
+ setWindowingMode(windowingMode);
+ setActivityType(activityType);
mWindowContainerController = createStackWindowController(display.mDisplayId, onTop,
mTmpRect2);
- mStackSupervisor.mStacks.put(mStackId, this);
postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
}
T createStackWindowController(int displayId, boolean onTop, Rect outBounds) {
- return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds);
+ return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds,
+ mStackSupervisor.mWindowManager);
}
T getWindowContainerController() {
return mWindowContainerController;
}
- // TODO: Not needed once we are no longer using stack ids as the override config. can be passed
- // in.
- private void updateOverrideConfiguration() {
- final int windowingMode = getWindowingModeForStackId(
- mStackId, mStackSupervisor.getStack(DOCKED_STACK_ID) != null);
- if (windowingMode != WINDOWING_MODE_UNDEFINED) {
- setWindowingMode(windowingMode);
- }
- final int activityType = getActivityTypeForStackId(mStackId);
- if (activityType != ACTIVITY_TYPE_UNDEFINED) {
- setActivityType(activityType);
- }
- }
-
/** Adds the stack to specified display and calls WindowManager to do the same. */
- void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+ void reparent(ActivityDisplay activityDisplay, boolean onTop) {
removeFromDisplay();
mTmpRect2.setEmpty();
postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
@@ -512,10 +498,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
* @param activityDisplay New display to which this stack was attached.
* @param bounds Updated bounds.
*/
- private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
- Rect bounds, boolean onTop) {
+ private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) {
mDisplayId = activityDisplay.mDisplayId;
- mStacks = activityDisplay.mStacks;
mBounds = bounds != null ? new Rect(bounds) : null;
mFullscreen = mBounds == null;
if (mTaskPositioner != null) {
@@ -524,7 +508,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
onParentChanged();
- activityDisplay.attachStack(this, findStackInsertIndex(onTop));
+ activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
if (mStackId == DOCKED_STACK_ID) {
// If we created a docked stack we want to resize it so it resizes all other stacks
// in the system.
@@ -538,42 +522,36 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
* either destroyed completely or re-parented.
*/
private void removeFromDisplay() {
- final ActivityStackSupervisor.ActivityDisplay display = getDisplay();
+ if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ // If we removed a docked stack we want to resize it so it resizes all other stacks
+ // in the system to fullscreen.
+ mStackSupervisor.resizeDockedStackLocked(
+ null, null, null, null, null, PRESERVE_WINDOWS);
+ }
+ final ActivityDisplay display = getDisplay();
if (display != null) {
- display.detachStack(this);
+ display.removeChild(this);
}
mDisplayId = INVALID_DISPLAY;
- mStacks = null;
if (mTaskPositioner != null) {
mTaskPositioner.reset();
}
- if (mStackId == DOCKED_STACK_ID) {
- // If we removed a docked stack we want to resize it so it resizes all other stacks
- // in the system to fullscreen.
- mStackSupervisor.resizeDockedStackLocked(
- null, null, null, null, null, PRESERVE_WINDOWS);
- }
}
/** Removes the stack completely. Also calls WindowManager to do the same on its side. */
void remove() {
removeFromDisplay();
- mStackSupervisor.mStacks.remove(mStackId);
mWindowContainerController.removeContainer();
mWindowContainerController = null;
onParentChanged();
}
- ActivityStackSupervisor.ActivityDisplay getDisplay() {
+ ActivityDisplay getDisplay() {
return mStackSupervisor.getActivityDisplay(mDisplayId);
}
- void getDisplaySize(Point out) {
- getDisplay().mDisplay.getSize(out);
- }
-
/**
- * @see ActivityStack.getStackDockedModeBounds(Rect, Rect, Rect, boolean)
+ * @see #getStackDockedModeBounds(Rect, Rect, Rect, boolean)
*/
void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds,
Rect outTempTaskBounds, boolean ignoreVisibility) {
@@ -837,7 +815,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
final boolean isHomeOrRecentsStack() {
- return StackId.isHomeOrRecentsStack(mStackId);
+ return isActivityTypeHome() || isActivityTypeRecents();
}
final boolean isDockedStack() {
@@ -849,7 +827,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
final boolean isOnHomeDisplay() {
- return isAttached() && mDisplayId == DEFAULT_DISPLAY;
+ return mDisplayId == DEFAULT_DISPLAY;
}
void moveToFront(String reason) {
@@ -865,8 +843,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return;
}
- mStacks.remove(this);
- mStacks.add(findStackInsertIndex(ON_TOP), this);
+ getDisplay().positionChildAtTop(this);
mStackSupervisor.setFocusStackUnchecked(reason, this);
if (task != null) {
insertTaskAtTop(task, null);
@@ -880,45 +857,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
}
- /**
- * @param task If non-null, the task will be moved to the back of the stack.
- * */
- private void moveToBack(TaskRecord task) {
- if (!isAttached()) {
- return;
- }
-
- mStacks.remove(this);
- mStacks.add(0, this);
-
- if (task != null) {
- mTaskHistory.remove(task);
- mTaskHistory.add(0, task);
- updateTaskMovement(task, false);
- mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
- }
- }
-
- /**
- * @return the index to insert a new stack into, taking the always-on-top stacks into account.
- */
- private int findStackInsertIndex(boolean onTop) {
- if (onTop) {
- int addIndex = mStacks.size();
- if (addIndex > 0) {
- final ActivityStack topStack = mStacks.get(addIndex - 1);
- if (topStack.getWindowConfiguration().isAlwaysOnTop()
- && topStack != this) {
- // If the top stack is always on top, we move this stack just below it.
- addIndex--;
- }
- }
- return addIndex;
- } else {
- return 0;
- }
- }
-
boolean isFocusable() {
if (getWindowConfiguration().canReceiveKeys()) {
return true;
@@ -930,7 +868,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
final boolean isAttached() {
- return mStacks != null;
+ return getParent() != null;
}
/**
@@ -1105,14 +1043,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
"Launch completed; removing icicle of " + r.icicle);
}
- void addRecentActivityLocked(ActivityRecord r) {
- if (r != null) {
- final TaskRecord task = r.getTask();
- mRecentTasks.addLocked(task);
- task.touchActiveTime();
- }
- }
-
private void startLaunchTraces(String packageName) {
if (mFullyDrawnStartTime != 0) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
@@ -1527,7 +1457,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// focus). Also if there is an active pinned stack - we always want to notify it about
// task stack changes, because its positioning may depend on it.
if (mStackSupervisor.mAppVisibilitiesChangedSinceLastPause
- || mService.mStackSupervisor.getStack(PINNED_STACK_ID) != null) {
+ || getDisplay().hasPinnedStack()) {
mService.mTaskChangeNotificationController.notifyTaskStackChanged();
mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
}
@@ -1559,54 +1489,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
}
- // Find the first visible activity above the passed activity and if it is translucent return it
- // otherwise return null;
- ActivityRecord findNextTranslucentActivity(ActivityRecord r) {
- TaskRecord task = r.getTask();
- if (task == null) {
- return null;
- }
-
- final ActivityStack stack = task.getStack();
- if (stack == null) {
- return null;
- }
-
- int stackNdx = mStacks.indexOf(stack);
-
- ArrayList<TaskRecord> tasks = stack.mTaskHistory;
- int taskNdx = tasks.indexOf(task);
-
- ArrayList<ActivityRecord> activities = task.mActivities;
- int activityNdx = activities.indexOf(r) + 1;
-
- final int numStacks = mStacks.size();
- while (stackNdx < numStacks) {
- final ActivityStack historyStack = mStacks.get(stackNdx);
- tasks = historyStack.mTaskHistory;
- final int numTasks = tasks.size();
- while (taskNdx < numTasks) {
- final TaskRecord currentTask = tasks.get(taskNdx);
- activities = currentTask.mActivities;
- final int numActivities = activities.size();
- while (activityNdx < numActivities) {
- final ActivityRecord activity = activities.get(activityNdx);
- if (!activity.finishing) {
- return historyStack.mFullscreen
- && currentTask.mFullscreen && activity.fullscreen ? null : activity;
- }
- ++activityNdx;
- }
- activityNdx = 0;
- ++taskNdx;
- }
- taskNdx = 0;
- ++stackNdx;
- }
-
- return null;
- }
-
/** Returns true if the stack contains a fullscreen task. */
private boolean hasFullscreenTask() {
for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
@@ -1650,9 +1532,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
return false;
}
+ final ActivityStack stackBehind = mStackSupervisor.getStack(stackBehindId);
+ final boolean stackBehindHomeOrRecent = stackBehind != null
+ && stackBehind.isHomeOrRecentsStack();
if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()
- && !StackId.isHomeOrRecentsStack(stackBehindId)
- && !isActivityTypeAssistant()) {
+ && !stackBehindHomeOrRecent && !isActivityTypeAssistant()) {
// Stack isn't translucent if it's top activity should have the home stack
// behind it and the stack currently behind it isn't the home or recents stack
// or the assistant stack.
@@ -1670,119 +1554,146 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
/**
- * Returns what the stack visibility should be: {@link #STACK_INVISIBLE} or
- * {@link #STACK_VISIBLE}.
+ * Returns true if the stack should be visible.
*
* @param starting The currently starting activity or null if there is none.
*/
- int shouldBeVisible(ActivityRecord starting) {
+ boolean shouldBeVisible(ActivityRecord starting) {
if (!isAttached() || mForceHidden) {
- return STACK_INVISIBLE;
+ return false;
}
if (mStackSupervisor.isFrontStackOnDisplay(this) || mStackSupervisor.isFocusedStack(this)) {
- return STACK_VISIBLE;
+ return true;
}
- final int stackIndex = mStacks.indexOf(this);
+ final ActivityDisplay display = getDisplay();
+ final ArrayList<ActivityStack> displayStacks = display.mStacks;
+ final int stackIndex = displayStacks.indexOf(this);
- if (stackIndex == mStacks.size() - 1) {
+ if (stackIndex == displayStacks.size() - 1) {
Slog.wtf(TAG,
"Stack=" + this + " isn't front stack but is at the top of the stack list");
- return STACK_INVISIBLE;
+ return false;
}
// Check position and visibility of this stack relative to the front stack on its display.
final ActivityStack topStack = getTopStackOnDisplay();
final int topStackId = topStack.mStackId;
+ final int windowingMode = getWindowingMode();
+ final int activityType = getActivityType();
- if (mStackId == DOCKED_STACK_ID) {
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
// If the assistant stack is focused and translucent, then the docked stack is always
// visible
if (topStack.isActivityTypeAssistant()) {
- return (topStack.isStackTranslucent(starting, DOCKED_STACK_ID)) ? STACK_VISIBLE
- : STACK_INVISIBLE;
+ return topStack.isStackTranslucent(starting, DOCKED_STACK_ID);
}
- return STACK_VISIBLE;
+ return true;
}
// Set home stack to invisible when it is below but not immediately below the docked stack
// A case would be if recents stack exists but has no tasks and is below the docked stack
// and home stack is below recents
- if (mStackId == HOME_STACK_ID) {
- int dockedStackIndex = mStacks.indexOf(mStackSupervisor.getStack(DOCKED_STACK_ID));
+ if (activityType == ACTIVITY_TYPE_HOME) {
+ final ActivityStack splitScreenStack = display.getStack(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+ int dockedStackIndex = displayStacks.indexOf(splitScreenStack);
if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
- return STACK_INVISIBLE;
+ return false;
}
}
// Find the first stack behind front stack that actually got something visible.
- int stackBehindTopIndex = mStacks.indexOf(topStack) - 1;
+ int stackBehindTopIndex = displayStacks.indexOf(topStack) - 1;
while (stackBehindTopIndex >= 0 &&
- mStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
+ displayStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
stackBehindTopIndex--;
}
- final int stackBehindTopId = (stackBehindTopIndex >= 0)
- ? mStacks.get(stackBehindTopIndex).mStackId : INVALID_STACK_ID;
+ final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
+ ? displayStacks.get(stackBehindTopIndex) : null;
+ int stackBehindTopId = INVALID_STACK_ID;
+ int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED;
+ int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED;
+ if (stackBehindTop != null) {
+ stackBehindTopId = stackBehindTop.mStackId;
+ stackBehindTopWindowingMode = stackBehindTop.getWindowingMode();
+ stackBehindTopActivityType = stackBehindTop.getActivityType();
+ }
+
final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop();
- if (topStackId == DOCKED_STACK_ID || alwaysOnTop) {
+ if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
if (stackIndex == stackBehindTopIndex) {
// Stacks directly behind the docked or pinned stack are always visible.
- return STACK_VISIBLE;
+ return true;
} else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
// Otherwise, this stack can also be visible if it is directly behind a docked stack
// or translucent assistant stack behind an always-on-top top-most stack
- if (stackBehindTopId == DOCKED_STACK_ID) {
- return STACK_VISIBLE;
- } else if (stackBehindTopId == ASSISTANT_STACK_ID) {
- return mStacks.get(stackBehindTopIndex).isStackTranslucent(starting, mStackId)
- ? STACK_VISIBLE : STACK_INVISIBLE;
+ if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ return true;
+ } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
+ return displayStacks.get(stackBehindTopIndex).isStackTranslucent(
+ starting, mStackId);
}
}
}
- if (StackId.isBackdropToTranslucentActivity(topStackId)
+ if (topStack.isBackdropToTranslucentActivity()
&& topStack.isStackTranslucent(starting, stackBehindTopId)) {
// Stacks behind the fullscreen or assistant stack with a translucent activity are
// always visible so they can act as a backdrop to the translucent activity.
// For example, dialog activities
if (stackIndex == stackBehindTopIndex) {
- return STACK_VISIBLE;
+ return true;
}
if (stackBehindTopIndex >= 0) {
- if ((stackBehindTopId == DOCKED_STACK_ID
- || stackBehindTopId == PINNED_STACK_ID)
+ if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED)
&& stackIndex == (stackBehindTopIndex - 1)) {
// The stack behind the docked or pinned stack is also visible so we can have a
// complete backdrop to the translucent activity when the docked stack is up.
- return STACK_VISIBLE;
+ return true;
}
}
}
- if (StackId.isStaticStack(mStackId)) {
+ if (StackId.isStaticStack(mStackId)
+ || isHomeOrRecentsStack() || isActivityTypeAssistant()) {
// Visibility of any static stack should have been determined by the conditions above.
- return STACK_INVISIBLE;
+ return false;
}
- for (int i = stackIndex + 1; i < mStacks.size(); i++) {
- final ActivityStack stack = mStacks.get(i);
+ for (int i = stackIndex + 1; i < displayStacks.size(); i++) {
+ final ActivityStack stack = displayStacks.get(i);
if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
continue;
}
- if (!StackId.isDynamicStacksVisibleBehindAllowed(stack.mStackId)) {
+ if (!stack.isDynamicStacksVisibleBehindAllowed()) {
// These stacks can't have any dynamic stacks visible behind them.
- return STACK_INVISIBLE;
+ return false;
}
if (!stack.isStackTranslucent(starting, INVALID_STACK_ID)) {
- return STACK_INVISIBLE;
+ return false;
}
}
- return STACK_VISIBLE;
+ return true;
+ }
+
+ private boolean isBackdropToTranslucentActivity() {
+ if (isActivityTypeAssistant()) {
+ return true;
+ }
+ final int windowingMode = getWindowingMode();
+ return windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+ }
+
+ private boolean isDynamicStacksVisibleBehindAllowed() {
+ return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED;
}
final int rankTaskLayers(int baseLayer) {
@@ -1819,12 +1730,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// If the top activity is not fullscreen, then we need to
// make sure any activities under it are now visible.
boolean aboveTop = top != null;
- final int stackVisibility = shouldBeVisible(starting);
- final boolean stackInvisible = stackVisibility != STACK_VISIBLE;
- boolean behindFullscreenActivity = stackInvisible;
+ final boolean stackShouldBeVisible = shouldBeVisible(starting);
+ boolean behindFullscreenActivity = !stackShouldBeVisible;
boolean resumeNextActivity = mStackSupervisor.isFocusedStack(this)
&& (isInStackLocked(starting) == null);
- boolean behindTranslucentActivity = false;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1848,11 +1757,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
final boolean reallyVisible = checkKeyguardVisibility(r,
visibleIgnoringKeyguard, isTop);
if (visibleIgnoringKeyguard) {
- behindFullscreenActivity = updateBehindFullscreen(stackInvisible,
+ behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
behindFullscreenActivity, task, r);
- if (behindFullscreenActivity && !r.fullscreen) {
- behindTranslucentActivity = true;
- }
}
if (reallyVisible) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
@@ -1888,10 +1794,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
configChanges |= r.configChangeFlags;
} else {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r
- + " finishing=" + r.finishing + " state=" + r.state + " stackInvisible="
- + stackInvisible + " behindFullscreenActivity="
- + behindFullscreenActivity + " mLaunchTaskBehind="
- + r.mLaunchTaskBehind);
+ + " finishing=" + r.finishing + " state=" + r.state
+ + " stackShouldBeVisible=" + stackShouldBeVisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity
+ + " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
makeInvisible(r);
}
}
@@ -1899,10 +1805,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// The visibility of tasks and the activities they contain in freeform stack are
// determined individually unlike other stacks where the visibility or fullscreen
// status of an activity in a previous task affects other.
- behindFullscreenActivity = stackVisibility == STACK_INVISIBLE;
- } else if (mStackId == HOME_STACK_ID) {
+ behindFullscreenActivity = !stackShouldBeVisible;
+ } else if (isActivityTypeHome()) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + task
- + " stackInvisible=" + stackInvisible
+ + " stackShouldBeVisible=" + stackShouldBeVisible
+ " behindFullscreenActivity=" + behindFullscreenActivity);
// No other task in the home stack should be visible behind the home activity.
// Home activities is usually a translucent activity with the wallpaper behind
@@ -1962,7 +1868,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible,
boolean isTop) {
final boolean isInPinnedStack = r.getStack().getStackId() == PINNED_STACK_ID;
- final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing();
+ final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(
+ mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked();
final boolean showWhenLocked = r.canShowWhenLocked() && !isInPinnedStack;
final boolean dismissKeyguard = r.hasDismissKeyguardWindows();
@@ -2002,7 +1909,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
* {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
*/
private boolean canShowWithInsecureKeyguard() {
- final ActivityStackSupervisor.ActivityDisplay activityDisplay = getDisplay();
+ final ActivityDisplay activityDisplay = getDisplay();
if (activityDisplay == null) {
throw new IllegalStateException("Stack is not attached to any display, stackId="
+ mStackId);
@@ -2182,7 +2089,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// activities as we need to display their starting window until they are done initializing.
boolean behindFullscreenActivity = false;
- if (shouldBeVisible(null) == STACK_INVISIBLE) {
+ if (!shouldBeVisible(null)) {
// The stack is not visible, so no activity in it should be displaying a starting
// window. Mark all activities below top and behind fullscreen.
aboveTop = false;
@@ -2258,9 +2165,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mResumedActivity = r;
r.state = ActivityState.RESUMED;
mService.setResumedActivityUncheckLocked(r, reason);
- final TaskRecord task = r.getTask();
- task.touchActiveTime();
- mRecentTasks.addLocked(task);
+ mStackSupervisor.addRecentActivity(r);
}
private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
@@ -2543,128 +2448,139 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
|| (lastStack.mLastPausedActivity != null
&& !lastStack.mLastPausedActivity.fullscreen));
- // This activity is now becoming visible.
- if (!next.visible || next.stopped || lastActivityTranslucent) {
- next.setVisibility(true);
- }
+ // The contained logic must be synchronized, since we are both changing the visibility
+ // and updating the {@link Configuration}. {@link ActivityRecord#setVisibility} will
+ // ultimately cause the client code to schedule a layout. Since layouts retrieve the
+ // current {@link Configuration}, we must ensure that the below code updates it before
+ // the layout can occur.
+ synchronized(mWindowManager.getWindowManagerLock()) {
+ // This activity is now becoming visible.
+ if (!next.visible || next.stopped || lastActivityTranslucent) {
+ next.setVisibility(true);
+ }
- // schedule launch ticks to collect information about slow apps.
- next.startLaunchTickingLocked();
+ // schedule launch ticks to collect information about slow apps.
+ next.startLaunchTickingLocked();
- ActivityRecord lastResumedActivity =
- lastStack == null ? null :lastStack.mResumedActivity;
- ActivityState lastState = next.state;
+ ActivityRecord lastResumedActivity =
+ lastStack == null ? null :lastStack.mResumedActivity;
+ ActivityState lastState = next.state;
- mService.updateCpuStats();
+ mService.updateCpuStats();
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next + " (in existing)");
+ if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next
+ + " (in existing)");
- setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
+ setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
- mService.updateLruProcessLocked(next.app, true, null);
- updateLRUListLocked(next);
- mService.updateOomAdjLocked();
+ mService.updateLruProcessLocked(next.app, true, null);
+ updateLRUListLocked(next);
+ mService.updateOomAdjLocked();
- // Have the window manager re-evaluate the orientation of
- // the screen based on the new activity order.
- boolean notUpdated = true;
- if (mStackSupervisor.isFocusedStack(this)) {
-
- // We have special rotation behavior when Keyguard is locked. Make sure all activity
- // visibilities are set correctly as well as the transition is updated if needed to
- // get the correct rotation behavior.
- // TODO: Remove this once visibilities are set correctly immediately when starting
- // an activity.
- if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
- mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
- 0 /* configChanges */, false /* preserveWindows */);
- }
- final Configuration config = mWindowManager.updateOrientationFromAppTokens(
- mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
- next.mayFreezeScreenLocked(next.app) ? next.appToken : null, mDisplayId);
- if (config != null) {
- next.frozenBeforeDestroy = true;
- }
- notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
- false /* deferResume */, mDisplayId);
- }
-
- if (notUpdated) {
- // The configuration update wasn't able to keep the existing
- // instance of the activity, and instead started a new one.
- // We should be all done, but let's just make sure our activity
- // is still at the top and schedule another run if something
- // weird happened.
- ActivityRecord nextNext = topRunningActivityLocked();
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
- "Activity config changed during resume: " + next
- + ", new next: " + nextNext);
- if (nextNext != next) {
- // Do over!
- mStackSupervisor.scheduleResumeTopActivities();
- }
- if (!next.visible || next.stopped) {
- next.setVisibility(true);
- }
- next.completeResumeLocked();
- if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
- return true;
- }
+ // Have the window manager re-evaluate the orientation of
+ // the screen based on the new activity order.
+ boolean notUpdated = true;
- try {
- // Deliver all pending results.
- ArrayList<ResultInfo> a = next.results;
- if (a != null) {
- final int N = a.size();
- if (!next.finishing && N > 0) {
- if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
- "Delivering results to " + next + ": " + a);
- next.app.thread.scheduleSendResult(next.appToken, a);
+ if (mStackSupervisor.isFocusedStack(this)) {
+
+ // We have special rotation behavior when Keyguard is locked. Make sure all
+ // activity visibilities are set correctly as well as the transition is updated
+ // if needed to get the correct rotation behavior.
+ // TODO: Remove this once visibilities are set correctly immediately when
+ // starting an activity.
+ if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
+ mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
+ 0 /* configChanges */, false /* preserveWindows */);
}
+ final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+ mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
+ next.mayFreezeScreenLocked(next.app) ? next.appToken : null,
+ mDisplayId);
+ if (config != null) {
+ next.frozenBeforeDestroy = true;
+ }
+ notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
+ false /* deferResume */, mDisplayId);
}
- if (next.newIntents != null) {
- next.app.thread.scheduleNewIntent(
- next.newIntents, next.appToken, false /* andPause */);
+ if (notUpdated) {
+ // The configuration update wasn't able to keep the existing
+ // instance of the activity, and instead started a new one.
+ // We should be all done, but let's just make sure our activity
+ // is still at the top and schedule another run if something
+ // weird happened.
+ ActivityRecord nextNext = topRunningActivityLocked();
+ if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
+ "Activity config changed during resume: " + next
+ + ", new next: " + nextNext);
+ if (nextNext != next) {
+ // Do over!
+ mStackSupervisor.scheduleResumeTopActivities();
+ }
+ if (!next.visible || next.stopped) {
+ next.setVisibility(true);
+ }
+ next.completeResumeLocked();
+ if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+ return true;
}
- // Well the app will no longer be stopped.
- // Clear app token stopped state in window manager if needed.
- next.notifyAppResumed(next.stopped);
-
- EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
- System.identityHashCode(next), next.getTask().taskId,
- next.shortComponentName);
+ try {
+ // Deliver all pending results.
+ ArrayList<ResultInfo> a = next.results;
+ if (a != null) {
+ final int N = a.size();
+ if (!next.finishing && N > 0) {
+ if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
+ "Delivering results to " + next + ": " + a);
+ next.app.thread.scheduleSendResult(next.appToken, a);
+ }
+ }
- next.sleeping = false;
- mService.showUnsupportedZoomDialogIfNeededLocked(next);
- mService.showAskCompatModeDialogLocked(next);
- next.app.pendingUiClean = true;
- next.app.forceProcessStateUpTo(mService.mTopProcessState);
- next.clearOptionsLocked();
- next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
- mService.isNextTransitionForward(), resumeAnimOptions);
+ if (next.newIntents != null) {
+ next.app.thread.scheduleNewIntent(
+ next.newIntents, next.appToken, false /* andPause */);
+ }
- if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next);
- } catch (Exception e) {
- // Whoops, need to restart this activity!
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
- + lastState + ": " + next);
- next.state = lastState;
- if (lastStack != null) {
- lastStack.mResumedActivity = lastResumedActivity;
- }
- Slog.i(TAG, "Restarting because process died: " + next);
- if (!next.hasBeenLaunched) {
- next.hasBeenLaunched = true;
- } else if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
- mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
- next.showStartingWindow(null /* prev */, false /* newTask */,
- false /* taskSwitch */);
+ // Well the app will no longer be stopped.
+ // Clear app token stopped state in window manager if needed.
+ next.notifyAppResumed(next.stopped);
+
+ EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
+ System.identityHashCode(next), next.getTask().taskId,
+ next.shortComponentName);
+
+ next.sleeping = false;
+ mService.showUnsupportedZoomDialogIfNeededLocked(next);
+ mService.showAskCompatModeDialogLocked(next);
+ next.app.pendingUiClean = true;
+ next.app.forceProcessStateUpTo(mService.mTopProcessState);
+ next.clearOptionsLocked();
+ next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
+ mService.isNextTransitionForward(), resumeAnimOptions);
+
+ if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
+ + next);
+ } catch (Exception e) {
+ // Whoops, need to restart this activity!
+ if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
+ + lastState + ": " + next);
+ next.state = lastState;
+ if (lastStack != null) {
+ lastStack.mResumedActivity = lastResumedActivity;
+ }
+ Slog.i(TAG, "Restarting because process died: " + next);
+ if (!next.hasBeenLaunched) {
+ next.hasBeenLaunched = true;
+ } else if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
+ mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
+ next.showStartingWindow(null /* prev */, false /* newTask */,
+ false /* taskSwitch */);
+ }
+ mStackSupervisor.startSpecificActivityLocked(next, true, false);
+ if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+ return true;
}
- mStackSupervisor.startSpecificActivityLocked(next, true, false);
- if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
- return true;
}
// From this point on, if something goes wrong there is no way
@@ -2989,9 +2905,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// Ensure that we do not trigger entering PiP an activity on the pinned stack
return false;
}
- final int targetStackId = toFrontTask != null ? toFrontTask.getStackId()
- : toFrontActivity.getStackId();
- if (targetStackId == ASSISTANT_STACK_ID) {
+ final ActivityStack targetStack = toFrontTask != null
+ ? toFrontTask.getStack() : toFrontActivity.getStack();
+ if (targetStack != null && targetStack.isActivityTypeAssistant()) {
// Ensure the task/activity being brought forward is not the assistant
return false;
}
@@ -4548,7 +4464,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
// Don't refocus if invisible to current user
final ActivityRecord top = tr.getTopActivity();
if (top == null || !top.okToShowLocked()) {
- addRecentActivityLocked(top);
+ mStackSupervisor.addRecentActivity(top);
ActivityOptions.abort(options);
return;
}
@@ -4601,7 +4517,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
Slog.i(TAG, "moveTaskToBack: " + tr);
// If the task is locked, then show the lock task toast
- if (!mService.mLockTaskController.checkLockedTask(tr)) {
+ if (mService.mLockTaskController.checkLockedTask(tr)) {
return false;
}
@@ -4982,8 +4898,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
ci.numActivities = numActivities;
ci.numRunning = numRunning;
- ci.supportsSplitScreenMultiWindow = task.supportsSplitScreen();
+ ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
ci.resizeMode = task.mResizeMode;
+ ci.configuration.setTo(task.getConfiguration());
list.add(ci);
}
}
@@ -5152,8 +5069,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
if (task.autoRemoveFromRecents() || isVoiceSession) {
// Task creator asked to remove this when done, or this task was a voice
// interaction, so it should not remain on the recent tasks list.
- mRecentTasks.remove(task);
- task.removedFromRecents();
+ mStackSupervisor.removeTaskFromRecents(task);
}
task.removeWindowContainer();
@@ -5170,11 +5086,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mStackSupervisor.moveHomeStackToFront(myReason);
}
}
- if (mStacks != null) {
- mStacks.remove(this);
- mStacks.add(0, this);
+ if (isAttached()) {
+ getDisplay().positionChildAtBottom(this);
}
- if (!isHomeOrRecentsStack()) {
+ if (!isActivityTypeHome()) {
remove();
}
}
@@ -5194,8 +5109,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
voiceInteractor);
// add the task to stack first, mTaskPositioner might need the stack association
addTask(task, toTop, "createTaskRecord");
- final boolean isLockscreenShown =
- mService.mStackSupervisor.mKeyguardController.isKeyguardShowing();
+ final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
+ .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable()
&& !isLockscreenShown) {
task.updateOverrideConfiguration(mBounds);
@@ -5349,11 +5264,30 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
boolean shouldSleepActivities() {
- final ActivityStackSupervisor.ActivityDisplay display = getDisplay();
+ final ActivityDisplay display = getDisplay();
return display != null ? display.isSleeping() : mService.isSleepingLocked();
}
boolean shouldSleepOrShutDownActivities() {
return shouldSleepActivities() || mService.isShuttingDownLocked();
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ proto.write(ID, mStackId);
+ for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ task.writeToProto(proto, TASKS);
+ }
+ if (mResumedActivity != null) {
+ mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+ }
+ proto.write(DISPLAY_ID, mDisplayId);
+ if (mBounds != null) {
+ mBounds.writeToProto(proto, BOUNDS);
+ }
+ proto.write(FULLSCREEN, mFullscreen);
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index fe28956d..da2827a6 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -23,30 +23,29 @@ import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
+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.ACTIVITY_TYPE_UNDEFINED;
+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;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.FLAG_PRIVATE;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
import static android.view.Display.TYPE_VIRTUAL;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -81,19 +80,24 @@ import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import static com.android.server.am.ActivityStack.STACK_VISIBLE;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.DISPLAYS;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.FOCUSED_STACK_ID;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.KEYGUARD_CONTROLLER;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.RESUMED_ACTIVITY;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER;
import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
import static java.lang.Integer.MAX_VALUE;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
@@ -106,6 +110,7 @@ import android.app.AppOpsManager;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
import android.app.WaitResult;
+import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -147,6 +152,7 @@ import android.util.Slog;
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.annotations.VisibleForTesting;
@@ -166,7 +172,6 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -375,15 +380,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
* They are used by components that may hide and block interaction with underlying
* activities.
*/
- final ArrayList<SleepToken> mSleepTokens = new ArrayList<SleepToken>();
+ final ArrayList<SleepToken> mSleepTokens = new ArrayList<>();
/** Stack id of the front stack when user switched, indexed by userId. */
SparseIntArray mUserStackInFront = new SparseIntArray(2);
- // TODO: Add listener for removal of references.
- /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
- SparseArray<ActivityStack> mStacks = new SparseArray<>();
-
// TODO: There should be an ActivityDisplayController coordinating am/wm interaction.
/** Mapping from displayId to display current state */
private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -602,16 +603,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
Display[] displays = mDisplayManager.getDisplays();
for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) {
final int displayId = displays[displayNdx].getDisplayId();
- ActivityDisplay activityDisplay = new ActivityDisplay(displayId);
- if (activityDisplay.mDisplay == null) {
- throw new IllegalStateException("Default Display does not exist");
- }
+ ActivityDisplay activityDisplay = new ActivityDisplay(this, displayId);
mActivityDisplays.put(displayId, activityDisplay);
calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
}
- mHomeStack = mFocusedStack = mLastFocusedStack =
- getStack(HOME_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+ mHomeStack = mFocusedStack = mLastFocusedStack = getDefaultDisplay().getOrCreateStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
}
@@ -667,7 +665,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
void moveRecentsStackToFront(String reason) {
- final ActivityStack recentsStack = getStack(RECENTS_STACK_ID);
+ final ActivityStack recentsStack = getDefaultDisplay().getStack(
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
if (recentsStack != null) {
recentsStack.moveToFront(reason);
}
@@ -708,24 +707,26 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
TaskRecord anyTaskForIdLocked(int id) {
- return anyTaskForIdLocked(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
- INVALID_STACK_ID);
+ return anyTaskForIdLocked(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE);
+ }
+
+ TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode) {
+ return anyTaskForIdLocked(id, matchMode, null);
}
/**
* Returns a {@link TaskRecord} for the input id if available. {@code null} otherwise.
* @param id Id of the task we would like returned.
* @param matchMode The mode to match the given task id in.
- * @param stackId The stack to restore the task to (default launch stack will be used if
- * stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}). Only
- * valid if the matchMode is
- * {@link #MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE}.
+ * @param aOptions The activity options to use for restoration. Can be null.
*/
- TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode, int stackId) {
+ TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode,
+ @Nullable ActivityOptions aOptions) {
// If there is a stack id set, ensure that we are attempting to actually restore a task
- if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE &&
- stackId != INVALID_STACK_ID) {
- throw new IllegalArgumentException("Should not specify stackId for non-restore lookup");
+ // TODO: Don't really know if this is needed...
+ if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE && aOptions != null) {
+ throw new IllegalArgumentException("Should not specify activity options for non-restore"
+ + " lookup");
}
int numDisplays = mActivityDisplays.size();
@@ -763,7 +764,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
// Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
- if (!restoreRecentTaskLocked(task, stackId)) {
+ if (!restoreRecentTaskLocked(task, aOptions)) {
if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
"Couldn't restore task id=" + id + " found in recents");
return null;
@@ -858,8 +859,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.
int candidateTaskId = nextTaskIdForUser(currentTaskId, userId);
while (mRecentTasks.taskIdTakenForUserLocked(candidateTaskId, userId)
- || anyTaskForIdLocked(candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
- INVALID_STACK_ID) != null) {
+ || anyTaskForIdLocked(
+ candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
candidateTaskId = nextTaskIdForUser(candidateTaskId, userId);
if (candidateTaskId == currentTaskId) {
// Something wrong!
@@ -1152,8 +1153,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
void getTasksLocked(int maxNum, List<RunningTaskInfo> list, int callingUid, boolean allowed) {
// Gather all of the running tasks for each stack into runningTaskLists.
- ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists =
- new ArrayList<ArrayList<RunningTaskInfo>>();
+ ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists = new ArrayList<>();
final int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -1235,7 +1235,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
synchronized (mService) {
return mService.getPackageManagerInternalLocked().resolveIntent(intent, resolvedType,
PackageManager.MATCH_INSTANT | PackageManager.MATCH_DEFAULT_ONLY | flags
- | ActivityManagerService.STOCK_PM_FLAGS, userId);
+ | ActivityManagerService.STOCK_PM_FLAGS, userId, true);
}
}
@@ -1639,6 +1639,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return true;
}
+ // Check if caller is already present on display
+ final boolean uidPresentOnDisplay = activityDisplay.isUidPresent(callingUid);
+
final int displayOwnerUid = activityDisplay.mDisplay.getOwnerUid();
if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID
&& displayOwnerUid != aInfo.applicationInfo.uid) {
@@ -1651,7 +1654,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
// Check if the caller is allowed to embed activities from other apps.
if (mService.checkPermission(ACTIVITY_EMBEDDING, callingPid, callingUid)
- == PERMISSION_DENIED) {
+ == PERMISSION_DENIED && !uidPresentOnDisplay) {
if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ " disallow activity embedding without permission.");
return false;
@@ -1672,8 +1675,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return true;
}
- // Check if caller is present on display
- if (activityDisplay.isUidPresent(callingUid)) {
+ if (uidPresentOnDisplay) {
if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ " allow launch for caller present on the display");
return true;
@@ -1953,7 +1955,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
*/
void updateUserStackLocked(int userId, ActivityStack stack) {
if (userId != mCurrentUser) {
- mUserStackInFront.put(userId, stack != null ? stack.getStackId() : HOME_STACK_ID);
+ mUserStackInFront.put(userId, stack != null ? stack.getStackId() : mHomeStack.mStackId);
}
}
@@ -2083,38 +2085,35 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// we'll just indicate that this task returns to the home task.
task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
}
- ActivityStack currentStack = task.getStack();
+ final ActivityStack currentStack = task.getStack();
if (currentStack == null) {
Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
+ task + " to front. Stack is null");
return;
}
- if (task.isResizeable() && options != null) {
- int stackId = options.getLaunchStackId();
- if (canUseActivityOptionsLaunchBounds(options, stackId)) {
- final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
- task.updateOverrideConfiguration(bounds);
- if (stackId == INVALID_STACK_ID) {
- stackId = task.getLaunchStackId();
- }
- if (stackId != currentStack.mStackId) {
- task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
- DEFER_RESUME, "findTaskToMoveToFrontLocked");
- stackId = currentStack.mStackId;
- // moveTaskToStackUncheckedLocked() should already placed the task on top,
- // still need moveTaskToFrontLocked() below for any transition settings.
- }
- if (StackId.resizeStackWithLaunchBounds(stackId)) {
- resizeStackLocked(stackId, bounds,
- null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, !DEFER_RESUME);
- } else {
- // WM resizeTask must be done after the task is moved to the correct stack,
- // because Task's setBounds() also updates dim layer's bounds, but that has
- // dependency on the stack.
- task.resizeWindowContainer();
- }
+ if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
+ final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+ task.updateOverrideConfiguration(bounds);
+
+ ActivityStack stack = getLaunchStack(null, options, task, ON_TOP);
+
+ if (stack != currentStack) {
+ task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME,
+ "findTaskToMoveToFrontLocked");
+ stack = currentStack;
+ // moveTaskToStackUncheckedLocked() should already placed the task on top,
+ // still need moveTaskToFrontLocked() below for any transition settings.
+ }
+ if (StackId.resizeStackWithLaunchBounds(stack.mStackId)) {
+ resizeStackLocked(stack.mStackId, bounds, null /* tempTaskBounds */,
+ null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
+ true /* allowResizeInDockedMode */, !DEFER_RESUME);
+ } else {
+ // WM resizeTask must be done after the task is moved to the correct stack,
+ // because Task's setBounds() also updates dim layer's bounds, but that has
+ // dependency on the stack.
+ task.resizeWindowContainer();
}
}
@@ -2125,39 +2124,246 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
if (DEBUG_STACK) Slog.d(TAG_STACK,
"findTaskToMoveToFront: moved to front of stack=" + currentStack);
- handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY,
+ handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY,
currentStack.mStackId, forceNonResizeable);
}
- boolean canUseActivityOptionsLaunchBounds(ActivityOptions options, int launchStackId) {
+ boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
// We use the launch bounds in the activity options is the device supports freeform
// window management or is launching into the pinned stack.
- if (options.getLaunchBounds() == null) {
+ if (options == null || options.getLaunchBounds() == null) {
return false;
}
- return (mService.mSupportsPictureInPicture && launchStackId == PINNED_STACK_ID)
+ return (mService.mSupportsPictureInPicture
+ && options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED)
|| mService.mSupportsFreeformWindowManagement;
}
protected <T extends ActivityStack> T getStack(int stackId) {
- return getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+ for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+ final T stack = mActivityDisplays.valueAt(i).getStack(stackId);
+ if (stack != null) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ /** @see ActivityDisplay#getStack(int, int) */
+ private <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+ for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+ final T stack = mActivityDisplays.valueAt(i).getStack(windowingMode, activityType);
+ if (stack != null) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+ * @param windowingMode The windowing mode we are checking support for.
+ * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+ * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+ * @param supportsFreeform If we should consider support for freeform multi-window.
+ * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+ * @param activityType The activity type under consideration.
+ * @return true if the windowing mode is supported.
+ */
+ boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+ boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+ int activityType) {
+
+ if (windowingMode == WINDOWING_MODE_UNDEFINED
+ || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ return true;
+ }
+ if (!supportsMultiWindow) {
+ return false;
+ }
+
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+ return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
+ windowingMode, activityType);
+ }
+
+ if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+ return false;
+ }
+
+ if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+ return false;
+ }
+ return true;
+ }
+
+ private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+ @Nullable TaskRecord task, int activityType) {
+
+ // First preference if the windowing mode in the activity options if set.
+ int windowingMode = (options != null)
+ ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+ // If windowing mode is unset, then next preference is the candidate task, then the
+ // activity record.
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ if (task != null) {
+ windowingMode = task.getWindowingMode();
+ }
+ if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+ windowingMode = r.getWindowingMode();
+ }
+ }
+
+ // Make sure the windowing mode we are trying to use makes sense for what is supported.
+ boolean supportsMultiWindow = mService.mSupportsMultiWindow;
+ boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow;
+ boolean supportsFreeform = mService.mSupportsFreeformWindowManagement;
+ boolean supportsPip = mService.mSupportsPictureInPicture;
+ if (supportsMultiWindow) {
+ if (task != null) {
+ supportsMultiWindow = task.isResizeable();
+ supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+ // TODO: Do we need to check for freeform and Pip support here?
+ } else if (r != null) {
+ supportsMultiWindow = r.isResizeable();
+ supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+ supportsFreeform = r.supportsFreeform();
+ supportsPip = r.supportsPictureInPicture();
+ }
+ }
+
+ if (isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+ supportsFreeform, supportsPip, activityType)) {
+ return windowingMode;
+ }
+ return WINDOWING_MODE_FULLSCREEN;
+ }
+
+ int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+ @Nullable TaskRecord task) {
+ // Preference is given to the activity type for the activity then the task since the type
+ // once set shouldn't change.
+ int activityType = r != null ? r.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+ if (activityType == ACTIVITY_TYPE_UNDEFINED && task != null) {
+ activityType = task.getActivityType();
+ }
+ if (activityType != ACTIVITY_TYPE_UNDEFINED) {
+ return activityType;
+ }
+ return options != null ? options.getLaunchActivityType() : ACTIVITY_TYPE_UNDEFINED;
+ }
+
+ <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
+ @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
+ return getLaunchStack(r, options, candidateTask, onTop, INVALID_DISPLAY);
}
- protected <T extends ActivityStack> T getStack(int stackId, boolean createStaticStackIfNeeded,
- boolean createOnTop) {
- final ActivityStack stack = mStacks.get(stackId);
+ /**
+ * Returns the right stack to use for launching factoring in all the input parameters.
+ *
+ * @param r The activity we are trying to launch. Can be null.
+ * @param options The activity options used to the launch. Can be null.
+ * @param candidateTask The possible task the activity might be launched in. Can be null.
+ *
+ * @return The stack to use for the launch or INVALID_STACK_ID.
+ */
+ <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
+ @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
+ int candidateDisplayId) {
+ int taskId = INVALID_TASK_ID;
+ int displayId = INVALID_DISPLAY;
+ //Rect bounds = null;
+
+ // We give preference to the launch preference in activity options.
+ if (options != null) {
+ taskId = options.getLaunchTaskId();
+ displayId = options.getLaunchDisplayId();
+ // TODO: Need to work this into the equation...
+ //bounds = options.getLaunchBounds();
+ }
+
+ // First preference for stack goes to the task Id set in the activity options. Use the stack
+ // associated with that if possible.
+ if (taskId != INVALID_TASK_ID) {
+ // Temporarily set the task id to invalid in case in re-entry.
+ options.setLaunchTaskId(INVALID_TASK_ID);
+ final TaskRecord task = anyTaskForIdLocked(taskId,
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options);
+ options.setLaunchTaskId(taskId);
+ if (task != null) {
+ return task.getStack();
+ }
+ }
+
+ final int activityType = resolveActivityType(r, options, candidateTask);
+ int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
+ T stack = null;
+
+ // Next preference for stack goes to the display Id set in the activity options or the
+ // candidate display.
+ if (displayId == INVALID_DISPLAY) {
+ displayId = candidateDisplayId;
+ }
+ if (displayId != INVALID_DISPLAY) {
+ if (r != null) {
+ // TODO: This should also take in the windowing mode and activity type into account.
+ stack = (T) getValidLaunchStackOnDisplay(displayId, r);
+ if (stack != null) {
+ return stack;
+ }
+ }
+ final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
+ if (display != null) {
+ for (int i = display.mStacks.size() - 1; i >= 0; --i) {
+ stack = (T) display.mStacks.get(i);
+ if (stack.isCompatible(windowingMode, activityType)) {
+ return stack;
+ }
+ }
+ // TODO: We should create the stack we want on the display at this point.
+ }
+ }
+
+ // Give preference to the stack and display of the input task and activity if they match the
+ // mode we want to launch into.
+ stack = null;
+ ActivityDisplay display = null;
+ if (candidateTask != null) {
+ stack = candidateTask.getStack();
+ }
+ if (stack == null && r != null) {
+ stack = r.getStack();
+ }
if (stack != null) {
- return (T) stack;
+ if (stack.isCompatible(windowingMode, activityType)) {
+ return stack;
+ }
+ display = stack.getDisplay();
}
- if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
- return null;
+
+ if (display == null
+ // TODO: Can be removed once we figure-out how non-standard types should launch
+ // outside the default display.
+ || (activityType != ACTIVITY_TYPE_STANDARD
+ && activityType != ACTIVITY_TYPE_UNDEFINED)) {
+ display = getDefaultDisplay();
}
- if (stackId == DOCKED_STACK_ID) {
- // Make sure recents stack exist when creating a dock stack as it normally need to be on
- // the other side of the docked stack and we make visibility decisions based on that.
- getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, createOnTop);
+
+ stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+ if (stack != null) {
+ return stack;
+ }
+
+ // Whatever...return some default for now.
+ if (candidateTask != null && candidateTask.mBounds != null
+ && mService.mSupportsFreeformWindowManagement) {
+ windowingMode = WINDOWING_MODE_FREEFORM;
+ } else {
+ windowingMode = WINDOWING_MODE_FULLSCREEN;
}
- return (T) createStackOnDisplay(stackId, DEFAULT_DISPLAY, createOnTop);
+ return display.getOrCreateStack(windowingMode, activityType, onTop);
}
/**
@@ -2174,26 +2380,50 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
"Display with displayId=" + displayId + " not found.");
}
+ if (!r.canBeLaunchedOnDisplay(displayId)) {
+ return null;
+ }
+
// Return the topmost valid stack on the display.
for (int i = activityDisplay.mStacks.size() - 1; i >= 0; --i) {
final ActivityStack stack = activityDisplay.mStacks.get(i);
- if (mService.mActivityStarter.isValidLaunchStackId(stack.mStackId, displayId, r)) {
+ if (isValidLaunchStack(stack, displayId, r)) {
return stack;
}
}
// If there is no valid stack on the external display - check if new dynamic stack will do.
- if (displayId != Display.DEFAULT_DISPLAY) {
- final int newDynamicStackId = getNextStackId();
- if (mService.mActivityStarter.isValidLaunchStackId(newDynamicStackId, displayId, r)) {
- return createStackOnDisplay(newDynamicStackId, displayId, true /*onTop*/);
- }
+ if (displayId != DEFAULT_DISPLAY) {
+ return activityDisplay.createStack(
+ r.getWindowingMode(), r.getActivityType(), true /*onTop*/);
}
Slog.w(TAG, "getValidLaunchStackOnDisplay: can't launch on displayId " + displayId);
return null;
}
+ // TODO: Can probably be consolidated into getLaunchStack()...
+ private boolean isValidLaunchStack(ActivityStack stack, int displayId, ActivityRecord r) {
+ switch (stack.getActivityType()) {
+ case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome();
+ case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents();
+ case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant();
+ }
+ switch (stack.getWindowingMode()) {
+ case WINDOWING_MODE_FULLSCREEN: return true;
+ case WINDOWING_MODE_FREEFORM: return r.supportsFreeform();
+ case WINDOWING_MODE_PINNED: return r.supportsPictureInPicture();
+ case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return r.supportsSplitScreenWindowingMode();
+ case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return r.supportsSplitScreenWindowingMode();
+ }
+
+ if (StackId.isDynamicStack(stack.mStackId)) {
+ return r.canBeLaunchedOnDisplay(displayId);
+ }
+ Slog.e(TAG, "isValidLaunchStack: Unexpected stack=" + stack);
+ return false;
+ }
+
ArrayList<ActivityStack> getStacks() {
ArrayList<ActivityStack> allStacks = new ArrayList<>();
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
@@ -2225,7 +2455,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
for (int j = stacks.size() - 1; j >= 0; --j) {
final ActivityStack stack = stacks.get(j);
if (stack != currentFocus && stack.isFocusable()
- && stack.shouldBeVisible(null) != STACK_INVISIBLE) {
+ && stack.shouldBeVisible(null)) {
return stack;
}
}
@@ -2294,7 +2524,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return;
}
- final boolean splitScreenActive = getStack(DOCKED_STACK_ID) != null;
+ final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenStack();
if (!allowResizeInDockedMode
&& !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {
// If the docked stack exists, don't resize non-floating stacks independently of the
@@ -2305,7 +2535,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
mWindowManager.deferSurfaceLayout();
try {
- if (stack.supportSplitScreenWindowingMode()) {
+ if (stack.supportsSplitScreenWindowingMode()) {
if (bounds == null && stack.inSplitScreenWindowingMode()) {
// null bounds = fullscreen windowing mode...at least for now.
stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -2326,26 +2556,25 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
- private void deferUpdateBounds(int stackId) {
- final ActivityStack stack = getStack(stackId);
+ private void deferUpdateBounds(int activityType) {
+ final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
if (stack != null) {
stack.deferUpdateBounds();
}
}
- private void continueUpdateBounds(int stackId) {
- final ActivityStack stack = getStack(stackId);
+ private void continueUpdateBounds(int activityType) {
+ final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
if (stack != null) {
stack.continueUpdateBounds();
}
}
void notifyAppTransitionDone() {
- continueUpdateBounds(RECENTS_STACK_ID);
+ continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {
final int taskId = mResizingTasksDuringAnimation.valueAt(i);
- final TaskRecord task =
- anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+ final TaskRecord task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY);
if (task != null) {
task.setTaskDockedResizing(false);
}
@@ -2353,29 +2582,31 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mResizingTasksDuringAnimation.clear();
}
- private void moveTasksToFullscreenStackInSurfaceTransaction(int fromStackId,
- boolean onTop) {
-
- final ActivityStack stack = getStack(fromStackId);
- if (stack == null) {
- return;
- }
+ /**
+ * TODO: This should just change the windowing mode and resize vs. actually moving task around.
+ * Can do that once we are no longer using static stack ids. Specially when
+ * {@link ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} is removed.
+ */
+ private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
+ int toDisplayId, boolean onTop) {
mWindowManager.deferSurfaceLayout();
try {
- if (fromStackId == DOCKED_STACK_ID) {
+ final int windowingMode = fromStack.getWindowingMode();
+ final boolean inPinnedWindowingMode = windowingMode == WINDOWING_MODE_PINNED;
+ final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId);
+
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
// We are moving all tasks from the docked stack to the fullscreen stack,
// which is dismissing the docked stack, so resize all other stacks to
// fullscreen here already so we don't end up with resize trashing.
- for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- final ActivityStack otherStack = getStack(i);
- if (otherStack == null) {
- continue;
- }
+ final ArrayList<ActivityStack> displayStacks = toDisplay.mStacks;
+ for (int i = displayStacks.size() - 1; i >= 0; --i) {
+ final ActivityStack otherStack = displayStacks.get(i);
if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
continue;
}
- resizeStackLocked(i, null, null, null, PRESERVE_WINDOWS,
+ resizeStackLocked(otherStack.mStackId, null, null, null, PRESERVE_WINDOWS,
true /* allowResizeInDockedMode */, DEFER_RESUME);
}
@@ -2384,48 +2615,51 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// resize when we remove task from it below and it is detached from the
// display because it no longer contains any tasks.
mAllowDockedStackResize = false;
- } else if (fromStackId == PINNED_STACK_ID) {
- if (onTop) {
- // Log if we are expanding the PiP to fullscreen
- MetricsLogger.action(mService.mContext,
- ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
- }
+ } else if (inPinnedWindowingMode && onTop) {
+ // Log if we are expanding the PiP to fullscreen
+ MetricsLogger.action(mService.mContext,
+ ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
}
- ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID);
- final boolean isFullscreenStackVisible = fullscreenStack != null &&
- fullscreenStack.shouldBeVisible(null) == STACK_VISIBLE;
+
// If we are moving from the pinned stack, then the animation takes care of updating
// the picture-in-picture mode.
- final boolean schedulePictureInPictureModeChange = (fromStackId == PINNED_STACK_ID);
- final ArrayList<TaskRecord> tasks = stack.getAllTasks();
- final int size = tasks.size();
- if (onTop) {
- for (int i = 0; i < size; i++) {
- final TaskRecord task = tasks.get(i);
- final boolean isTopTask = i == (size - 1);
- if (fromStackId == PINNED_STACK_ID) {
- // Update the return-to to reflect where the pinned stack task was moved
- // from so that we retain the stack that was previously visible if the
- // pinned stack is recreated. See moveActivityToPinnedStackLocked().
- task.setTaskToReturnTo(isFullscreenStackVisible ?
- ACTIVITY_TYPE_STANDARD : ACTIVITY_TYPE_HOME);
+ final boolean schedulePictureInPictureModeChange = inPinnedWindowingMode;
+ final ArrayList<TaskRecord> tasks = fromStack.getAllTasks();
+
+ if (!tasks.isEmpty()) {
+ final int size = tasks.size();
+ final ActivityStack fullscreenStack = toDisplay.getOrCreateStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop);
+
+ if (onTop) {
+ final int returnToType =
+ toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
+ for (int i = 0; i < size; i++) {
+ final TaskRecord task = tasks.get(i);
+ final boolean isTopTask = i == (size - 1);
+ if (inPinnedWindowingMode) {
+ // Update the return-to to reflect where the pinned stack task was moved
+ // from so that we retain the stack that was previously visible if the
+ // pinned stack is recreated. See moveActivityToPinnedStackLocked().
+ task.setTaskToReturnTo(returnToType);
+ }
+ // Defer resume until all the tasks have been moved to the fullscreen stack
+ task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+ isTopTask /* animate */, DEFER_RESUME,
+ schedulePictureInPictureModeChange,
+ "moveTasksToFullscreenStack - onTop");
+ }
+ } else {
+ for (int i = 0; i < size; i++) {
+ final TaskRecord task = tasks.get(i);
+ // Position the tasks in the fullscreen stack in order at the bottom of the
+ // stack. Also defer resume until all the tasks have been moved to the
+ // fullscreen stack.
+ task.reparent(fullscreenStack, i /* position */,
+ REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
+ schedulePictureInPictureModeChange,
+ "moveTasksToFullscreenStack - NOT_onTop");
}
- // Defer resume until all the tasks have been moved to the fullscreen stack
- task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
- REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME,
- schedulePictureInPictureModeChange,
- "moveTasksToFullscreenStack - onTop");
- }
- } else {
- for (int i = 0; i < size; i++) {
- final TaskRecord task = tasks.get(i);
- // Position the tasks in the fullscreen stack in order at the bottom of the
- // stack. Also defer resume until all the tasks have been moved to the
- // fullscreen stack.
- task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, i /* position */,
- REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
- schedulePictureInPictureModeChange,
- "moveTasksToFullscreenStack - NOT_onTop");
}
}
@@ -2437,9 +2671,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
- void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
- mWindowManager.inSurfaceTransaction(
- () -> moveTasksToFullscreenStackInSurfaceTransaction(fromStackId, onTop));
+ void moveTasksToFullscreenStackLocked(ActivityStack fromStack, boolean onTop) {
+ moveTasksToFullscreenStackLocked(fromStack, DEFAULT_DISPLAY, onTop);
+ }
+
+ void moveTasksToFullscreenStackLocked(ActivityStack fromStack, int toDisplayId, boolean onTop) {
+ mWindowManager.inSurfaceTransaction(() ->
+ moveTasksToFullscreenStackInSurfaceTransaction(fromStack, toDisplayId, onTop));
}
void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
@@ -2450,7 +2688,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
false /* deferResume */);
}
- void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
+ private void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
boolean preserveWindows, boolean deferResume) {
@@ -2459,7 +2697,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return;
}
- final ActivityStack stack = getStack(DOCKED_STACK_ID);
+ final ActivityStack stack = getDefaultDisplay().getStack(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
if (stack == null) {
Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");
return;
@@ -2479,7 +2718,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// The dock stack either was dismissed or went fullscreen, which is kinda the same.
// In this case we make all other static stacks fullscreen and move all
// docked stack tasks to the fullscreen stack.
- moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, ON_TOP);
+ moveTasksToFullscreenStackLocked(stack, ON_TOP);
// stack shouldn't contain anymore activities, so nothing to resume.
r = null;
@@ -2488,13 +2727,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// static stacks need to be adjusted so they don't overlap with the docked stack.
// We get the bounds to use from window manager which has been adjusted for any
// screen controls and is also the same for all stacks.
+ final ArrayList<ActivityStack> stacks = getStacksOnDefaultDisplay();
final Rect otherTaskRect = new Rect();
- for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
- if (i == DOCKED_STACK_ID) {
+ for (int i = stacks.size() - 1; i >= 0; --i) {
+ final ActivityStack current = stacks.get(i);
+ if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
continue;
}
- final ActivityStack current = getStack(i);
- if (current == null || !current.supportSplitScreenWindowingMode()) {
+ if (!current.supportsSplitScreenWindowingMode()) {
continue;
}
// Need to set windowing mode here before we try to get the dock bounds.
@@ -2504,7 +2744,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
tempRect /* outStackBounds */,
otherTaskRect /* outTempTaskBounds */, true /* ignoreVisibility */);
- resizeStackLocked(i, !tempRect.isEmpty() ? tempRect : null,
+ resizeStackLocked(current.mStackId, !tempRect.isEmpty() ? tempRect : null,
!otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
tempOtherTaskInsetBounds, preserveWindows,
true /* allowResizeInDockedMode */, deferResume);
@@ -2521,7 +2761,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
- final PinnedActivityStack stack = getStack(PINNED_STACK_ID);
+ // TODO(multi-display): Pinned stack display should be passed in.
+ final PinnedActivityStack stack = getDefaultDisplay().getStack(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (stack == null) {
Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
return;
@@ -2559,32 +2801,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
- ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
- final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId);
- if (activityDisplay == null) {
- return null;
- }
- return createStack(stackId, activityDisplay, onTop);
-
- }
-
- ActivityStack createStack(int stackId, ActivityDisplay display, boolean onTop) {
- switch (stackId) {
- case PINNED_STACK_ID:
- return new PinnedActivityStack(display, stackId, this, mRecentTasks, onTop);
- default:
- return new ActivityStack(display, stackId, this, mRecentTasks, onTop);
- }
- }
-
- void removeStackInSurfaceTransaction(int stackId) {
+ private void removeStackInSurfaceTransaction(int stackId) {
final ActivityStack stack = getStack(stackId);
if (stack == null) {
return;
}
final ArrayList<TaskRecord> tasks = stack.getAllTasks();
- if (stack.getStackId() == PINNED_STACK_ID) {
+ if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) {
/**
* Workaround: Force-stop all the activities in the pinned stack before we reparent them
* to the fullscreen stack. This is to guarantee that when we are removing a stack,
@@ -2602,7 +2826,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
true /* processPausingActivites */, null /* configuration */);
// Move all the tasks to the bottom of the fullscreen stack
- moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+ moveTasksToFullscreenStackLocked(pinnedStack, !ON_TOP);
} else {
for (int i = tasks.size() - 1; i >= 0; i--) {
removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
@@ -2617,8 +2841,23 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
* instead moved back onto the fullscreen stack.
*/
void removeStackLocked(int stackId) {
- mWindowManager.inSurfaceTransaction(
- () -> removeStackInSurfaceTransaction(stackId));
+ mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stackId));
+ }
+
+ /**
+ * Removes stacks in the input windowing modes from the system if they are of activity type
+ * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+ */
+ void removeStacksInWindowingModes(int... windowingModes) {
+ for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+ mActivityDisplays.valueAt(i).removeStacksInWindowingModes(windowingModes);
+ }
+ }
+
+ void removeStacksWithActivityTypes(int... activityTypes) {
+ for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+ mActivityDisplays.valueAt(i).removeStacksWithActivityTypes(activityTypes);
+ }
}
/**
@@ -2640,8 +2879,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
*/
boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
boolean pauseImmediately) {
- final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
- INVALID_STACK_ID);
+ final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr != null) {
tr.removeTaskActivitiesLocked(pauseImmediately);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
@@ -2654,10 +2892,23 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return false;
}
+ void addRecentActivity(ActivityRecord r) {
+ if (r == null) {
+ return;
+ }
+ final TaskRecord task = r.getTask();
+ mRecentTasks.addLocked(task);
+ task.touchActiveTime();
+ }
+
+ void removeTaskFromRecents(TaskRecord task) {
+ mRecentTasks.remove(task);
+ task.removedFromRecents();
+ }
+
void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) {
if (removeFromRecents) {
- mRecentTasks.remove(tr);
- tr.removedFromRecents();
+ removeTaskFromRecents(tr);
}
ComponentName component = tr.getBaseIntent().getComponent();
if (component == null) {
@@ -2740,27 +2991,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
/**
* Restores a recent task to a stack
* @param task The recent task to be restored.
- * @param stackId The stack to restore the task to (default launch stack will be used
- * if stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}
- * or is not a static stack).
+ * @param aOptions The activity options to use for restoration.
* @return true if the task has been restored successfully.
*/
- boolean restoreRecentTaskLocked(TaskRecord task, int stackId) {
- if (!StackId.isStaticStack(stackId)) {
- // If stack is not static (or stack id is invalid) - use the default one.
- // This means that tasks that were on external displays will be restored on the
- // primary display.
- stackId = task.getLaunchStackId();
- } else if (stackId == DOCKED_STACK_ID && !task.supportsSplitScreen()) {
- // Preferred stack is the docked stack, but the task can't go in the docked stack.
- // Put it in the fullscreen stack.
- stackId = FULLSCREEN_WORKSPACE_STACK_ID;
- }
-
+ boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions) {
+ final ActivityStack stack = getLaunchStack(null, aOptions, task, !ON_TOP);
final ActivityStack currentStack = task.getStack();
if (currentStack != null) {
// Task has already been restored once. See if we need to do anything more
- if (currentStack.mStackId == stackId) {
+ if (currentStack == stack) {
// Nothing else to do since it is already restored in the right stack.
return true;
}
@@ -2769,19 +3008,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
currentStack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
}
- final ActivityStack stack =
- getStack(stackId, CREATE_IF_NEEDED, !ON_TOP);
-
- if (stack == null) {
- // What does this mean??? Not sure how we would get here...
- if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
- "Unable to find/create stack to restore recent task=" + task);
- return false;
- }
-
- stack.addTask(task, false /* toTop */, "restoreRecentTask");
+ stack.addTask(task, !ON_TOP, "restoreRecentTask");
// TODO: move call for creation here and other place into Stack.addTask()
- task.createWindowContainer(false /* toTop */, true /* showForAllUsers */);
+ task.createWindowContainer(!ON_TOP, true /* showForAllUsers */);
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
"Added restored task=" + task + " to stack=" + stack);
final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -2803,7 +3032,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown displayId="
+ displayId);
}
- final ActivityStack stack = mStacks.get(stackId);
+ final ActivityStack stack = getStack(stackId);
if (stack == null) {
throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown stackId="
+ stackId);
@@ -2828,8 +3057,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
* Returns the reparent target stack, creating the stack if necessary. This call also enforces
* the various checks on tasks that are going to be reparented from one stack to another.
*/
- ActivityStack getReparentTargetStack(TaskRecord task, int stackId, boolean toTop) {
+ ActivityStack getReparentTargetStack(TaskRecord task, ActivityStack stack, boolean toTop) {
final ActivityStack prevStack = task.getStack();
+ final int stackId = stack.mStackId;
+ final boolean inMultiWindowMode = stack.inMultiWindowMode();
// Check that we aren't reparenting to the same stack that the task is already in
if (prevStack != null && prevStack.mStackId == stackId) {
@@ -2840,22 +3071,22 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// Ensure that we aren't trying to move into a multi-window stack without multi-window
// support
- if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
+ if (inMultiWindowMode && !mService.mSupportsMultiWindow) {
throw new IllegalArgumentException("Device doesn't support multi-window, can not"
- + " reparent task=" + task + " to stackId=" + stackId);
+ + " reparent task=" + task + " to stack=" + stack);
}
// Ensure that we're not moving a task to a dynamic stack if device doesn't support
// multi-display.
- // TODO(multi-display): Support non-dynamic stacks on secondary displays.
- if (StackId.isDynamicStack(stackId) && !mService.mSupportsMultiDisplay) {
+ if (stack.mDisplayId != DEFAULT_DISPLAY && !mService.mSupportsMultiDisplay) {
throw new IllegalArgumentException("Device doesn't support multi-display, can not"
+ " reparent task=" + task + " to stackId=" + stackId);
}
// Ensure that we aren't trying to move into a freeform stack without freeform
// support
- if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
+ if (stack.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && !mService.mSupportsFreeformWindowManagement) {
throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
+ " task=" + task);
}
@@ -2864,25 +3095,25 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// used for split-screen mode and will cause things like the docked divider to show up. We
// instead leave the task in its current stack or move it to the fullscreen stack if it
// isn't currently in a stack.
- if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
- stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
+ if (inMultiWindowMode && !task.isResizeable()) {
Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
+ " Moving to stackId=" + stackId + " instead.");
+ // Temporarily disable resizeablility of the task as we don't want it to be resized if,
+ // for example, a docked stack is created which will lead to the stack we are moving
+ // from being resized and and its resizeable tasks being resized.
+ try {
+ task.mTemporarilyUnresizable = true;
+ stack = stack.getDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
+ } finally {
+ task.mTemporarilyUnresizable = false;
+ }
}
-
- // Temporarily disable resizeablility of the task as we don't want it to be resized if, for
- // example, a docked stack is created which will lead to the stack we are moving from being
- // resized and and its resizeable tasks being resized.
- try {
- task.mTemporarilyUnresizable = true;
- return getStack(stackId, CREATE_IF_NEEDED, toTop);
- } finally {
- task.mTemporarilyUnresizable = false;
- }
+ return stack;
}
boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect destBounds) {
- final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+ final ActivityStack stack = getStack(stackId);
if (stack == null) {
throw new IllegalArgumentException(
"moveTopStackActivityToPinnedStackLocked: Unknown stackId=" + stackId);
@@ -2912,12 +3143,17 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mWindowManager.deferSurfaceLayout();
+ final ActivityDisplay display = r.getStack().getDisplay();
+ PinnedActivityStack stack = display.getPinnedStack();
+
// This will clear the pinned stack by moving an existing task to the full screen stack,
// ensuring only one task is present.
- moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+ if (stack != null) {
+ moveTasksToFullscreenStackLocked(stack, !ON_TOP);
+ }
// Need to make sure the pinned stack exist so we can resize it below...
- final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+ stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
try {
final TaskRecord task = r.getTask();
@@ -2941,8 +3177,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
moveHomeStackToFront(reason);
}
// Defer resume until below, and do not schedule PiP changes until we animate below
- task.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
- DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
+ task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
+ false /* schedulePictureInPictureModeChange */, reason);
} else {
// There are multiple activities in the task and moving the top activity should
// reveal/leave the other activities in their original task.
@@ -2957,7 +3193,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
// Defer resume until below, and do not schedule PiP changes until we animate below
- newTask.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+ newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
}
@@ -2983,8 +3219,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
- mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName,
- r.getTask().taskId);
+ mService.mTaskChangeNotificationController.notifyActivityPinned(r);
}
/** Move activity with its stack to front and make the stack focused. */
@@ -3044,6 +3279,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// tasks should always have lower priority than any affinity-matching tasks
// in the fullscreen stacks
affinityMatch = mTmpFindTaskResult.r;
+ } else if (DEBUG_TASKS && mTmpFindTaskResult.matchedByRootAffinity) {
+ Slog.d(TAG_TASKS, "Skipping match on different display "
+ + mTmpFindTaskResult.r.getDisplayId() + " " + displayId);
}
}
}
@@ -3408,14 +3646,17 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
boolean switchUserLocked(int userId, UserState uss) {
final int focusStackId = mFocusedStack.getStackId();
// We dismiss the docked stack whenever we switch users.
- moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, focusStackId == DOCKED_STACK_ID);
+ final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenStack();
+ if (dockedStack != null) {
+ moveTasksToFullscreenStackLocked(dockedStack, mFocusedStack == dockedStack);
+ }
// Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will
// also cause all tasks to be moved to the fullscreen stack at a position that is
// appropriate.
removeStackLocked(PINNED_STACK_ID);
mUserStackInFront.put(mCurrentUser, focusStackId);
- final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
+ final int restoreStackId = mUserStackInFront.get(userId, mHomeStack.mStackId);
mCurrentUser = userId;
mStartingUsers.add(uss);
@@ -3448,7 +3689,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
/** Checks whether the userid is a profile of the current user. */
boolean isCurrentProfileLocked(int userId) {
if (userId == mCurrentUser) return true;
- return mService.mUserController.isCurrentProfileLocked(userId);
+ return mService.mUserController.isCurrentProfile(userId);
}
/**
@@ -3555,15 +3796,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
pw.print(prefix);
pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser);
pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
- pw.print(prefix); pw.println("mStacks=" + mStacks);
- // TODO: move this to LockTaskController
- final SparseArray<String[]> packages = mService.mLockTaskPackages;
- if (packages.size() > 0) {
- pw.print(prefix); pw.println("mLockTaskPackages (userId:packages)=");
- for (int i = 0; i < packages.size(); ++i) {
- pw.print(prefix); pw.print(prefix); pw.print(packages.keyAt(i));
- pw.print(":"); pw.println(Arrays.toString(packages.valueAt(i)));
- }
+ for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+ final ActivityDisplay display = mActivityDisplays.valueAt(i);
+ pw.println(prefix + "displayId=" + display.mDisplayId + " mStacks=" + display.mStacks);
}
if (!mWaitingForActivityVisible.isEmpty()) {
pw.print(prefix); pw.println("mWaitingForActivityVisible=");
@@ -3576,6 +3811,26 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mService.mLockTaskController.dump(pw, prefix);
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
+ ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
+ activityDisplay.writeToProto(proto, DISPLAYS);
+ }
+ mKeyguardController.writeToProto(proto, KEYGUARD_CONTROLLER);
+ if (mFocusedStack != null) {
+ proto.write(FOCUSED_STACK_ID, mFocusedStack.mStackId);
+ ActivityRecord focusedActivity = getResumedActivityLocked();
+ if (focusedActivity != null) {
+ focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+ }
+ } else {
+ proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
+ }
+ proto.end(token);
+ }
+
/**
* Dump all connected displays' configurations.
* @param prefix Prefix to apply to each line of the dump.
@@ -3605,8 +3860,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
ActivityStack stack = stacks.get(stackNdx);
- if (!dumpVisibleStacksOnly ||
- stack.shouldBeVisible(null) == STACK_VISIBLE) {
+ if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
activities.addAll(stack.getDumpActivitiesLocked(name));
}
}
@@ -3832,15 +4086,22 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return getActivityDisplayOrCreateLocked(displayId) != null;
}
+ // TODO: Look into consolidating with getActivityDisplayOrCreateLocked()
ActivityDisplay getActivityDisplay(int displayId) {
return mActivityDisplays.get(displayId);
}
+ // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display.
+ ActivityDisplay getDefaultDisplay() {
+ return mActivityDisplays.get(DEFAULT_DISPLAY);
+ }
+
/**
* Get an existing instance of {@link ActivityDisplay} or create new if there is a
* corresponding record in display manager.
*/
- private ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) {
+ // TODO: Look into consolidating with getActivityDisplay()
+ ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay != null) {
return activityDisplay;
@@ -3855,17 +4116,18 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return null;
}
// The display hasn't been added to ActivityManager yet, create a new record now.
- activityDisplay = new ActivityDisplay(displayId);
- if (activityDisplay.mDisplay == null) {
- Slog.w(TAG, "Display " + displayId + " gone before initialization complete");
- return null;
- }
- mActivityDisplays.put(displayId, activityDisplay);
+ activityDisplay = new ActivityDisplay(this, displayId);
+ attachDisplay(activityDisplay);
calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
mWindowManager.onDisplayAdded(displayId);
return activityDisplay;
}
+ @VisibleForTesting
+ void attachDisplay(ActivityDisplay display) {
+ mActivityDisplays.put(display.mDisplayId, display);
+ }
+
private void calculateDefaultMinimalSizeOfResizeableTasks(ActivityDisplay display) {
mDefaultMinSizeOfResizeableTask =
mService.mContext.getResources().getDimensionPixelSize(
@@ -3893,7 +4155,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// Moving all tasks to fullscreen stack, because it's guaranteed to be
// a valid launch stack for all activities. This way the task history from
// external display will be preserved on primary after move.
- moveTasksToFullscreenStackLocked(stack.getStackId(), true /* onTop */);
+ moveTasksToFullscreenStackLocked(stack, true /* onTop */);
}
}
@@ -3955,7 +4217,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
if (display.mAllSleepTokens.isEmpty()) {
return;
}
- for (SleepTokenImpl token : display.mAllSleepTokens) {
+ for (SleepToken token : display.mAllSleepTokens) {
mSleepTokens.remove(token);
}
display.mAllSleepTokens.clear();
@@ -3963,7 +4225,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
mService.updateSleepIfNeededLocked();
}
- private StackInfo getStackInfoLocked(ActivityStack stack) {
+ private StackInfo getStackInfo(ActivityStack stack) {
final int displayId = stack.mDisplayId;
final ActivityDisplay display = mActivityDisplays.get(displayId);
StackInfo info = new StackInfo();
@@ -3971,11 +4233,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
info.displayId = displayId;
info.stackId = stack.mStackId;
info.userId = stack.mCurrentUser;
- info.visible = stack.shouldBeVisible(null) == STACK_VISIBLE;
+ info.visible = stack.shouldBeVisible(null);
// A stack might be not attached to a display.
- info.position = display != null
- ? display.mStacks.indexOf(stack)
- : 0;
+ info.position = display != null ? display.mStacks.indexOf(stack) : 0;
+ info.configuration.setTo(stack.getConfiguration());
ArrayList<TaskRecord> tasks = stack.getAllTasks();
final int numTasks = tasks.size();
@@ -4004,40 +4265,44 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
return info;
}
- StackInfo getStackInfoLocked(int stackId) {
+ StackInfo getStackInfo(int stackId) {
ActivityStack stack = getStack(stackId);
if (stack != null) {
- return getStackInfoLocked(stack);
+ return getStackInfo(stack);
}
return null;
}
+ StackInfo getStackInfo(int windowingMode, int activityType) {
+ final ActivityStack stack = getStack(windowingMode, activityType);
+ return (stack != null) ? getStackInfo(stack) : null;
+ }
+
ArrayList<StackInfo> getAllStackInfosLocked() {
ArrayList<StackInfo> list = new ArrayList<>();
for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) {
- list.add(getStackInfoLocked(stacks.get(ndx)));
+ list.add(getStackInfo(stacks.get(ndx)));
}
}
return list;
}
- void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId,
+ void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
int preferredDisplayId, int actualStackId) {
- handleNonResizableTaskIfNeeded(task, preferredStackId, preferredDisplayId, actualStackId,
- false /* forceNonResizable */);
+ handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayId,
+ actualStackId, false /* forceNonResizable */);
}
- void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId,
+ void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
int preferredDisplayId, int actualStackId, boolean forceNonResizable) {
final boolean isSecondaryDisplayPreferred =
- (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY)
- || StackId.isDynamicStack(preferredStackId);
+ (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
final ActivityStack actualStack = getStack(actualStackId);
final boolean inSplitScreenMode = actualStack != null
&& actualStack.inSplitScreenWindowingMode();
- if (((!inSplitScreenMode && preferredStackId != DOCKED_STACK_ID)
+ if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
&& !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) {
return;
}
@@ -4065,7 +4330,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
final ActivityRecord topActivity = task.getTopActivity();
- if (launchOnSecondaryDisplayFailed || !task.supportsSplitScreen() || forceNonResizable) {
+ if (launchOnSecondaryDisplayFailed
+ || !task.supportsSplitScreenWindowingMode() || forceNonResizable) {
if (launchOnSecondaryDisplayFailed) {
// Display a warning toast that we tried to put a non-resizeable task on a secondary
// display with config different from global config.
@@ -4079,7 +4345,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
// we need to move it to top of fullscreen stack, otherwise it will be covered.
- moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID);
+
+ final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenStack();
+ if (dockedStack != null) {
+ moveTasksToFullscreenStackLocked(dockedStack,
+ actualStackId == dockedStack.getStackId());
+ }
} else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
&& !topActivity.noDisplay) {
final String packageName = topActivity.appInfo.packageName;
@@ -4284,122 +4555,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
}
}
- // TODO: Move to its own file.
- /** Exactly one of these classes per Display in the system. Capable of holding zero or more
- * attached {@link ActivityStack}s */
- class ActivityDisplay extends ConfigurationContainer {
- /** Actual Display this object tracks. */
- int mDisplayId;
- Display mDisplay;
-
- /** All of the stacks on this display. Order matters, topmost stack is in front of all other
- * stacks, bottommost behind. Accessed directly by ActivityManager package classes */
- final ArrayList<ActivityStack> mStacks = new ArrayList<>();
-
- /** Array of all UIDs that are present on the display. */
- private IntArray mDisplayAccessUIDs = new IntArray();
-
- /** All tokens used to put activities on this stack to sleep (including mOffToken) */
- final ArrayList<SleepTokenImpl> mAllSleepTokens = new ArrayList<>();
- /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
- SleepToken mOffToken;
-
- private boolean mSleeping;
-
- @VisibleForTesting
- ActivityDisplay() {
- mActivityDisplays.put(mDisplayId, this);
- }
-
- // After instantiation, check that mDisplay is not null before using this. The alternative
- // is for this to throw an exception if mDisplayManager.getDisplay() returns null.
- ActivityDisplay(int displayId) {
- final Display display = mDisplayManager.getDisplay(displayId);
- if (display == null) {
- return;
- }
- init(display);
- }
-
- void init(Display display) {
- mDisplay = display;
- mDisplayId = display.getDisplayId();
- }
-
- void attachStack(ActivityStack stack, int position) {
- if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack
- + " to displayId=" + mDisplayId + " position=" + position);
- mStacks.add(position, stack);
- mService.updateSleepIfNeededLocked();
- }
-
- void detachStack(ActivityStack stack) {
- if (DEBUG_STACK) Slog.v(TAG_STACK, "detachStack: detaching " + stack
- + " from displayId=" + mDisplayId);
- mStacks.remove(stack);
- mService.updateSleepIfNeededLocked();
- }
-
- @Override
- public String toString() {
- return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
- }
-
- @Override
- protected int getChildCount() {
- return mStacks.size();
- }
-
- @Override
- protected ConfigurationContainer getChildAt(int index) {
- return mStacks.get(index);
- }
-
- @Override
- protected ConfigurationContainer getParent() {
- return ActivityStackSupervisor.this;
- }
-
- boolean isPrivate() {
- return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
- }
-
- boolean isUidPresent(int uid) {
- for (ActivityStack stack : mStacks) {
- if (stack.isUidPresent(uid)) {
- return true;
- }
- }
- return false;
- }
-
- /** Update and get all UIDs that are present on the display and have access to it. */
- private IntArray getPresentUIDs() {
- mDisplayAccessUIDs.clear();
- for (ActivityStack stack : mStacks) {
- stack.getPresentUIDs(mDisplayAccessUIDs);
- }
- return mDisplayAccessUIDs;
- }
-
- boolean shouldDestroyContentOnRemove() {
- return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
- }
-
- boolean shouldSleep() {
- return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
- && (mService.mRunningVoice == null);
- }
-
- boolean isSleeping() {
- return mSleeping;
- }
-
- void setIsSleeping(boolean asleep) {
- mSleeping = asleep;
- }
- }
-
ActivityStack findStackBehind(ActivityStack stack) {
// TODO(multi-display): We are only looking for stacks on the default display.
final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
@@ -4432,32 +4587,36 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
final String callingPackage;
final Intent intent;
final int userId;
+ int activityType = ACTIVITY_TYPE_UNDEFINED;
+ int windowingMode = WINDOWING_MODE_UNDEFINED;
final ActivityOptions activityOptions = (bOptions != null)
? new ActivityOptions(bOptions) : null;
- final int launchStackId = (activityOptions != null)
- ? activityOptions.getLaunchStackId() : INVALID_STACK_ID;
- if (StackId.isHomeOrRecentsStack(launchStackId)) {
+ if (activityOptions != null) {
+ activityType = activityOptions.getLaunchActivityType();
+ windowingMode = activityOptions.getLaunchWindowingMode();
+ }
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
+ taskId + " can't be launch in the home/recents stack.");
}
mWindowManager.deferSurfaceLayout();
try {
- if (launchStackId == DOCKED_STACK_ID) {
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
mWindowManager.setDockedStackCreateState(
activityOptions.getDockCreateMode(), null /* initialBounds */);
// Defer updating the stack in which recents is until the app transition is done, to
// not run into issues where we still need to draw the task in recents but the
// docked stack is already created.
- deferUpdateBounds(RECENTS_STACK_ID);
+ deferUpdateBounds(ACTIVITY_TYPE_RECENTS);
mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
}
task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
- launchStackId);
+ activityOptions);
if (task == null) {
- continueUpdateBounds(RECENTS_STACK_ID);
+ continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
mWindowManager.executeAppTransition();
throw new IllegalArgumentException(
"startActivityFromRecentsInner: Task " + taskId + " not found.");
@@ -4465,15 +4624,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// Since we don't have an actual source record here, we assume that the currently
// focused activity was the source.
- final ActivityStack focusedStack = getFocusedStack();
- final ActivityRecord sourceRecord =
- focusedStack != null ? focusedStack.topActivity() : null;
-
- if (launchStackId != INVALID_STACK_ID) {
- if (task.getStackId() != launchStackId) {
- task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
- DEFER_RESUME, "startActivityFromRecents");
- }
+ final ActivityStack stack = getLaunchStack(null, activityOptions, task, ON_TOP);
+
+ if (stack != null && task.getStack() != stack) {
+ task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+ "startActivityFromRecents");
}
// If the user must confirm credentials (e.g. when first launching a work app and the
@@ -4492,15 +4647,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
// If we are launching the task in the docked stack, put it into resizing mode so
// the window renders full-screen with the background filling the void. Also only
// call this at the end to make sure that tasks exists on the window manager side.
- if (launchStackId == DOCKED_STACK_ID) {
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
setResizingDuringAnimation(task);
}
mService.mActivityStarter.postStartActivityProcessing(task.getTopActivity(),
- ActivityManager.START_TASK_TO_FRONT,
- sourceRecord != null
- ? sourceRecord.getTask().getStackId() : INVALID_STACK_ID,
- sourceRecord, task.getStack());
+ ActivityManager.START_TASK_TO_FRONT, task.getStack());
return ActivityManager.START_TASK_TO_FRONT;
}
callingUid = task.mCallingUid;
@@ -4510,7 +4662,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
userId = task.userId;
int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,
null, null, 0, 0, bOptions, userId, task, "startActivityFromRecents");
- if (launchStackId == DOCKED_STACK_ID) {
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
setResizingDuringAnimation(task);
}
return result;
@@ -4532,8 +4684,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
for (int j = display.mStacks.size() - 1; j >= 0; j--) {
final ActivityStack stack = display.mStacks.get(j);
// Get top activity from a visible stack and add it to the list.
- if (stack.shouldBeVisible(null /* starting */)
- == ActivityStack.STACK_VISIBLE) {
+ if (stack.shouldBeVisible(null /* starting */)) {
final ActivityRecord top = stack.topActivity();
if (top != null) {
if (stack == mFocusedStack) {
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 16abcfb6..d444c663 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -27,18 +27,19 @@ import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
import static android.app.ActivityManager.StackId.isDynamicStack;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+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;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -76,8 +77,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.ANIMATE;
import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -167,7 +166,8 @@ class ActivityStarter {
private boolean mDoResume;
private int mStartFlags;
private ActivityRecord mSourceRecord;
- private int mSourceDisplayId;
+ // The display to launch the activity onto, barring any strong reason to do otherwise.
+ private int mPreferredDisplayId;
private TaskRecord mInTask;
private boolean mAddingToTask;
@@ -186,6 +186,12 @@ class ActivityStarter {
private boolean mAvoidMoveToFront;
private boolean mPowerHintSent;
+ // We must track when we deliver the new intent since multiple code paths invoke
+ // {@link #deliverNewIntent}. This is due to early returns in the code path. This flag is used
+ // inside {@link #deliverNewIntent} to suppress duplicate requests and ensure the intent is
+ // delivered at most once.
+ private boolean mIntentDelivered;
+
private IVoiceInteractionSession mVoiceSession;
private IVoiceInteractor mVoiceInteractor;
@@ -222,7 +228,7 @@ class ActivityStarter {
mDoResume = false;
mStartFlags = 0;
mSourceRecord = null;
- mSourceDisplayId = INVALID_DISPLAY;
+ mPreferredDisplayId = INVALID_DISPLAY;
mInTask = null;
mAddingToTask = false;
@@ -243,6 +249,8 @@ class ActivityStarter {
mVoiceInteractor = null;
mUsingVr2dDisplay = false;
+
+ mIntentDelivered = false;
}
ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) {
@@ -589,9 +597,7 @@ class ActivityStarter {
auxiliaryResponse.token, auxiliaryResponse.needsPhaseTwo);
}
- void postStartActivityProcessing(
- ActivityRecord r, int result, int prevFocusedStackId, ActivityRecord sourceRecord,
- ActivityStack targetStack) {
+ void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) {
if (ActivityManager.isStartResultFatalError(result)) {
return;
@@ -613,7 +619,8 @@ class ActivityStarter {
}
if (startedActivityStackId == DOCKED_STACK_ID) {
- final ActivityStack homeStack = mSupervisor.getStack(HOME_STACK_ID);
+ final ActivityStack homeStack = mSupervisor.getDefaultDisplay().getStack(
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
if (homeStackVisible) {
// We launch an activity while being in home stack, which means either launcher or
@@ -1001,8 +1008,7 @@ class ActivityStarter {
mService.mWindowManager.continueSurfaceLayout();
}
- postStartActivityProcessing(r, result, mSupervisor.getLastStack().mStackId, mSourceRecord,
- mTargetStack);
+ postStartActivityProcessing(r, result, mTargetStack);
return result;
}
@@ -1024,10 +1030,12 @@ class ActivityStarter {
ActivityRecord reusedActivity = getReusableIntentActivity();
- final int preferredLaunchStackId =
- (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID;
- final int preferredLaunchDisplayId =
- (mOptions != null) ? mOptions.getLaunchDisplayId() : DEFAULT_DISPLAY;
+ int preferredWindowingMode = WINDOWING_MODE_UNDEFINED;
+ int preferredLaunchDisplayId = DEFAULT_DISPLAY;
+ if (mOptions != null) {
+ preferredWindowingMode = mOptions.getLaunchWindowingMode();
+ preferredLaunchDisplayId = mOptions.getLaunchDisplayId();
+ }
if (reusedActivity != null) {
// When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
@@ -1077,9 +1085,7 @@ class ActivityStarter {
// so make sure the task now has the identity of the new intent.
top.getTask().setIntent(mStartActivity);
}
- ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask());
- top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
- mStartActivity.launchedFromPackage);
+ deliverNewIntent(top);
}
}
@@ -1141,7 +1147,6 @@ class ActivityStarter {
&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| mLaunchSingleTop || mLaunchSingleTask);
if (dontStart) {
- ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask());
// For paranoia, make sure we have correctly resumed the top activity.
topStack.mLastPausedActivity = null;
if (mDoResume) {
@@ -1153,12 +1158,12 @@ class ActivityStarter {
// anything if that is the case, so this is it!
return START_RETURN_INTENT_TO_CALLER;
}
- top.deliverNewIntentLocked(
- mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+
+ deliverNewIntent(top);
// Don't use mStartActivity.task to show the toast. We're not starting a new activity
// but reusing 'top'. Fields in mStartActivity may not be fully initialized.
- mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredLaunchStackId,
+ mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
preferredLaunchDisplayId, topStack.mStackId);
return START_DELIVERED_TO_TOP;
@@ -1173,8 +1178,7 @@ class ActivityStarter {
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
newTask = true;
- result = setTaskFromReuseOrCreateNewTask(
- taskToAffiliate, preferredLaunchStackId, topStack);
+ result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);
} else if (mSourceRecord != null) {
result = setTaskFromSourceRecord();
} else if (mInTask != null) {
@@ -1237,11 +1241,11 @@ class ActivityStarter {
mOptions);
}
} else {
- mTargetStack.addRecentActivityLocked(mStartActivity);
+ mSupervisor.addRecentActivity(mStartActivity);
}
mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
- mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredLaunchStackId,
+ mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode,
preferredLaunchDisplayId, mTargetStack.mStackId);
return START_SUCCESS;
@@ -1260,7 +1264,7 @@ class ActivityStarter {
mVoiceSession = voiceSession;
mVoiceInteractor = voiceInteractor;
- mSourceDisplayId = getSourceDisplayId(mSourceRecord, mStartActivity);
+ mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);
mLaunchBounds = getOverrideBounds(r, options, inTask);
@@ -1515,7 +1519,7 @@ class ActivityStarter {
!mLaunchSingleTask);
} else {
// Otherwise find the best task to put the activity in.
- intentActivity = mSupervisor.findTaskLocked(mStartActivity, mSourceDisplayId);
+ intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
}
}
return intentActivity;
@@ -1523,10 +1527,12 @@ class ActivityStarter {
/**
* Returns the ID of the display to use for a new activity. If the device is in VR mode,
- * then return the Vr mode's virtual display ID. If not, if the source activity has
- * a explicit display ID set, use that to launch the activity.
+ * then return the Vr mode's virtual display ID. If not, if the activity was started with
+ * a launchDisplayId, use that. Otherwise, if the source activity has a explicit display ID
+ * set, use that to launch the activity.
*/
- private int getSourceDisplayId(ActivityRecord sourceRecord, ActivityRecord startingActivity) {
+ private int getPreferedDisplayId(
+ ActivityRecord sourceRecord, ActivityRecord startingActivity, ActivityOptions options) {
// Check if the Activity is a VR activity. If so, the activity should be launched in
// main display.
if (startingActivity != null && startingActivity.requestedVrComponent != null) {
@@ -1543,6 +1549,13 @@ class ActivityStarter {
return displayId;
}
+ // If the caller requested a display, prefer that display.
+ final int launchDisplayId =
+ (options != null) ? options.getLaunchDisplayId() : INVALID_DISPLAY;
+ if (launchDisplayId != INVALID_DISPLAY) {
+ return launchDisplayId;
+ }
+
displayId = sourceRecord != null ? sourceRecord.getDisplayId() : INVALID_DISPLAY;
// If the activity has a displayId set explicitly, launch it on the same displayId.
if (displayId != INVALID_DISPLAY) {
@@ -1601,12 +1614,11 @@ class ActivityStarter {
mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
mMovedToFront = true;
- } else if (launchStack.mStackId == DOCKED_STACK_ID
- || launchStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ } else if (launchStack.inSplitScreenWindowingMode()) {
if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
// If we want to launch adjacent and mTargetStack is not the computed
// launch stack - move task to top of computed stack.
- intentTask.reparent(launchStack.mStackId, ON_TOP,
+ intentTask.reparent(launchStack, ON_TOP,
REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
"launchToSide");
} else {
@@ -1623,17 +1635,17 @@ class ActivityStarter {
// Target and computed stacks are on different displays and we've
// found a matching task - move the existing instance to that display and
// move it to front.
- intentActivity.getTask().reparent(launchStack.mStackId, ON_TOP,
+ intentActivity.getTask().reparent(launchStack, ON_TOP,
REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
"reparentToDisplay");
mMovedToFront = true;
- } else if (launchStack.getStackId() == StackId.HOME_STACK_ID
- && mTargetStack.getStackId() != StackId.HOME_STACK_ID) {
+ } else if (launchStack.isActivityTypeHome()
+ && !mTargetStack.isActivityTypeHome()) {
// It is possible for the home activity to be in another stack initially.
// For example, the activity may have been initially started with an intent
// which placed it in the fullscreen stack. To ensure the proper handling of
// the activity based on home stack assumptions, we must move it over.
- intentActivity.getTask().reparent(launchStack.mStackId, ON_TOP,
+ intentActivity.getTask().reparent(launchStack, ON_TOP,
REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
"reparentingHome");
mMovedToFront = true;
@@ -1654,8 +1666,8 @@ class ActivityStarter {
mTargetStack.moveToFront("intentActivityFound");
}
- mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(), INVALID_STACK_ID,
- DEFAULT_DISPLAY, mTargetStack.mStackId);
+ mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(),
+ WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack.mStackId);
// If the caller has requested that the target task be reset, then do so.
if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
@@ -1675,8 +1687,7 @@ class ActivityStarter {
// Task will be launched over the home stack, so return home.
task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
return;
- } else if (focusedStack != null && focusedStack != task.getStack() &&
- focusedStack.isActivityTypeAssistant()) {
+ } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) {
// Task was launched over the assistant stack, so return there
task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
return;
@@ -1739,13 +1750,10 @@ class ActivityStarter {
// desires.
if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
- ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
- intentActivity.getTask());
if (intentActivity.frontOfTask) {
intentActivity.getTask().setIntent(mStartActivity);
}
- intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
- mStartActivity.launchedFromPackage);
+ deliverNewIntent(intentActivity);
} else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
// In this case we are launching the root activity of the task, but with a
// different intent. We should start a new instance on top.
@@ -1779,7 +1787,7 @@ class ActivityStarter {
}
private int setTaskFromReuseOrCreateNewTask(
- TaskRecord taskToAffiliate, int preferredLaunchStackId, ActivityStack topStack) {
+ TaskRecord taskToAffiliate, ActivityStack topStack) {
mTargetStack = computeStackFocus(
mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions);
@@ -1793,15 +1801,8 @@ class ActivityStarter {
mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
- if (mLaunchBounds != null) {
- final int stackId = mTargetStack.mStackId;
- if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mService.resizeStack(
- stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
- } else {
- mStartActivity.getTask().updateOverrideConfiguration(mLaunchBounds);
- }
- }
+ updateBounds(mStartActivity.getTask(), mLaunchBounds);
+
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
+ " in new task " + mStartActivity.getTask());
} else {
@@ -1821,8 +1822,10 @@ class ActivityStarter {
// If stack id is specified in activity options, usually it means that activity is
// launched not from currently focused stack (e.g. from SysUI or from shell) - in
// that case we check the target stack.
+ // TODO: Not sure I understand the value or use of the commented out code and the
+ // comment above. See if this causes any issues and why...
updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags,
- preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : topStack);
+ /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack);
}
if (mDoResume) {
mTargetStack.moveToFront("reuseOrNewTask");
@@ -1830,6 +1833,17 @@ class ActivityStarter {
return START_SUCCESS;
}
+ private void deliverNewIntent(ActivityRecord activity) {
+ if (mIntentDelivered) {
+ return;
+ }
+
+ ActivityStack.logStartActivity(AM_NEW_INTENT, activity, activity.getTask());
+ activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
+ mStartActivity.launchedFromPackage);
+ mIntentDelivered = true;
+ }
+
private int setTaskFromSourceRecord() {
if (mService.mLockTaskController.isLockTaskModeViolation(mSourceRecord.getTask())) {
Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
@@ -1867,8 +1881,8 @@ class ActivityStarter {
if (mTargetStack == null) {
mTargetStack = sourceStack;
} else if (mTargetStack != sourceStack) {
- sourceTask.reparent(mTargetStack.mStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
- !ANIMATE, DEFER_RESUME, "launchToSide");
+ sourceTask.reparent(mTargetStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+ DEFER_RESUME, "launchToSide");
}
final TaskRecord topTask = mTargetStack.topTask();
@@ -1886,7 +1900,7 @@ class ActivityStarter {
mKeepCurTransition = true;
if (top != null) {
ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask());
- top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+ deliverNewIntent(top);
// For paranoia, make sure we have correctly resumed the top activity.
mTargetStack.mLastPausedActivity = null;
if (mDoResume) {
@@ -1905,7 +1919,7 @@ class ActivityStarter {
task.moveActivityToFrontLocked(top);
top.updateOptionsLocked(mOptions);
ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, task);
- top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+ deliverNewIntent(top);
mTargetStack.mLastPausedActivity = null;
if (mDoResume) {
mSupervisor.resumeFocusedStackTopActivityLocked();
@@ -1941,14 +1955,12 @@ class ActivityStarter {
|| mLaunchSingleTop || mLaunchSingleTask) {
mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
mStartActivity.appTimeTracker, "inTaskToFront");
- ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask());
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
// We don't need to start a new activity, and the client said not to do
// anything if that is the case, so this is it!
return START_RETURN_INTENT_TO_CALLER;
}
- top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
- mStartActivity.launchedFromPackage);
+ deliverNewIntent(top);
return START_DELIVERED_TO_TOP;
}
}
@@ -1963,17 +1975,15 @@ class ActivityStarter {
}
if (mLaunchBounds != null) {
- mInTask.updateOverrideConfiguration(mLaunchBounds);
- int stackId = mInTask.getLaunchStackId();
- if (stackId != mInTask.getStackId()) {
- mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+ // TODO: Shouldn't we already know what stack to use by the time we get here?
+ ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP);
+ if (stack != mInTask.getStack()) {
+ mInTask.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
DEFER_RESUME, "inTaskToFront");
- stackId = mInTask.getStackId();
mTargetStack = mInTask.getStack();
}
- if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
- }
+
+ updateBounds(mInTask, mLaunchBounds);
}
mTargetStack.moveTaskToFrontLocked(
@@ -1986,6 +1996,19 @@ class ActivityStarter {
return START_SUCCESS;
}
+ void updateBounds(TaskRecord task, Rect bounds) {
+ if (bounds == null) {
+ return;
+ }
+
+ final int stackId = task.getStackId();
+ if (StackId.resizeStackWithLaunchBounds(stackId)) {
+ mService.resizeStack(stackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+ } else {
+ task.updateOverrideConfiguration(bounds);
+ }
+ }
+
private void setTaskToCurrentTopOrCreateNewTask() {
mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags,
mOptions);
@@ -2079,20 +2102,21 @@ class ActivityStarter {
return mSupervisor.mFocusedStack;
}
- if (mSourceDisplayId != DEFAULT_DISPLAY) {
+ if (mPreferredDisplayId != DEFAULT_DISPLAY) {
// Try to put the activity in a stack on a secondary display.
- stack = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
+ stack = mSupervisor.getValidLaunchStackOnDisplay(mPreferredDisplayId, r);
if (stack == null) {
// If source display is not suitable - look for topmost valid stack in the system.
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
- "computeStackFocus: Can't launch on mSourceDisplayId=" + mSourceDisplayId
- + ", looking on all displays.");
- stack = mSupervisor.getNextValidLaunchStackLocked(r, mSourceDisplayId);
+ "computeStackFocus: Can't launch on mPreferredDisplayId="
+ + mPreferredDisplayId + ", looking on all displays.");
+ stack = mSupervisor.getNextValidLaunchStackLocked(r, mPreferredDisplayId);
}
}
if (stack == null) {
// We first try to put the task in the first dynamic stack on home display.
- final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
+ final ArrayList<ActivityStack> homeDisplayStacks =
+ mSupervisor.getStacksOnDefaultDisplay();
for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
stack = homeDisplayStacks.get(stackNdx);
if (isDynamicStack(stack.mStackId)) {
@@ -2102,10 +2126,7 @@ class ActivityStarter {
}
}
// If there is no suitable dynamic stack then we figure out which static stack to use.
- final int stackId = task != null ? task.getLaunchStackId() :
- bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
- FULLSCREEN_WORKSPACE_STACK_ID;
- stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+ stack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
}
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
+ r + " stackId=" + stack.mStackId);
@@ -2113,37 +2134,40 @@ class ActivityStarter {
}
/** Check if provided activity record can launch in currently focused stack. */
+ // TODO: This method can probably be consolidated into getLaunchStack() below.
private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
final ActivityStack focusedStack = mSupervisor.mFocusedStack;
- final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
final boolean canUseFocusedStack;
- switch (focusedStackId) {
- case FULLSCREEN_WORKSPACE_STACK_ID:
- // The fullscreen stack can contain any task regardless of if the task is resizeable
- // or not. So, we let the task go in the fullscreen task if it is the focus stack.
- canUseFocusedStack = true;
- break;
- case ASSISTANT_STACK_ID:
- canUseFocusedStack = r.isActivityTypeAssistant();
- break;
- case DOCKED_STACK_ID:
- // Any activity which supports split screen can go in the docked stack.
- canUseFocusedStack = r.supportsSplitScreen();
- break;
- case FREEFORM_WORKSPACE_STACK_ID:
- // Any activity which supports freeform can go in the freeform stack.
- canUseFocusedStack = r.supportsFreeform();
- break;
- default:
- // Dynamic stacks behave similarly to the fullscreen stack and can contain any
- // resizeable task.
- canUseFocusedStack = isDynamicStack(focusedStackId)
- && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
+ final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
+ if (focusedStack.isActivityTypeAssistant()) {
+ canUseFocusedStack = r.isActivityTypeAssistant();
+ } else {
+ switch (focusedStack.getWindowingMode()) {
+ case WINDOWING_MODE_FULLSCREEN:
+ // The fullscreen stack can contain any task regardless of if the task is
+ // resizeable or not. So, we let the task go in the fullscreen task if it is the
+ // focus stack.
+ canUseFocusedStack = true;
+ break;
+ case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+ case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
+ // Any activity which supports split screen can go in the docked stack.
+ canUseFocusedStack = r.supportsSplitScreenWindowingMode();
+ break;
+ case WINDOWING_MODE_FREEFORM:
+ // Any activity which supports freeform can go in the freeform stack.
+ canUseFocusedStack = r.supportsFreeform();
+ break;
+ default:
+ // Dynamic stacks behave similarly to the fullscreen stack and can contain any
+ // resizeable task.
+ canUseFocusedStack = isDynamicStack(focusedStackId)
+ && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
+ }
}
-
return canUseFocusedStack && !newTask
- // We strongly prefer to launch activities on the same display as their source.
- && (mSourceDisplayId == focusedStack.mDisplayId);
+ // Using the focus stack isn't important enough to override the preferred display.
+ && (mPreferredDisplayId == focusedStack.mDisplayId);
}
private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
@@ -2153,54 +2177,16 @@ class ActivityStarter {
return mReuseTask.getStack();
}
- // If the activity is of a specific type, return the associated stack, creating it if
- // necessary
- if (r.isActivityTypeHome()) {
- return mSupervisor.mHomeStack;
- }
- if (r.isActivityTypeRecents()) {
- return mSupervisor.getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
- }
- if (r.isActivityTypeAssistant()) {
- return mSupervisor.getStack(ASSISTANT_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
- }
-
- final int launchDisplayId =
- (aOptions != null) ? aOptions.getLaunchDisplayId() : INVALID_DISPLAY;
-
- final int launchStackId =
- (aOptions != null) ? aOptions.getLaunchStackId() : INVALID_STACK_ID;
-
- if (launchStackId != INVALID_STACK_ID && launchDisplayId != INVALID_DISPLAY) {
- throw new IllegalArgumentException(
- "Stack and display id can't be set at the same time.");
- }
+ final int vrDisplayId = mUsingVr2dDisplay ? mPreferredDisplayId : INVALID_DISPLAY;
+ final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
+ vrDisplayId);
- if (isValidLaunchStackId(launchStackId, launchDisplayId, r)) {
- return mSupervisor.getStack(launchStackId, CREATE_IF_NEEDED, ON_TOP);
- }
- if (launchStackId == DOCKED_STACK_ID) {
- // The preferred launch stack is the docked stack, but it isn't a valid launch stack
- // for this activity, so we put the activity in the fullscreen stack.
- return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
- }
- if (launchDisplayId != INVALID_DISPLAY) {
- // Stack id has higher priority than display id.
- return mSupervisor.getValidLaunchStackOnDisplay(launchDisplayId, r);
- }
-
- // If we are using Vr2d display, find the virtual display stack.
- if (mUsingVr2dDisplay) {
- ActivityStack as = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
- if (DEBUG_STACK) {
- Slog.v(TAG, "Launch stack for app: " + r.toString() +
- ", on virtual display stack:" + as.toString());
- }
- return as;
+ if (launchStack != null) {
+ return launchStack;
}
if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
- || mSourceDisplayId != DEFAULT_DISPLAY) {
+ || mPreferredDisplayId != DEFAULT_DISPLAY) {
return null;
}
// Otherwise handle adjacent launch.
@@ -2222,15 +2208,16 @@ class ActivityStarter {
if (parentStack != null && parentStack.isDockedStack()) {
// If parent was in docked stack, the natural place to launch another activity
// will be fullscreen, so it can appear alongside the docked window.
- return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED,
- ON_TOP);
+ final int activityType = mSupervisor.resolveActivityType(r, mOptions, task);
+ return parentStack.getDisplay().getOrCreateStack(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, activityType, ON_TOP);
} else {
// If the parent is not in the docked stack, we check if there is docked window
// and if yes, we will launch into that stack. If not, we just put the new
// activity into parent's stack, because we can't find a better place.
- final ActivityStack dockedStack = mSupervisor.getStack(DOCKED_STACK_ID);
- if (dockedStack != null
- && dockedStack.shouldBeVisible(r) == STACK_INVISIBLE) {
+ final ActivityStack dockedStack = mSupervisor.getDefaultDisplay().getStack(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+ if (dockedStack != null && !dockedStack.shouldBeVisible(r)) {
// There is a docked stack, but it isn't visible, so we can't launch into that.
return null;
} else {
@@ -2240,39 +2227,11 @@ class ActivityStarter {
}
}
- boolean isValidLaunchStackId(int stackId, int displayId, ActivityRecord r) {
- switch (stackId) {
- case INVALID_STACK_ID:
- case HOME_STACK_ID:
- return false;
- case FULLSCREEN_WORKSPACE_STACK_ID:
- return true;
- case FREEFORM_WORKSPACE_STACK_ID:
- return r.supportsFreeform();
- case DOCKED_STACK_ID:
- return r.supportsSplitScreen();
- case PINNED_STACK_ID:
- return r.supportsPictureInPicture();
- case RECENTS_STACK_ID:
- return r.isActivityTypeRecents();
- case ASSISTANT_STACK_ID:
- return r.isActivityTypeAssistant();
- default:
- if (StackId.isDynamicStack(stackId)) {
- return r.canBeLaunchedOnDisplay(displayId);
- }
- Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
- return false;
- }
- }
-
- Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
+ private Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
Rect newBounds = null;
- if (options != null && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
- if (mSupervisor.canUseActivityOptionsLaunchBounds(
- options, options.getLaunchStackId())) {
- newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
- }
+ if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
+ && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
+ newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
}
return newBounds;
}
@@ -2281,15 +2240,6 @@ class ActivityStarter {
mWindowManager = wm;
}
- void removePendingActivityLaunchesLocked(ActivityStack stack) {
- for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
- PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
- if (pal.stack == stack) {
- mPendingActivityLaunches.remove(palNdx);
- }
- }
- }
-
static boolean isDocumentLaunchesIntoExisting(int flags) {
return (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
(flags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0;
diff --git a/com/android/server/am/AppErrors.java b/com/android/server/am/AppErrors.java
index 440b3d3b..fe380970 100644
--- a/com/android/server/am/AppErrors.java
+++ b/com/android/server/am/AppErrors.java
@@ -507,7 +507,7 @@ class AppErrors {
// launching the report UI under a different user.
app.errorReportReceiver = null;
- for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+ for (int userId : mService.mUserController.getCurrentProfileIds()) {
if (app.userId == userId) {
app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
mContext, app.info.packageName, app.info.flags);
@@ -728,7 +728,7 @@ class AppErrors {
boolean isBackground = (UserHandle.getAppId(proc.uid)
>= Process.FIRST_APPLICATION_UID
&& proc.pid != MY_PID);
- for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+ for (int userId : mService.mUserController.getCurrentProfileIds()) {
isBackground &= (proc.userId != userId);
}
if (isBackground && !showBackground) {
diff --git a/com/android/server/am/BatteryStatsService.java b/com/android/server/am/BatteryStatsService.java
index 3105e37f..7c9cd00e 100644
--- a/com/android/server/am/BatteryStatsService.java
+++ b/com/android/server/am/BatteryStatsService.java
@@ -220,7 +220,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
// Shutdown the thread we made.
mWorker.shutdown();
}
-
+
public static IBatteryStats getService() {
if (sService != null) {
return sService;
@@ -300,10 +300,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
// TODO: remove this once we figure out properly where and how
// PROCESS_EVENT = 1112
- // EVENT SUBTYPE: START = 1
- // KEY_NAME: 1
+ // KEY_STATE = 1
+ // KEY_PACKAGE_NAME: 1002
// KEY_UID: 2
- StatsLog.writeArray(1112, 1, 1, name, 2, uid);
+ StatsLog.writeArray(1112, 1, 1, 1002, name, 2, uid);
}
}
@@ -313,10 +313,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
// TODO: remove this once we figure out properly where and how
// PROCESS_EVENT = 1112
- // EVENT SUBTYPE: CRASH = 2
- // KEY_NAME: 1
+ // KEY_STATE = 1
+ // KEY_PACKAGE_NAME: 1002
// KEY_UID: 2
- StatsLog.writeArray(1112, 2, 1, name, 2, uid);
+ StatsLog.writeArray(1112, 1, 2, 1002, name, 2, uid);
}
}
@@ -550,10 +550,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub
synchronized (mStats) {
mStats.noteScreenStateLocked(state);
// TODO: remove this once we figure out properly where and how
- // SCREEN_EVENT = 1003
- // State key: 1
+ // SCREEN_EVENT = 2
+ // KEY_STATE: 1
// State value: state. We can change this to our own def later.
- StatsLog.writeArray(1003, 1, state);
+ StatsLog.writeArray(2, 1, state);
}
if (DBG) Slog.d(TAG, "end noteScreenState");
}
@@ -564,14 +564,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.noteScreenBrightnessLocked(brightness);
}
}
-
+
public void noteUserActivity(int uid, int event) {
enforceCallingPermission();
synchronized (mStats) {
mStats.noteUserActivityLocked(uid, event);
}
}
-
+
public void noteWakeUp(String reason, int reasonUid) {
enforceCallingPermission();
synchronized (mStats) {
@@ -611,21 +611,21 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.notePhoneOnLocked();
}
}
-
+
public void notePhoneOff() {
enforceCallingPermission();
synchronized (mStats) {
mStats.notePhoneOffLocked();
}
}
-
+
public void notePhoneSignalStrength(SignalStrength signalStrength) {
enforceCallingPermission();
synchronized (mStats) {
mStats.notePhoneSignalStrengthLocked(signalStrength);
}
}
-
+
public void notePhoneDataConnectionState(int dataType, boolean hasData) {
enforceCallingPermission();
synchronized (mStats) {
@@ -647,7 +647,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.noteWifiOnLocked();
}
}
-
+
public void noteWifiOff() {
enforceCallingPermission();
synchronized (mStats) {
@@ -806,7 +806,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mStats.noteFullWifiLockAcquiredLocked(uid);
}
}
-
+
public void noteFullWifiLockReleased(int uid) {
enforceCallingPermission();
synchronized (mStats) {
@@ -1043,7 +1043,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
});
});
}
-
+
public long getAwakeTimeBattery() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BATTERY_STATS, null);
@@ -1186,6 +1186,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
int flags = 0;
boolean useCheckinFormat = false;
+ boolean toProto = false;
boolean isRealCheckin = false;
boolean noOutput = false;
boolean writeData = false;
@@ -1212,6 +1213,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if ("-c".equals(arg)) {
useCheckinFormat = true;
flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
+ } else if ("--proto".equals(arg)) {
+ toProto = true;
} else if ("--charged".equals(arg)) {
flags |= BatteryStats.DUMP_CHARGED_ONLY;
} else if ("--daily".equals(arg)) {
@@ -1304,7 +1307,45 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
- if (useCheckinFormat) {
+ if (toProto) {
+ List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+ PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
+ if (isRealCheckin) {
+ // For a real checkin, first we want to prefer to use the last complete checkin
+ // file if there is one.
+ synchronized (mStats.mCheckinFile) {
+ if (mStats.mCheckinFile.exists()) {
+ try {
+ byte[] raw = mStats.mCheckinFile.readFully();
+ if (raw != null) {
+ Parcel in = Parcel.obtain();
+ in.unmarshall(raw, 0, raw.length);
+ in.setDataPosition(0);
+ BatteryStatsImpl checkinStats = new BatteryStatsImpl(
+ null, mStats.mHandler, null, mUserManagerUserInfoProvider);
+ checkinStats.readSummaryFromParcel(in);
+ in.recycle();
+ checkinStats.dumpProtoLocked(mContext, fd, apps, flags,
+ historyStart);
+ mStats.mCheckinFile.delete();
+ return;
+ }
+ } catch (IOException | ParcelFormatException e) {
+ Slog.w(TAG, "Failure reading checkin file "
+ + mStats.mCheckinFile.getBaseFile(), e);
+ }
+ }
+ }
+ }
+ if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid());
+ synchronized (mStats) {
+ mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart);
+ if (writeData) {
+ mStats.writeAsyncLocked();
+ }
+ }
+ if (DBG) Slog.d(TAG, "end dumpProtoLocked");
+ } else if (useCheckinFormat) {
List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
if (isRealCheckin) {
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index cea80c8d..5c48f90d 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -19,12 +19,15 @@ package com.android.server.am;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.proto.KeyguardControllerProto.KEYGUARD_OCCLUDED;
+import static com.android.server.am.proto.KeyguardControllerProto.KEYGUARD_SHOWING;
import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
@@ -38,6 +41,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.wm.WindowManagerService;
@@ -66,6 +70,7 @@ class KeyguardController {
private int mBeforeUnoccludeTransit;
private int mVisibilityTransactionDepth;
private SleepToken mSleepToken;
+ private int mSecondaryDisplayShowing = INVALID_DISPLAY;
KeyguardController(ActivityManagerService service,
ActivityStackSupervisor stackSupervisor) {
@@ -78,10 +83,12 @@ class KeyguardController {
}
/**
- * @return true if Keyguard is showing, not going away, and not being occluded, false otherwise
+ * @return true if Keyguard is showing, not going away, and not being occluded on the given
+ * display, false otherwise
*/
- boolean isKeyguardShowing() {
- return mKeyguardShowing && !mKeyguardGoingAway && !mOccluded;
+ boolean isKeyguardShowing(int displayId) {
+ return mKeyguardShowing && !mKeyguardGoingAway &&
+ (displayId == DEFAULT_DISPLAY ? !mOccluded : displayId == mSecondaryDisplayShowing);
}
/**
@@ -94,15 +101,19 @@ class KeyguardController {
/**
* Update the Keyguard showing state.
*/
- void setKeyguardShown(boolean showing) {
- if (showing == mKeyguardShowing) {
+ void setKeyguardShown(boolean showing, int secondaryDisplayShowing) {
+ boolean showingChanged = showing != mKeyguardShowing;
+ if (!showingChanged && secondaryDisplayShowing == mSecondaryDisplayShowing) {
return;
}
mKeyguardShowing = showing;
- dismissDockedStackIfNeeded();
- if (showing) {
- setKeyguardGoingAway(false);
- mDismissalRequested = false;
+ mSecondaryDisplayShowing = secondaryDisplayShowing;
+ if (showingChanged) {
+ dismissDockedStackIfNeeded();
+ if (showing) {
+ setKeyguardGoingAway(false);
+ mDismissalRequested = false;
+ }
}
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
updateKeyguardSleepToken();
@@ -331,15 +342,19 @@ class KeyguardController {
// show on top of the lock screen. In this can we want to dismiss the docked
// stack since it will be complicated/risky to try to put the activity on top
// of the lock screen in the right fullscreen configuration.
- mStackSupervisor.moveTasksToFullscreenStackLocked(DOCKED_STACK_ID,
- mStackSupervisor.mFocusedStack.getStackId() == DOCKED_STACK_ID);
+ final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+ if (stack == null) {
+ return;
+ }
+ mStackSupervisor.moveTasksToFullscreenStackLocked(stack,
+ mStackSupervisor.mFocusedStack == stack);
}
}
private void updateKeyguardSleepToken() {
- if (mSleepToken == null && isKeyguardShowing()) {
+ if (mSleepToken == null && isKeyguardShowing(DEFAULT_DISPLAY)) {
mSleepToken = mService.acquireSleepToken("Keyguard", DEFAULT_DISPLAY);
- } else if (mSleepToken != null && !isKeyguardShowing()) {
+ } else if (mSleepToken != null && !isKeyguardShowing(DEFAULT_DISPLAY)) {
mSleepToken.release();
mSleepToken = null;
}
@@ -354,4 +369,11 @@ class KeyguardController {
pw.println(prefix + " mDismissalRequested=" + mDismissalRequested);
pw.println(prefix + " mVisibilityTransactionDepth=" + mVisibilityTransactionDepth);
}
+
+ void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(KEYGUARD_SHOWING, mKeyguardShowing);
+ proto.write(KEYGUARD_OCCLUDED, mOccluded);
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index d8706bcc..72b5de88 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -25,12 +25,14 @@ import static android.app.StatusBarManager.DISABLE_HOME;
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.app.StatusBarManager.DISABLE_NONE;
import static android.app.StatusBarManager.DISABLE_RECENT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.content.Context.STATUS_BAR_SERVICE;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_CURRENT;
import static android.provider.Settings.Secure.LOCK_TO_APP_EXIT_LOCKED;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -55,7 +57,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Slog;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
@@ -65,6 +69,7 @@ import com.android.server.wm.WindowManagerService;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* Helper class that deals with all things related to task locking. This includes the screen pinning
@@ -123,6 +128,11 @@ public class LockTaskController {
private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>();
/**
+ * Packages that are allowed to be launched into the lock task mode for each user.
+ */
+ private final SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
+
+ /**
* Store the current lock task mode. Possible values:
* {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
* {@link ActivityManager#LOCK_TASK_MODE_PINNED}
@@ -159,8 +169,8 @@ public class LockTaskController {
}
/**
- * @return whether the given task can be moved to the back of the stack. Locked tasks cannot be
- * moved to the back of the stack.
+ * @return whether the given task is locked at the moment. Locked tasks cannot be moved to the
+ * back of the stack.
*/
boolean checkLockedTask(TaskRecord task) {
if (mLockTaskModeTasks.contains(task)) {
@@ -422,8 +432,8 @@ public class LockTaskController {
mSupervisor.resumeFocusedStackTopActivityLocked();
mWindowManager.executeAppTransition();
} else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
- mSupervisor.handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY,
- task.getStackId(), true /* forceNonResizable */);
+ mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
+ DEFAULT_DISPLAY, task.getStackId(), true /* forceNonResizable */);
}
}
@@ -452,29 +462,35 @@ public class LockTaskController {
}
/**
- * Called when the list of packages whitelisted for lock task mode is changed. Any currently
- * locked tasks that got removed from the whitelist will be finished.
+ * Update packages that are allowed to be launched in lock task mode.
+ * @param userId Which user this whitelist is associated with
+ * @param packages The whitelist of packages allowed in lock task mode
+ * @see #mLockTaskPackages
*/
- // TODO: Missing unit tests
- void onLockTaskPackagesUpdated() {
- boolean didSomething = false;
+ void updateLockTaskPackages(int userId, String[] packages) {
+ mLockTaskPackages.put(userId, packages);
+
+ boolean taskChanged = false;
for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord lockedTask = mLockTaskModeTasks.get(taskNdx);
- final boolean wasWhitelisted =
- (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) ||
- (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED);
+ final boolean wasWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+ || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED;
lockedTask.setLockTaskAuth();
- final boolean isWhitelisted =
- (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) ||
- (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED);
- if (wasWhitelisted && !isWhitelisted) {
- // Lost whitelisting authorization. End it now.
- if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
- lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
- removeLockedTask(lockedTask);
- lockedTask.performClearTaskLocked();
- didSomething = true;
+ final boolean isWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+ || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED;
+
+ if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED
+ || lockedTask.userId != userId
+ || !wasWhitelisted || isWhitelisted) {
+ continue;
}
+
+ // Terminate locked tasks that have recently lost whitelist authorization.
+ if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
+ lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
+ removeLockedTask(lockedTask);
+ lockedTask.performClearTaskLocked();
+ taskChanged = true;
}
for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) {
@@ -484,22 +500,40 @@ public class LockTaskController {
stack.onLockTaskPackagesUpdatedLocked();
}
}
+
final ActivityRecord r = mSupervisor.topRunningActivityLocked();
- final TaskRecord task = r != null ? r.getTask() : null;
- if (mLockTaskModeTasks.isEmpty() && task != null
+ final TaskRecord task = (r != null) ? r.getTask() : null;
+ if (mLockTaskModeTasks.isEmpty() && task!= null
&& task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) {
// This task must have just been authorized.
if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK,
"onLockTaskPackagesUpdated: starting new locktask task=" + task);
- setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated",
- false);
- didSomething = true;
+ setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated", false);
+ taskChanged = true;
}
- if (didSomething) {
+
+ if (taskChanged) {
mSupervisor.resumeFocusedStackTopActivityLocked();
}
}
+ boolean isPackageWhitelisted(int userId, String pkg) {
+ if (pkg == null) {
+ return false;
+ }
+ String[] whitelist;
+ whitelist = mLockTaskPackages.get(userId);
+ if (whitelist == null) {
+ return false;
+ }
+ for (String whitelistedPkg : whitelist) {
+ if (pkg.equals(whitelistedPkg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* @return the topmost locked task
*/
@@ -556,8 +590,18 @@ public class LockTaskController {
}
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mLockTaskModeState=" + lockTaskModeToString());
- pw.println(" mLockTaskModeTasks" + mLockTaskModeTasks);
+ pw.println(prefix + "LockTaskController");
+ prefix = prefix + " ";
+ pw.println(prefix + "mLockTaskModeState=" + lockTaskModeToString());
+ pw.println(prefix + "mLockTaskModeTasks=");
+ for (int i = 0; i < mLockTaskModeTasks.size(); ++i) {
+ pw.println(prefix + " #" + i + " " + mLockTaskModeTasks.get(i));
+ }
+ pw.println(prefix + "mLockTaskPackages (userId:packages)=");
+ for (int i = 0; i < mLockTaskPackages.size(); ++i) {
+ pw.println(prefix + " u" + mLockTaskPackages.keyAt(i)
+ + ":" + Arrays.toString(mLockTaskPackages.valueAt(i)));
+ }
}
private String lockTaskModeToString() {
diff --git a/com/android/server/am/PendingIntentRecord.java b/com/android/server/am/PendingIntentRecord.java
index ee593866..7930f534 100644
--- a/com/android/server/am/PendingIntentRecord.java
+++ b/com/android/server/am/PendingIntentRecord.java
@@ -308,7 +308,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
boolean sendFinish = finishedReceiver != null;
int userId = key.userId;
if (userId == UserHandle.USER_CURRENT) {
- userId = owner.mUserController.getCurrentOrTargetUserIdLocked();
+ userId = owner.mUserController.getCurrentOrTargetUserId();
}
int res = 0;
switch (key.type) {
diff --git a/com/android/server/am/PinnedActivityStack.java b/com/android/server/am/PinnedActivityStack.java
index a601ee1c..27269791 100644
--- a/com/android/server/am/PinnedActivityStack.java
+++ b/com/android/server/am/PinnedActivityStack.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
import android.app.RemoteAction;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -32,15 +35,16 @@ import java.util.List;
class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
implements PinnedStackWindowListener {
- PinnedActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
- ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
- super(display, stackId, supervisor, recentTasks, onTop);
+ PinnedActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+ boolean onTop) {
+ super(display, stackId, supervisor, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, onTop);
}
@Override
PinnedStackWindowController createStackWindowController(int displayId, boolean onTop,
Rect outBounds) {
- return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds);
+ return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds,
+ mStackSupervisor.mWindowManager);
}
Rect getDefaultPictureInPictureBounds(float aspectRatio) {
diff --git a/com/android/server/am/ProcessStatsService.java b/com/android/server/am/ProcessStatsService.java
index 39aed7cf..effb86c1 100644
--- a/com/android/server/am/ProcessStatsService.java
+++ b/com/android/server/am/ProcessStatsService.java
@@ -23,11 +23,14 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.service.procstats.ProcessStatsProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.DumpUtils;
@@ -622,13 +625,17 @@ public final class ProcessStatsService extends IProcessStats.Stub {
long ident = Binder.clearCallingIdentity();
try {
- dumpInner(fd, pw, args);
+ if (args.length > 0 && "--proto".equals(args[0])) {
+ dumpProto(fd);
+ } else {
+ dumpInner(pw, args);
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- private void dumpInner(FileDescriptor fd, PrintWriter pw, String[] args) {
+ private void dumpInner(PrintWriter pw, String[] args) {
final long now = SystemClock.uptimeMillis();
boolean isCheckin = false;
@@ -1038,4 +1045,44 @@ public final class ProcessStatsService extends IProcessStats.Stub {
}
}
}
+
+ private void dumpAggregatedStats(ProtoOutputStream proto, int aggregateHours, long now) {
+ ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000
+ - (ProcessStats.COMMIT_PERIOD/2));
+ if (pfd == null) {
+ return;
+ }
+ ProcessStats stats = new ProcessStats(false);
+ InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ stats.read(stream);
+ if (stats.mReadError != null) {
+ return;
+ }
+ stats.toProto(proto, now);
+ }
+
+ private void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+ // dump current procstats
+ long nowToken = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_NOW);
+ long now;
+ synchronized (mAm) {
+ now = SystemClock.uptimeMillis();
+ mProcessStats.toProto(proto, now);
+ }
+ proto.end(nowToken);
+
+ // aggregated over last 3 hours procstats
+ long tokenOf3Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_3HRS);
+ dumpAggregatedStats(proto, 3, now);
+ proto.end(tokenOf3Hrs);
+
+ // aggregated over last 24 hours procstats
+ long tokenOf24Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_24HRS);
+ dumpAggregatedStats(proto, 24, now);
+ proto.end(tokenOf24Hrs);
+
+ proto.flush();
+ }
}
diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java
index 027dc086..ac85e6b1 100644
--- a/com/android/server/am/ServiceRecord.java
+++ b/com/android/server/am/ServiceRecord.java
@@ -517,11 +517,14 @@ final class ServiceRecord extends Binder {
} catch (PackageManager.NameNotFoundException e) {
}
}
- if (localForegroundNoti.getSmallIcon() == null) {
+ if (localForegroundNoti.getSmallIcon() == null
+ || nm.getNotificationChannel(localPackageName, appUid,
+ localForegroundNoti.getChannelId()) == null) {
// Notifications whose icon is 0 are defined to not show
// a notification, silently ignoring it. We don't want to
// just ignore it, we want to prevent the service from
// being foreground.
+ // Also every notification needs a channel.
throw new RuntimeException("invalid service notification: "
+ foregroundNoti);
}
diff --git a/com/android/server/am/TaskChangeNotificationController.java b/com/android/server/am/TaskChangeNotificationController.java
index 82971696..5a7e7ced 100644
--- a/com/android/server/am/TaskChangeNotificationController.java
+++ b/com/android/server/am/TaskChangeNotificationController.java
@@ -95,7 +95,8 @@ class TaskChangeNotificationController {
};
private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
- l.onActivityPinned((String) m.obj, m.arg1);
+ final ActivityRecord r = (ActivityRecord) m.obj;
+ l.onActivityPinned(r.packageName, r.userId, r.getTask().taskId, r.getStackId());
};
private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
@@ -278,10 +279,9 @@ class TaskChangeNotificationController {
}
/** Notifies all listeners when an Activity is pinned. */
- void notifyActivityPinned(String packageName, int taskId) {
+ void notifyActivityPinned(ActivityRecord r) {
mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
- final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
- taskId, 0, packageName);
+ final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG, r);
forAllLocalListeners(mNotifyActivityPinned, msg);
msg.sendToTarget();
}
diff --git a/com/android/server/am/TaskPersister.java b/com/android/server/am/TaskPersister.java
index 74c4826f..61994b55 100644
--- a/com/android/server/am/TaskPersister.java
+++ b/com/android/server/am/TaskPersister.java
@@ -54,9 +54,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
public class TaskPersister {
@@ -472,8 +469,7 @@ public class TaskPersister {
final int taskId = task.taskId;
if (mStackSupervisor.anyTaskForIdLocked(taskId,
- MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
- INVALID_STACK_ID) != null) {
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
// Should not happen.
Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
} else if (userId != task.userId) {
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 0e651845..0d8df796 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -18,19 +18,19 @@ package com.android.server.am;
import static android.app.ActivityManager.RESIZE_MODE_FORCED;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
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.ACTIVITY_TYPE_UNDEFINED;
+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.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
@@ -65,6 +65,20 @@ import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP;
import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.proto.TaskRecordProto.ACTIVITIES;
+import static com.android.server.am.proto.TaskRecordProto.BOUNDS;
+import static com.android.server.am.proto.TaskRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.TaskRecordProto.FULLSCREEN;
+import static com.android.server.am.proto.TaskRecordProto.ID;
+import static com.android.server.am.proto.TaskRecordProto.LAST_NON_FULLSCREEN_BOUNDS;
+import static com.android.server.am.proto.TaskRecordProto.MIN_HEIGHT;
+import static com.android.server.am.proto.TaskRecordProto.MIN_WIDTH;
+import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY;
+import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY;
+import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE;
+import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE;
+import static com.android.server.am.proto.TaskRecordProto.STACK_ID;
+import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE;
import static java.lang.Integer.MAX_VALUE;
@@ -78,7 +92,6 @@ import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityManager;
-import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -95,6 +108,7 @@ import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.util.DisplayMetrics;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
@@ -508,8 +522,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
updateOverrideConfiguration(bounds);
if (getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
// re-restore the task so it can have the proper stack association.
- mService.mStackSupervisor.restoreRecentTaskLocked(this,
- FREEFORM_WORKSPACE_STACK_ID);
+ mService.mStackSupervisor.restoreRecentTaskLocked(this, null);
}
return true;
}
@@ -559,36 +572,36 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
/**
* Convenience method to reparent a task to the top or bottom position of the stack.
*/
- boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
- boolean animate, boolean deferResume, String reason) {
- return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
- deferResume, true /* schedulePictureInPictureModeChange */, reason);
+ boolean reparent(ActivityStack preferredStack, boolean toTop,
+ @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+ String reason) {
+ return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate, deferResume,
+ true /* schedulePictureInPictureModeChange */, reason);
}
/**
* Convenience method to reparent a task to the top or bottom position of the stack, with
* an option to skip scheduling the picture-in-picture mode change.
*/
- boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
- boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange,
- String reason) {
- return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
+ boolean reparent(ActivityStack preferredStack, boolean toTop,
+ @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+ boolean schedulePictureInPictureModeChange, String reason) {
+ return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate,
deferResume, schedulePictureInPictureModeChange, reason);
}
- /**
- * Convenience method to reparent a task to a specific position of the stack.
- */
- boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
- boolean animate, boolean deferResume, String reason) {
- return reparent(preferredStackId, position, moveStackMode, animate, deferResume,
+ /** Convenience method to reparent a task to a specific position of the stack. */
+ boolean reparent(ActivityStack preferredStack, int position,
+ @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+ String reason) {
+ return reparent(preferredStack, position, moveStackMode, animate, deferResume,
true /* schedulePictureInPictureModeChange */, reason);
}
/**
* Reparents the task into a preferred stack, creating it if necessary.
*
- * @param preferredStackId the stack id of the target stack to move this task
+ * @param preferredStack the target stack to move this task
* @param position the position to place this task in the new stack
* @param animate whether or not we should wait for the new window created as a part of the
* reparenting to be drawn and animated in
@@ -602,13 +615,16 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
* @param reason the caller of this reparenting
* @return whether the task was reparented
*/
- boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
- boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange,
- String reason) {
+ // TODO: Inspect all call sites and change to just changing windowing mode of the stack vs.
+ // re-parenting the task. Can only be done when we are no longer using static stack Ids like
+ /** {@link ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} */
+ boolean reparent(ActivityStack preferredStack, int position,
+ @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+ boolean schedulePictureInPictureModeChange, String reason) {
final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
final WindowManagerService windowManager = mService.mWindowManager;
final ActivityStack sourceStack = getStack();
- final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStackId,
+ final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStack,
position == MAX_VALUE);
if (toStack == sourceStack) {
return false;
@@ -691,19 +707,22 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
toStack.prepareFreezingTaskBounds();
// Make sure the task has the appropriate bounds/size for the stack it is in.
+ final int toStackWindowingMode = toStack.getWindowingMode();
+ final boolean toStackSplitScreenPrimary =
+ toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
&& !Objects.equals(mBounds, toStack.mBounds)) {
kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
deferResume);
- } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
+ } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
Rect bounds = getLaunchBounds();
if (bounds == null) {
toStack.layoutTaskInStack(this, null);
bounds = mBounds;
}
kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
- } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
- if (stackId == DOCKED_STACK_ID && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
+ } else if (toStackSplitScreenPrimary || toStackWindowingMode == WINDOWING_MODE_PINNED) {
+ if (toStackSplitScreenPrimary && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
// Move recents to front so it is not behind home stack when going into docked
// mode
mService.mStackSupervisor.moveRecentsStackToFront(reason);
@@ -730,10 +749,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
// TODO: Handle incorrect request to move before the actual move, not after.
- supervisor.handleNonResizableTaskIfNeeded(this, preferredStackId, DEFAULT_DISPLAY, stackId);
+ final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenStack();
+ supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(),
+ DEFAULT_DISPLAY, stackId);
- boolean successful = (preferredStackId == stackId);
- if (successful && stackId == DOCKED_STACK_ID) {
+ boolean successful = (preferredStack == toStack);
+ if (successful && toStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
// If task moved to docked stack - show recents if needed.
mService.mWindowManager.showRecentApps(false /* fromHome */);
}
@@ -857,8 +878,13 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
mResizeMode = info.resizeMode;
mSupportsPictureInPicture = info.supportsPictureInPicture();
- mLockTaskMode = info.lockTaskLaunchMode;
mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
+ mLockTaskMode = info.lockTaskLaunchMode;
+ if (!mPrivileged && (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+ || mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+ // Non-priv apps are not allowed to use always or never, fall back to default
+ mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ }
setLockTaskAuth();
}
@@ -921,8 +947,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
}
- ActivityStack getStack() {
- return mStack;
+ <T extends ActivityStack> T getStack() {
+ return (T) mStack;
}
/**
@@ -1396,16 +1422,11 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
void setLockTaskAuth() {
- if (!mPrivileged &&
- (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS ||
- mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
- // Non-priv apps are not allowed to use always or never, fall back to default
- mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
- }
+ final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
switch (mLockTaskMode) {
case LOCK_TASK_LAUNCH_MODE_DEFAULT:
- mLockTaskAuth = isLockTaskWhitelistedLocked() ?
- LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
+ mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+ ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
break;
case LOCK_TASK_LAUNCH_MODE_NEVER:
@@ -1417,31 +1438,14 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
break;
case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:
- mLockTaskAuth = isLockTaskWhitelistedLocked() ?
- LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
+ mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+ ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
break;
}
if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "setLockTaskAuth: task=" + this +
" mLockTaskAuth=" + lockTaskAuthToString());
}
- private boolean isLockTaskWhitelistedLocked() {
- String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
- if (pkg == null) {
- return false;
- }
- String[] packages = mService.mLockTaskPackages.get(userId);
- if (packages == null) {
- return false;
- }
- for (int i = packages.length - 1; i >= 0; --i) {
- if (pkg.equals(packages[i])) {
- return true;
- }
- }
- return false;
- }
-
boolean isOverHomeStack() {
return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
}
@@ -1459,10 +1463,12 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
return isResizeable(true /* checkSupportsPip */);
}
- boolean supportsSplitScreen() {
+ @Override
+ public boolean supportsSplitScreenWindowingMode() {
// A task can not be docked even if it is considered resizeable because it only supports
// picture-in-picture mode but has a non-resizeable resizeMode
- return mService.mSupportsSplitScreenMultiWindow
+ return super.supportsSplitScreenWindowingMode()
+ && mService.mSupportsSplitScreenMultiWindow
&& (mService.mForceResizableActivities
|| (isResizeable(false /* checkSupportsPip */)
&& !ActivityInfo.isPreserveOrientationMode(mResizeMode)));
@@ -2097,39 +2103,16 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
}
}
- /**
- * Returns the correct stack to use based on task type and currently set bounds,
- * regardless of the focused stack and current stack association of the task.
- * The task will be moved (and stack focus changed) later if necessary.
- */
- int getLaunchStackId() {
- if (isActivityTypeRecents()) {
- return RECENTS_STACK_ID;
- }
- if (isActivityTypeHome()) {
- return HOME_STACK_ID;
- }
- if (isActivityTypeAssistant()) {
- return ASSISTANT_STACK_ID;
- }
- if (mBounds != null) {
- return FREEFORM_WORKSPACE_STACK_ID;
- }
- return FULLSCREEN_WORKSPACE_STACK_ID;
- }
-
/** Returns the bounds that should be used to launch this task. */
private Rect getLaunchBounds() {
if (mStack == null) {
return null;
}
- final int stackId = mStack.mStackId;
- if (stackId == HOME_STACK_ID
- || stackId == RECENTS_STACK_ID
- || stackId == ASSISTANT_STACK_ID
- || stackId == FULLSCREEN_WORKSPACE_STACK_ID
- || (stackId == DOCKED_STACK_ID && !isResizeable())) {
+ final int windowingMode = getWindowingMode();
+ if (!isActivityTypeStandardOrUndefined()
+ || windowingMode == WINDOWING_MODE_FULLSCREEN
+ || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) {
return isResizeable() ? mStack.mBounds : null;
} else if (!getWindowConfiguration().persistTaskBounds()) {
return mStack.mBounds;
@@ -2275,4 +2258,34 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi
stringName = sb.toString();
return toString();
}
+
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ super.writeToProto(proto, CONFIGURATION_CONTAINER);
+ proto.write(ID, taskId);
+ for (int i = mActivities.size() - 1; i >= 0; i--) {
+ ActivityRecord activity = mActivities.get(i);
+ activity.writeToProto(proto, ACTIVITIES);
+ }
+ proto.write(STACK_ID, mStack.mStackId);
+ if (mLastNonFullscreenBounds != null) {
+ mLastNonFullscreenBounds.writeToProto(proto, LAST_NON_FULLSCREEN_BOUNDS);
+ }
+ if (realActivity != null) {
+ proto.write(REAL_ACTIVITY, realActivity.flattenToShortString());
+ }
+ if (origActivity != null) {
+ proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
+ }
+ proto.write(ACTIVITY_TYPE, getActivityType());
+ proto.write(RETURN_TO_TYPE, mTaskToReturnTo);
+ proto.write(RESIZE_MODE, mResizeMode);
+ proto.write(FULLSCREEN, mFullscreen);
+ if (mBounds != null) {
+ mBounds.writeToProto(proto, BOUNDS);
+ }
+ proto.write(MIN_WIDTH, mMinWidth);
+ proto.write(MIN_HEIGHT, mMinHeight);
+ proto.end(token);
+ }
}
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index db6bb7d8..5a295942 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -22,6 +22,7 @@ import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -30,14 +31,6 @@ import static com.android.server.am.ActivityManagerService.ALLOW_FULL_ONLY;
import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL;
import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE;
import static com.android.server.am.ActivityManagerService.MY_PID;
-import static com.android.server.am.ActivityManagerService.REPORT_LOCKED_BOOT_COMPLETE_MSG;
-import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
-import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_UNLOCK_MSG;
-import static com.android.server.am.ActivityManagerService.USER_SWITCH_CALLBACKS_TIMEOUT_MSG;
-import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
import static com.android.server.am.UserState.STATE_BOOTING;
import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
@@ -68,6 +61,7 @@ import android.os.IBinder;
import android.os.IProgressListener;
import android.os.IRemoteCallback;
import android.os.IUserManager;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -78,6 +72,7 @@ import android.os.UserManager;
import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
+import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
@@ -93,6 +88,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
+import com.android.server.SystemServiceManager;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -107,25 +103,64 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
+ *
+ * <p>This class use {@link #mLock} to synchronize access to internal state. Methods that require
+ * {@link #mLock} to be held should have "LU" suffix in the name.
+ *
+ * <p><strong>Important:</strong> Synchronized code, i.e. one executed inside a synchronized(mLock)
+ * block or inside LU method, should only access internal state of this class or make calls to
+ * other LU methods. Non-LU method calls or calls to external classes are discouraged as they
+ * may cause lock inversion.
*/
-class UserController {
+class UserController implements Handler.Callback {
private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
- // Maximum number of users we allow to be running at a time.
+ /**
+ * Maximum number of users we allow to be running at a time, including the system user and
+ * its profiles.
+ * Note changing this to 2 is not recommended, since that would mean, if the user uses
+ * work profile and then switch to a secondary user, then the work profile user would be killed,
+ * which should work fine, but aggressively killing the work profile user that has just been
+ * running could cause data loss. (Even without work profile, witching from secondary user A
+ * to secondary user B would cause similar issues on user B.)
+ *
+ * TODO: Consider adding or replacing with "MAX_RUNNING_*SECONDARY*_USERS", which is the max
+ * number of running *secondary, switchable* users.
+ */
static final int MAX_RUNNING_USERS = 3;
// Amount of time we wait for observers to handle a user switch before
// giving up on them and unfreezing the screen.
static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000;
+ // ActivityManager thread message constants
+ static final int REPORT_USER_SWITCH_MSG = 10;
+ static final int CONTINUE_USER_SWITCH_MSG = 20;
+ static final int USER_SWITCH_TIMEOUT_MSG = 30;
+ static final int START_PROFILES_MSG = 40;
+ static final int SYSTEM_USER_START_MSG = 50;
+ static final int SYSTEM_USER_CURRENT_MSG = 60;
+ static final int FOREGROUND_PROFILE_CHANGED_MSG = 70;
+ static final int REPORT_USER_SWITCH_COMPLETE_MSG = 80;
+ static final int USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 90;
+ static final int SYSTEM_USER_UNLOCK_MSG = 100;
+ static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
+ static final int START_USER_SWITCH_FG_MSG = 120;
+
+ // UI thread message constants
+ static final int START_USER_SWITCH_UI_MSG = 1000;
+
// If a callback wasn't called within USER_SWITCH_CALLBACKS_TIMEOUT_MS after
// USER_SWITCH_TIMEOUT_MS, an error is reported. Usually it indicates a problem in the observer
// when it never calls back.
private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
- private final Object mLock;
+ // Lock for internal state.
+ private final Object mLock = new Object();
+
private final Injector mInjector;
private final Handler mHandler;
+ private final Handler mUiHandler;
// Holds the current foreground user's id. Use mLock when updating
@GuardedBy("mLock")
@@ -161,7 +196,8 @@ class UserController {
/**
* Mapping from each known user ID to the profile group ID it is associated with.
*/
- private final SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+ @GuardedBy("mLock")
+ private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();
/**
* Registered observers of the user switching mechanics.
@@ -192,26 +228,25 @@ class UserController {
@VisibleForTesting
UserController(Injector injector) {
mInjector = injector;
- mLock = injector.getLock();
- mHandler = injector.getHandler();
+ mHandler = mInjector.getHandler(this);
+ mUiHandler = mInjector.getUiHandler(this);
// User 0 is the first and only user that runs at boot.
final UserState uss = new UserState(UserHandle.SYSTEM);
mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
mUserLru.add(UserHandle.USER_SYSTEM);
mLockPatternUtils = mInjector.getLockPatternUtils();
- updateStartedUserArrayLocked();
+ updateStartedUserArrayLU();
}
void finishUserSwitch(UserState uss) {
+ finishUserBoot(uss);
+ startProfiles();
synchronized (mLock) {
- finishUserBoot(uss);
-
- startProfilesLocked();
- stopRunningUsersLocked(MAX_RUNNING_USERS);
+ stopRunningUsersLU(MAX_RUNNING_USERS);
}
}
- void stopRunningUsersLocked(int maxRunningUsers) {
+ void stopRunningUsersLU(int maxRunningUsers) {
int num = mUserLru.size();
int i = 0;
while (num > maxRunningUsers && i < mUserLru.size()) {
@@ -240,7 +275,7 @@ class UserController {
continue;
}
// This is a user to be stopped.
- if (stopUsersLocked(oldUserId, false, null) != USER_OP_SUCCESS) {
+ if (stopUsersLU(oldUserId, false, null) != USER_OP_SUCCESS) {
num--;
}
num--;
@@ -258,55 +293,57 @@ class UserController {
Slog.d(TAG, "Finishing user boot " + userId);
synchronized (mLock) {
// Bail if we ended up with a stale user
- if (mStartedUsers.get(userId) != uss) return;
+ if (mStartedUsers.get(userId) != uss) {
+ return;
+ }
+ }
- // We always walk through all the user lifecycle states to send
- // consistent developer events. We step into RUNNING_LOCKED here,
- // but we might immediately step into RUNNING below if the user
- // storage is already unlocked.
- if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- // Do not report secondary users, runtime restarts or first boot/upgrade
- if (userId == UserHandle.USER_SYSTEM
- && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
- int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
- MetricsLogger.histogram(mInjector.getContext(),
- "framework_locked_boot_completed", uptimeSeconds);
- final int MAX_UPTIME_SECONDS = 120;
- if (uptimeSeconds > MAX_UPTIME_SECONDS) {
- Slog.wtf("SystemServerTiming",
- "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
- }
+ // We always walk through all the user lifecycle states to send
+ // consistent developer events. We step into RUNNING_LOCKED here,
+ // but we might immediately step into RUNNING below if the user
+ // storage is already unlocked.
+ if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ // Do not report secondary users, runtime restarts or first boot/upgrade
+ if (userId == UserHandle.USER_SYSTEM
+ && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
+ int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
+ MetricsLogger.histogram(mInjector.getContext(),
+ "framework_locked_boot_completed", uptimeSeconds);
+ final int MAX_UPTIME_SECONDS = 120;
+ if (uptimeSeconds > MAX_UPTIME_SECONDS) {
+ Slog.wtf("SystemServerTiming",
+ "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
}
+ }
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
- userId, 0));
- Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mInjector.broadcastIntentLocked(intent, null, resultTo, 0, null, null,
- new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
- AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
- }
-
- // We need to delay unlocking managed profiles until the parent user
- // is also unlocked.
- if (mInjector.getUserManager().isManagedProfile(userId)) {
- final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
- if (parent != null
- && isUserRunningLocked(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
- Slog.d(TAG, "User " + userId + " (parent " + parent.id
- + "): attempting unlock because parent is unlocked");
- maybeUnlockUser(userId);
- } else {
- String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
- Slog.d(TAG, "User " + userId + " (parent " + parentId
- + "): delaying unlock because parent is locked");
- }
- } else {
+ mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
+ userId, 0));
+ Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
+ new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
+ }
+
+ // We need to delay unlocking managed profiles until the parent user
+ // is also unlocked.
+ if (mInjector.getUserManager().isManagedProfile(userId)) {
+ final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+ if (parent != null
+ && isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
+ Slog.d(TAG, "User " + userId + " (parent " + parent.id
+ + "): attempting unlock because parent is unlocked");
maybeUnlockUser(userId);
+ } else {
+ String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
+ Slog.d(TAG, "User " + userId + " (parent " + parentId
+ + "): delaying unlock because parent is locked");
}
+ } else {
+ maybeUnlockUser(userId);
}
}
@@ -316,34 +353,30 @@ class UserController {
*/
private void finishUserUnlocking(final UserState uss) {
final int userId = uss.mHandle.getIdentifier();
- boolean proceedWithUnlock = false;
+ // Only keep marching forward if user is actually unlocked
+ if (!StorageManager.isUserKeyUnlocked(userId)) return;
synchronized (mLock) {
// Bail if we ended up with a stale user
if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
- // Only keep marching forward if user is actually unlocked
- if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
- if (uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- proceedWithUnlock = true;
+ // Do not proceed if unexpected state
+ if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+ return;
}
}
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ uss.mUnlockProgress.start();
- if (proceedWithUnlock) {
- uss.mUnlockProgress.start();
-
- // Prepare app storage before we go any further
- uss.mUnlockProgress.setProgress(5,
- mInjector.getContext().getString(R.string.android_start_title));
- mInjector.getUserManager().onBeforeUnlockUser(userId);
- uss.mUnlockProgress.setProgress(20);
+ // Prepare app storage before we go any further
+ uss.mUnlockProgress.setProgress(5,
+ mInjector.getContext().getString(R.string.android_start_title));
+ mInjector.getUserManager().onBeforeUnlockUser(userId);
+ uss.mUnlockProgress.setProgress(20);
- // Dispatch unlocked to system services; when fully dispatched,
- // that calls through to the next "unlocked" phase
- mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
- .sendToTarget();
- }
+ // Dispatch unlocked to system services; when fully dispatched,
+ // that calls through to the next "unlocked" phase
+ mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
+ .sendToTarget();
}
/**
@@ -352,63 +385,63 @@ class UserController {
*/
void finishUserUnlocked(final UserState uss) {
final int userId = uss.mHandle.getIdentifier();
+ // Only keep marching forward if user is actually unlocked
+ if (!StorageManager.isUserKeyUnlocked(userId)) return;
synchronized (mLock) {
// Bail if we ended up with a stale user
if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
- // Only keep marching forward if user is actually unlocked
- if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
- if (uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- uss.mUnlockProgress.finish();
-
- // Dispatch unlocked to external apps
- final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
- unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- unlockedIntent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- mInjector.broadcastIntentLocked(unlockedIntent, null, null, 0, null,
- null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
- userId);
-
- if (getUserInfo(userId).isManagedProfile()) {
- UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
- if (parent != null) {
- final Intent profileUnlockedIntent = new Intent(
- Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
- profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
- profileUnlockedIntent.addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ // Do not proceed if unexpected state
+ if (!uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
+ return;
+ }
+ }
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ uss.mUnlockProgress.finish();
+ // Dispatch unlocked to external apps
+ final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+ unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ unlockedIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
+ null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ userId);
+
+ if (getUserInfo(userId).isManagedProfile()) {
+ UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+ if (parent != null) {
+ final Intent profileUnlockedIntent = new Intent(
+ Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
+ profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
+ profileUnlockedIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
- mInjector.broadcastIntentLocked(profileUnlockedIntent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, SYSTEM_UID,
- parent.id);
- }
- }
+ mInjector.broadcastIntent(profileUnlockedIntent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, MY_PID, SYSTEM_UID,
+ parent.id);
+ }
+ }
- // Send PRE_BOOT broadcasts if user fingerprint changed; we
- // purposefully block sending BOOT_COMPLETED until after all
- // PRE_BOOT receivers are finished to avoid ANR'ing apps
- final UserInfo info = getUserInfo(userId);
- if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
- // Suppress double notifications for managed profiles that
- // were unlocked automatically as part of their parent user
- // being unlocked.
- final boolean quiet;
- if (info.isManagedProfile()) {
- quiet = !uss.tokenProvided
- || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
- } else {
- quiet = false;
- }
- mInjector.sendPreBootBroadcast(userId, quiet,
- () -> finishUserUnlockedCompleted(uss));
- } else {
- finishUserUnlockedCompleted(uss);
- }
+ // Send PRE_BOOT broadcasts if user fingerprint changed; we
+ // purposefully block sending BOOT_COMPLETED until after all
+ // PRE_BOOT receivers are finished to avoid ANR'ing apps
+ final UserInfo info = getUserInfo(userId);
+ if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
+ // Suppress double notifications for managed profiles that
+ // were unlocked automatically as part of their parent user
+ // being unlocked.
+ final boolean quiet;
+ if (info.isManagedProfile()) {
+ quiet = !uss.tokenProvided
+ || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
+ } else {
+ quiet = false;
}
+ mInjector.sendPreBootBroadcast(userId, quiet,
+ () -> finishUserUnlockedCompleted(uss));
+ } else {
+ finishUserUnlockedCompleted(uss);
}
}
@@ -417,60 +450,59 @@ class UserController {
synchronized (mLock) {
// Bail if we ended up with a stale user
if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
- final UserInfo userInfo = getUserInfo(userId);
- if (userInfo == null) {
- return;
- }
+ }
+ UserInfo userInfo = getUserInfo(userId);
+ if (userInfo == null) {
+ return;
+ }
+ // Only keep marching forward if user is actually unlocked
+ if (!StorageManager.isUserKeyUnlocked(userId)) return;
- // Only keep marching forward if user is actually unlocked
- if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
- // Remember that we logged in
- mInjector.getUserManager().onUserLoggedIn(userId);
-
- if (!userInfo.isInitialized()) {
- if (userId != UserHandle.USER_SYSTEM) {
- Slog.d(TAG, "Initializing user #" + userId);
- Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mInjector.broadcastIntentLocked(intent, null,
- new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered,
- boolean sticky, int sendingUser) {
- // Note: performReceive is called with mService lock held
- mInjector.getUserManager().makeInitialized(userInfo.id);
- }
- }, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, SYSTEM_UID, userId);
- }
- }
+ // Remember that we logged in
+ mInjector.getUserManager().onUserLoggedIn(userId);
- Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
- // Do not report secondary users, runtime restarts or first boot/upgrade
- if (userId == UserHandle.USER_SYSTEM
- && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
- int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
- MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
- uptimeSeconds);
+ if (!userInfo.isInitialized()) {
+ if (userId != UserHandle.USER_SYSTEM) {
+ Slog.d(TAG, "Initializing user #" + userId);
+ Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mInjector.broadcastIntent(intent, null,
+ new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser) {
+ // Note: performReceive is called with mService lock held
+ mInjector.getUserManager().makeInitialized(userInfo.id);
+ }
+ }, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, true, false, MY_PID, SYSTEM_UID, userId);
}
- final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
- bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- mInjector.broadcastIntentLocked(bootIntent, null, new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky, int sendingUser)
- throws RemoteException {
- Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
- }
- }, 0, null, null,
- new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
- AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
}
+
+ Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
+ // Do not report secondary users, runtime restarts or first boot/upgrade
+ if (userId == UserHandle.USER_SYSTEM
+ && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
+ int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
+ MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
+ uptimeSeconds);
+ }
+ final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+ bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mInjector.broadcastIntent(bootIntent, null, new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser)
+ throws RemoteException {
+ Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
+ }
+ }, 0, null, null,
+ new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
}
int restartUser(final int userId, final boolean foreground) {
@@ -499,35 +531,35 @@ class UserController {
if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
throw new IllegalArgumentException("Can't stop system user " + userId);
}
- mInjector.enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
+ enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLocked(userId, force, callback);
+ return stopUsersLU(userId, force, callback);
}
}
/**
* Stops the user along with its related users. The method calls
- * {@link #getUsersToStopLocked(int)} to determine the list of users that should be stopped.
+ * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
*/
- private int stopUsersLocked(final int userId, boolean force, final IStopUserCallback callback) {
+ private int stopUsersLU(final int userId, boolean force, final IStopUserCallback callback) {
if (userId == UserHandle.USER_SYSTEM) {
return USER_OP_ERROR_IS_SYSTEM;
}
- if (isCurrentUserLocked(userId)) {
+ if (isCurrentUserLU(userId)) {
return USER_OP_IS_CURRENT;
}
- int[] usersToStop = getUsersToStopLocked(userId);
+ int[] usersToStop = getUsersToStopLU(userId);
// If one of related users is system or current, no related users should be stopped
for (int i = 0; i < usersToStop.length; i++) {
int relatedUserId = usersToStop[i];
- if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLocked(relatedUserId)) {
+ if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked cannot stop related user "
+ relatedUserId);
// We still need to stop the requested user if it's a force stop.
if (force) {
Slog.i(TAG,
"Force stop user " + userId + ". Related users will not be stopped");
- stopSingleUserLocked(userId, callback);
+ stopSingleUserLU(userId, callback);
return USER_OP_SUCCESS;
}
return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -535,25 +567,22 @@ class UserController {
}
if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
for (int userIdToStop : usersToStop) {
- stopSingleUserLocked(userIdToStop, userIdToStop == userId ? callback : null);
+ stopSingleUserLU(userIdToStop, userIdToStop == userId ? callback : null);
}
return USER_OP_SUCCESS;
}
- private void stopSingleUserLocked(final int userId, final IStopUserCallback callback) {
+ private void stopSingleUserLU(final int userId, final IStopUserCallback callback) {
if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
final UserState uss = mStartedUsers.get(userId);
if (uss == null) {
// User is not started, nothing to do... but we do need to
// callback if requested.
if (callback != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- callback.userStopped(userId);
- } catch (RemoteException e) {
- }
+ mHandler.post(() -> {
+ try {
+ callback.userStopped(userId);
+ } catch (RemoteException e) {
}
});
}
@@ -568,10 +597,10 @@ class UserController {
&& uss.state != UserState.STATE_SHUTDOWN) {
uss.setState(UserState.STATE_STOPPING);
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- updateStartedUserArrayLocked();
+ updateStartedUserArrayLU();
- long ident = Binder.clearCallingIdentity();
- try {
+ // Post to handler to obtain amLock
+ mHandler.post(() -> {
// We are going to broadcast ACTION_USER_STOPPING and then
// once that is done send a final ACTION_SHUTDOWN and then
// stop the user.
@@ -584,31 +613,24 @@ class UserController {
@Override
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- finishUserStopping(userId, uss);
- }
- });
+ mHandler.post(() -> finishUserStopping(userId, uss));
}
};
+
// Clear broadcast queue for the user to avoid delivering stale broadcasts
- mInjector.clearBroadcastQueueForUserLocked(userId);
+ mInjector.clearBroadcastQueueForUser(userId);
// Kick things off.
- mInjector.broadcastIntentLocked(stoppingIntent,
+ mInjector.broadcastIntent(stoppingIntent,
null, stoppingReceiver, 0, null, null,
new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ });
}
}
void finishUserStopping(final int userId, final UserState uss) {
// On to the next.
final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
- shutdownIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
// This is the result receiver for the final shutdown broadcast.
final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
@Override
@@ -635,20 +657,19 @@ class UserController {
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
Integer.toString(userId), userId);
- mInjector.systemServiceManagerStopUser(userId);
+ mInjector.getSystemServiceManager().stopUser(userId);
- synchronized (mLock) {
- mInjector.broadcastIntentLocked(shutdownIntent,
- null, shutdownReceiver, 0, null, null, null,
- AppOpsManager.OP_NONE,
- null, true, false, MY_PID, SYSTEM_UID, userId);
- }
+ mInjector.broadcastIntent(shutdownIntent,
+ null, shutdownReceiver, 0, null, null, null,
+ AppOpsManager.OP_NONE,
+ null, true, false, MY_PID, SYSTEM_UID, userId);
}
void finishUserStopped(UserState uss) {
final int userId = uss.mHandle.getIdentifier();
boolean stopped;
ArrayList<IStopUserCallback> callbacks;
+ boolean forceStopUser = false;
synchronized (mLock) {
callbacks = new ArrayList<>(uss.mStopCallbacks);
if (mStartedUsers.get(userId) != uss) {
@@ -659,16 +680,18 @@ class UserController {
stopped = true;
// User can no longer run.
mStartedUsers.remove(userId);
- mInjector.getUserManagerInternal().removeUserState(userId);
mUserLru.remove(Integer.valueOf(userId));
- updateStartedUserArrayLocked();
-
- mInjector.activityManagerOnUserStopped(userId);
- // Clean up all state and processes associated with the user.
- // Kill all the processes for the user.
- forceStopUserLocked(userId, "finish user");
+ updateStartedUserArrayLU();
+ forceStopUser = true;
}
}
+ if (forceStopUser) {
+ mInjector.getUserManagerInternal().removeUserState(userId);
+ mInjector.activityManagerOnUserStopped(userId);
+ // Clean up all state and processes associated with the user.
+ // Kill all the processes for the user.
+ forceStopUser(userId, "finish user");
+ }
for (int i = 0; i < callbacks.size(); i++) {
try {
@@ -680,9 +703,7 @@ class UserController {
if (stopped) {
mInjector.systemServiceManagerCleanupUser(userId);
- synchronized (mLock) {
- mInjector.getActivityStackSupervisor().removeUserLocked(userId);
- }
+ mInjector.stackSupervisorRemoveUser(userId);
// Remove the user if it is ephemeral.
if (getUserInfo(userId).isEphemeral()) {
mInjector.getUserManager().removeUser(userId);
@@ -700,39 +721,36 @@ class UserController {
* Determines the list of users that should be stopped together with the specified
* {@code userId}. The returned list includes {@code userId}.
*/
- private @NonNull int[] getUsersToStopLocked(int userId) {
+ private @NonNull int[] getUsersToStopLU(int userId) {
int startedUsersSize = mStartedUsers.size();
IntArray userIds = new IntArray();
userIds.add(userId);
- synchronized (mUserProfileGroupIdsSelfLocked) {
- int userGroupId = mUserProfileGroupIdsSelfLocked.get(userId,
+ int userGroupId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+ for (int i = 0; i < startedUsersSize; i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ int startedUserId = uss.mHandle.getIdentifier();
+ // Skip unrelated users (profileGroupId mismatch)
+ int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
UserInfo.NO_PROFILE_GROUP_ID);
- for (int i = 0; i < startedUsersSize; i++) {
- UserState uss = mStartedUsers.valueAt(i);
- int startedUserId = uss.mHandle.getIdentifier();
- // Skip unrelated users (profileGroupId mismatch)
- int startedUserGroupId = mUserProfileGroupIdsSelfLocked.get(startedUserId,
- UserInfo.NO_PROFILE_GROUP_ID);
- boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
- && (userGroupId == startedUserGroupId);
- // userId has already been added
- boolean sameUserId = startedUserId == userId;
- if (!sameGroup || sameUserId) {
- continue;
- }
- userIds.add(startedUserId);
+ boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
+ && (userGroupId == startedUserGroupId);
+ // userId has already been added
+ boolean sameUserId = startedUserId == userId;
+ if (!sameGroup || sameUserId) {
+ continue;
}
+ userIds.add(startedUserId);
}
return userIds.toArray();
}
- private void forceStopUserLocked(int userId, String reason) {
- mInjector.activityManagerForceStopPackageLocked(userId, reason);
+ private void forceStopUser(int userId, String reason) {
+ mInjector.activityManagerForceStopPackage(userId, reason);
Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- mInjector.broadcastIntentLocked(intent,
+ mInjector.broadcastIntent(intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
}
@@ -741,6 +759,7 @@ class UserController {
* Stops the guest or ephemeral user if it has gone to the background.
*/
private void stopGuestOrEphemeralUserIfBackground() {
+ IntArray userIds = new IntArray();
synchronized (mLock) {
final int num = mUserLru.size();
for (int i = 0; i < num; i++) {
@@ -751,28 +770,42 @@ class UserController {
|| oldUss.state == UserState.STATE_SHUTDOWN) {
continue;
}
- UserInfo userInfo = getUserInfo(oldUserId);
- if (userInfo.isEphemeral()) {
- LocalServices.getService(UserManagerInternal.class)
- .onEphemeralUserStop(oldUserId);
- }
- if (userInfo.isGuest() || userInfo.isEphemeral()) {
- // This is a user to be stopped.
- stopUsersLocked(oldUserId, true, null);
- break;
+ userIds.add(oldUserId);
+ }
+ }
+ final int userIdsSize = userIds.size();
+ for (int i = 0; i < userIdsSize; i++) {
+ int oldUserId = userIds.get(i);
+ UserInfo userInfo = getUserInfo(oldUserId);
+ if (userInfo.isEphemeral()) {
+ LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
+ }
+ if (userInfo.isGuest() || userInfo.isEphemeral()) {
+ // This is a user to be stopped.
+ synchronized (mLock) {
+ stopUsersLU(oldUserId, true, null);
}
+ break;
}
}
}
- void startProfilesLocked() {
+ void scheduleStartProfiles() {
+ if (!mHandler.hasMessages(START_PROFILES_MSG)) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
+ DateUtils.SECOND_IN_MILLIS);
+ }
+ }
+
+ void startProfiles() {
+ int currentUserId = getCurrentUserId();
if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
- mCurrentUserId, false /* enabledOnly */);
+ currentUserId, false /* enabledOnly */);
List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
for (UserInfo user : profiles) {
if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
- && user.id != mCurrentUserId && !user.isQuietModeEnabled()) {
+ && user.id != currentUserId && !user.isQuietModeEnabled()) {
profilesToStart.add(user);
}
}
@@ -834,143 +867,156 @@ class UserController {
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- final int oldUserId = mCurrentUserId;
- if (oldUserId == userId) {
- return true;
- }
+ final int oldUserId = getCurrentUserId();
+ if (oldUserId == userId) {
+ return true;
+ }
- if (foreground) {
- // TODO: I don't think this does what the caller think it does. Seems to only
- // remove one locked task and won't work if multiple locked tasks are present.
- mInjector.getLockTaskController().clearLockTaskMode("startUser");
- }
+ if (foreground) {
+ // TODO: I don't think this does what the caller think it does. Seems to only
+ // remove one locked task and won't work if multiple locked tasks are present.
+ mInjector.clearLockTaskMode("startUser");
+ }
- final UserInfo userInfo = getUserInfo(userId);
- if (userInfo == null) {
- Slog.w(TAG, "No user info for user #" + userId);
- return false;
- }
- if (foreground && userInfo.isManagedProfile()) {
- Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
- return false;
- }
+ final UserInfo userInfo = getUserInfo(userId);
+ if (userInfo == null) {
+ Slog.w(TAG, "No user info for user #" + userId);
+ return false;
+ }
+ if (foreground && userInfo.isManagedProfile()) {
+ Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+ return false;
+ }
- if (foreground && mUserSwitchUiEnabled) {
- mInjector.getWindowManager().startFreezingScreen(
- R.anim.screen_user_exit, R.anim.screen_user_enter);
- }
+ if (foreground && mUserSwitchUiEnabled) {
+ mInjector.getWindowManager().startFreezingScreen(
+ R.anim.screen_user_exit, R.anim.screen_user_enter);
+ }
- boolean needStart = false;
+ boolean needStart = false;
+ boolean updateUmState = false;
+ UserState uss;
- // If the user we are switching to is not currently started, then
- // we need to start it now.
- if (mStartedUsers.get(userId) == null) {
- UserState userState = new UserState(UserHandle.of(userId));
- mStartedUsers.put(userId, userState);
- mInjector.getUserManagerInternal().setUserState(userId, userState.state);
- updateStartedUserArrayLocked();
+ // If the user we are switching to is not currently started, then
+ // we need to start it now.
+ synchronized (mLock) {
+ uss = mStartedUsers.get(userId);
+ if (uss == null) {
+ uss = new UserState(UserHandle.of(userId));
+ mStartedUsers.put(userId, uss);
+ updateStartedUserArrayLU();
needStart = true;
+ updateUmState = true;
}
-
- final UserState uss = mStartedUsers.get(userId);
final Integer userIdInt = userId;
mUserLru.remove(userIdInt);
mUserLru.add(userIdInt);
-
- if (foreground) {
+ }
+ if (updateUmState) {
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ }
+ if (foreground) {
+ synchronized (mLock) {
mCurrentUserId = userId;
- mInjector.updateUserConfigurationLocked();
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
- updateCurrentProfileIdsLocked();
- mInjector.getWindowManager().setCurrentUser(userId, mCurrentProfileIds);
- // Once the internal notion of the active user has switched, we lock the device
- // with the option to show the user switcher on the keyguard.
- if (mUserSwitchUiEnabled) {
- mInjector.getWindowManager().setSwitchingUser(true);
- mInjector.getWindowManager().lockNow(null);
- }
- } else {
- final Integer currentUserIdInt = mCurrentUserId;
- updateCurrentProfileIdsLocked();
- mInjector.getWindowManager().setCurrentProfileIds(mCurrentProfileIds);
+ }
+ mInjector.updateUserConfiguration();
+ updateCurrentProfileIds();
+ mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
+ // Once the internal notion of the active user has switched, we lock the device
+ // with the option to show the user switcher on the keyguard.
+ if (mUserSwitchUiEnabled) {
+ mInjector.getWindowManager().setSwitchingUser(true);
+ mInjector.getWindowManager().lockNow(null);
+ }
+ } else {
+ final Integer currentUserIdInt = mCurrentUserId;
+ updateCurrentProfileIds();
+ mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
+ synchronized (mLock) {
mUserLru.remove(currentUserIdInt);
mUserLru.add(currentUserIdInt);
}
+ }
- // Make sure user is in the started state. If it is currently
- // stopping, we need to knock that off.
- if (uss.state == UserState.STATE_STOPPING) {
- // If we are stopping, we haven't sent ACTION_SHUTDOWN,
- // so we can just fairly silently bring the user back from
- // the almost-dead.
- uss.setState(uss.lastState);
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- updateStartedUserArrayLocked();
- needStart = true;
- } else if (uss.state == UserState.STATE_SHUTDOWN) {
- // This means ACTION_SHUTDOWN has been sent, so we will
- // need to treat this as a new boot of the user.
- uss.setState(UserState.STATE_BOOTING);
- mInjector.getUserManagerInternal().setUserState(userId, uss.state);
- updateStartedUserArrayLocked();
- needStart = true;
+ // Make sure user is in the started state. If it is currently
+ // stopping, we need to knock that off.
+ if (uss.state == UserState.STATE_STOPPING) {
+ // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+ // so we can just fairly silently bring the user back from
+ // the almost-dead.
+ uss.setState(uss.lastState);
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ synchronized (mLock) {
+ updateStartedUserArrayLU();
}
+ needStart = true;
+ } else if (uss.state == UserState.STATE_SHUTDOWN) {
+ // This means ACTION_SHUTDOWN has been sent, so we will
+ // need to treat this as a new boot of the user.
+ uss.setState(UserState.STATE_BOOTING);
+ mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+ synchronized (mLock) {
+ updateStartedUserArrayLU();
+ }
+ needStart = true;
+ }
- if (uss.state == UserState.STATE_BOOTING) {
- // Give user manager a chance to propagate user restrictions
- // to other services and prepare app storage
- mInjector.getUserManager().onBeforeStartUser(userId);
+ if (uss.state == UserState.STATE_BOOTING) {
+ // Give user manager a chance to propagate user restrictions
+ // to other services and prepare app storage
+ mInjector.getUserManager().onBeforeStartUser(userId);
- // Booting up a new user, need to tell system services about it.
- // Note that this is on the same handler as scheduling of broadcasts,
- // which is important because it needs to go first.
- mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
- }
+ // Booting up a new user, need to tell system services about it.
+ // Note that this is on the same handler as scheduling of broadcasts,
+ // which is important because it needs to go first.
+ mHandler.sendMessage(
+ mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+ }
- if (foreground) {
- mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
- oldUserId));
- mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
- mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
- oldUserId, userId, uss));
- mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
- oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
- }
+ if (foreground) {
+ mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+ oldUserId));
+ mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+ mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+ oldUserId, userId, uss));
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+ oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
+ }
- if (needStart) {
- // Send USER_STARTED broadcast
- Intent intent = new Intent(Intent.ACTION_USER_STARTED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- mInjector.broadcastIntentLocked(intent,
- null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, SYSTEM_UID, userId);
- }
+ if (needStart) {
+ // Send USER_STARTED broadcast
+ Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mInjector.broadcastIntent(intent,
+ null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+ null, false, false, MY_PID, SYSTEM_UID, userId);
+ }
- if (foreground) {
- moveUserToForegroundLocked(uss, oldUserId, userId);
- } else {
- finishUserBoot(uss);
- }
+ if (foreground) {
+ moveUserToForeground(uss, oldUserId, userId);
+ } else {
+ finishUserBoot(uss);
+ }
- if (needStart) {
- Intent intent = new Intent(Intent.ACTION_USER_STARTING);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- mInjector.broadcastIntentLocked(intent,
- null, new IIntentReceiver.Stub() {
- @Override
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered, boolean sticky,
- int sendingUser) throws RemoteException {
- }
- }, 0, null, null,
- new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
- }
+ if (needStart) {
+ Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ mInjector.broadcastIntent(intent,
+ null, new IIntentReceiver.Stub() {
+ @Override
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered,
+ boolean sticky,
+ int sendingUser) throws RemoteException {
+ }
+ }, 0, null, null,
+ new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+ null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1014,7 +1060,7 @@ class UserController {
* when the the credential-encrypted storage isn't tied to a user-provided
* PIN or pattern.
*/
- boolean maybeUnlockUser(final int userId) {
+ private boolean maybeUnlockUser(final int userId) {
// Try unlocking storage using empty token
return unlockUserCleared(userId, null, null, null);
}
@@ -1027,65 +1073,106 @@ class UserController {
}
}
- boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
+ private boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
IProgressListener listener) {
UserState uss;
- synchronized (mLock) {
- // TODO Move this block outside of synchronized if it causes lock contention
- if (!StorageManager.isUserKeyUnlocked(userId)) {
- final UserInfo userInfo = getUserInfo(userId);
- final IStorageManager storageManager = getStorageManager();
- try {
- // We always want to unlock user storage, even user is not started yet
- storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
- } catch (RemoteException | RuntimeException e) {
- Slog.w(TAG, "Failed to unlock: " + e.getMessage());
- }
+ if (!StorageManager.isUserKeyUnlocked(userId)) {
+ final UserInfo userInfo = getUserInfo(userId);
+ final IStorageManager storageManager = getStorageManager();
+ try {
+ // We always want to unlock user storage, even user is not started yet
+ storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
+ } catch (RemoteException | RuntimeException e) {
+ Slog.w(TAG, "Failed to unlock: " + e.getMessage());
}
- // Bail if user isn't actually running, otherwise register the given
- // listener to watch for unlock progress
+ }
+ synchronized (mLock) {
+ // Register the given listener to watch for unlock progress
uss = mStartedUsers.get(userId);
- if (uss == null) {
- notifyFinished(userId, listener);
- return false;
- } else {
+ if (uss != null) {
uss.mUnlockProgress.addListener(listener);
uss.tokenProvided = (token != null);
}
}
+ // Bail if user isn't actually running
+ if (uss == null) {
+ notifyFinished(userId, listener);
+ return false;
+ }
finishUserUnlocking(uss);
- final ArraySet<Integer> childProfilesToUnlock = new ArraySet<>();
- synchronized (mLock) {
+ // We just unlocked a user, so let's now attempt to unlock any
+ // managed profiles under that user.
- // We just unlocked a user, so let's now attempt to unlock any
- // managed profiles under that user.
- for (int i = 0; i < mStartedUsers.size(); i++) {
- final int testUserId = mStartedUsers.keyAt(i);
- final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
- if (parent != null && parent.id == userId && testUserId != userId) {
- Slog.d(TAG, "User " + testUserId + " (parent " + parent.id
- + "): attempting unlock because parent was just unlocked");
- childProfilesToUnlock.add(testUserId);
- }
+ // First, get list of userIds. Requires mLock, so we cannot make external calls, e.g. to UMS
+ int[] userIds;
+ synchronized (mLock) {
+ userIds = new int[mStartedUsers.size()];
+ for (int i = 0; i < userIds.length; i++) {
+ userIds[i] = mStartedUsers.keyAt(i);
}
}
-
- final int size = childProfilesToUnlock.size();
- for (int i = 0; i < size; i++) {
- maybeUnlockUser(childProfilesToUnlock.valueAt(i));
+ for (int testUserId : userIds) {
+ final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
+ if (parent != null && parent.id == userId && testUserId != userId) {
+ Slog.d(TAG, "User " + testUserId + " (parent " + parent.id
+ + "): attempting unlock because parent was just unlocked");
+ maybeUnlockUser(testUserId);
+ }
}
return true;
}
- void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
+ boolean switchUser(final int targetUserId) {
+ enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
+ int currentUserId = getCurrentUserId();
+ UserInfo targetUserInfo = getUserInfo(targetUserId);
+ if (targetUserId == currentUserId) {
+ Slog.i(TAG, "user #" + targetUserId + " is already the current user");
+ return true;
+ }
+ if (targetUserInfo == null) {
+ Slog.w(TAG, "No user info for user #" + targetUserId);
+ return false;
+ }
+ if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mInjector.getContext())) {
+ Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
+ + " when device is in demo mode");
+ return false;
+ }
+ if (!targetUserInfo.supportsSwitchTo()) {
+ Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
+ return false;
+ }
+ if (targetUserInfo.isManagedProfile()) {
+ Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
+ return false;
+ }
+ synchronized (mLock) {
+ mTargetUserId = targetUserId;
+ }
+ if (mUserSwitchUiEnabled) {
+ UserInfo currentUserInfo = getUserInfo(currentUserId);
+ Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
+ mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
+ mUiHandler.sendMessage(mHandler.obtainMessage(
+ START_USER_SWITCH_UI_MSG, userNames));
+ } else {
+ mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
+ mHandler.sendMessage(mHandler.obtainMessage(
+ START_USER_SWITCH_FG_MSG, targetUserId, 0));
+ }
+ return true;
+ }
+
+ private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
// The dialog will show and then initiate the user switch by calling startUserInForeground
mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second);
}
- void dispatchForegroundProfileChanged(int userId) {
+ private void dispatchForegroundProfileChanged(int userId) {
final int observerCount = mUserSwitchObservers.beginBroadcast();
for (int i = 0; i < observerCount; i++) {
try {
@@ -1110,7 +1197,7 @@ class UserController {
mUserSwitchObservers.finishBroadcast();
}
- void dispatchLockedBootComplete(int userId) {
+ private void dispatchLockedBootComplete(int userId) {
final int observerCount = mUserSwitchObservers.beginBroadcast();
for (int i = 0; i < observerCount; i++) {
try {
@@ -1136,23 +1223,23 @@ class UserController {
synchronized (mLock) {
if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
+ " and related users");
- stopUsersLocked(oldUserId, false, null);
+ stopUsersLU(oldUserId, false, null);
}
}
- void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+ private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
synchronized (mLock) {
Slog.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks;
mHandler.removeMessages(USER_SWITCH_CALLBACKS_TIMEOUT_MSG);
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+ sendContinueUserSwitchLU(uss, oldUserId, newUserId);
// Report observers that never called back (USER_SWITCH_CALLBACKS_TIMEOUT)
mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG,
oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS);
}
}
- void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
+ private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
synchronized (mLock) {
if (mTimeoutUserSwitchCallbacks != null && !mTimeoutUserSwitchCallbacks.isEmpty()) {
Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId
@@ -1195,7 +1282,7 @@ class UserController {
if (waitingCallbacksCount.decrementAndGet() == 0
&& (curWaitingUserSwitchCallbacks
== mCurWaitingUserSwitchCallbacks)) {
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+ sendContinueUserSwitchLU(uss, oldUserId, newUserId);
}
}
}
@@ -1206,25 +1293,23 @@ class UserController {
}
} else {
synchronized (mLock) {
- sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+ sendContinueUserSwitchLU(uss, oldUserId, newUserId);
}
}
mUserSwitchObservers.finishBroadcast();
}
- void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
+ void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
mCurWaitingUserSwitchCallbacks = null;
mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
- mHandler.sendMessage(mHandler.obtainMessage(ActivityManagerService.CONTINUE_USER_SWITCH_MSG,
+ mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
oldUserId, newUserId, uss));
}
void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
if (mUserSwitchUiEnabled) {
- synchronized (mLock) {
- mInjector.getWindowManager().stopFreezingScreen();
- }
+ mInjector.getWindowManager().stopFreezingScreen();
}
uss.switching = false;
mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -1234,19 +1319,18 @@ class UserController {
stopBackgroundUsersIfEnforced(oldUserId);
}
- void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
- boolean homeInFront =
- mInjector.getActivityStackSupervisor().switchUserLocked(newUserId, uss);
+ private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
+ boolean homeInFront = mInjector.stackSupervisorSwitchUser(newUserId, uss);
if (homeInFront) {
- mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
+ mInjector.startHomeActivity(newUserId, "moveUserToForeground");
} else {
- mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
+ mInjector.stackSupervisorResumeFocusedStackTopActivity();
}
EventLogTags.writeAmSwitchUser(newUserId);
- sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+ sendUserSwitchBroadcasts(oldUserId, newUserId);
}
- void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+ void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
long ident = Binder.clearCallingIdentity();
try {
Intent intent;
@@ -1260,7 +1344,7 @@ class UserController {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
- mInjector.broadcastIntentLocked(intent,
+ mInjector.broadcastIntent(intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, profileUserId);
}
@@ -1275,7 +1359,7 @@ class UserController {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
- mInjector.broadcastIntentLocked(intent,
+ mInjector.broadcastIntent(intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, profileUserId);
}
@@ -1283,7 +1367,7 @@ class UserController {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
- mInjector.broadcastIntentLocked(intent,
+ mInjector.broadcastIntent(intent,
null, null, 0, null, null,
new String[] {android.Manifest.permission.MANAGE_USERS},
AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
@@ -1308,7 +1392,7 @@ class UserController {
// the value the caller will receive and someone else changing it.
// We assume that USER_CURRENT_OR_SELF will use the current user; later
// we will switch to the calling user if access to the current user fails.
- int targetUserId = unsafeConvertIncomingUserLocked(userId);
+ int targetUserId = unsafeConvertIncomingUser(userId);
if (callingUid != 0 && callingUid != SYSTEM_UID) {
final boolean allow;
@@ -1376,9 +1460,9 @@ class UserController {
return targetUserId;
}
- int unsafeConvertIncomingUserLocked(int userId) {
+ int unsafeConvertIncomingUser(int userId) {
return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
- ? getCurrentUserIdLocked(): userId;
+ ? getCurrentUserId(): userId;
}
void registerUserSwitchObserver(IUserSwitchObserver observer, String name) {
@@ -1395,19 +1479,26 @@ class UserController {
mUserSwitchObservers.register(observer, name);
}
+ void sendForegroundProfileChanged(int userId) {
+ mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
+ mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG, userId, 0).sendToTarget();
+ }
+
void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
mUserSwitchObservers.unregister(observer);
}
- UserState getStartedUserStateLocked(int userId) {
- return mStartedUsers.get(userId);
+ UserState getStartedUserState(int userId) {
+ synchronized (mLock) {
+ return mStartedUsers.get(userId);
+ }
}
boolean hasStartedUserState(int userId) {
return mStartedUsers.get(userId) != null;
}
- private void updateStartedUserArrayLocked() {
+ private void updateStartedUserArrayLU() {
int num = 0;
for (int i = 0; i < mStartedUsers.size(); i++) {
UserState uss = mStartedUsers.valueAt(i);
@@ -1428,15 +1519,20 @@ class UserController {
}
}
- void sendBootCompletedLocked(IIntentReceiver resultTo) {
- for (int i = 0; i < mStartedUsers.size(); i++) {
- UserState uss = mStartedUsers.valueAt(i);
+ void sendBootCompleted(IIntentReceiver resultTo) {
+ // Get a copy of mStartedUsers to use outside of lock
+ SparseArray<UserState> startedUsers;
+ synchronized (mLock) {
+ startedUsers = mStartedUsers.clone();
+ }
+ for (int i = 0; i < startedUsers.size(); i++) {
+ UserState uss = startedUsers.valueAt(i);
finishUserBoot(uss, resultTo);
}
}
void onSystemReady() {
- updateCurrentProfileIdsLocked();
+ updateCurrentProfileIds();
}
/**
@@ -1444,33 +1540,35 @@ class UserController {
* user switch happens or when a new related user is started in the
* background.
*/
- private void updateCurrentProfileIdsLocked() {
- final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(mCurrentUserId,
+ private void updateCurrentProfileIds() {
+ final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(getCurrentUserId(),
false /* enabledOnly */);
int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
for (int i = 0; i < currentProfileIds.length; i++) {
currentProfileIds[i] = profiles.get(i).id;
}
- mCurrentProfileIds = currentProfileIds;
+ final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
+ synchronized (mLock) {
+ mCurrentProfileIds = currentProfileIds;
- synchronized (mUserProfileGroupIdsSelfLocked) {
- mUserProfileGroupIdsSelfLocked.clear();
- final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
+ mUserProfileGroupIds.clear();
for (int i = 0; i < users.size(); i++) {
UserInfo user = users.get(i);
if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
- mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+ mUserProfileGroupIds.put(user.id, user.profileGroupId);
}
}
}
}
- int[] getStartedUserArrayLocked() {
- return mStartedUserArray;
+ int[] getStartedUserArray() {
+ synchronized (mLock) {
+ return mStartedUserArray;
+ }
}
- boolean isUserRunningLocked(int userId, int flags) {
- UserState state = getStartedUserStateLocked(userId);
+ boolean isUserRunning(int userId, int flags) {
+ UserState state = getStartedUserState(userId);
if (state == null) {
return false;
}
@@ -1533,29 +1631,38 @@ class UserController {
return getUserInfo(mCurrentUserId);
}
synchronized (mLock) {
- return getCurrentUserLocked();
+ return getCurrentUserLU();
}
}
- UserInfo getCurrentUserLocked() {
+ UserInfo getCurrentUserLU() {
int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
return getUserInfo(userId);
}
- int getCurrentOrTargetUserIdLocked() {
+ int getCurrentOrTargetUserId() {
+ synchronized (mLock) {
+ return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+ }
+ }
+
+ int getCurrentOrTargetUserIdLU() {
return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
}
- int getCurrentUserIdLocked() {
+
+ int getCurrentUserIdLU() {
return mCurrentUserId;
}
- private boolean isCurrentUserLocked(int userId) {
- return userId == getCurrentOrTargetUserIdLocked();
+ int getCurrentUserId() {
+ synchronized (mLock) {
+ return mCurrentUserId;
+ }
}
- int setTargetUserIdLocked(int targetUserId) {
- return mTargetUserId = targetUserId;
+ private boolean isCurrentUserLU(int userId) {
+ return userId == getCurrentOrTargetUserIdLU();
}
int[] getUsers() {
@@ -1575,6 +1682,15 @@ class UserController {
return mInjector.getUserManager().exists(userId);
}
+ void enforceShellRestriction(String restriction, int userHandle) {
+ if (Binder.getCallingUid() == SHELL_UID) {
+ if (userHandle < 0 || hasUserRestriction(restriction, userHandle)) {
+ throw new SecurityException("Shell does not have permission to access user "
+ + userHandle);
+ }
+ }
+ }
+
boolean hasUserRestriction(String restriction, int userId) {
return mInjector.getUserManager().hasUserRestriction(restriction, userId);
}
@@ -1593,22 +1709,26 @@ class UserController {
if (callingUserId == targetUserId) {
return true;
}
- synchronized (mUserProfileGroupIdsSelfLocked) {
- int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+ synchronized (mLock) {
+ int callingProfile = mUserProfileGroupIds.get(callingUserId,
UserInfo.NO_PROFILE_GROUP_ID);
- int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+ int targetProfile = mUserProfileGroupIds.get(targetUserId,
UserInfo.NO_PROFILE_GROUP_ID);
return callingProfile != UserInfo.NO_PROFILE_GROUP_ID
&& callingProfile == targetProfile;
}
}
- boolean isCurrentProfileLocked(int userId) {
- return ArrayUtils.contains(mCurrentProfileIds, userId);
+ boolean isCurrentProfile(int userId) {
+ synchronized (mLock) {
+ return ArrayUtils.contains(mCurrentProfileIds, userId);
+ }
}
- int[] getCurrentProfileIdsLocked() {
- return mCurrentProfileIds;
+ int[] getCurrentProfileIds() {
+ synchronized (mLock) {
+ return mCurrentProfileIds;
+ }
}
/**
@@ -1633,40 +1753,107 @@ class UserController {
}
void dump(PrintWriter pw, boolean dumpAll) {
- pw.println(" mStartedUsers:");
- for (int i = 0; i < mStartedUsers.size(); i++) {
- UserState uss = mStartedUsers.valueAt(i);
- pw.print(" User #"); pw.print(uss.mHandle.getIdentifier());
- pw.print(": "); uss.dump("", pw);
- }
- pw.print(" mStartedUserArray: [");
- for (int i = 0; i < mStartedUserArray.length; i++) {
- if (i > 0) pw.print(", ");
- pw.print(mStartedUserArray[i]);
- }
- pw.println("]");
- pw.print(" mUserLru: [");
- for (int i = 0; i < mUserLru.size(); i++) {
- if (i > 0) pw.print(", ");
- pw.print(mUserLru.get(i));
- }
- pw.println("]");
- if (dumpAll) {
- pw.print(" mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
- }
- synchronized (mUserProfileGroupIdsSelfLocked) {
- if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+ synchronized (mLock) {
+ pw.println(" mStartedUsers:");
+ for (int i = 0; i < mStartedUsers.size(); i++) {
+ UserState uss = mStartedUsers.valueAt(i);
+ pw.print(" User #");
+ pw.print(uss.mHandle.getIdentifier());
+ pw.print(": ");
+ uss.dump("", pw);
+ }
+ pw.print(" mStartedUserArray: [");
+ for (int i = 0; i < mStartedUserArray.length; i++) {
+ if (i > 0)
+ pw.print(", ");
+ pw.print(mStartedUserArray[i]);
+ }
+ pw.println("]");
+ pw.print(" mUserLru: [");
+ for (int i = 0; i < mUserLru.size(); i++) {
+ if (i > 0)
+ pw.print(", ");
+ pw.print(mUserLru.get(i));
+ }
+ pw.println("]");
+ if (dumpAll) {
+ pw.print(" mStartedUserArray: ");
+ pw.println(Arrays.toString(mStartedUserArray));
+ }
+ if (mUserProfileGroupIds.size() > 0) {
pw.println(" mUserProfileGroupIds:");
- for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+ for (int i=0; i< mUserProfileGroupIds.size(); i++) {
pw.print(" User #");
- pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+ pw.print(mUserProfileGroupIds.keyAt(i));
pw.print(" -> profile #");
- pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+ pw.println(mUserProfileGroupIds.valueAt(i));
}
}
}
}
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case START_USER_SWITCH_FG_MSG:
+ startUserInForeground(msg.arg1);
+ break;
+ case REPORT_USER_SWITCH_MSG:
+ dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+ break;
+ case CONTINUE_USER_SWITCH_MSG:
+ continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+ break;
+ case USER_SWITCH_TIMEOUT_MSG:
+ timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+ break;
+ case USER_SWITCH_CALLBACKS_TIMEOUT_MSG:
+ timeoutUserSwitchCallbacks(msg.arg1, msg.arg2);
+ break;
+ case START_PROFILES_MSG:
+ startProfiles();
+ break;
+ case SYSTEM_USER_START_MSG:
+ mInjector.batteryStatsServiceNoteEvent(
+ BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+ Integer.toString(msg.arg1), msg.arg1);
+ mInjector.getSystemServiceManager().startUser(msg.arg1);
+ break;
+ case SYSTEM_USER_UNLOCK_MSG:
+ final int userId = msg.arg1;
+ mInjector.getSystemServiceManager().unlockUser(userId);
+ mInjector.loadUserRecents(userId);
+ if (userId == UserHandle.USER_SYSTEM) {
+ mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ }
+ mInjector.installEncryptionUnawareProviders(userId);
+ finishUserUnlocked((UserState) msg.obj);
+ break;
+ case SYSTEM_USER_CURRENT_MSG:
+ mInjector.batteryStatsServiceNoteEvent(
+ BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
+ Integer.toString(msg.arg2), msg.arg2);
+ mInjector.batteryStatsServiceNoteEvent(
+ BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+ Integer.toString(msg.arg1), msg.arg1);
+
+ mInjector.getSystemServiceManager().switchUser(msg.arg1);
+ break;
+ case FOREGROUND_PROFILE_CHANGED_MSG:
+ dispatchForegroundProfileChanged(msg.arg1);
+ break;
+ case REPORT_USER_SWITCH_COMPLETE_MSG:
+ dispatchUserSwitchComplete(msg.arg1);
+ break;
+ case REPORT_LOCKED_BOOT_COMPLETE_MSG:
+ dispatchLockedBootComplete(msg.arg1);
+ break;
+ case START_USER_SWITCH_UI_MSG:
+ showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
+ break;
+ }
+ return false;
+ }
+
@VisibleForTesting
static class Injector {
private final ActivityManagerService mService;
@@ -1677,12 +1864,12 @@ class UserController {
mService = service;
}
- protected Object getLock() {
- return mService;
+ protected Handler getHandler(Handler.Callback callback) {
+ return new Handler(mService.mHandlerThread.getLooper(), callback);
}
- protected Handler getHandler() {
- return mService.mHandler;
+ protected Handler getUiHandler(Handler.Callback callback) {
+ return new Handler(mService.mUiHandler.getLooper(), callback);
}
protected Context getContext() {
@@ -1693,13 +1880,16 @@ class UserController {
return new LockPatternUtils(getContext());
}
- protected int broadcastIntentLocked(Intent intent, String resolvedType,
+ protected int broadcastIntent(Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
- return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
- resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
- ordered, sticky, callingPid, callingUid, userId);
+ // TODO b/64165549 Verify that mLock is not held before calling AMS methods
+ synchronized (mService) {
+ return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
+ resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
+ ordered, sticky, callingPid, callingUid, userId);
+ }
}
int checkCallingPermission(String permission) {
@@ -1710,7 +1900,9 @@ class UserController {
return mService.mWindowManager;
}
void activityManagerOnUserStopped(int userId) {
- mService.onUserStoppedLocked(userId);
+ synchronized (mService) {
+ mService.onUserStoppedLocked(userId);
+ }
}
void systemServiceManagerCleanupUser(int userId) {
@@ -1740,14 +1932,14 @@ class UserController {
mService.mBatteryStatsService.noteEvent(code, name, uid);
}
- void systemServiceManagerStopUser(int userId) {
- mService.mSystemServiceManager.stopUser(userId);
- }
-
boolean isRuntimeRestarted() {
return mService.mSystemServiceManager.isRuntimeRestarted();
}
+ SystemServiceManager getSystemServiceManager() {
+ return mService.mSystemServiceManager;
+ }
+
boolean isFirstBootOrUpgrade() {
IPackageManager pm = AppGlobals.getPackageManager();
try {
@@ -1766,9 +1958,11 @@ class UserController {
}.sendNext();
}
- void activityManagerForceStopPackageLocked(int userId, String reason) {
- mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
- userId, reason);
+ void activityManagerForceStopPackage(int userId, String reason) {
+ synchronized (mService) {
+ mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
+ userId, reason);
+ }
};
int checkComponentPermission(String permission, int pid, int uid, int owningUid,
@@ -1776,20 +1970,36 @@ class UserController {
return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
}
- void startHomeActivityLocked(int userId, String reason) {
- mService.startHomeActivityLocked(userId, reason);
+ protected void startHomeActivity(int userId, String reason) {
+ synchronized (mService) {
+ mService.startHomeActivityLocked(userId, reason);
+ }
+ }
+
+ void updateUserConfiguration() {
+ synchronized (mService) {
+ mService.updateUserConfigurationLocked();
+ }
}
- void updateUserConfigurationLocked() {
- mService.updateUserConfigurationLocked();
+ void clearBroadcastQueueForUser(int userId) {
+ synchronized (mService) {
+ mService.clearBroadcastQueueForUserLocked(userId);
+ }
+ }
+
+ void loadUserRecents(int userId) {
+ synchronized (mService) {
+ mService.mRecentTasks.loadUserRecentsLocked(userId);
+ }
}
- void clearBroadcastQueueForUserLocked(int userId) {
- mService.clearBroadcastQueueForUserLocked(userId);
+ void startPersistentApps(int matchFlags) {
+ mService.startPersistentApps(matchFlags);
}
- void enforceShellRestriction(String restriction, int userId) {
- mService.enforceShellRestriction(restriction, userId);
+ void installEncryptionUnawareProviders(int userId) {
+ mService.installEncryptionUnawareProviders(userId);
}
void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser) {
@@ -1798,12 +2008,28 @@ class UserController {
d.show();
}
- ActivityStackSupervisor getActivityStackSupervisor() {
- return mService.mStackSupervisor;
+ void stackSupervisorRemoveUser(int userId) {
+ synchronized (mService) {
+ mService.mStackSupervisor.removeUserLocked(userId);
+ }
+ }
+
+ protected boolean stackSupervisorSwitchUser(int userId, UserState uss) {
+ synchronized (mService) {
+ return mService.mStackSupervisor.switchUserLocked(userId, uss);
+ }
}
- LockTaskController getLockTaskController() {
- return mService.mLockTaskController;
+ protected void stackSupervisorResumeFocusedStackTopActivity() {
+ synchronized (mService) {
+ mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+ }
+ }
+
+ protected void clearLockTaskMode(String reason) {
+ synchronized (mService) {
+ mService.mLockTaskController.clearLockTaskMode(reason);
+ }
}
}
}
diff --git a/com/android/server/am/UserState.java b/com/android/server/am/UserState.java
index 2e27387a..d36d9cbe 100644
--- a/com/android/server/am/UserState.java
+++ b/com/android/server/am/UserState.java
@@ -59,8 +59,10 @@ public final class UserState {
/**
* The last time that a provider was reported to usage stats as being brought to important
* foreground procstate.
+ * <p><strong>Important: </strong>Only access this field when holding ActivityManagerService
+ * lock.
*/
- public final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
+ final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
public UserState(UserHandle handle) {
mHandle = handle;
diff --git a/com/android/server/appwidget/AppWidgetServiceImpl.java b/com/android/server/appwidget/AppWidgetServiceImpl.java
index 80b54770..a6aaaa67 100644
--- a/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2427,14 +2427,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
out.attribute(null, "p", Integer.toHexString(widget.provider.tag));
}
if (widget.options != null) {
- out.attribute(null, "min_width", Integer.toHexString(widget.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
- out.attribute(null, "min_height", Integer.toHexString(widget.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
- out.attribute(null, "max_width", Integer.toHexString(widget.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
- out.attribute(null, "max_height", Integer.toHexString(widget.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
+ int minWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
+ int minHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+ int maxWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
+ int maxHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
+ out.attribute(null, "min_width", Integer.toHexString((minWidth > 0) ? minWidth : 0));
+ out.attribute(null, "min_height", Integer.toHexString((minHeight > 0) ? minHeight : 0));
+ out.attribute(null, "max_width", Integer.toHexString((maxWidth > 0) ? maxWidth : 0));
+ out.attribute(null, "max_height", Integer.toHexString((maxHeight > 0) ? maxHeight : 0));
out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
}
diff --git a/com/android/server/audio/AudioEventLogger.java b/com/android/server/audio/AudioEventLogger.java
index c96138ff..9ebd75bd 100644
--- a/com/android/server/audio/AudioEventLogger.java
+++ b/com/android/server/audio/AudioEventLogger.java
@@ -16,6 +16,8 @@
package com.android.server.audio;
+import android.util.Log;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -47,6 +49,22 @@ public class AudioEventLogger {
}
/**
+ * Causes the string message for the event to appear in the logcat.
+ * Here is an example of how to create a new event (a StringEvent), adding it to the logger
+ * (an instance of AudioEventLogger) while also making it show in the logcat:
+ * <pre>
+ * myLogger.log(
+ * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
+ * </pre>
+ * @param tag the tag for the android.util.Log.v
+ * @return the same instance of the event
+ */
+ public Event printLog(String tag) {
+ Log.i(tag, eventToString());
+ return this;
+ }
+
+ /**
* Convert event to String.
* This method is only called when the logger history is about to the dumped,
* so this method is where expensive String conversions should be made, not when the Event
diff --git a/com/android/server/audio/AudioService.java b/com/android/server/audio/AudioService.java
index 91b15912..5eb2a8d2 100644
--- a/com/android/server/audio/AudioService.java
+++ b/com/android/server/audio/AudioService.java
@@ -461,6 +461,8 @@ public class AudioService extends IAudioService.Stub
// Forced device usage for communications
private int mForcedUseForComm;
+ private int mForcedUseForCommExt; // External state returned by getters: always consistent
+ // with requests by setters
// List of binder death handlers for setMode() client processes.
// The last process to have called setMode() is at the top of the list.
@@ -749,6 +751,9 @@ public class AudioService extends IAudioService.Stub
// relies on audio policy having correct ranges for volume indexes.
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+ mPlaybackMonitor =
+ new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
mRecordMonitor = new RecordingActivityMonitor(mContext);
@@ -2544,13 +2549,15 @@ public class AudioService extends IAudioService.Stub
}
}
int status = AudioSystem.AUDIO_STATUS_OK;
+ int actualMode;
do {
+ actualMode = mode;
if (mode == AudioSystem.MODE_NORMAL) {
// get new mode from client at top the list if any
if (!mSetModeDeathHandlers.isEmpty()) {
hdlr = mSetModeDeathHandlers.get(0);
cb = hdlr.getBinder();
- mode = hdlr.getMode();
+ actualMode = hdlr.getMode();
if (DEBUG_MODE) {
Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid="
+ hdlr.mPid);
@@ -2574,12 +2581,11 @@ public class AudioService extends IAudioService.Stub
hdlr.setMode(mode);
}
- if (mode != mMode) {
- status = AudioSystem.setPhoneState(mode);
+ if (actualMode != mMode) {
+ status = AudioSystem.setPhoneState(actualMode);
if (status == AudioSystem.AUDIO_STATUS_OK) {
- if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + mode); }
- mMode = mode;
- mModeLogger.log(new PhoneStateEvent(caller, pid, mode));
+ if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); }
+ mMode = actualMode;
} else {
if (hdlr != null) {
mSetModeDeathHandlers.remove(hdlr);
@@ -2595,13 +2601,16 @@ public class AudioService extends IAudioService.Stub
} while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty());
if (status == AudioSystem.AUDIO_STATUS_OK) {
- if (mode != AudioSystem.MODE_NORMAL) {
+ if (actualMode != AudioSystem.MODE_NORMAL) {
if (mSetModeDeathHandlers.isEmpty()) {
Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack");
} else {
newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
}
}
+ // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
+ mModeLogger.log(
+ new PhoneStateEvent(caller, pid, mode, newModeOwnerPid, actualMode));
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
int device = getDeviceForStream(streamType);
int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
@@ -2890,13 +2899,14 @@ public class AudioService extends IAudioService.Stub
mForcedUseForComm = AudioSystem.FORCE_NONE;
}
+ mForcedUseForCommExt = mForcedUseForComm;
sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
}
/** @see AudioManager#isSpeakerphoneOn() */
public boolean isSpeakerphoneOn() {
- return (mForcedUseForComm == AudioSystem.FORCE_SPEAKER);
+ return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
}
/** @see AudioManager#setBluetoothScoOn(boolean) */
@@ -2904,6 +2914,13 @@ public class AudioService extends IAudioService.Stub
if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
return;
}
+
+ // Only enable calls from system components
+ if (Binder.getCallingUid() >= FIRST_APPLICATION_UID) {
+ mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+ return;
+ }
+
// for logging only
final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
.append(") from u/pid:").append(Binder.getCallingUid()).append("/")
@@ -2913,11 +2930,21 @@ public class AudioService extends IAudioService.Stub
public void setBluetoothScoOnInt(boolean on, String eventSource) {
if (on) {
+ // do not accept SCO ON if SCO audio is not connected
+ synchronized(mScoClients) {
+ if ((mBluetoothHeadset != null) &&
+ (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+ mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+ return;
+ }
+ }
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
} else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
mForcedUseForComm = AudioSystem.FORCE_NONE;
}
-
+ mForcedUseForCommExt = mForcedUseForComm;
+ AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off"));
sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
@@ -2926,7 +2953,7 @@ public class AudioService extends IAudioService.Stub
/** @see AudioManager#isBluetoothScoOn() */
public boolean isBluetoothScoOn() {
- return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO);
+ return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO);
}
/** @see AudioManager#setBluetoothA2dpOn(boolean) */
@@ -4134,7 +4161,8 @@ public class AudioService extends IAudioService.Stub
newDevice, AudioSystem.getOutputDeviceName(newDevice)));
}
synchronized (mConnectedDevices) {
- if ((newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+ if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+ && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
&& mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
&& mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
&& (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
@@ -6134,12 +6162,12 @@ public class AudioService extends IAudioService.Stub
private int mSafeMediaVolumeIndex;
// mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
// corresponding to a gain of -30 dBFS in audio flinger mixer.
- // We remove -15 dBs from the theoretical -15dB to account for the EQ boost when bands are set
- // to max gain.
+ // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
+ // amplification when both effects are on with all band gains at maximum.
// This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
// the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
private int mSafeUsbMediaVolumeIndex;
- private static final float SAFE_VOLUME_GAIN_DBFS = -30.0f;
+ private static final float SAFE_VOLUME_GAIN_DBFS = -37.0f;
// mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
@@ -6952,7 +6980,7 @@ public class AudioService extends IAudioService.Stub
//======================
// Audio playback notification
//======================
- private final PlaybackActivityMonitor mPlaybackMonitor = new PlaybackActivityMonitor();
+ private final PlaybackActivityMonitor mPlaybackMonitor;
public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
final boolean isPrivileged =
diff --git a/com/android/server/audio/AudioServiceEvents.java b/com/android/server/audio/AudioServiceEvents.java
index 634c8c27..9d9e35bd 100644
--- a/com/android/server/audio/AudioServiceEvents.java
+++ b/com/android/server/audio/AudioServiceEvents.java
@@ -26,20 +26,27 @@ public class AudioServiceEvents {
final static class PhoneStateEvent extends AudioEventLogger.Event {
final String mPackage;
- final int mPid;
- final int mMode;
+ final int mOwnerPid;
+ final int mRequesterPid;
+ final int mRequestedMode;
+ final int mActualMode;
- PhoneStateEvent(String callingPackage, int pid, int mode) {
+ PhoneStateEvent(String callingPackage, int requesterPid, int requestedMode,
+ int ownerPid, int actualMode) {
mPackage = callingPackage;
- mPid = pid;
- mMode = mode;
+ mRequesterPid = requesterPid;
+ mRequestedMode = requestedMode;
+ mOwnerPid = ownerPid;
+ mActualMode = actualMode;
}
@Override
public String eventToString() {
- return new StringBuilder("setMode(").append(AudioSystem.modeToString(mMode))
+ return new StringBuilder("setMode(").append(AudioSystem.modeToString(mRequestedMode))
.append(") from package=").append(mPackage)
- .append(" pid=").append(mPid).toString();
+ .append(" pid=").append(mRequesterPid)
+ .append(" selected mode=").append(AudioSystem.modeToString(mActualMode))
+ .append(" by pid=").append(mOwnerPid).toString();
}
}
diff --git a/com/android/server/audio/MediaFocusControl.java b/com/android/server/audio/MediaFocusControl.java
index 7d742ffd..c5f563c7 100644
--- a/com/android/server/audio/MediaFocusControl.java
+++ b/com/android/server/audio/MediaFocusControl.java
@@ -89,6 +89,9 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
pw.println("\nMediaFocusControl dump time: "
+ DateFormat.getTimeInstance().format(new Date()));
dumpFocusStack(pw);
+ pw.println("\n");
+ // log
+ mEventLogger.dump(pw);
}
//=================================================================
@@ -120,6 +123,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
private final static Object mAudioFocusLock = new Object();
/**
+ * Arbitrary maximum size of audio focus stack to prevent apps OOM'ing this process.
+ */
+ private static final int MAX_STACK_SIZE = 100;
+
+ private static final AudioEventLogger mEventLogger = new AudioEventLogger(50,
+ "focus commands as seen by MediaFocusControl");
+
+ /**
* Discard the current audio focus owner.
* Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
* focus), remove it from the stack, and clear the remote control display.
@@ -643,11 +654,14 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
int sdk) {
- Log.i(TAG, " AudioFocus requestAudioFocus() from uid/pid " + Binder.getCallingUid()
- + "/" + Binder.getCallingPid()
- + " clientId=" + clientId
- + " req=" + focusChangeHint
- + " flags=0x" + Integer.toHexString(flags));
+ mEventLogger.log((new AudioEventLogger.StringEvent(
+ "requestAudioFocus() from uid/pid " + Binder.getCallingUid()
+ + "/" + Binder.getCallingPid()
+ + " clientId=" + clientId + " callingPack=" + callingPackageName
+ + " req=" + focusChangeHint
+ + " flags=0x" + Integer.toHexString(flags)
+ + " sdk=" + sdk))
+ .printLog(TAG));
// we need a valid binder callback for clients
if (!cb.pingBinder()) {
Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
@@ -660,6 +674,11 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
}
synchronized(mAudioFocusLock) {
+ if (mFocusStack.size() > MAX_STACK_SIZE) {
+ Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
+ return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ }
+
boolean enteringRingOrCall = !mRingOrCallActive
& (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
if (enteringRingOrCall) { mRingOrCallActive = true; }
@@ -770,10 +789,12 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
* */
protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
String callingPackageName) {
- // AudioAttributes are currently ignored, to be used for zones
- Log.i(TAG, " AudioFocus abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
- + "/" + Binder.getCallingPid()
- + " clientId=" + clientId);
+ // AudioAttributes are currently ignored, to be used for zones / a11y
+ mEventLogger.log((new AudioEventLogger.StringEvent(
+ "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+ + "/" + Binder.getCallingPid()
+ + " clientId=" + clientId))
+ .printLog(TAG));
try {
// this will take care of notifying the new focus owner if needed
synchronized(mAudioFocusLock) {
diff --git a/com/android/server/audio/PlaybackActivityMonitor.java b/com/android/server/audio/PlaybackActivityMonitor.java
index d1a37af5..6506cf7f 100644
--- a/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,6 +17,8 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
@@ -90,7 +92,14 @@ public final class PlaybackActivityMonitor
private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
new HashMap<Integer, AudioPlaybackConfiguration>();
- PlaybackActivityMonitor() {
+ private final Context mContext;
+ private int mSavedAlarmVolume = -1;
+ private final int mMaxAlarmVolume;
+ private int mPrivilegedAlarmActiveCount = 0;
+
+ PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
+ mContext = context;
+ mMaxAlarmVolume = maxAlarmVolume;
PlayMonitorClient.sListenerDeathMonitor = this;
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
}
@@ -105,7 +114,7 @@ public final class PlaybackActivityMonitor
if (index >= 0) {
if (!disable) {
if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
- mEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
+ sEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
}
mBannedUids.remove(index);
// nothing else to do, future playback requests from this uid are ok
@@ -116,7 +125,7 @@ public final class PlaybackActivityMonitor
checkBanPlayer(apc, uid);
}
if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
- mEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
+ sEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
}
mBannedUids.add(new Integer(uid));
} // no else to handle, uid already not in list, so enabling again is no-op
@@ -151,7 +160,7 @@ public final class PlaybackActivityMonitor
new AudioPlaybackConfiguration(pic, newPiid,
Binder.getCallingUid(), Binder.getCallingPid());
apc.init();
- mEventLogger.log(new NewPlayerEvent(apc));
+ sEventLogger.log(new NewPlayerEvent(apc));
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
}
@@ -163,7 +172,7 @@ public final class PlaybackActivityMonitor
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
- mEventLogger.log(new AudioAttrEvent(piid, attr));
+ sEventLogger.log(new AudioAttrEvent(piid, attr));
change = apc.handleAudioAttributesEvent(attr);
} else {
Log.e(TAG, "Error updating audio attributes");
@@ -175,6 +184,38 @@ public final class PlaybackActivityMonitor
}
}
+ private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
+ if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
+ apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if ((apc.getAudioAttributes().getAllFlags() &
+ AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0 &&
+ apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
+ mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
+ apc.getClientPid(), apc.getClientUid()) ==
+ PackageManager.PERMISSION_GRANTED) {
+ if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+ apc.getPlayerState() != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if (mPrivilegedAlarmActiveCount++ == 0) {
+ mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
+ AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
+ AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+ mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+ }
+ } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+ apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ if (--mPrivilegedAlarmActiveCount == 0) {
+ if (AudioSystem.getStreamVolumeIndex(
+ AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
+ mMaxAlarmVolume) {
+ AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+ mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+ }
+ }
+ }
+ }
+ }
+ }
+
public void playerEvent(int piid, int event, int binderUid) {
if (DEBUG) { Log.v(TAG, String.format("playerEvent(piid=%d, event=%d)", piid, event)); }
final boolean change;
@@ -183,12 +224,12 @@ public final class PlaybackActivityMonitor
if (apc == null) {
return;
}
- mEventLogger.log(new PlayerEvent(piid, event));
+ sEventLogger.log(new PlayerEvent(piid, event));
if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
for (Integer uidInteger: mBannedUids) {
if (checkBanPlayer(apc, uidInteger.intValue())) {
// player was banned, do not update its state
- mEventLogger.log(new AudioEventLogger.StringEvent(
+ sEventLogger.log(new AudioEventLogger.StringEvent(
"not starting piid:" + piid + " ,is banned"));
return;
}
@@ -200,6 +241,7 @@ public final class PlaybackActivityMonitor
}
if (checkConfigurationCaller(piid, apc, binderUid)) {
//TODO add generation counter to only update to the latest state
+ checkVolumeForPrivilegedAlarm(apc, event);
change = apc.handleStateEvent(event);
} else {
Log.e(TAG, "Error handling event " + event);
@@ -216,7 +258,7 @@ public final class PlaybackActivityMonitor
public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
// no check on UID yet because this is only for logging at the moment
- mEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
+ sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
}
public void releasePlayer(int piid, int binderUid) {
@@ -224,10 +266,11 @@ public final class PlaybackActivityMonitor
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
- mEventLogger.log(new AudioEventLogger.StringEvent(
+ sEventLogger.log(new AudioEventLogger.StringEvent(
"releasing player piid:" + piid));
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
+ checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
}
}
@@ -278,7 +321,7 @@ public final class PlaybackActivityMonitor
}
pw.println("\n");
// log
- mEventLogger.dump(pw);
+ sEventLogger.dump(pw);
}
}
@@ -456,7 +499,8 @@ public final class PlaybackActivityMonitor
}
if (mute) {
try {
- Log.v(TAG, "call: muting player" + piid + " uid:" + apc.getClientUid());
+ sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
+ + piid + " uid:" + apc.getClientUid())).printLog(TAG));
apc.getPlayerProxy().setVolume(0.0f);
mMutedPlayers.add(new Integer(piid));
} catch (Exception e) {
@@ -480,7 +524,8 @@ public final class PlaybackActivityMonitor
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc != null) {
try {
- Log.v(TAG, "call: unmuting player" + piid + " uid:" + apc.getClientUid());
+ sEventLogger.log(new AudioEventLogger.StringEvent("call: unmuting piid:"
+ + piid).printLog(TAG));
apc.getPlayerProxy().setVolume(1.0f);
} catch (Exception e) {
Log.e(TAG, "call: error unmuting player " + piid + " uid:"
@@ -669,8 +714,7 @@ public final class PlaybackActivityMonitor
return;
}
try {
- Log.v(TAG, "ducking (skipRamp=" + skipRamp + ") player piid:"
- + apc.getPlayerInterfaceId() + " uid:" + mUid);
+ sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
DUCK_VSHAPE,
skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
@@ -685,7 +729,8 @@ public final class PlaybackActivityMonitor
final AudioPlaybackConfiguration apc = players.get(piid);
if (apc != null) {
try {
- Log.v(TAG, "unducking player " + piid + " uid:" + mUid);
+ sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
+ + piid)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
DUCK_ID,
VolumeShaper.Operation.REVERSE);
@@ -772,7 +817,28 @@ public final class PlaybackActivityMonitor
}
}
- private final static class AudioAttrEvent extends AudioEventLogger.Event {
+ private static final class DuckEvent extends AudioEventLogger.Event {
+ private final int mPlayerIId;
+ private final boolean mSkipRamp;
+ private final int mClientUid;
+ private final int mClientPid;
+
+ DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+ mPlayerIId = apc.getPlayerInterfaceId();
+ mSkipRamp = skipRamp;
+ mClientUid = apc.getClientUid();
+ mClientPid = apc.getClientPid();
+ }
+
+ @Override
+ public String eventToString() {
+ return new StringBuilder("ducking player piid:").append(mPlayerIId)
+ .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
+ .append(" skip ramp:").append(mSkipRamp).toString();
+ }
+ }
+
+ private static final class AudioAttrEvent extends AudioEventLogger.Event {
private final int mPlayerIId;
private final AudioAttributes mPlayerAttr;
@@ -787,6 +853,6 @@ public final class PlaybackActivityMonitor
}
}
- private final AudioEventLogger mEventLogger = new AudioEventLogger(100,
+ private static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
"playback activity as reported through PlayerBase");
}
diff --git a/com/android/server/autofill/AutofillManagerService.java b/com/android/server/autofill/AutofillManagerService.java
index ddc819d3..1f4161ac 100644
--- a/com/android/server/autofill/AutofillManagerService.java
+++ b/com/android/server/autofill/AutofillManagerService.java
@@ -115,11 +115,24 @@ public final class AutofillManagerService extends SystemService {
private final SparseBooleanArray mDisabledUsers = new SparseBooleanArray();
private final LocalLog mRequestsHistory = new LocalLog(20);
+ private final LocalLog mUiLatencyHistory = new LocalLog(20);
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+ if (sDebug) Slog.d(TAG, "Close system dialogs");
+
+ // TODO(b/64940307): we need to destroy all sessions that are finished but showing
+ // Save UI because there is no way to show the Save UI back when the activity
+ // beneath it is brought back to top. Ideally, we should just hide the UI and
+ // bring it back when the activity resumes.
+ synchronized (mLock) {
+ for (int i = 0; i < mServicesCache.size(); i++) {
+ mServicesCache.valueAt(i).destroyFinishedSessionsLocked();
+ }
+ }
+
mUi.hideAll(null);
}
}
@@ -294,7 +307,7 @@ public final class AutofillManagerService extends SystemService {
AutofillManagerServiceImpl service = mServicesCache.get(resolvedUserId);
if (service == null) {
service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory,
- resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
+ mUiLatencyHistory, resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
mServicesCache.put(userId, service);
}
return service;
@@ -650,7 +663,7 @@ public final class AutofillManagerService extends SystemService {
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service == null) return false;
- return Objects.equals(packageName, service.getPackageName());
+ return Objects.equals(packageName, service.getServicePackageName());
}
}
@@ -724,6 +737,8 @@ public final class AutofillManagerService extends SystemService {
if (showHistory) {
pw.println("Requests history:");
mRequestsHistory.reverseDump(fd, pw, args);
+ pw.println("UI latency history:");
+ mUiLatencyHistory.reverseDump(fd, pw, args);
}
} finally {
setDebugLocked(oldDebug);
diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java
index f2f96f82..862070ad 100644
--- a/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -35,6 +35,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
@@ -63,6 +64,8 @@ import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.HandlerCaller;
import com.android.server.autofill.ui.AutoFillUI;
@@ -89,6 +92,7 @@ final class AutofillManagerServiceImpl {
private final Context mContext;
private final Object mLock;
private final AutoFillUI mUi;
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
private RemoteCallbackList<IAutoFillManagerClient> mClients;
private AutofillServiceInfo mInfo;
@@ -96,6 +100,8 @@ final class AutofillManagerServiceImpl {
private static final Random sRandom = new Random();
private final LocalLog mRequestsHistory;
+ private final LocalLog mUiLatencyHistory;
+
/**
* Whether service was disabled for user due to {@link UserManager} restrictions.
*/
@@ -137,17 +143,19 @@ final class AutofillManagerServiceImpl {
private long mLastPrune = 0;
AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
- int userId, AutoFillUI ui, boolean disabled) {
+ LocalLog uiLatencyHistory, int userId, AutoFillUI ui, boolean disabled) {
mContext = context;
mLock = lock;
mRequestsHistory = requestsHistory;
+ mUiLatencyHistory = uiLatencyHistory;
mUserId = userId;
mUi = ui;
updateLocked(disabled);
}
+ @Nullable
CharSequence getServiceName() {
- final String packageName = getPackageName();
+ final String packageName = getServicePackageName();
if (packageName == null) {
return null;
}
@@ -162,7 +170,8 @@ final class AutofillManagerServiceImpl {
}
}
- String getPackageName() {
+ @Nullable
+ String getServicePackageName() {
final ComponentName serviceComponent = getServiceComponentName();
if (serviceComponent != null) {
return serviceComponent.getPackageName();
@@ -216,8 +225,10 @@ final class AutofillManagerServiceImpl {
if (serviceInfo != null) {
mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
serviceComponent, mUserId);
+ if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo);
} else {
mInfo = null;
+ if (sDebug) Slog.d(TAG, "Reset component for user " + mUserId);
}
final boolean isEnabled = isEnabled();
if (wasEnabled != isEnabled) {
@@ -343,17 +354,31 @@ final class AutofillManagerServiceImpl {
}
void disableOwnedAutofillServicesLocked(int uid) {
- if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid != uid) {
+ Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
+ if (mInfo == null) return;
+
+ final ServiceInfo serviceInfo = mInfo.getServiceInfo();
+ if (serviceInfo.applicationInfo.uid != uid) {
+ Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid
+ + " instead of " + serviceInfo.applicationInfo.uid
+ + " for service " + mInfo);
return;
}
+
+
final long identity = Binder.clearCallingIdentity();
try {
final String autoFillService = getComponentNameFromSettings();
- if (mInfo.getServiceInfo().getComponentName().equals(
- ComponentName.unflattenFromString(autoFillService))) {
+ final ComponentName componentName = serviceInfo.getComponentName();
+ if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
+ mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
+ componentName.getPackageName());
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
destroySessionsLocked();
+ } else {
+ Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
+ + serviceInfo + ") does not match Settings (" + autoFillService + ")");
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -377,7 +402,7 @@ final class AutofillManagerServiceImpl {
final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
sessionId, uid, activityToken, appCallbackToken, hasCallback,
- mInfo.getServiceInfo().getComponentName(), packageName);
+ mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName);
mSessions.put(newSession.id, newSession);
return newSession;
@@ -450,7 +475,7 @@ final class AutofillManagerServiceImpl {
final int sessionCount = mSessions.size();
for (int i = sessionCount - 1; i >= 0; i--) {
final Session session = mSessions.valueAt(i);
- if (session.isSaveUiPendingForToken(token)) {
+ if (session.isSaveUiPendingForTokenLocked(token)) {
session.onPendingSaveUi(operation, token);
return;
}
@@ -636,7 +661,7 @@ final class AutofillManagerServiceImpl {
void destroySessionsLocked() {
if (mSessions.size() == 0) {
- mUi.destroyAll(null, null);
+ mUi.destroyAll(null, null, false);
return;
}
while (mSessions.size() > 0) {
@@ -644,6 +669,18 @@ final class AutofillManagerServiceImpl {
}
}
+ // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
+ void destroyFinishedSessionsLocked() {
+ final int sessionCount = mSessions.size();
+ for (int i = sessionCount - 1; i >= 0; i--) {
+ final Session session = mSessions.valueAt(i);
+ if (session.isSavingLocked()) {
+ if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id);
+ session.forceRemoveSelfLocked();
+ }
+ }
+ }
+
void listSessionsLocked(ArrayList<String> output) {
final int numSessions = mSessions.size();
for (int i = 0; i < numSessions; i++) {
diff --git a/com/android/server/autofill/Helper.java b/com/android/server/autofill/Helper.java
index 086dd29f..236fbfd9 100644
--- a/com/android/server/autofill/Helper.java
+++ b/com/android/server/autofill/Helper.java
@@ -18,6 +18,7 @@ package com.android.server.autofill;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.metrics.LogMaker;
import android.os.Bundle;
import android.service.autofill.Dataset;
import android.util.ArrayMap;
@@ -25,6 +26,8 @@ import android.util.ArraySet;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@@ -99,4 +102,14 @@ public final class Helper {
}
return fields;
}
+
+ @NonNull
+ public static LogMaker newLogMaker(int category, String packageName,
+ String servicePackageName) {
+ final LogMaker log = new LogMaker(category).setPackageName(packageName);
+ if (servicePackageName != null) {
+ log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+ }
+ return log;
+ }
}
diff --git a/com/android/server/autofill/RemoteFillService.java b/com/android/server/autofill/RemoteFillService.java
index f3151485..af55807f 100644
--- a/com/android/server/autofill/RemoteFillService.java
+++ b/com/android/server/autofill/RemoteFillService.java
@@ -578,9 +578,8 @@ final class RemoteFillService implements DeathRecipient {
public void run() {
synchronized (mLock) {
if (isCancelledLocked()) {
- // TODO(b/653742740): we should probably return here, but for now we're justing
- // logging to confirm this is the problem if it happens again.
- Slog.e(LOG_TAG, "run() called after canceled: " + mRequest);
+ if (sDebug) Slog.d(LOG_TAG, "run() called after canceled: " + mRequest);
+ return;
}
}
final RemoteFillService remoteService = getService();
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index 171053fd..ed00ffed 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -50,19 +50,24 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
+import android.service.autofill.InternalSanitizer;
import android.service.autofill.InternalValidator;
import android.service.autofill.SaveInfo;
import android.service.autofill.SaveRequest;
+import android.service.autofill.Transformation;
import android.service.autofill.ValueFinder;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.LocalLog;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -183,6 +188,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private ArrayList<String> mSelectedDatasetIds;
/**
+ * When the session started (using elapsed time since boot).
+ */
+ private final long mStartTime;
+
+ /**
+ * When the UI was shown for the first time (using elapsed time since boot).
+ */
+ @GuardedBy("mLock")
+ private long mUiShownTime;
+
+ @GuardedBy("mLock")
+ private final LocalLog mUiLatencyHistory;
+
+ /**
* Receiver of assist data from the app's {@link Activity}.
*/
private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
@@ -403,10 +422,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
@NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
@NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
- @NonNull IBinder client, boolean hasCallback,
+ @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
@NonNull ComponentName componentName, @NonNull String packageName) {
id = sessionId;
this.uid = uid;
+ mStartTime = SystemClock.elapsedRealtime();
mService = service;
mLock = lock;
mUi = ui;
@@ -414,10 +434,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
mActivityToken = activityToken;
mHasCallback = hasCallback;
+ mUiLatencyHistory = uiLatencyHistory;
mPackageName = packageName;
mClient = IAutoFillManagerClient.Stub.asInterface(client);
- mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
+ writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED);
}
/**
@@ -471,19 +492,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if ((response.getDatasets() == null || response.getDatasets().isEmpty())
&& response.getAuthentication() == null) {
// Response is "empty" from an UI point of view, need to notify client.
- notifyUnavailableToClient();
+ notifyUnavailableToClient(false);
}
synchronized (mLock) {
processResponseLocked(response, requestFlags);
}
- final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
.setType(MetricsEvent.TYPE_SUCCESS)
- .setPackageName(mPackageName)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
- response.getDatasets() == null ? 0 : response.getDatasets().size())
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE,
- servicePackageName);
+ response.getDatasets() == null ? 0 : response.getDatasets().size());
mMetricsLogger.write(log);
}
@@ -499,10 +517,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
mService.resetLastResponse();
}
- LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
- .setType(MetricsEvent.TYPE_FAILURE)
- .setPackageName(mPackageName)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+ LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
+ .setType(MetricsEvent.TYPE_FAILURE);
mMetricsLogger.write(log);
getUiForShowing().showError(message, this);
@@ -521,11 +537,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
}
- LogMaker log = (new LogMaker(
- MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
- .setType(MetricsEvent.TYPE_SUCCESS)
- .setPackageName(mPackageName)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+ LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+ .setType(MetricsEvent.TYPE_SUCCESS);
mMetricsLogger.write(log);
// Nothing left to do...
@@ -545,11 +558,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
}
- LogMaker log = (new LogMaker(
- MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
- .setType(MetricsEvent.TYPE_FAILURE)
- .setPackageName(mPackageName)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+ LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+ .setType(MetricsEvent.TYPE_FAILURE);
mMetricsLogger.write(log);
getUiForShowing().showError(message, this);
@@ -583,6 +593,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// FillServiceCallbacks
@Override
public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
+ if (sDebug) {
+ Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
+ + "; intentSender=" + intent);
+ }
final Intent fillInIntent;
synchronized (mLock) {
if (mDestroyed) {
@@ -591,6 +605,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
fillInIntent = createAuthFillInIntentLocked(requestId, extras);
+ if (fillInIntent == null) {
+ forceRemoveSelfLocked();
+ return;
+ }
}
mService.setAuthenticationSelected(id, mClientState);
@@ -746,21 +764,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
if (result instanceof FillResponse) {
- final FillResponse response = (FillResponse) result;
- mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
- replaceResponseLocked(authenticatedResponse, response);
+ writeLog(MetricsEvent.AUTOFILL_AUTHENTICATED);
+ replaceResponseLocked(authenticatedResponse, (FillResponse) result);
} else if (result instanceof Dataset) {
- // TODO: add proper metric
if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
+ writeLog(MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
final Dataset dataset = (Dataset) result;
authenticatedResponse.getDatasets().set(datasetIdx, dataset);
autoFill(requestId, datasetIdx, dataset, false);
+ } else {
+ writeLog(MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
}
} else {
if (result != null) {
Slog.w(TAG, "service returned invalid auth type: " + result);
}
- // TODO: add proper metric (on else)
+ writeLog(MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
processNullResponseLocked(0);
}
}
@@ -774,43 +793,57 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mHasCallback = hasIt;
}
- /**
- * Shows the save UI, when session can be saved.
- *
- * @return {@code true} if session is done, or {@code false} if it's pending user action.
- */
- public boolean showSaveLocked() {
- if (mDestroyed) {
- Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
- + id + " destroyed");
- return false;
- }
+ @Nullable
+ private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
if (mContexts == null) {
- Slog.d(TAG, "showSaveLocked(): no contexts");
- return true;
+ if (sDebug && logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts");
+ return null;
}
if (mResponses == null) {
// Happens when the activity / session was finished before the service replied, or
// when the service cannot autofill it (and returned a null response).
- if (sVerbose) {
- Slog.v(TAG, "showSaveLocked(): no responses on session");
+ if (sVerbose && logPrefix != null) {
+ Slog.v(TAG, logPrefix + ": no responses on session");
}
- return true;
+ return null;
}
final int lastResponseIdx = getLastResponseIndexLocked();
if (lastResponseIdx < 0) {
- Slog.w(TAG, "showSaveLocked(): did not get last response. mResponses=" + mResponses
- + ", mViewStates=" + mViewStates);
- return true;
+ if (logPrefix != null) {
+ Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses
+ + ", mViewStates=" + mViewStates);
+ }
+ return null;
}
final FillResponse response = mResponses.valueAt(lastResponseIdx);
- final SaveInfo saveInfo = response.getSaveInfo();
- if (sVerbose) {
- Slog.v(TAG, "showSaveLocked(): mResponses=" + mResponses + ", mContexts=" + mContexts
+ if (sVerbose && logPrefix != null) {
+ Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts
+ ", mViewStates=" + mViewStates);
}
+ return response;
+ }
+
+ @Nullable
+ private SaveInfo getSaveInfoLocked() {
+ final FillResponse response = getLastResponseLocked(null);
+ return response == null ? null : response.getSaveInfo();
+ }
+
+ /**
+ * Shows the save UI, when session can be saved.
+ *
+ * @return {@code true} if session is done, or {@code false} if it's pending user action.
+ */
+ public boolean showSaveLocked() {
+ if (mDestroyed) {
+ Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: "
+ + id + " destroyed");
+ return false;
+ }
+ final FillResponse response = getLastResponseLocked("showSaveLocked()");
+ final SaveInfo saveInfo = response == null ? null : response.getSaveInfo();
/*
* The Save dialog is only shown if all conditions below are met:
@@ -825,6 +858,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return true;
}
+ final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo);
+
// Cache used to make sure changed fields do not belong to a dataset.
final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>();
final ArraySet<AutofillId> allIds = new ArraySet<>();
@@ -864,6 +899,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
break;
}
}
+ value = getSanitizedValue(sanitizers, id, value);
currentValues.put(id, value);
final AutofillValue filledValue = viewState.getAutofilledValue();
@@ -922,15 +958,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final InternalValidator validator = saveInfo.getValidator();
if (validator != null) {
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION);
boolean isValid;
try {
isValid = validator.isValid(valueFinder);
+ log.setType(isValid
+ ? MetricsEvent.TYPE_SUCCESS
+ : MetricsEvent.TYPE_DISMISS);
} catch (Exception e) {
- Slog.e(TAG, "Not showing save UI because of exception during validation "
- + e.getClass());
+ Slog.e(TAG, "Not showing save UI because validation failed:", e);
+ log.setType(MetricsEvent.TYPE_FAILURE);
+ mMetricsLogger.write(log);
return true;
}
+ mMetricsLogger.write(log);
if (!isValid) {
Slog.i(TAG, "not showing save UI because fields failed validation");
return true;
@@ -978,7 +1020,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final IAutoFillManagerClient client = getClient();
mPendingSaveUi = new PendingUi(mActivityToken, id, client);
getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
- saveInfo, valueFinder, mPackageName, this, mPendingSaveUi);
+ mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this,
+ mPendingSaveUi);
if (client != null) {
try {
client.setSaveUiState(id, true);
@@ -999,6 +1042,48 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return true;
}
+ @Nullable
+ private ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) {
+ if (saveInfo == null) return null;
+
+ final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys();
+ if (sanitizerKeys == null) return null;
+
+ final int size = sanitizerKeys.length ;
+ final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size);
+ if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers");
+ final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues();
+ for (int i = 0; i < size; i++) {
+ final InternalSanitizer sanitizer = sanitizerKeys[i];
+ final AutofillId[] ids = sanitizerValues[i];
+ if (sDebug) {
+ Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids "
+ + Arrays.toString(ids));
+ }
+ for (AutofillId id : ids) {
+ sanitizers.put(id, sanitizer);
+ }
+ }
+ return sanitizers;
+ }
+
+ @NonNull
+ private AutofillValue getSanitizedValue(
+ @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
+ @NonNull AutofillId id,
+ @NonNull AutofillValue value) {
+ if (sanitizers == null) return value;
+
+ final InternalSanitizer sanitizer = sanitizers.get(id);
+ if (sanitizer == null) {
+ return value;
+ }
+
+ final AutofillValue sanitized = sanitizer.sanitize(value);
+ if (sDebug) Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized);
+ return sanitized;
+ }
+
/**
* Returns whether the session is currently showing the save UI
*/
@@ -1062,6 +1147,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
+ final ArrayMap<AutofillId, InternalSanitizer> sanitizers =
+ createSanitizers(getSaveInfoLocked());
+
final int numContexts = mContexts.size();
for (int contextNum = 0; contextNum < numContexts; contextNum++) {
@@ -1088,7 +1176,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
- node.updateAutofillValue(value);
+ final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+
+ node.updateAutofillValue(sanitizedValue);
}
// Sanitize structure before it's sent to service.
@@ -1244,6 +1334,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
break;
case ACTION_VALUE_CHANGED:
if (value != null && !value.equals(viewState.getCurrentValue())) {
+ if (value.isEmpty()
+ && viewState.getCurrentValue() != null
+ && viewState.getCurrentValue().isText()
+ && viewState.getCurrentValue().getTextValue() != null
+ && getSaveInfoLocked() != null) {
+ final int length = viewState.getCurrentValue().getTextValue().length();
+ if (sDebug) {
+ Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
+ + length + " chars long");
+ }
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
+ mMetricsLogger.write(log);
+ }
+
// Always update the internal state.
viewState.setCurrentValue(value);
@@ -1319,7 +1424,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
filterText = value.getTextValue().toString();
}
- getUiForShowing().showFillUi(filledId, response, filterText, mPackageName, this);
+ getUiForShowing().showFillUi(filledId, response, filterText,
+ mService.getServicePackageName(), mPackageName, this);
+
+ synchronized (mLock) {
+ if (mUiShownTime == 0) {
+ // Log first time UI is shown.
+ mUiShownTime = SystemClock.elapsedRealtime();
+ final long duration = mUiShownTime - mStartTime;
+ if (sDebug) {
+ final StringBuilder msg = new StringBuilder("1st UI for ")
+ .append(mActivityToken)
+ .append(" shown in ");
+ TimeUtils.formatDuration(duration, msg);
+ Slog.d(TAG, msg.toString());
+ }
+ final StringBuilder historyLog = new StringBuilder("id=").append(id)
+ .append(" app=").append(mActivityToken)
+ .append(" svc=").append(mService.getServicePackageName())
+ .append(" latency=");
+ TimeUtils.formatDuration(duration, historyLog);
+ mUiLatencyHistory.log(historyLog.toString());
+
+ final LogMaker metricsLog = newLogMaker(MetricsEvent.AUTOFILL_UI_LATENCY)
+ .setCounterValue((int) duration);
+ mMetricsLogger.write(metricsLog);
+ }
+ }
}
boolean isDestroyed() {
@@ -1334,11 +1465,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- private void notifyUnavailableToClient() {
+ private void notifyUnavailableToClient(boolean sessionFinished) {
synchronized (mLock) {
- if (!mHasCallback || mCurrentViewId == null) return;
+ if (mCurrentViewId == null) return;
try {
- mClient.notifyNoFillUi(id, mCurrentViewId);
+ if (mHasCallback) {
+ mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinished);
+ } else if (sessionFinished) {
+ mClient.setSessionFinished(AutofillManager.STATE_FINISHED);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
}
@@ -1426,7 +1561,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
mService.resetLastResponse();
// Nothing to be done, but need to notify client.
- notifyUnavailableToClient();
+ notifyUnavailableToClient(true);
removeSelf();
}
@@ -1545,6 +1680,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) {
+ if (sDebug) {
+ Slog.d(TAG, "autoFill(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
+ + "; dataset=" + dataset);
+ }
synchronized (mLock) {
if (mDestroyed) {
Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
@@ -1565,10 +1704,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState);
setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
-
+ if (fillInIntent == null) {
+ forceRemoveSelfLocked();
+ return;
+ }
final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
datasetIndex);
startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent);
+
}
}
@@ -1578,14 +1721,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
+ @Nullable
private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
final Intent fillInIntent = new Intent();
final FillContext context = getFillContextByRequestIdLocked(requestId);
if (context == null) {
- // TODO(b/653742740): this will crash system_server. We need to handle it, but we're
- // keeping it crashing for now so we can diagnose when it happens again
- Slog.wtf(TAG, "no FillContext for requestId" + requestId + "; mContexts= " + mContexts);
+ Slog.wtf(TAG, "createAuthFillInIntentLocked(): no FillContext. requestId=" + requestId
+ + "; mContexts= " + mContexts);
+ return null;
}
fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
@@ -1614,6 +1759,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
pw.print(prefix); pw.print("uid: "); pw.println(uid);
pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName);
pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
+ pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
+ pw.print(prefix); pw.print("Time to show UI: ");
+ if (mUiShownTime == 0) {
+ pw.println("N/A");
+ } else {
+ TimeUtils.formatDuration(mUiShownTime - mStartTime, pw);
+ pw.println();
+ }
pw.print(prefix); pw.print("mResponses: ");
if (mResponses == null) {
pw.println("null");
@@ -1732,10 +1885,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (mDestroyed) {
return null;
}
- mUi.destroyAll(mPendingSaveUi, this);
+ mUi.destroyAll(mPendingSaveUi, this, true);
mUi.clearCallback(this);
mDestroyed = true;
- mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
+ writeLog(MetricsEvent.AUTOFILL_SESSION_FINISHED);
return mRemoteFillService;
}
@@ -1746,9 +1899,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
void forceRemoveSelfLocked() {
if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
+ final boolean isPendingSaveUi = isSaveUiPendingLocked();
mPendingSaveUi = null;
removeSelfLocked();
- mUi.destroyAll(mPendingSaveUi, this);
+ mUi.destroyAll(mPendingSaveUi, this, false);
+ if (!isPendingSaveUi) {
+ try {
+ mClient.setSessionFinished(AutofillManager.STATE_UNKNOWN);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error notifying client to finish session", e);
+ }
+ }
}
/**
@@ -1771,7 +1932,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
+ id + " destroyed");
return;
}
- if (isSaveUiPending()) {
+ if (isSaveUiPendingLocked()) {
Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui");
return;
}
@@ -1792,14 +1953,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
* a specific {@code token} created by
* {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
*/
- boolean isSaveUiPendingForToken(@NonNull IBinder token) {
- return isSaveUiPending() && token.equals(mPendingSaveUi.getToken());
+ boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
+ return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
}
/**
* Checks whether this session is hiding the Save UI to handle a custom description link.
*/
- private boolean isSaveUiPending() {
+ private boolean isSaveUiPendingLocked() {
return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
}
@@ -1820,4 +1981,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
return lastResponseIdx;
}
+
+ private LogMaker newLogMaker(int category) {
+ return newLogMaker(category, mService.getServicePackageName());
+ }
+
+ private LogMaker newLogMaker(int category, String servicePackageName) {
+ return Helper.newLogMaker(category, mPackageName, servicePackageName);
+ }
+
+ private void writeLog(int category) {
+ mMetricsLogger.write(newLogMaker(category));
+ }
}
diff --git a/com/android/server/autofill/ui/AutoFillUI.java b/com/android/server/autofill/ui/AutoFillUI.java
index cac2bff5..36b95fc0 100644
--- a/com/android/server/autofill/ui/AutoFillUI.java
+++ b/com/android/server/autofill/ui/AutoFillUI.java
@@ -40,8 +40,9 @@ import android.view.autofill.IAutofillWindowPresenter;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
import java.io.PrintWriter;
@@ -158,21 +159,22 @@ public final class AutoFillUI {
* @param focusedId the currently focused field
* @param response the current fill response
* @param filterText text of the view to be filled
+ * @param servicePackageName package name of the autofill service filling the activity
* @param packageName package name of the activity that is filled
* @param callback Identifier for the caller
*/
public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
- @Nullable String filterText, @NonNull String packageName,
- @NonNull AutoFillUiCallback callback) {
+ @Nullable String filterText, @Nullable String servicePackageName,
+ @NonNull String packageName, @NonNull AutoFillUiCallback callback) {
if (sDebug) {
final int size = filterText == null ? 0 : filterText.length();
Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars");
}
- final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
- .setPackageName(packageName)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
+ final LogMaker log =
+ Helper.newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
filterText == null ? 0 : filterText.length())
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
response.getDatasets() == null ? 0 : response.getDatasets().size());
mHandler.post(() -> {
@@ -184,7 +186,7 @@ public final class AutoFillUI {
filterText, mOverlayControl, new FillUi.Callback() {
@Override
public void onResponsePicked(FillResponse response) {
- log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL);
+ log.setType(MetricsEvent.TYPE_DETAIL);
hideFillUiUiThread(callback);
if (mCallback != null) {
mCallback.authenticate(response.getRequestId(),
@@ -195,7 +197,7 @@ public final class AutoFillUI {
@Override
public void onDatasetPicked(Dataset dataset) {
- log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ log.setType(MetricsEvent.TYPE_ACTION);
hideFillUiUiThread(callback);
if (mCallback != null) {
final int datasetIndex = response.getDatasets().indexOf(dataset);
@@ -205,14 +207,14 @@ public final class AutoFillUI {
@Override
public void onCanceled() {
- log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
+ log.setType(MetricsEvent.TYPE_DISMISS);
hideFillUiUiThread(callback);
}
@Override
public void onDestroy() {
- if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
- log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+ if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
+ log.setType(MetricsEvent.TYPE_CLOSE);
}
mMetricsLogger.write(log);
}
@@ -246,37 +248,39 @@ public final class AutoFillUI {
* Shows the UI asking the user to save for autofill.
*/
public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
- @NonNull SaveInfo info,@NonNull ValueFinder valueFinder, @NonNull String packageName,
+ @Nullable String servicePackageName, @NonNull SaveInfo info,
+ @NonNull ValueFinder valueFinder, @NonNull String packageName,
@NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi) {
if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
int numIds = 0;
numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
- LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_SAVE_UI))
- .setPackageName(packageName).addTaggedData(
- MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
+ final LogMaker log =
+ Helper.newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
mHandler.post(() -> {
if (callback != mCallback) {
return;
}
hideAllUiThread(callback);
- mSaveUi = new SaveUi(mContext, pendingSaveUi, serviceLabel, serviceIcon, info,
- valueFinder, mOverlayControl, new SaveUi.OnSaveListener() {
+ mSaveUi = new SaveUi(mContext, pendingSaveUi, serviceLabel, serviceIcon,
+ servicePackageName, packageName, info, valueFinder, mOverlayControl,
+ new SaveUi.OnSaveListener() {
@Override
public void onSave() {
- log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ log.setType(MetricsEvent.TYPE_ACTION);
hideSaveUiUiThread(mCallback);
if (mCallback != null) {
mCallback.save();
}
- destroySaveUiUiThread(pendingSaveUi);
+ destroySaveUiUiThread(pendingSaveUi, true);
}
@Override
public void onCancel(IntentSender listener) {
- log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
+ log.setType(MetricsEvent.TYPE_DISMISS);
hideSaveUiUiThread(mCallback);
if (listener != null) {
try {
@@ -289,13 +293,13 @@ public final class AutoFillUI {
if (mCallback != null) {
mCallback.cancelSave();
}
- destroySaveUiUiThread(pendingSaveUi);
+ destroySaveUiUiThread(pendingSaveUi, true);
}
@Override
public void onDestroy() {
- if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
- log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+ if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
+ log.setType(MetricsEvent.TYPE_CLOSE);
if (mCallback != null) {
mCallback.cancelSave();
@@ -331,8 +335,8 @@ public final class AutoFillUI {
* Destroy all UI affordances.
*/
public void destroyAll(@Nullable PendingUi pendingSaveUi,
- @Nullable AutoFillUiCallback callback) {
- mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback));
+ @Nullable AutoFillUiCallback callback, boolean notifyClient) {
+ mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback, notifyClient));
}
public void dump(PrintWriter pw) {
@@ -375,7 +379,7 @@ public final class AutoFillUI {
}
@android.annotation.UiThread
- private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi) {
+ private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) {
if (mSaveUi == null) {
// Calling destroySaveUiUiThread() twice is normal - it usually happens when the
// first call is made after the SaveUI is hidden and the second when the session is
@@ -387,7 +391,7 @@ public final class AutoFillUI {
if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): " + pendingSaveUi);
mSaveUi.destroy();
mSaveUi = null;
- if (pendingSaveUi != null) {
+ if (pendingSaveUi != null && notifyClient) {
try {
if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): notifying client");
pendingSaveUi.client.setSaveUiState(pendingSaveUi.id, false);
@@ -399,9 +403,9 @@ public final class AutoFillUI {
@android.annotation.UiThread
private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
- @Nullable AutoFillUiCallback callback) {
+ @Nullable AutoFillUiCallback callback, boolean notifyClient) {
hideFillUiUiThread(callback);
- destroySaveUiUiThread(pendingSaveUi);
+ destroySaveUiUiThread(pendingSaveUi, notifyClient);
}
@android.annotation.UiThread
@@ -413,7 +417,7 @@ public final class AutoFillUI {
Slog.d(TAG, "hideAllUiThread(): "
+ "destroying Save UI because pending restoration is finished");
}
- destroySaveUiUiThread(pendingSaveUi);
+ destroySaveUiUiThread(pendingSaveUi, true);
}
}
}
diff --git a/com/android/server/autofill/ui/FillUi.java b/com/android/server/autofill/ui/FillUi.java
index 371e74d6..bf442dce 100644
--- a/com/android/server/autofill/ui/FillUi.java
+++ b/com/android/server/autofill/ui/FillUi.java
@@ -55,6 +55,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.regex.Pattern;
final class FillUi {
private static final String TAG = "FillUi";
@@ -164,15 +165,18 @@ final class FillUi {
Slog.e(TAG, "Error inflating remote views", e);
continue;
}
- final AutofillValue value = dataset.getFieldValues().get(index);
+ final Pattern filter = dataset.getFilter(index);
String valueText = null;
- // If the dataset needs auth - don't add its text to allow guessing
- // its content based on how filtering behaves.
- if (value != null && value.isText() && dataset.getAuthentication() == null) {
- valueText = value.getTextValue().toString().toLowerCase();
+ if (filter == null) {
+ final AutofillValue value = dataset.getFieldValues().get(index);
+ // If the dataset needs auth - don't add its text to allow guessing
+ // its content based on how filtering behaves.
+ if (value != null && value.isText() && dataset.getAuthentication() == null) {
+ valueText = value.getTextValue().toString().toLowerCase();
+ }
}
- items.add(new ViewItem(dataset, valueText, view));
+ items.add(new ViewItem(dataset, filter, valueText, view));
}
}
@@ -331,11 +335,17 @@ final class FillUi {
private final String mValue;
private final Dataset mDataset;
private final View mView;
+ private final Pattern mFilter;
- ViewItem(Dataset dataset, String value, View view) {
+ ViewItem(Dataset dataset, Pattern filter, String value, View view) {
mDataset = dataset;
mValue = value;
mView = view;
+ mFilter = filter;
+ }
+
+ public Pattern getFilter() {
+ return mFilter;
}
public View getView() {
@@ -349,12 +359,6 @@ final class FillUi {
public String getValue() {
return mValue;
}
-
- @Override
- public String toString() {
- // Used for filtering in the adapter
- return mValue;
- }
}
private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub {
@@ -516,10 +520,16 @@ final class FillUi {
for (int i = 0; i < itemCount; i++) {
final ViewItem item = mAllItems.get(i);
final String value = item.getValue();
- // No value, i.e. null, matches any filter
- if ((value == null && item.mDataset.getAuthentication() == null)
- || (value != null
- && value.toLowerCase().startsWith(constraintLowerCase))) {
+ final Pattern filter = item.getFilter();
+ final boolean matches;
+ if (filter != null) {
+ matches = filter.matcher(constraintLowerCase).matches();
+ } else {
+ matches = (value == null)
+ ? (item.mDataset.getAuthentication() == null)
+ : value.toLowerCase().startsWith(constraintLowerCase);
+ }
+ if (matches) {
filteredItems.add(item);
}
}
diff --git a/com/android/server/autofill/ui/SaveUi.java b/com/android/server/autofill/ui/SaveUi.java
index d0b2e924..d48f23ca 100644
--- a/com/android/server/autofill/ui/SaveUi.java
+++ b/com/android/server/autofill/ui/SaveUi.java
@@ -20,16 +20,15 @@ import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -53,6 +52,8 @@ import android.widget.ScrollView;
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.UiThread;
import java.io.PrintWriter;
@@ -111,6 +112,7 @@ final class SaveUi {
}
private final Handler mHandler = UiThread.getHandler();
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
private final @NonNull Dialog mDialog;
@@ -121,16 +123,21 @@ final class SaveUi {
private final CharSequence mTitle;
private final CharSequence mSubTitle;
private final PendingUi mPendingUi;
+ private final String mServicePackageName;
+ private final String mPackageName;
private boolean mDestroyed;
SaveUi(@NonNull Context context, @NonNull PendingUi pendingUi,
@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
+ @Nullable String servicePackageName, @NonNull String packageName,
@NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
@NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener) {
mPendingUi= pendingUi;
mListener = new OneTimeListener(listener);
mOverlayControl = overlayControl;
+ mServicePackageName = servicePackageName;
+ mPackageName = packageName;
final LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(R.layout.autofill_save, null);
@@ -181,6 +188,8 @@ final class SaveUi {
ScrollView subtitleContainer = null;
final CustomDescription customDescription = info.getCustomDescription();
if (customDescription != null) {
+ writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION, type);
+
mSubTitle = null;
if (sDebug) Slog.d(TAG, "Using custom description");
@@ -190,40 +199,35 @@ final class SaveUi {
@Override
public boolean onClickHandler(View view, PendingIntent pendingIntent,
Intent intent) {
+ final LogMaker log =
+ newLogMaker(MetricsEvent.AUTOFILL_SAVE_LINK_TAPPED, type);
// We need to hide the Save UI before launching the pending intent, and
// restore back it once the activity is finished, and that's achieved by
// adding a custom extra in the activity intent.
- if (pendingIntent != null) {
- if (intent == null) {
- Slog.w(TAG,
- "remote view on custom description does not have intent");
- return false;
- }
- if (!pendingIntent.isActivity()) {
- Slog.w(TAG, "ignoring custom description pending intent that's not "
- + "for an activity: " + pendingIntent);
- return false;
- }
- if (sVerbose) {
- Slog.v(TAG,
- "Intercepting custom description intent: " + intent);
- }
- final IBinder token = mPendingUi.getToken();
- intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
- try {
- pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
- intent);
- mPendingUi.setState(PendingUi.STATE_PENDING);
- if (sDebug) {
- Slog.d(TAG, "hiding UI until restored with token " + token);
- }
- hide();
- } catch (RemoteException e) {
- Slog.w(TAG, "error triggering pending intent: " + intent);
- return false;
- }
+ final boolean isValid = isValidLink(pendingIntent, intent);
+ if (!isValid) {
+ log.setType(MetricsEvent.TYPE_UNKNOWN);
+ mMetricsLogger.write(log);
+ return false;
+ }
+ if (sVerbose) Slog.v(TAG, "Intercepting custom description intent");
+ final IBinder token = mPendingUi.getToken();
+ intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
+ try {
+ pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
+ intent);
+ mPendingUi.setState(PendingUi.STATE_PENDING);
+ if (sDebug) Slog.d(TAG, "hiding UI until restored with token " + token);
+ hide();
+ log.setType(MetricsEvent.TYPE_OPEN);
+ mMetricsLogger.write(log);
+ return true;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "error triggering pending intent: " + intent);
+ log.setType(MetricsEvent.TYPE_FAILURE);
+ mMetricsLogger.write(log);
+ return false;
}
- return true;
}
};
@@ -241,6 +245,7 @@ final class SaveUi {
} else {
mSubTitle = info.getDescription();
if (mSubTitle != null) {
+ writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_SUBTITLE, type);
subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
final TextView subtitleView = new TextView(context);
subtitleView.setText(mSubTitle);
@@ -258,9 +263,7 @@ final class SaveUi {
} else {
noButton.setText(R.string.autofill_save_no);
}
- final View.OnClickListener cancelListener =
- (v) -> mListener.onCancel(info.getNegativeActionListener());
- noButton.setOnClickListener(cancelListener);
+ noButton.setOnClickListener((v) -> mListener.onCancel(info.getNegativeActionListener()));
final View yesButton = view.findViewById(R.id.autofill_save_yes);
yesButton.setOnClickListener((v) -> mListener.onSave());
@@ -268,8 +271,9 @@ final class SaveUi {
mDialog = new Dialog(context, R.style.Theme_DeviceDefault_Light_Panel);
mDialog.setContentView(view);
- // Dialog can be dismissed when touched outside.
- mDialog.setOnDismissListener((d) -> mListener.onCancel(info.getNegativeActionListener()));
+ // Dialog can be dismissed when touched outside, but the negative listener should not be
+ // notified (hence the null argument).
+ mDialog.setOnDismissListener((d) -> mListener.onCancel(null));
final Window window = mDialog.getWindow();
window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
@@ -300,7 +304,7 @@ final class SaveUi {
if (actualWidth <= maxWidth && actualHeight <= maxHeight) {
if (sDebug) {
- Slog.d(TAG, "Addingservice icon "
+ Slog.d(TAG, "Adding service icon "
+ "(" + actualWidth + "x" + actualHeight + ") as it's less than maximum "
+ "(" + maxWidth + "x" + maxHeight + ").");
}
@@ -309,8 +313,39 @@ final class SaveUi {
Slog.w(TAG, "Not adding service icon of size "
+ "(" + actualWidth + "x" + actualHeight + ") because maximum is "
+ "(" + maxWidth + "x" + maxHeight + ").");
- iconView.setVisibility(View.INVISIBLE);
+ ((ViewGroup)iconView.getParent()).removeView(iconView);
+ }
+ }
+
+ private static boolean isValidLink(PendingIntent pendingIntent, Intent intent) {
+ if (pendingIntent == null) {
+ Slog.w(TAG, "isValidLink(): custom description without pending intent");
+ return false;
}
+ if (!pendingIntent.isActivity()) {
+ Slog.w(TAG, "isValidLink(): pending intent not for activity");
+ return false;
+ }
+ if (intent == null) {
+ Slog.w(TAG, "isValidLink(): no intent");
+ return false;
+ }
+ return true;
+ }
+
+ private LogMaker newLogMaker(int category, int saveType) {
+ return newLogMaker(category)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SAVE_TYPE, saveType);
+ }
+
+ private LogMaker newLogMaker(int category) {
+ return new LogMaker(category)
+ .setPackageName(mPackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, mServicePackageName);
+ }
+
+ private void writeLog(int category, int saveType) {
+ mMetricsLogger.write(newLogMaker(category, saveType));
}
/**
@@ -326,17 +361,25 @@ final class SaveUi {
+ mPendingUi.getToken());
return;
}
- switch (operation) {
- case AutofillManager.PENDING_UI_OPERATION_RESTORE:
- if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
- show();
- break;
- case AutofillManager.PENDING_UI_OPERATION_CANCEL:
- if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
- hide();
- break;
- default:
- Slog.w(TAG, "restore(): invalid operation " + operation);
+ final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_PENDING_SAVE_UI_OPERATION);
+ try {
+ switch (operation) {
+ case AutofillManager.PENDING_UI_OPERATION_RESTORE:
+ if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
+ log.setType(MetricsEvent.TYPE_OPEN);
+ show();
+ break;
+ case AutofillManager.PENDING_UI_OPERATION_CANCEL:
+ log.setType(MetricsEvent.TYPE_DISMISS);
+ if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
+ hide();
+ break;
+ default:
+ log.setType(MetricsEvent.TYPE_FAILURE);
+ Slog.w(TAG, "restore(): invalid operation " + operation);
+ }
+ } finally {
+ mMetricsLogger.write(log);
}
mPendingUi.setState(PendingUi.STATE_FINISHED);
}
@@ -385,6 +428,8 @@ final class SaveUi {
pw.print(prefix); pw.print("title: "); pw.println(mTitle);
pw.print(prefix); pw.print("subtitle: "); pw.println(mSubTitle);
pw.print(prefix); pw.print("pendingUi: "); pw.println(mPendingUi);
+ pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
+ pw.print(prefix); pw.print("app: "); pw.println(mPackageName);
final View view = mDialog.getWindow().getDecorView();
final int[] loc = view.getLocationOnScreen();
diff --git a/com/android/server/backup/BackupManagerConstants.java b/com/android/server/backup/BackupManagerConstants.java
index cd601826..245241cb 100644
--- a/com/android/server/backup/BackupManagerConstants.java
+++ b/com/android/server/backup/BackupManagerConstants.java
@@ -123,7 +123,7 @@ class BackupManagerConstants extends ContentObserver {
// group the calls of these methods in a block syncrhonized on
// a reference of this object.
public synchronized long getKeyValueBackupIntervalMilliseconds() {
- if (BackupManagerService.DEBUG_SCHEDULING) {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "getKeyValueBackupIntervalMilliseconds(...) returns "
+ mKeyValueBackupIntervalMilliseconds);
}
@@ -131,7 +131,7 @@ class BackupManagerConstants extends ContentObserver {
}
public synchronized long getKeyValueBackupFuzzMilliseconds() {
- if (BackupManagerService.DEBUG_SCHEDULING) {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "getKeyValueBackupFuzzMilliseconds(...) returns "
+ mKeyValueBackupFuzzMilliseconds);
}
@@ -139,7 +139,7 @@ class BackupManagerConstants extends ContentObserver {
}
public synchronized boolean getKeyValueBackupRequireCharging() {
- if (BackupManagerService.DEBUG_SCHEDULING) {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "getKeyValueBackupRequireCharging(...) returns "
+ mKeyValueBackupRequireCharging);
}
@@ -147,7 +147,7 @@ class BackupManagerConstants extends ContentObserver {
}
public synchronized int getKeyValueBackupRequiredNetworkType() {
- if (BackupManagerService.DEBUG_SCHEDULING) {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "getKeyValueBackupRequiredNetworkType(...) returns "
+ mKeyValueBackupRequiredNetworkType);
}
@@ -155,7 +155,7 @@ class BackupManagerConstants extends ContentObserver {
}
public synchronized long getFullBackupIntervalMilliseconds() {
- if (BackupManagerService.DEBUG_SCHEDULING) {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "getFullBackupIntervalMilliseconds(...) returns "
+ mFullBackupIntervalMilliseconds);
}
@@ -163,7 +163,7 @@ class BackupManagerConstants extends ContentObserver {
}
public synchronized boolean getFullBackupRequireCharging() {
- if (BackupManagerService.DEBUG_SCHEDULING) {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "getFullBackupRequireCharging(...) returns " + mFullBackupRequireCharging);
}
return mFullBackupRequireCharging;
@@ -171,7 +171,7 @@ class BackupManagerConstants extends ContentObserver {
}
public synchronized int getFullBackupRequiredNetworkType() {
- if (BackupManagerService.DEBUG_SCHEDULING) {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "getFullBackupRequiredNetworkType(...) returns "
+ mFullBackupRequiredNetworkType);
}
diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java
index f5257978..eabe21fe 100644
--- a/com/android/server/backup/BackupManagerService.java
+++ b/com/android/server/backup/BackupManagerService.java
@@ -192,6 +192,10 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
+/**
+ * @Deprecated Use RefactoredBackupManagerService instead. This class is only
+ * kept for fallback and archeology reasons and will be removed soon.
+ */
public class BackupManagerService implements BackupManagerServiceInterface {
private static final String TAG = "BackupManagerService";
@@ -397,43 +401,51 @@ public class BackupManagerService implements BackupManagerServiceInterface {
@Override
public void onUnlockUser(int userId) {
if (userId == UserHandle.USER_SYSTEM) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
- sInstance.initialize(userId);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ sInstance.unlockSystemUser();
+ }
+ }
+ }
+
+ // Called through the trampoline from onUnlockUser(), then we buck the work
+ // off to the background thread to keep the unlock time down.
+ public void unlockSystemUser() {
+ mBackupHandler.post(() -> {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
+ sInstance.initialize(UserHandle.USER_SYSTEM);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- // Migrate legacy setting
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
- if (!backupSettingMigrated(userId)) {
+ // Migrate legacy setting
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
+ if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Backup enable apparently not migrated");
+ }
+ final ContentResolver r = sInstance.mContext.getContentResolver();
+ final int enableState = Settings.Secure.getIntForUser(r,
+ Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
+ if (enableState >= 0) {
if (DEBUG) {
- Slog.i(TAG, "Backup enable apparently not migrated");
+ Slog.i(TAG, "Migrating enable state " + (enableState != 0));
}
- final ContentResolver r = sInstance.mContext.getContentResolver();
- final int enableState = Settings.Secure.getIntForUser(r,
- Settings.Secure.BACKUP_ENABLED, -1, userId);
- if (enableState >= 0) {
- if (DEBUG) {
- Slog.i(TAG, "Migrating enable state " + (enableState != 0));
- }
- writeBackupEnableState(enableState != 0, userId);
- Settings.Secure.putStringForUser(r,
- Settings.Secure.BACKUP_ENABLED, null, userId);
- } else {
- if (DEBUG) {
- Slog.i(TAG, "Backup not yet configured; retaining null enable state");
- }
+ writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
+ Settings.Secure.putStringForUser(r,
+ Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Backup not yet configured; retaining null enable state");
}
}
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
- try {
- sInstance.setBackupEnabled(readBackupEnableState(userId));
- } catch (RemoteException e) {
- // can't happen; it's a local object
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+ try {
+ sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
+ } catch (RemoteException e) {
+ // can't happen; it's a local object
}
- }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ });
}
class ProvisionedObserver extends ContentObserver {
@@ -1983,7 +1995,7 @@ public class BackupManagerService implements BackupManagerServiceInterface {
if (uri == null) {
return;
}
- String pkgName = uri.getSchemeSpecificPart();
+ final String pkgName = uri.getSchemeSpecificPart();
if (pkgName != null) {
pkgList = new String[] { pkgName };
}
@@ -1991,7 +2003,7 @@ public class BackupManagerService implements BackupManagerServiceInterface {
// At package-changed we only care about looking at new transport states
if (changed) {
- String[] components =
+ final String[] components =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (MORE_DEBUG) {
@@ -2001,7 +2013,8 @@ public class BackupManagerService implements BackupManagerServiceInterface {
}
}
- mTransportManager.onPackageChanged(pkgName, components);
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageChanged(pkgName, components));
return; // nothing more to do in the PACKAGE_CHANGED case
}
@@ -2033,7 +2046,7 @@ public class BackupManagerService implements BackupManagerServiceInterface {
}
// If they're full-backup candidates, add them there instead
final long now = System.currentTimeMillis();
- for (String packageName : pkgList) {
+ for (final String packageName : pkgList) {
try {
PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
if (appGetsFullBackup(app)
@@ -2050,7 +2063,8 @@ public class BackupManagerService implements BackupManagerServiceInterface {
writeFullBackupScheduleAsync();
}
- mTransportManager.onPackageAdded(packageName);
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageAdded(packageName));
} catch (NameNotFoundException e) {
// doesn't really exist; ignore it
@@ -2074,8 +2088,9 @@ public class BackupManagerService implements BackupManagerServiceInterface {
removePackageParticipantsLocked(pkgList, uid);
}
}
- for (String pkgName : pkgList) {
- mTransportManager.onPackageRemoved(pkgName);
+ for (final String pkgName : pkgList) {
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageRemoved(pkgName));
}
}
}
diff --git a/com/android/server/backup/BackupManagerServiceInterface.java b/com/android/server/backup/BackupManagerServiceInterface.java
index 5dfa6302..041f9ed5 100644
--- a/com/android/server/backup/BackupManagerServiceInterface.java
+++ b/com/android/server/backup/BackupManagerServiceInterface.java
@@ -39,6 +39,8 @@ import java.io.PrintWriter;
*/
public interface BackupManagerServiceInterface {
+ void unlockSystemUser();
+
// Utility: build a new random integer token
int generateRandomIntegerToken();
diff --git a/com/android/server/backup/FullBackupJob.java b/com/android/server/backup/FullBackupJob.java
index 82638b4e..b81a54d3 100644
--- a/com/android/server/backup/FullBackupJob.java
+++ b/com/android/server/backup/FullBackupJob.java
@@ -61,7 +61,7 @@ public class FullBackupJob extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
mParams = params;
- Trampoline service = BackupManagerService.getInstance();
+ Trampoline service = RefactoredBackupManagerService.getInstance();
return service.beginFullBackup(this);
}
@@ -69,7 +69,7 @@ public class FullBackupJob extends JobService {
public boolean onStopJob(JobParameters params) {
if (mParams != null) {
mParams = null;
- Trampoline service = BackupManagerService.getInstance();
+ Trampoline service = RefactoredBackupManagerService.getInstance();
service.endFullBackup();
}
return false;
diff --git a/com/android/server/backup/KeyValueAdbBackupEngine.java b/com/android/server/backup/KeyValueAdbBackupEngine.java
index 279c8284..b38b25a3 100644
--- a/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -4,8 +4,8 @@ import static android.os.ParcelFileDescriptor.MODE_CREATE;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
-import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
-import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_BACKUP_INTERVAL;
import android.app.ApplicationThreadConstants;
import android.app.IBackupAgent;
@@ -19,6 +19,8 @@ import android.os.RemoteException;
import android.os.SELinux;
import android.util.Slog;
+import com.android.server.backup.utils.FullBackupUtils;
+
import libcore.io.IoUtils;
import java.io.File;
@@ -78,7 +80,7 @@ public class KeyValueAdbBackupEngine {
mNewStateName = new File(mStateDir,
pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
- mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
+ mManifestFile = new File(mDataDir, RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME);
}
public void backupOnePackage() throws IOException {
@@ -188,7 +190,7 @@ public class KeyValueAdbBackupEngine {
if (DEBUG) {
Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
}
- BackupManagerService.writeAppManifest(
+ FullBackupUtils.writeAppManifest(
mPackage, mPackageManager, mManifestFile, false, false);
FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
mDataDir.getAbsolutePath(),
@@ -251,7 +253,7 @@ public class KeyValueAdbBackupEngine {
t.start();
// Now pull data from the app and stuff it into the output
- BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
+ FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput);
if (!mBackupManagerService.waitUntilOperationComplete(token)) {
Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
diff --git a/com/android/server/backup/KeyValueAdbRestoreEngine.java b/com/android/server/backup/KeyValueAdbRestoreEngine.java
index b62bb5c9..a2de8e73 100644
--- a/com/android/server/backup/KeyValueAdbRestoreEngine.java
+++ b/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -13,6 +13,8 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.backup.restore.PerformAdbRestoreTask;
+
import libcore.io.IoUtils;
import java.io.File;
@@ -41,7 +43,7 @@ public class KeyValueAdbRestoreEngine implements Runnable {
private final File mDataDir;
FileMetadata mInfo;
- BackupManagerService.PerformAdbRestoreTask mRestoreTask;
+ PerformAdbRestoreTask mRestoreTask;
ParcelFileDescriptor mInFD;
IBackupAgent mAgent;
int mToken;
diff --git a/com/android/server/backup/KeyValueBackupJob.java b/com/android/server/backup/KeyValueBackupJob.java
index d8411e2a..5dfb0bce 100644
--- a/com/android/server/backup/KeyValueBackupJob.java
+++ b/com/android/server/backup/KeyValueBackupJob.java
@@ -71,7 +71,7 @@ public class KeyValueBackupJob extends JobService {
if (delay <= 0) {
delay = interval + new Random().nextInt((int) fuzz);
}
- if (BackupManagerService.DEBUG_SCHEDULING) {
+ if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
}
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
@@ -110,7 +110,7 @@ public class KeyValueBackupJob extends JobService {
}
// Time to run a key/value backup!
- Trampoline service = BackupManagerService.getInstance();
+ Trampoline service = RefactoredBackupManagerService.getInstance();
try {
service.backupNow();
} catch (RemoteException e) {}
diff --git a/com/android/server/backup/PackageManagerBackupAgent.java b/com/android/server/backup/PackageManagerBackupAgent.java
index 8d91e0dd..f658f22d 100644
--- a/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/com/android/server/backup/PackageManagerBackupAgent.java
@@ -30,6 +30,8 @@ import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Slog;
+import com.android.server.backup.utils.AppBackupUtils;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -140,7 +142,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
int N = pkgs.size();
for (int a = N-1; a >= 0; a--) {
PackageInfo pkg = pkgs.get(a);
- if (!BackupManagerService.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
pkgs.remove(a);
}
}
diff --git a/com/android/server/backup/ProcessedPackagesJournal.java b/com/android/server/backup/ProcessedPackagesJournal.java
index 187d5d93..e29b7d58 100644
--- a/com/android/server/backup/ProcessedPackagesJournal.java
+++ b/com/android/server/backup/ProcessedPackagesJournal.java
@@ -21,7 +21,10 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.backup.RefactoredBackupManagerService;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
import java.io.EOFException;
+import java.io.FileInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
@@ -130,7 +133,8 @@ final class ProcessedPackagesJournal {
return;
}
- try (RandomAccessFile oldJournal = new RandomAccessFile(journalFile, "r")) {
+ try (DataInputStream oldJournal = new DataInputStream(
+ new BufferedInputStream(new FileInputStream(journalFile)))) {
while (true) {
String packageName = oldJournal.readUTF();
if (DEBUG) {
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index 141f9207..f2980659 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -77,6 +77,7 @@ import android.os.RemoteException;
import android.os.SELinux;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
@@ -149,6 +150,7 @@ import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
public class RefactoredBackupManagerService implements BackupManagerServiceInterface {
@@ -546,37 +548,51 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
@Override
public void onUnlockUser(int userId) {
if (userId == UserHandle.USER_SYSTEM) {
- sInstance.initialize(userId);
+ sInstance.unlockSystemUser();
+ }
+ }
+ }
+
+ // Called through the trampoline from onUnlockUser(), then we buck the work
+ // off to the background thread to keep the unlock time down.
+ public void unlockSystemUser() {
+ mBackupHandler.post(() -> {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
+ sInstance.initialize(UserHandle.USER_SYSTEM);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- // Migrate legacy setting
- if (!backupSettingMigrated(userId)) {
+ // Migrate legacy setting
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
+ if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Backup enable apparently not migrated");
+ }
+ final ContentResolver r = sInstance.mContext.getContentResolver();
+ final int enableState = Settings.Secure.getIntForUser(r,
+ Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
+ if (enableState >= 0) {
if (DEBUG) {
- Slog.i(TAG, "Backup enable apparently not migrated");
+ Slog.i(TAG, "Migrating enable state " + (enableState != 0));
}
- final ContentResolver r = sInstance.mContext.getContentResolver();
- final int enableState = Settings.Secure.getIntForUser(r,
- Settings.Secure.BACKUP_ENABLED, -1, userId);
- if (enableState >= 0) {
- if (DEBUG) {
- Slog.i(TAG, "Migrating enable state " + (enableState != 0));
- }
- writeBackupEnableState(enableState != 0, userId);
- Settings.Secure.putStringForUser(r,
- Settings.Secure.BACKUP_ENABLED, null, userId);
- } else {
- if (DEBUG) {
- Slog.i(TAG, "Backup not yet configured; retaining null enable state");
- }
+ writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
+ Settings.Secure.putStringForUser(r,
+ Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Backup not yet configured; retaining null enable state");
}
}
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- try {
- sInstance.setBackupEnabled(readBackupEnableState(userId));
- } catch (RemoteException e) {
- // can't happen; it's a local object
- }
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+ try {
+ sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
+ } catch (RemoteException e) {
+ // can't happen; it's a local object
}
- }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ });
}
// Bookkeeping of in-flight operations for timeout etc. purposes. The operation
@@ -619,6 +635,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
private final Object mCurrentOpLock = new Object();
private final Random mTokenGenerator = new Random();
+ final AtomicInteger mNextToken = new AtomicInteger();
private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
@@ -644,7 +661,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
// Persistently track the need to do a full init
private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
- private ArraySet<String> mPendingInits = new ArraySet<>(); // transport names
+ private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names
// Round-robin queue for scheduling full backup passes
private static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
@@ -658,18 +675,41 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
@GuardedBy("mQueueLock")
private ArrayList<FullBackupEntry> mFullBackupQueue;
- // Utility: build a new random integer token
+ // Utility: build a new random integer token. The low bits are the ordinal of the
+ // operation for near-time uniqueness, and the upper bits are random for app-
+ // side unpredictability.
@Override
public int generateRandomIntegerToken() {
- int token;
- do {
- synchronized (mTokenGenerator) {
- token = mTokenGenerator.nextInt();
- }
- } while (token < 0);
+ int token = mTokenGenerator.nextInt();
+ if (token < 0) token = -token;
+ token &= ~0xFF;
+ token |= (mNextToken.incrementAndGet() & 0xFF);
return token;
}
+ /*
+ * Construct a backup agent instance for the metadata pseudopackage. This is a
+ * process-local non-lifecycle agent instance, so we manually set up the context
+ * topology for it.
+ */
+ public PackageManagerBackupAgent makeMetadataAgent() {
+ PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager);
+ pmAgent.attach(mContext);
+ pmAgent.onCreate();
+ return pmAgent;
+ }
+
+ /*
+ * Same as above but with the explicit package-set configuration.
+ */
+ public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) {
+ PackageManagerBackupAgent pmAgent =
+ new PackageManagerBackupAgent(mPackageManager, packages);
+ pmAgent.attach(mContext);
+ pmAgent.onCreate();
+ return pmAgent;
+ }
+
// ----- Debug-only backup operation trace -----
public void addBackupTrace(String s) {
if (DEBUG_BACKUP_TRACE) {
@@ -800,17 +840,18 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
// Remember our ancestral dataset
mTokenFile = new File(mBaseStateDir, "ancestral");
- try (RandomAccessFile tf = new RandomAccessFile(mTokenFile, "r")) {
- int version = tf.readInt();
+ try (DataInputStream tokenStream = new DataInputStream(new BufferedInputStream(
+ new FileInputStream(mTokenFile)))) {
+ int version = tokenStream.readInt();
if (version == CURRENT_ANCESTRAL_RECORD_VERSION) {
- mAncestralToken = tf.readLong();
- mCurrentToken = tf.readLong();
+ mAncestralToken = tokenStream.readLong();
+ mCurrentToken = tokenStream.readLong();
- int numPackages = tf.readInt();
+ int numPackages = tokenStream.readInt();
if (numPackages >= 0) {
mAncestralPackages = new HashSet<>();
for (int i = 0; i < numPackages; i++) {
- String pkgName = tf.readUTF();
+ String pkgName = tokenStream.readUTF();
mAncestralPackages.add(pkgName);
}
}
@@ -878,7 +919,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
if (AppBackupUtils.appGetsFullBackup(pkg)
&& AppBackupUtils.appIsEligibleForBackup(
- pkg.applicationInfo)) {
+ pkg.applicationInfo, mPackageManager)) {
schedule.add(new FullBackupEntry(pkgName, lastBackup));
} else {
if (DEBUG) {
@@ -899,7 +940,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
for (PackageInfo app : apps) {
if (AppBackupUtils.appGetsFullBackup(app)
&& AppBackupUtils.appIsEligibleForBackup(
- app.applicationInfo)) {
+ app.applicationInfo, mPackageManager)) {
if (!foundApps.contains(app.packageName)) {
if (MORE_DEBUG) {
Slog.i(TAG, "New full backup app " + app.packageName + " found");
@@ -925,7 +966,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
schedule = new ArrayList<>(apps.size());
for (PackageInfo info : apps) {
if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup(
- info.applicationInfo)) {
+ info.applicationInfo, mPackageManager)) {
schedule.add(new FullBackupEntry(info.packageName, 0));
}
}
@@ -1155,7 +1196,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
if (uri == null) {
return;
}
- String pkgName = uri.getSchemeSpecificPart();
+ final String pkgName = uri.getSchemeSpecificPart();
if (pkgName != null) {
pkgList = new String[]{pkgName};
}
@@ -1163,7 +1204,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
// At package-changed we only care about looking at new transport states
if (changed) {
- String[] components =
+ final String[] components =
intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (MORE_DEBUG) {
@@ -1173,7 +1214,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
}
}
- mTransportManager.onPackageChanged(pkgName, components);
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageChanged(pkgName, components));
return; // nothing more to do in the PACKAGE_CHANGED case
}
@@ -1205,12 +1247,12 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
}
// If they're full-backup candidates, add them there instead
final long now = System.currentTimeMillis();
- for (String packageName : pkgList) {
+ for (final String packageName : pkgList) {
try {
PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
if (AppBackupUtils.appGetsFullBackup(app)
&& AppBackupUtils.appIsEligibleForBackup(
- app.applicationInfo)) {
+ app.applicationInfo, mPackageManager)) {
enqueueFullBackup(packageName, now);
scheduleNextFullBackupJob(0);
} else {
@@ -1223,7 +1265,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
writeFullBackupScheduleAsync();
}
- mTransportManager.onPackageAdded(packageName);
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageAdded(packageName));
} catch (NameNotFoundException e) {
// doesn't really exist; ignore it
@@ -1247,8 +1290,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
removePackageParticipantsLocked(pkgList, uid);
}
}
- for (String pkgName : pkgList) {
- mTransportManager.onPackageRemoved(pkgName);
+ for (final String pkgName : pkgList) {
+ mBackupHandler.post(
+ () -> mTransportManager.onPackageRemoved(pkgName));
}
}
}
@@ -1507,7 +1551,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
long token = mAncestralToken;
synchronized (mQueueLock) {
- if (mProcessedPackagesJournal.hasBeenProcessed(packageName)) {
+ if (mCurrentToken != 0 && mProcessedPackagesJournal.hasBeenProcessed(packageName)) {
if (MORE_DEBUG) {
Slog.i(TAG, "App in ever-stored, so using current token");
}
@@ -1568,7 +1612,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES);
- if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
+ mPackageManager)) {
BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
@@ -1759,8 +1804,12 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
// Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
// called after we receive cancel here. We need this op's state there.
- // Remove all pending timeout messages for this operation type.
- mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
+ // Remove all pending timeout messages of types OP_TYPE_BACKUP_WAIT and
+ // OP_TYPE_RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
+ // doesn't require cancellation.
+ if (op.type == OP_TYPE_BACKUP_WAIT || op.type == OP_TYPE_RESTORE_WAIT) {
+ mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
+ }
}
mCurrentOpLock.notifyAll();
}
@@ -2108,14 +2157,26 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
// so tear down any ongoing backup task right away.
@Override
public void endFullBackup() {
- synchronized (mQueueLock) {
- if (mRunningFullBackupTask != null) {
- if (DEBUG_SCHEDULING) {
- Slog.i(TAG, "Telling running backup to stop");
+ // offload the mRunningFullBackupTask.handleCancel() call to another thread,
+ // as we might have to wait for mCancelLock
+ Runnable endFullBackupRunnable = new Runnable() {
+ @Override
+ public void run() {
+ PerformFullTransportBackupTask pftbt = null;
+ synchronized (mQueueLock) {
+ if (mRunningFullBackupTask != null) {
+ pftbt = mRunningFullBackupTask;
+ }
+ }
+ if (pftbt != null) {
+ if (DEBUG_SCHEDULING) {
+ Slog.i(TAG, "Telling running backup to stop");
+ }
+ pftbt.handleCancel(true);
}
- mRunningFullBackupTask.handleCancel(true);
}
- }
+ };
+ new Thread(endFullBackupRunnable, "end-full-backup").start();
}
// Used by both incremental and full restore
@@ -2800,8 +2861,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
final long oldId = Binder.clearCallingIdentity();
try {
String prevTransport = mTransportManager.selectTransport(transport);
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.BACKUP_TRANSPORT, transport);
+ updateStateForTransport(transport);
Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
+ " returning " + prevTransport);
return prevTransport;
@@ -2826,9 +2886,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
@Override
public void onSuccess(String transportName) {
mTransportManager.selectTransport(transportName);
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.BACKUP_TRANSPORT,
- mTransportManager.getCurrentTransportName());
+ updateStateForTransport(mTransportManager.getCurrentTransportName());
Slog.v(TAG, "Transport successfully selected: "
+ transport.flattenToShortString());
try {
@@ -2853,6 +2911,28 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
Binder.restoreCallingIdentity(oldId);
}
+ private void updateStateForTransport(String newTransportName) {
+ // Publish the name change
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT, newTransportName);
+
+ // And update our current-dataset bookkeeping
+ IBackupTransport transport = mTransportManager.getTransportBinder(newTransportName);
+ if (transport != null) {
+ try {
+ mCurrentToken = transport.getCurrentRestoreSet();
+ } catch (Exception e) {
+ // Oops. We can't know the current dataset token, so reset and figure it out
+ // when we do the next k/v backup operation on this transport.
+ mCurrentToken = 0;
+ }
+ } else {
+ // The named transport isn't bound at this particular moment, so we can't
+ // know yet what its current dataset token is. Reset as above.
+ mCurrentToken = 0;
+ }
+ }
+
// Supply the configuration Intent for the given transport. If the name is not one
// of the available transports, or if the transport does not supply any configuration
// UI, the method returns null.
@@ -3162,19 +3242,6 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
}
}
- // We also avoid backups of 'disabled' apps
- private static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
- switch (pm.getApplicationEnabledSetting(app.packageName)) {
- case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
- case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
- case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
- return true;
-
- default:
- return false;
- }
- }
-
@Override
public boolean isAppEligibleForBackup(String packageName) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
@@ -3182,9 +3249,10 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
PackageManager.GET_SIGNATURES);
- if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo) ||
+ if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
+ mPackageManager) ||
AppBackupUtils.appIsStopped(packageInfo.applicationInfo) ||
- appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
+ AppBackupUtils.appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
return false;
}
IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
diff --git a/com/android/server/backup/Trampoline.java b/com/android/server/backup/Trampoline.java
index fcd929a7..9739e380 100644
--- a/com/android/server/backup/Trampoline.java
+++ b/com/android/server/backup/Trampoline.java
@@ -98,7 +98,7 @@ public class Trampoline extends IBackupManager.Stub {
protected boolean isRefactoredServiceEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 1) == 0;
+ Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 0) == 0;
}
protected int binderGetCallingUid() {
@@ -139,6 +139,13 @@ public class Trampoline extends IBackupManager.Stub {
}
}
+ void unlockSystemUser() {
+ BackupManagerServiceInterface svc = mService;
+ if (svc != null) {
+ svc.unlockSystemUser();
+ }
+ }
+
public void setBackupServiceActive(final int userHandle, boolean makeActive) {
// Only the DPM should be changing the active state of backup
final int caller = binderGetCallingUid();
diff --git a/com/android/server/backup/TransportManager.java b/com/android/server/backup/TransportManager.java
index 9aae3841..7a0173f6 100644
--- a/com/android/server/backup/TransportManager.java
+++ b/com/android/server/backup/TransportManager.java
@@ -341,9 +341,9 @@ public class TransportManager {
private class TransportConnection implements ServiceConnection {
// Hold mTransportsLock to access these fields so as to provide a consistent view of them.
- private IBackupTransport mBinder;
+ private volatile IBackupTransport mBinder;
private final List<TransportReadyCallback> mListeners = new ArrayList<>();
- private String mTransportName;
+ private volatile String mTransportName;
private final ComponentName mTransportComponent;
@@ -426,25 +426,24 @@ public class TransportManager {
+ rebindTimeout + "ms");
}
+ // Intentionally not synchronized -- the variable is volatile and changes to its value
+ // are inside synchronized blocks, providing a memory sync barrier; and this method
+ // does not touch any other state protected by that lock.
private IBackupTransport getBinder() {
- synchronized (mTransportLock) {
- return mBinder;
- }
+ return mBinder;
}
+ // Intentionally not synchronized; same as getBinder()
private String getName() {
- synchronized (mTransportLock) {
- return mTransportName;
- }
+ return mTransportName;
}
+ // Intentionally not synchronized; same as getBinder()
private void bindIfUnbound() {
- synchronized (mTransportLock) {
- if (mBinder == null) {
- Slog.d(TAG,
- "Rebinding to transport " + mTransportComponent.flattenToShortString());
- bindToTransport(mTransportComponent, this);
- }
+ if (mBinder == null) {
+ Slog.d(TAG,
+ "Rebinding to transport " + mTransportComponent.flattenToShortString());
+ bindToTransport(mTransportComponent, this);
}
}
diff --git a/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 4085f63a..f0b3e4a0 100644
--- a/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -236,12 +236,11 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
obbConnection.establish(); // we'll want this later
sendStartBackup();
+ PackageManager pm = backupManagerService.getPackageManager();
// doAllApps supersedes the package set if any
if (mAllApps) {
- List<PackageInfo> allPackages =
- backupManagerService.getPackageManager().getInstalledPackages(
- PackageManager.GET_SIGNATURES);
+ List<PackageInfo> allPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
for (int i = 0; i < allPackages.size(); i++) {
PackageInfo pkg = allPackages.get(i);
// Exclude system apps if we've been asked to do so
@@ -288,7 +287,7 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
while (iter.hasNext()) {
PackageInfo pkg = iter.next().getValue();
- if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo)
+ if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)
|| AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
iter.remove();
if (DEBUG) {
diff --git a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index bc7c1174..90134e1a 100644
--- a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -140,10 +140,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
for (String pkg : whichPackages) {
try {
- PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(pkg,
- PackageManager.GET_SIGNATURES);
+ PackageManager pm = backupManagerService.getPackageManager();
+ PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
mCurrentPackage = info;
- if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo)) {
+ if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
// Cull any packages that have indicated that backups are not permitted,
// that run as system-domain uids but do not define their own backup agents,
// as well as any explicit mention of the 'special' shared-storage agent
@@ -306,6 +306,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
final int N = mPackages.size();
final byte[] buffer = new byte[8192];
for (int i = 0; i < N; i++) {
+ mBackupRunner = null;
PackageInfo currentPackage = mPackages.get(i);
String packageName = currentPackage.packageName;
if (DEBUG) {
@@ -491,7 +492,13 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
}
EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE, packageName,
"transport rejected");
- // Do nothing, clean up, and continue looping.
+ // This failure state can come either a-priori from the transport, or
+ // from the preflight pass. If we got as far as preflight, we now need
+ // to tear down the target process.
+ if (mBackupRunner != null) {
+ backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+ }
+ // ... and continue looping.
} else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
BackupObserverUtils
.sendBackupOnPackageResult(mBackupObserver, packageName,
@@ -501,6 +508,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
packageName);
}
+ backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
BackupObserverUtils
@@ -527,6 +535,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
// Abort entire backup pass.
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+ backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
return;
} else {
// Success!
diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java
index ce4f906e..7a8a920e 100644
--- a/com/android/server/backup/internal/PerformBackupTask.java
+++ b/com/android/server/backup/internal/PerformBackupTask.java
@@ -227,9 +227,8 @@ public class PerformBackupTask implements BackupRestoreTask {
if (!mFinished) {
finalizeBackup();
} else {
- Slog.e(TAG, "Duplicate finish");
+ Slog.e(TAG, "Duplicate finish of K/V pass");
}
- mFinished = true;
break;
}
}
@@ -322,8 +321,7 @@ public class PerformBackupTask implements BackupRestoreTask {
// because it's cheap and this way we guarantee that we don't get out of
// step even if we're selecting among various transports at run time.
if (mStatus == BackupTransport.TRANSPORT_OK) {
- PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
- backupManagerService.getPackageManager());
+ PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
mStatus = invokeAgentForBackup(
PACKAGE_MANAGER_SENTINEL,
IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
@@ -391,11 +389,9 @@ public class PerformBackupTask implements BackupRestoreTask {
// to sanity-check here. This also gives us the classname of the
// package's backup agent.
try {
- mCurrentPackage = backupManagerService.getPackageManager().getPackageInfo(
- request.packageName,
- PackageManager.GET_SIGNATURES);
- if (!AppBackupUtils.appIsEligibleForBackup(
- mCurrentPackage.applicationInfo)) {
+ PackageManager pm = backupManagerService.getPackageManager();
+ mCurrentPackage = pm.getPackageInfo(request.packageName, PackageManager.GET_SIGNATURES);
+ if (!AppBackupUtils.appIsEligibleForBackup(mCurrentPackage.applicationInfo, pm)) {
// The manifest has changed but we had a stale backup request pending.
// This won't happen again because the app won't be requesting further
// backups.
@@ -609,6 +605,7 @@ public class PerformBackupTask implements BackupRestoreTask {
break;
}
}
+ mFinished = true;
Slog.i(TAG, "K/V backup pass finished.");
// Only once we're entirely finished do we release the wakelock for k/v backup.
backupManagerService.getWakelock().release();
diff --git a/com/android/server/backup/internal/RunInitializeReceiver.java b/com/android/server/backup/internal/RunInitializeReceiver.java
index a6897d0e..1df0bf0c 100644
--- a/com/android/server/backup/internal/RunInitializeReceiver.java
+++ b/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -23,6 +23,7 @@ import static com.android.server.backup.RefactoredBackupManagerService.TAG;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.util.ArraySet;
import android.util.Slog;
import com.android.server.backup.RefactoredBackupManagerService;
@@ -38,19 +39,22 @@ public class RunInitializeReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
synchronized (backupManagerService.getQueueLock()) {
+ final ArraySet<String> pendingInits = backupManagerService.getPendingInits();
if (DEBUG) {
- Slog.v(TAG, "Running a device init");
+ Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending");
}
- String[] pendingInits = (String[]) backupManagerService.getPendingInits().toArray();
- backupManagerService.clearPendingInits();
- PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
- pendingInits, null);
+ if (pendingInits.size() > 0) {
+ final String[] transports = pendingInits.toArray(new String[pendingInits.size()]);
+ PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
+ transports, null);
- // Acquire the wakelock and pass it to the init thread. it will
- // be released once init concludes.
- backupManagerService.getWakelock().acquire();
- backupManagerService.getBackupHandler().post(initTask);
+ // Acquire the wakelock and pass it to the init thread. it will
+ // be released once init concludes.
+ backupManagerService.clearPendingInits();
+ backupManagerService.getWakelock().acquire();
+ backupManagerService.getBackupHandler().post(initTask);
+ }
}
}
}
diff --git a/com/android/server/backup/restore/PerformAdbRestoreTask.java b/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 62ae065b..22691bb6 100644
--- a/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -150,8 +150,7 @@ public class PerformAdbRestoreTask implements Runnable {
mObserver = observer;
mLatchObject = latch;
mAgent = null;
- mPackageManagerBackupAgent = new PackageManagerBackupAgent(
- backupManagerService.getPackageManager());
+ mPackageManagerBackupAgent = backupManagerService.makeMetadataAgent();
mAgentPackage = null;
mTargetApp = null;
mObbConnection = new FullBackupObbConnection(backupManagerService);
diff --git a/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 21d5dc21..b538c6d4 100644
--- a/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -198,8 +198,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
boolean hasSettings = false;
for (int i = 0; i < filterSet.length; i++) {
try {
- PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(
- filterSet[i], 0);
+ PackageManager pm = backupManagerService.getPackageManager();
+ PackageInfo info = pm.getPackageInfo(filterSet[i], 0);
if ("android".equals(info.packageName)) {
hasSystem = true;
continue;
@@ -209,8 +209,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
continue;
}
- if (AppBackupUtils.appIsEligibleForBackup(
- info.applicationInfo)) {
+ if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
mAcceptSet.add(info);
}
} catch (NameNotFoundException e) {
@@ -387,8 +386,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Pull the Package Manager metadata from the restore set first
mCurrentPackage = new PackageInfo();
mCurrentPackage.packageName = PACKAGE_MANAGER_SENTINEL;
- mPmAgent = new PackageManagerBackupAgent(backupManagerService.getPackageManager(),
- null);
+ mPmAgent = backupManagerService.makeMetadataAgent(null);
mAgent = IBackupAgent.Stub.asInterface(mPmAgent.onBind());
if (MORE_DEBUG) {
Slog.v(TAG, "initiating restore for PMBA");
@@ -779,6 +777,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
private void restoreFinished() {
+ if (DEBUG) {
+ Slog.d(TAG, "restoreFinished packageName=" + mCurrentPackage.packageName);
+ }
try {
backupManagerService
.prepareOperationTimeout(mEphemeralOpToken,
diff --git a/com/android/server/backup/utils/AppBackupUtils.java b/com/android/server/backup/utils/AppBackupUtils.java
index 4abf18ad..d7cac777 100644
--- a/com/android/server/backup/utils/AppBackupUtils.java
+++ b/com/android/server/backup/utils/AppBackupUtils.java
@@ -22,6 +22,7 @@ import static com.android.server.backup.RefactoredBackupManagerService.TAG;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Process;
import android.util.Slog;
@@ -44,7 +45,7 @@ public class AppBackupUtils {
* <li>it is the special shared-storage backup package used for 'adb backup'
* </ol>
*/
- public static boolean appIsEligibleForBackup(ApplicationInfo app) {
+ public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
// 1. their manifest states android:allowBackup="false"
if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
return false;
@@ -60,11 +61,33 @@ public class AppBackupUtils {
return false;
}
- return true;
+ // 4. it is an "instant" app
+ if (app.isInstantApp()) {
+ return false;
+ }
+
+ // Everything else checks out; the only remaining roadblock would be if the
+ // package were disabled
+ return !appIsDisabled(app, pm);
+ }
+
+ /** Avoid backups of 'disabled' apps. */
+ public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
+ switch (pm.getApplicationEnabledSetting(app.packageName)) {
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+ case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+ return true;
+
+ default:
+ return false;
+ }
}
/**
- * Checks if the app is in a stopped state, that means it won't receive broadcasts.
+ * Checks if the app is in a stopped state. This is not part of the general "eligible for
+ * backup?" check because we *do* still need to restore data to apps in this state (e.g.
+ * newly-installing ones)
*/
public static boolean appIsStopped(ApplicationInfo app) {
return ((app.flags & ApplicationInfo.FLAG_STOPPED) != 0);
diff --git a/com/android/server/connectivity/IpConnectivityEventBuilder.java b/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 22330e66..67e72167 100644
--- a/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -126,7 +126,7 @@ final public class IpConnectivityEventBuilder {
wakeupStats.systemWakeups = in.systemWakeups;
wakeupStats.nonApplicationWakeups = in.nonApplicationWakeups;
wakeupStats.applicationWakeups = in.applicationWakeups;
- wakeupStats.unroutedWakeups = in.unroutedWakeups;
+ wakeupStats.noUidWakeups = in.noUidWakeups;
final IpConnectivityEvent out = buildEvent(0, 0, in.iface);
out.setWakeupStats(wakeupStats);
return out;
diff --git a/com/android/server/connectivity/IpConnectivityMetrics.java b/com/android/server/connectivity/IpConnectivityMetrics.java
index 475d786a..f2445fa3 100644
--- a/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -34,6 +34,7 @@ import android.util.Base64;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
import com.android.internal.util.TokenBucket;
import com.android.server.SystemService;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
@@ -44,7 +45,11 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.ToIntFunction;
-/** {@hide} */
+/**
+ * Event buffering service for core networking and connectivity metrics.
+ *
+ * {@hide}
+ */
final public class IpConnectivityMetrics extends SystemService {
private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
private static final boolean DBG = false;
@@ -58,7 +63,10 @@ final public class IpConnectivityMetrics extends SystemService {
private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
- // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
+ // Default size of the event rolling log for bug report dumps.
+ private static final int DEFAULT_LOG_SIZE = 500;
+ // Default size of the event buffer for metrics reporting.
+ // Once the buffer is full, incoming events are dropped.
private static final int DEFAULT_BUFFER_SIZE = 2000;
// Maximum size of the event buffer.
private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
@@ -67,24 +75,38 @@ final public class IpConnectivityMetrics extends SystemService {
private static final int ERROR_RATE_LIMITED = -1;
- // Lock ensuring that concurrent manipulations of the event buffer are correct.
+ // Lock ensuring that concurrent manipulations of the event buffers are correct.
// There are three concurrent operations to synchronize:
// - appending events to the buffer.
// - iterating throught the buffer.
// - flushing the buffer content and replacing it by a new buffer.
private final Object mLock = new Object();
+ // Implementation instance of IIpConnectivityMetrics.aidl.
@VisibleForTesting
public final Impl impl = new Impl();
+ // Subservice listening to Netd events via INetdEventListener.aidl.
@VisibleForTesting
NetdEventListenerService mNetdListener;
+ // Rolling log of the most recent events. This log is used for dumping
+ // connectivity events in bug reports.
+ @GuardedBy("mLock")
+ private final RingBuffer<ConnectivityMetricsEvent> mEventLog =
+ new RingBuffer(ConnectivityMetricsEvent.class, DEFAULT_LOG_SIZE);
+ // Buffer of connectivity events used for metrics reporting. This buffer
+ // does not rotate automatically and instead saturates when it becomes full.
+ // It is flushed at metrics reporting.
@GuardedBy("mLock")
private ArrayList<ConnectivityMetricsEvent> mBuffer;
+ // Total number of events dropped from mBuffer since last metrics reporting.
@GuardedBy("mLock")
private int mDropped;
+ // Capacity of mBuffer
@GuardedBy("mLock")
private int mCapacity;
+ // A list of rate limiting counters keyed by connectivity event types for
+ // metrics reporting mBuffer.
@GuardedBy("mLock")
private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
@@ -132,6 +154,7 @@ final public class IpConnectivityMetrics extends SystemService {
private int append(ConnectivityMetricsEvent event) {
if (DBG) Log.d(TAG, "logEvent: " + event);
synchronized (mLock) {
+ mEventLog.append(event);
final int left = mCapacity - mBuffer.size();
if (event == null) {
return left;
@@ -216,6 +239,23 @@ final public class IpConnectivityMetrics extends SystemService {
}
}
+ /**
+ * Prints for bug reports the content of the rolling event log and the
+ * content of Netd event listener.
+ */
+ private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final ConnectivityMetricsEvent[] events;
+ synchronized (mLock) {
+ events = mEventLog.toArray();
+ }
+ for (ConnectivityMetricsEvent ev : events) {
+ pw.println(ev.toString());
+ }
+ if (mNetdListener != null) {
+ mNetdListener.list(pw);
+ }
+ }
+
private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
pw.println("Buffered events: " + mBuffer.size());
@@ -258,7 +298,8 @@ final public class IpConnectivityMetrics extends SystemService {
cmdFlush(fd, pw, args);
return;
case CMD_DUMPSYS:
- // Fallthrough to CMD_LIST when dumpsys.cpp dumps services states (bug reports)
+ cmdDumpsys(fd, pw, args);
+ return;
case CMD_LIST:
cmdList(fd, pw, args);
return;
diff --git a/com/android/server/connectivity/Nat464Xlat.java b/com/android/server/connectivity/Nat464Xlat.java
index e6585ad1..fceacba4 100644
--- a/com/android/server/connectivity/Nat464Xlat.java
+++ b/com/android/server/connectivity/Nat464Xlat.java
@@ -20,6 +20,7 @@ import android.net.InterfaceConfiguration;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.NetworkInfo;
import android.net.RouteInfo;
import android.os.INetworkManagementService;
import android.os.RemoteException;
@@ -44,12 +45,18 @@ public class Nat464Xlat extends BaseNetworkObserver {
// This must match the interface prefix in clatd.c.
private static final String CLAT_PREFIX = "v4-";
- // The network types we will start clatd on,
+ // The network types on which we will start clatd,
// allowing clat only on networks for which we can support IPv6-only.
private static final int[] NETWORK_TYPES = {
- ConnectivityManager.TYPE_MOBILE,
- ConnectivityManager.TYPE_WIFI,
- ConnectivityManager.TYPE_ETHERNET,
+ ConnectivityManager.TYPE_MOBILE,
+ ConnectivityManager.TYPE_WIFI,
+ ConnectivityManager.TYPE_ETHERNET,
+ };
+
+ // The network states in which running clatd is supported.
+ private static final NetworkInfo.State[] NETWORK_STATES = {
+ NetworkInfo.State.CONNECTED,
+ NetworkInfo.State.SUSPENDED,
};
private final INetworkManagementService mNMService;
@@ -81,11 +88,8 @@ public class Nat464Xlat extends BaseNetworkObserver {
*/
public static boolean requiresClat(NetworkAgentInfo nai) {
// TODO: migrate to NetworkCapabilities.TRANSPORT_*.
- final int netType = nai.networkInfo.getType();
final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
- // TODO: this should also consider if the network is in SUSPENDED state to avoid stopping
- // clatd in SUSPENDED state.
- final boolean connected = nai.networkInfo.isConnected();
+ final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState());
// We only run clat on networks that don't have a native IPv4 address.
final boolean hasIPv4Address =
(nai.linkProperties != null) && nai.linkProperties.hasIPv4Address();
@@ -148,7 +152,6 @@ public class Nat464Xlat extends BaseNetworkObserver {
* turn ND offload off if on WiFi.
*/
private void enterRunningState() {
- maybeSetIpv6NdOffload(mBaseIface, false);
mState = State.RUNNING;
}
@@ -156,10 +159,6 @@ public class Nat464Xlat extends BaseNetworkObserver {
* Stop clatd, and turn ND offload on if it had been turned off.
*/
private void enterStoppingState() {
- if (isRunning()) {
- maybeSetIpv6NdOffload(mBaseIface, true);
- }
-
try {
mNMService.stopClatd(mBaseIface);
} catch(RemoteException|IllegalStateException e) {
@@ -275,19 +274,6 @@ public class Nat464Xlat extends BaseNetworkObserver {
}
}
- private void maybeSetIpv6NdOffload(String iface, boolean on) {
- // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
- if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
- return;
- }
- try {
- Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
- mNMService.setInterfaceIpv6NdOffload(iface, on);
- } catch(RemoteException|IllegalStateException e) {
- Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
- }
- }
-
/**
* Adds stacked link on base link and transitions to RUNNING state.
*/
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 6f7ace2f..6206dfcd 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -38,6 +38,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.BitUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.PrintWriter;
@@ -82,9 +83,8 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>();
// Ring buffer array for storing packet wake up events sent by Netd.
@GuardedBy("this")
- private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH];
- @GuardedBy("this")
- private long mWakeupEventCursor = 0;
+ private final RingBuffer<WakeupEvent> mWakeupEvents =
+ new RingBuffer(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
private final ConnectivityManager mCm;
@@ -170,18 +170,16 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
timestampMs = System.currentTimeMillis();
}
- addWakupEvent(iface, timestampMs, uid);
+ addWakeupEvent(iface, timestampMs, uid);
}
@GuardedBy("this")
- private void addWakupEvent(String iface, long timestampMs, int uid) {
- int index = wakeupEventIndex(mWakeupEventCursor);
- mWakeupEventCursor++;
+ private void addWakeupEvent(String iface, long timestampMs, int uid) {
WakeupEvent event = new WakeupEvent();
event.iface = iface;
event.timestampMs = timestampMs;
event.uid = uid;
- mWakeupEvents[index] = event;
+ mWakeupEvents.append(event);
WakeupStats stats = mWakeupStats.get(iface);
if (stats == null) {
stats = new WakeupStats(iface);
@@ -190,23 +188,6 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
stats.countEvent(event);
}
- @GuardedBy("this")
- private WakeupEvent[] getWakeupEvents() {
- int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length);
- WakeupEvent[] out = new WakeupEvent[length];
- // Reverse iteration from youngest event to oldest event.
- long inCursor = mWakeupEventCursor - 1;
- int outIdx = out.length - 1;
- while (outIdx >= 0) {
- out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)];
- }
- return out;
- }
-
- private static int wakeupEventIndex(long cursor) {
- return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH);
- }
-
public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
@@ -230,7 +211,7 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
for (int i = 0; i < mWakeupStats.size(); i++) {
pw.println(mWakeupStats.valueAt(i));
}
- for (WakeupEvent wakeup : getWakeupEvents()) {
+ for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
pw.println(wakeup);
}
}
diff --git a/com/android/server/connectivity/tethering/OffloadController.java b/com/android/server/connectivity/tethering/OffloadController.java
index 5eafe5f9..cff216c7 100644
--- a/com/android/server/connectivity/tethering/OffloadController.java
+++ b/com/android/server/connectivity/tethering/OffloadController.java
@@ -52,6 +52,7 @@ import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -73,6 +74,8 @@ public class OffloadController {
private static final String ANYIP = "0.0.0.0";
private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
+ private static enum UpdateType { IF_NEEDED, FORCE };
+
private final Handler mHandler;
private final OffloadHardwareInterface mHwInterface;
private final ContentResolver mContentResolver;
@@ -185,8 +188,8 @@ public class OffloadController {
updateStatsForAllUpstreams();
forceTetherStatsPoll();
// [2] (Re)Push all state.
- // TODO: computeAndPushLocalPrefixes()
- // TODO: push all downstream state.
+ computeAndPushLocalPrefixes(UpdateType.FORCE);
+ pushAllDownstreamState();
pushUpstreamParameters(null);
}
@@ -319,7 +322,7 @@ public class OffloadController {
}
private boolean maybeUpdateDataLimit(String iface) {
- // setDataLimit may only be called while offload is occuring on this upstream.
+ // setDataLimit may only be called while offload is occurring on this upstream.
if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
return true;
}
@@ -368,15 +371,15 @@ public class OffloadController {
// upstream parameters fails (probably just wait for a subsequent
// onOffloadEvent() callback to tell us offload is available again and
// then reapply all state).
- computeAndPushLocalPrefixes();
+ computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
pushUpstreamParameters(prevUpstream);
}
public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
- if (!started()) return;
-
mExemptPrefixes = localPrefixes;
- computeAndPushLocalPrefixes();
+
+ if (!started()) return;
+ computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
}
public void notifyDownstreamLinkProperties(LinkProperties lp) {
@@ -385,27 +388,38 @@ public class OffloadController {
if (Objects.equals(oldLp, lp)) return;
if (!started()) return;
+ pushDownstreamState(oldLp, lp);
+ }
- final List<RouteInfo> oldRoutes = (oldLp != null) ? oldLp.getRoutes() : new ArrayList<>();
- final List<RouteInfo> newRoutes = lp.getRoutes();
+ private void pushDownstreamState(LinkProperties oldLp, LinkProperties newLp) {
+ final String ifname = newLp.getInterfaceName();
+ final List<RouteInfo> oldRoutes =
+ (oldLp != null) ? oldLp.getRoutes() : Collections.EMPTY_LIST;
+ final List<RouteInfo> newRoutes = newLp.getRoutes();
// For each old route, if not in new routes: remove.
- for (RouteInfo oldRoute : oldRoutes) {
- if (shouldIgnoreDownstreamRoute(oldRoute)) continue;
- if (!newRoutes.contains(oldRoute)) {
- mHwInterface.removeDownstreamPrefix(ifname, oldRoute.getDestination().toString());
+ for (RouteInfo ri : oldRoutes) {
+ if (shouldIgnoreDownstreamRoute(ri)) continue;
+ if (!newRoutes.contains(ri)) {
+ mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString());
}
}
// For each new route, if not in old routes: add.
- for (RouteInfo newRoute : newRoutes) {
- if (shouldIgnoreDownstreamRoute(newRoute)) continue;
- if (!oldRoutes.contains(newRoute)) {
- mHwInterface.addDownstreamPrefix(ifname, newRoute.getDestination().toString());
+ for (RouteInfo ri : newRoutes) {
+ if (shouldIgnoreDownstreamRoute(ri)) continue;
+ if (!oldRoutes.contains(ri)) {
+ mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString());
}
}
}
+ private void pushAllDownstreamState() {
+ for (LinkProperties lp : mDownstreams.values()) {
+ pushDownstreamState(null, lp);
+ }
+ }
+
public void removeDownstreamInterface(String ifname) {
final LinkProperties lp = mDownstreams.remove(ifname);
if (lp == null) return;
@@ -484,10 +498,11 @@ public class OffloadController {
return success;
}
- private boolean computeAndPushLocalPrefixes() {
+ private boolean computeAndPushLocalPrefixes(UpdateType how) {
+ final boolean force = (how == UpdateType.FORCE);
final Set<String> localPrefixStrs = computeLocalPrefixStrings(
mExemptPrefixes, mUpstreamLinkProperties);
- if (mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
+ if (!force && mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
mLastLocalPrefixStrs = localPrefixStrs;
return mHwInterface.setLocalPrefixes(new ArrayList<>(localPrefixStrs));
@@ -581,9 +596,10 @@ public class OffloadController {
}
mNatUpdateCallbacksReceived++;
+ final String natDescription = String.format("%s (%s, %s) -> (%s, %s)",
+ protoName, srcAddr, srcPort, dstAddr, dstPort);
if (DBG) {
- mLog.log(String.format("NAT timeout update: %s (%s, %s) -> (%s, %s)",
- protoName, srcAddr, srcPort, dstAddr, dstPort));
+ mLog.log("NAT timeout update: " + natDescription);
}
final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto);
@@ -594,7 +610,7 @@ public class OffloadController {
NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
} catch (ErrnoException e) {
mNatUpdateNetlinkErrors++;
- mLog.e("Error updating NAT conntrack entry: " + e
+ mLog.e("Error updating NAT conntrack entry >" + natDescription + "<: " + e
+ ", msg: " + NetlinkConstants.hexify(msg));
mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived);
mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors);
diff --git a/com/android/server/content/SyncManager.java b/com/android/server/content/SyncManager.java
index 2f3b5596..9cd52d77 100644
--- a/com/android/server/content/SyncManager.java
+++ b/com/android/server/content/SyncManager.java
@@ -46,8 +46,8 @@ import android.content.SyncStatusInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
import android.content.pm.RegisteredServicesCache;
import android.content.pm.RegisteredServicesCacheListener;
@@ -143,6 +143,7 @@ public class SyncManager {
private static final boolean DEBUG_ACCOUNT_ACCESS = false;
+ // Only do the check on a debuggable build.
private static final boolean ENABLE_SUSPICIOUS_CHECK = Build.IS_DEBUGGABLE;
/** Delay a sync due to local changes this long. In milliseconds */
@@ -537,9 +538,11 @@ public class SyncManager {
* @return whether the device most likely has some periodic syncs.
*/
private boolean likelyHasPeriodicSyncs() {
- // STOPSHIP Remove the google specific string.
try {
- return AccountManager.get(mContext).getAccountsByType("com.google").length > 0;
+ // Each sync adapter has a daily periodic sync by default, but sync adapters can remove
+ // them by themselves. So here, we use an arbitrary threshold. If there are more than
+ // this many sync endpoints, surely one of them should have a periodic sync...
+ return mSyncStorageEngine.getAuthorityCount() >= 6;
} catch (Throwable th) {
// Just in case.
}
@@ -3775,48 +3778,10 @@ public class SyncManager {
}
if (op.isPeriodic) {
mLogger.log("Removing periodic sync ", op, " for ", why);
-
- if (ENABLE_SUSPICIOUS_CHECK && isSuspiciousPeriodicSyncRemoval(op)) {
- wtfWithLog("Suspicious removal of " + op + " for " + why);
- }
}
getJobScheduler().cancel(op.jobId);
}
- private boolean isSuspiciousPeriodicSyncRemoval(SyncOperation op) {
- // STOPSHIP Remove the google specific string.
- if (!op.isPeriodic){
- return false;
- }
- boolean found = false;
- for (UserInfo user : UserManager.get(mContext).getUsers(/*excludeDying=*/ true)) {
- if (op.target.userId == user.id) {
- found = true;
- break;
- }
- }
- if (!found) {
- return false; // User is being removed, okay.
- }
- switch (op.target.provider) {
- case "gmail-ls":
- case "com.android.contacts.metadata":
- break;
- default:
- return false;
- }
- final Account account = op.target.account;
- final Account[] accounts = AccountManager.get(mContext)
- .getAccountsByTypeAsUser(account.type, UserHandle.of(op.target.userId));
- for (Account a : accounts) {
- if (a.equals(account)) {
- return true; // Account still exists. Suspicious!
- }
- }
- // Account no longer exists. Makes sense...
- return false;
- }
-
private void wtfWithLog(String message) {
Slog.wtf(TAG, message);
mLogger.log("WTF: ", message);
diff --git a/com/android/server/content/SyncStorageEngine.java b/com/android/server/content/SyncStorageEngine.java
index 7b277c06..3591871f 100644
--- a/com/android/server/content/SyncStorageEngine.java
+++ b/com/android/server/content/SyncStorageEngine.java
@@ -911,6 +911,12 @@ public class SyncStorageEngine extends Handler {
}
}
+ public int getAuthorityCount() {
+ synchronized (mAuthorities) {
+ return mAuthorities.size();
+ }
+ }
+
public AuthorityInfo getAuthority(int authorityId) {
synchronized (mAuthorities) {
return mAuthorities.get(authorityId);
diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6c859f76..c59f44e7 100644
--- a/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -97,8 +97,8 @@ import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
@@ -5350,7 +5350,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- private void forceWipeUser(int userId) {
+ private void forceWipeUser(int userId, String wipeReasonForUser) {
try {
IActivityManager am = mInjector.getIActivityManager();
if (am.getCurrentUser().id == userId) {
@@ -5361,7 +5361,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!userRemoved) {
Slog.w(LOG_TAG, "Couldn't remove user " + userId);
} else if (isManagedProfile(userId)) {
- sendWipeProfileNotification();
+ sendWipeProfileNotification(wipeReasonForUser);
}
} catch (RemoteException re) {
// Shouldn't happen
@@ -5369,23 +5369,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
- public void wipeData(int flags) {
+ public void wipeDataWithReason(int flags, String wipeReasonForUser) {
if (!mHasFeature) {
return;
}
+ Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
final ActiveAdmin admin;
synchronized (this) {
admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
}
- String reason = "DevicePolicyManager.wipeData() from "
+ String internalReason = "DevicePolicyManager.wipeDataWithReason() from "
+ admin.info.getComponent().flattenToShortString();
wipeDataNoLock(
- admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier());
+ admin.info.getComponent(), flags, internalReason, wipeReasonForUser,
+ admin.getUserHandle().getIdentifier());
}
- private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) {
+ private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
+ String wipeReasonForUser, int userId) {
wtfIfInLock();
long ident = mInjector.binderClearCallingIdentity();
@@ -5420,25 +5423,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// (rather than system), we should probably trigger factory reset. Current code just
// removes that user (but still clears FRP...)
if (userId == UserHandle.USER_SYSTEM) {
- forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
- reason, /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
+ forceWipeDeviceNoLock(/*wipeExtRequested=*/ (
+ flags & WIPE_EXTERNAL_STORAGE) != 0,
+ internalReason,
+ /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
} else {
- forceWipeUser(userId);
+ forceWipeUser(userId, wipeReasonForUser);
}
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
}
- private void sendWipeProfileNotification() {
- String contentText = mContext.getString(R.string.work_profile_deleted_description_dpm_wipe);
+ private void sendWipeProfileNotification(String wipeReasonForUser) {
Notification notification =
new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setContentTitle(mContext.getString(R.string.work_profile_deleted))
- .setContentText(contentText)
+ .setContentText(wipeReasonForUser)
.setColor(mContext.getColor(R.color.system_notification_accent_color))
- .setStyle(new Notification.BigTextStyle().bigText(contentText))
+ .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser))
.build();
mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification);
}
@@ -5610,9 +5614,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// able to do so).
// IMPORTANT: Call without holding the lock to prevent deadlock.
try {
+ String wipeReasonForUser = mContext.getString(
+ R.string.work_profile_deleted_reason_maximum_password_failure);
wipeDataNoLock(strictestAdmin.info.getComponent(),
/*flags=*/ 0,
/*reason=*/ "reportFailedPasswordAttempt()",
+ wipeReasonForUser,
userId);
} catch (SecurityException e) {
Slog.w(LOG_TAG, "Failed to wipe user " + userId
@@ -5621,7 +5628,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (mInjector.securityLogIsLoggingEnabled()) {
- SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
+ SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
+ /*result*/ 0,
/*method strength*/ 1);
}
}
diff --git a/com/android/server/display/DisplayDeviceInfo.java b/com/android/server/display/DisplayDeviceInfo.java
index ef6de4c1..fddb81ba 100644
--- a/com/android/server/display/DisplayDeviceInfo.java
+++ b/com/android/server/display/DisplayDeviceInfo.java
@@ -98,6 +98,12 @@ final class DisplayDeviceInfo {
public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 9;
/**
+ * Flag: This display will destroy its content on removal.
+ * @hide
+ */
+ public static final int FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 10;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
diff --git a/com/android/server/display/LocalDisplayAdapter.java b/com/android/server/display/LocalDisplayAdapter.java
index 87564846..d61a418c 100644
--- a/com/android/server/display/LocalDisplayAdapter.java
+++ b/com/android/server/display/LocalDisplayAdapter.java
@@ -473,17 +473,18 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
// If the state change was from or to VR, then we need to tell the light
- // so that it can apply appropriate VR brightness settings. This should
- // happen prior to changing the brightness but also if there is no
- // brightness change at all.
+ // so that it can apply appropriate VR brightness settings. Also, update the
+ // brightness so the state is propogated to light.
+ boolean vrModeChange = false;
if ((state == Display.STATE_VR || currentState == Display.STATE_VR) &&
currentState != state) {
setVrMode(state == Display.STATE_VR);
+ vrModeChange = true;
}
// Apply brightness changes given that we are in a non-suspended state.
- if (brightnessChanged) {
+ if (brightnessChanged || vrModeChange) {
setDisplayBrightness(brightness);
}
diff --git a/com/android/server/display/LogicalDisplay.java b/com/android/server/display/LogicalDisplay.java
index addad0b4..78a54079 100644
--- a/com/android/server/display/LogicalDisplay.java
+++ b/com/android/server/display/LogicalDisplay.java
@@ -238,6 +238,9 @@ final class LogicalDisplay {
// For private displays by default content is destroyed on removal.
mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
}
+ if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+ mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
+ }
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRESENTATION) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_PRESENTATION;
}
diff --git a/com/android/server/display/NightDisplayService.java b/com/android/server/display/NightDisplayService.java
index aafc6317..9cf13672 100644
--- a/com/android/server/display/NightDisplayService.java
+++ b/com/android/server/display/NightDisplayService.java
@@ -48,8 +48,10 @@ import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.Calendar;
import java.util.TimeZone;
import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
@@ -306,7 +308,7 @@ public final class NightDisplayService extends SystemService
}
@Override
- public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+ public void onCustomStartTimeChanged(LocalTime startTime) {
Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
if (mAutoMode != null) {
@@ -315,7 +317,7 @@ public final class NightDisplayService extends SystemService
}
@Override
- public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+ public void onCustomEndTimeChanged(LocalTime endTime) {
Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
if (mAutoMode != null) {
@@ -414,6 +416,36 @@ public final class NightDisplayService extends SystemService
outTemp[10] = blue;
}
+ /**
+ * Returns the first date time corresponding to the local time that occurs before the
+ * provided date time.
+ *
+ * @param compareTime the LocalDateTime to compare against
+ * @return the prior LocalDateTime corresponding to this local time
+ */
+ public static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
+ final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
+ compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
+
+ // Check if the local time has passed, if so return the same time yesterday.
+ return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
+ }
+
+ /**
+ * Returns the first date time corresponding to this local time that occurs after the
+ * provided date time.
+ *
+ * @param compareTime the LocalDateTime to compare against
+ * @return the next LocalDateTime corresponding to this local time
+ */
+ public static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
+ final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
+ compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
+
+ // Check if the local time has passed, if so return the same time tomorrow.
+ return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
+ }
+
private abstract class AutoMode implements NightDisplayController.Callback {
public abstract void onStart();
@@ -425,10 +457,10 @@ public final class NightDisplayService extends SystemService
private final AlarmManager mAlarmManager;
private final BroadcastReceiver mTimeChangedReceiver;
- private NightDisplayController.LocalTime mStartTime;
- private NightDisplayController.LocalTime mEndTime;
+ private LocalTime mStartTime;
+ private LocalTime mEndTime;
- private Calendar mLastActivatedTime;
+ private LocalDateTime mLastActivatedTime;
CustomAutoMode() {
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
@@ -441,31 +473,15 @@ public final class NightDisplayService extends SystemService
}
private void updateActivated() {
- final Calendar now = Calendar.getInstance();
- final Calendar startTime = mStartTime.getDateTimeBefore(now);
- final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
+ final LocalDateTime now = LocalDateTime.now();
+ final LocalDateTime start = getDateTimeBefore(mStartTime, now);
+ final LocalDateTime end = getDateTimeAfter(mEndTime, start);
+ boolean activate = now.isBefore(end);
- boolean activate = now.before(endTime);
if (mLastActivatedTime != null) {
- // Convert mLastActivatedTime to the current timezone if needed.
- final TimeZone currentTimeZone = now.getTimeZone();
- if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
- final int year = mLastActivatedTime.get(Calendar.YEAR);
- final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
- final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
- final int minute = mLastActivatedTime.get(Calendar.MINUTE);
-
- mLastActivatedTime.setTimeZone(currentTimeZone);
- mLastActivatedTime.set(Calendar.YEAR, year);
- mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
- mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
- mLastActivatedTime.set(Calendar.MINUTE, minute);
- }
-
// Maintain the existing activated state if within the current period.
- if (mLastActivatedTime.before(now)
- && mLastActivatedTime.after(startTime)
- && (mLastActivatedTime.after(endTime) || now.before(endTime))) {
+ if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
+ && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
activate = mController.isActivated();
}
}
@@ -473,14 +489,16 @@ public final class NightDisplayService extends SystemService
if (mIsActivated == null || mIsActivated != activate) {
mController.setActivated(activate);
}
+
updateNextAlarm(mIsActivated, now);
}
- private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
+ private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
if (activated != null) {
- final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
- : mStartTime.getDateTimeAfter(now);
- mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
+ final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
+ : getDateTimeAfter(mStartTime, now);
+ final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
}
}
@@ -510,18 +528,18 @@ public final class NightDisplayService extends SystemService
@Override
public void onActivated(boolean activated) {
mLastActivatedTime = mController.getLastActivatedTime();
- updateNextAlarm(activated, Calendar.getInstance());
+ updateNextAlarm(activated, LocalDateTime.now());
}
@Override
- public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+ public void onCustomStartTimeChanged(LocalTime startTime) {
mStartTime = startTime;
mLastActivatedTime = null;
updateActivated();
}
@Override
- public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+ public void onCustomEndTimeChanged(LocalTime endTime) {
mEndTime = endTime;
mLastActivatedTime = null;
updateActivated();
@@ -550,15 +568,14 @@ public final class NightDisplayService extends SystemService
}
boolean activate = state.isNight();
- final Calendar lastActivatedTime = mController.getLastActivatedTime();
+ final LocalDateTime lastActivatedTime = mController.getLastActivatedTime();
if (lastActivatedTime != null) {
- final Calendar now = Calendar.getInstance();
- final Calendar sunrise = state.sunrise();
- final Calendar sunset = state.sunset();
-
+ final LocalDateTime now = LocalDateTime.now();
+ final LocalDateTime sunrise = state.sunrise();
+ final LocalDateTime sunset = state.sunset();
// Maintain the existing activated state if within the current period.
- if (lastActivatedTime.before(now)
- && (lastActivatedTime.after(sunrise) ^ lastActivatedTime.after(sunset))) {
+ if (lastActivatedTime.isBefore(now) && (lastActivatedTime.isBefore(sunrise)
+ ^ lastActivatedTime.isBefore(sunset))) {
activate = mController.isActivated();
}
}
diff --git a/com/android/server/display/VirtualDisplayAdapter.java b/com/android/server/display/VirtualDisplayAdapter.java
index d6ab8881..f86d5763 100644
--- a/com/android/server/display/VirtualDisplayAdapter.java
+++ b/com/android/server/display/VirtualDisplayAdapter.java
@@ -24,6 +24,8 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLI
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+import static android.hardware.display.DisplayManager
+ .VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
import android.content.Context;
import android.hardware.display.IVirtualDisplayCallback;
@@ -363,6 +365,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
if ((mFlags & VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT) != 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
}
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL;
+ }
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
diff --git a/com/android/server/fingerprint/FingerprintService.java b/com/android/server/fingerprint/FingerprintService.java
index b1c165ef..1df9c861 100644
--- a/com/android/server/fingerprint/FingerprintService.java
+++ b/com/android/server/fingerprint/FingerprintService.java
@@ -63,6 +63,8 @@ import android.service.fingerprint.FingerprintActionStatsProto;
import android.service.fingerprint.FingerprintServiceDumpProto;
import android.service.fingerprint.FingerprintUserStatsProto;
import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
@@ -81,7 +83,6 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -89,18 +90,19 @@ import java.util.concurrent.CopyOnWriteArrayList;
/**
* A service to manage multiple clients that want to access the fingerprint HAL API.
* The service is responsible for maintaining a list of clients and dispatching all
- * fingerprint -related events.
+ * fingerprint-related events.
*
* @hide
*/
public class FingerprintService extends SystemService implements IHwBinder.DeathRecipient {
static final String TAG = "FingerprintService";
static final boolean DEBUG = true;
- private static final boolean CLEANUP_UNUSED_FP = false;
+ private static final boolean CLEANUP_UNUSED_FP = true;
private static final String FP_DATA_DIR = "fpdata";
private static final int MSG_USER_SWITCHING = 10;
private static final String ACTION_LOCKOUT_RESET =
"com.android.server.fingerprint.ACTION_LOCKOUT_RESET";
+ private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";
private class PerformanceStats {
int accept; // number of accepted fingerprints
@@ -128,8 +130,8 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
private Context mContext;
private long mHalDeviceId;
- private boolean mTimedLockoutCleared;
- private int mFailedAttempts;
+ private SparseBooleanArray mTimedLockoutCleared;
+ private SparseIntArray mFailedAttempts;
@GuardedBy("this")
private IBiometricsFingerprint mDaemon;
private final PowerManager mPowerManager;
@@ -139,10 +141,8 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
private ClientMonitor mPendingClient;
private PerformanceStats mPerformanceStats;
-
private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
- private LinkedList<Integer> mEnumeratingUserIds = new LinkedList<>();
- private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw finterprints
+ private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints
private class UserFingerprint {
Fingerprint f;
@@ -177,15 +177,17 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
- resetFailedAttempts(false /* clearAttemptCounter */);
+ final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
+ resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
}
}
};
- private final Runnable mResetFailedAttemptsRunnable = new Runnable() {
+ private final Runnable mResetFailedAttemptsForCurrentUserRunnable = new Runnable() {
@Override
public void run() {
- resetFailedAttempts(true /* clearAttemptCounter */);
+ resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+ ActivityManager.getCurrentUser());
}
};
@@ -221,6 +223,8 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
RESET_FINGERPRINT_LOCKOUT, null /* handler */);
mUserManager = UserManager.get(mContext);
+ mTimedLockoutCleared = new SparseBooleanArray();
+ mFailedAttempts = new SparseIntArray();
}
@Override
@@ -233,7 +237,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
public synchronized IBiometricsFingerprint getFingerprintDaemon() {
if (mDaemon == null) {
- Slog.v(TAG, "mDeamon was null, reconnect to fingerprint");
+ Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
try {
mDaemon = IBiometricsFingerprint.getService();
} catch (java.util.NoSuchElementException e) {
@@ -259,7 +263,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
if (mHalDeviceId != 0) {
loadAuthenticatorIds();
updateActiveGroup(ActivityManager.getCurrentUser(), null);
- doFingerprintCleanup(ActivityManager.getCurrentUser());
+ doFingerprintCleanupForUser(ActivityManager.getCurrentUser());
} else {
Slog.w(TAG, "Failed to open Fingerprint HAL!");
MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
@@ -288,52 +292,41 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
}
}
- private void doFingerprintCleanup(int userId) {
+ /**
+ * This method should be called upon connection to the daemon, and when user switches.
+ * @param userId
+ */
+ private void doFingerprintCleanupForUser(int userId) {
if (CLEANUP_UNUSED_FP) {
- resetEnumerateState();
- mEnumeratingUserIds.push(userId);
- enumerateNextUser();
+ enumerateUser(userId);
}
}
- private void resetEnumerateState() {
- if (DEBUG) Slog.v(TAG, "Enumerate cleaning up");
- mEnumeratingUserIds.clear();
+ private void clearEnumerateState() {
+ if (DEBUG) Slog.v(TAG, "clearEnumerateState()");
mUnknownFingerprints.clear();
}
- private void enumerateNextUser() {
- int nextUser = mEnumeratingUserIds.getFirst();
- updateActiveGroup(nextUser, null);
+ private void enumerateUser(int userId) {
+ if (DEBUG) Slog.v(TAG, "Enumerating user(" + userId + ")");
boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-
- if (DEBUG) Slog.v(TAG, "Enumerating user id " + nextUser + " of "
- + mEnumeratingUserIds.size() + " remaining users");
-
- startEnumerate(mToken, nextUser, null, restricted, true /* internal */);
+ startEnumerate(mToken, userId, null, restricted, true /* internal */);
}
// Remove unknown fingerprints from hardware
private void cleanupUnknownFingerprints() {
if (!mUnknownFingerprints.isEmpty()) {
- Slog.w(TAG, "unknown fingerprint size: " + mUnknownFingerprints.size());
UserFingerprint uf = mUnknownFingerprints.get(0);
mUnknownFingerprints.remove(uf);
boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
- updateActiveGroup(uf.userId, null);
startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null,
restricted, true /* internal */);
} else {
- resetEnumerateState();
+ clearEnumerateState();
}
}
protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
- if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId
- + ", gid=" + groupId
- + ", dev=" + deviceId
- + ", rem=" + remaining);
-
ClientMonitor client = mCurrentClient;
if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) {
@@ -343,24 +336,21 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
// All fingerprints in hardware for this user were enumerated
if (remaining == 0) {
- mEnumeratingUserIds.poll();
-
if (client instanceof InternalEnumerateClient) {
- List<Fingerprint> enrolled = ((InternalEnumerateClient) client).getEnumeratedList();
- Slog.w(TAG, "Added " + enrolled.size() + " enumerated fingerprints for deletion");
- for (Fingerprint f : enrolled) {
+ List<Fingerprint> unknownFingerprints =
+ ((InternalEnumerateClient) client).getUnknownFingerprints();
+
+ if (!unknownFingerprints.isEmpty()) {
+ Slog.w(TAG, "Adding " + unknownFingerprints.size() +
+ " fingerprints for deletion");
+ }
+ for (Fingerprint f : unknownFingerprints) {
mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
}
- }
-
- removeClient(client);
-
- if (!mEnumeratingUserIds.isEmpty()) {
- enumerateNextUser();
- } else if (client instanceof InternalEnumerateClient) {
- if (DEBUG) Slog.v(TAG, "Finished enumerating all users");
- // This will start a chain of InternalRemovalClients
+ removeClient(client);
cleanupUnknownFingerprints();
+ } else {
+ removeClient(client);
}
}
}
@@ -368,7 +358,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
protected void handleError(long deviceId, int error, int vendorCode) {
ClientMonitor client = mCurrentClient;
if (client instanceof InternalRemovalClient || client instanceof InternalEnumerateClient) {
- resetEnumerateState();
+ clearEnumerateState();
}
if (client != null && client.onError(error, vendorCode)) {
removeClient(client);
@@ -412,7 +402,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
cleanupUnknownFingerprints();
} else if (client instanceof InternalRemovalClient){
- resetEnumerateState();
+ clearEnumerateState();
}
}
@@ -466,8 +456,14 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
}
void handleUserSwitching(int userId) {
+ if (mCurrentClient instanceof InternalRemovalClient
+ || mCurrentClient instanceof InternalEnumerateClient) {
+ Slog.w(TAG, "User switched while performing cleanup");
+ removeClient(mCurrentClient);
+ clearEnumerateState();
+ }
updateActiveGroup(userId, null);
- doFingerprintCleanup(userId);
+ doFingerprintCleanupForUser(userId);
}
private void removeClient(ClientMonitor client) {
@@ -488,27 +484,32 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
}
private int getLockoutMode() {
- if (mFailedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
+ final int currentUser = ActivityManager.getCurrentUser();
+ final int failedAttempts = mFailedAttempts.get(currentUser, 0);
+ if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
return AuthenticationClient.LOCKOUT_PERMANENT;
- } else if (mFailedAttempts > 0 && mTimedLockoutCleared == false &&
- (mFailedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
+ } else if (failedAttempts > 0 &&
+ mTimedLockoutCleared.get(currentUser, false) == false
+ && (failedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
return AuthenticationClient.LOCKOUT_TIMED;
}
return AuthenticationClient.LOCKOUT_NONE;
}
- private void scheduleLockoutReset() {
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent());
+ private void scheduleLockoutResetForUser(int userId) {
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+ getLockoutResetIntentForUser(userId));
}
- private void cancelLockoutReset() {
- mAlarmManager.cancel(getLockoutResetIntent());
+ private void cancelLockoutResetForUser(int userId) {
+ mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
}
- private PendingIntent getLockoutResetIntent() {
- return PendingIntent.getBroadcast(mContext, 0,
- new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT);
+ private PendingIntent getLockoutResetIntentForUser(int userId) {
+ return PendingIntent.getBroadcast(mContext, userId,
+ new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+ PendingIntent.FLAG_UPDATE_CURRENT);
}
public long startPreEnroll(IBinder token) {
@@ -555,6 +556,12 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
// This condition means we're currently running internal diagnostics to
// remove extra fingerprints in the hardware and/or the software
// TODO: design an escape hatch in case client never finishes
+ if (newClient != null) {
+ Slog.w(TAG, "Internal cleanup in progress but trying to start client "
+ + newClient.getClass().getSuperclass().getSimpleName()
+ + "(" + newClient.getOwnerString() + ")"
+ + ", initiatedByClient = " + initiatedByClient);
+ }
}
else {
currentClient.stop(initiatedByClient);
@@ -567,7 +574,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
if (DEBUG) Slog.v(TAG, "starting client "
+ newClient.getClass().getSuperclass().getSimpleName()
+ "(" + newClient.getOwnerString() + ")"
- + ", initiatedByClient = " + initiatedByClient + ")");
+ + ", initiatedByClient = " + initiatedByClient);
notifyClientActiveCallbacks(true);
newClient.start();
@@ -813,8 +820,9 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
receiver, mCurrentUserId, groupId, opId, restricted, opPackageName) {
@Override
public int handleFailedAttempt() {
- mFailedAttempts++;
- mTimedLockoutCleared = false;
+ final int currentUser = ActivityManager.getCurrentUser();
+ mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1);
+ mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false);
final int lockoutMode = getLockoutMode();
if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
mPerformanceStats.permanentLockout++;
@@ -824,7 +832,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
// Failing multiple times will continue to push out the lockout time
if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
- scheduleLockoutReset();
+ scheduleLockoutResetForUser(currentUser);
return lockoutMode;
}
return AuthenticationClient.LOCKOUT_NONE;
@@ -832,7 +840,8 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
@Override
public void resetFailedAttempts() {
- FingerprintService.this.resetFailedAttempts(true /* clearAttemptCounter */);
+ FingerprintService.this.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+ ActivityManager.getCurrentUser());
}
@Override
@@ -886,17 +895,17 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
// attempt counter should only be cleared when Keyguard goes away or when
// a fingerprint is successfully authenticated
- protected void resetFailedAttempts(boolean clearAttemptCounter) {
+ protected void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
Slog.v(TAG, "Reset fingerprint lockout, clearAttemptCounter=" + clearAttemptCounter);
}
if (clearAttemptCounter) {
- mFailedAttempts = 0;
+ mFailedAttempts.put(userId, 0);
}
- mTimedLockoutCleared = true;
+ mTimedLockoutCleared.put(userId, true);
// If we're asked to reset failed attempts externally (i.e. from Keyguard),
// the alarm might still be pending; remove it.
- cancelLockoutReset();
+ cancelLockoutResetForUser(userId);
notifyLockoutResetMonitors();
}
@@ -1277,7 +1286,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
public void resetTimeout(byte [] token) {
checkPermission(RESET_FINGERPRINT_LOCKOUT);
// TODO: confirm security token when we move timeout management into the HAL layer.
- mHandler.post(mResetFailedAttemptsRunnable);
+ mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
}
@Override
@@ -1338,6 +1347,8 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
+ set.put("permanentLockoutCrypto",
+ (cryptoStats != null) ? cryptoStats.permanentLockout : 0);
sets.put(set);
}
@@ -1367,7 +1378,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
proto.write(FingerprintActionStatsProto.REJECT, normal.reject);
proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire);
proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout);
- proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.lockout);
+ proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.permanentLockout);
proto.end(countsToken);
}
@@ -1380,7 +1391,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death
proto.write(FingerprintActionStatsProto.REJECT, crypto.reject);
proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire);
proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout);
- proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.lockout);
+ proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.permanentLockout);
proto.end(countsToken);
}
diff --git a/com/android/server/fingerprint/InternalEnumerateClient.java b/com/android/server/fingerprint/InternalEnumerateClient.java
index 88d9ef43..434db98a 100644
--- a/com/android/server/fingerprint/InternalEnumerateClient.java
+++ b/com/android/server/fingerprint/InternalEnumerateClient.java
@@ -30,7 +30,7 @@ import java.util.List;
public abstract class InternalEnumerateClient extends EnumerateClient {
private List<Fingerprint> mEnrolledList;
- private List<Fingerprint> mEnumeratedList = new ArrayList<>(); // list of fp to delete
+ private List<Fingerprint> mUnknownFingerprints = new ArrayList<>(); // list of fp to delete
public InternalEnumerateClient(Context context, long halDeviceId, IBinder token,
IFingerprintServiceReceiver receiver, int groupId, int userId,
@@ -47,7 +47,6 @@ public abstract class InternalEnumerateClient extends EnumerateClient {
if (mEnrolledList.get(i).getFingerId() == fingerId) {
mEnrolledList.remove(i);
matched = true;
- Slog.e(TAG, "Matched fingerprint fid=" + fingerId);
break;
}
}
@@ -55,7 +54,7 @@ public abstract class InternalEnumerateClient extends EnumerateClient {
// fingerId 0 means no fingerprints are in hardware
if (!matched && fingerId != 0) {
Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
- mEnumeratedList.add(fingerprint);
+ mUnknownFingerprints.add(fingerprint);
}
}
@@ -76,8 +75,8 @@ public abstract class InternalEnumerateClient extends EnumerateClient {
mEnrolledList.clear();
}
- public List<Fingerprint> getEnumeratedList() {
- return mEnumeratedList;
+ public List<Fingerprint> getUnknownFingerprints() {
+ return mUnknownFingerprints;
}
@Override
diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java
index ac807941..78aa2f94 100644
--- a/com/android/server/job/JobSchedulerService.java
+++ b/com/android/server/job/JobSchedulerService.java
@@ -795,18 +795,22 @@ public final class JobSchedulerService extends com.android.server.SystemService
* @param uid Uid to check against for removal of a job.
*
*/
- public void cancelJobsForUid(int uid, String reason) {
+ public boolean cancelJobsForUid(int uid, String reason) {
if (uid == Process.SYSTEM_UID) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
- return;
+ return false;
}
+
+ boolean jobsCanceled = false;
synchronized (mLock) {
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i<jobsForUid.size(); i++) {
JobStatus toRemove = jobsForUid.get(i);
cancelJobImplLocked(toRemove, null, reason);
+ jobsCanceled = true;
}
}
+ return jobsCanceled;
}
/**
@@ -816,13 +820,14 @@ public final class JobSchedulerService extends com.android.server.SystemService
* @param uid Uid of the calling client.
* @param jobId Id of the job, provided at schedule-time.
*/
- public void cancelJob(int uid, int jobId) {
+ public boolean cancelJob(int uid, int jobId) {
JobStatus toCancel;
synchronized (mLock) {
toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
if (toCancel != null) {
cancelJobImplLocked(toCancel, null, "cancel() called by app");
}
+ return (toCancel != null);
}
}
@@ -2147,6 +2152,39 @@ public final class JobSchedulerService extends com.android.server.SystemService
return 0;
}
+ // Shell command infrastructure: cancel a scheduled job
+ int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
+ boolean hasJobId, int jobId) {
+ if (DEBUG) {
+ Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
+ }
+
+ int pkgUid = -1;
+ try {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ pkgUid = pm.getPackageUid(pkgName, 0, userId);
+ } catch (RemoteException e) { /* can't happen */ }
+
+ if (pkgUid < 0) {
+ pw.println("Package " + pkgName + " not found.");
+ return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+ }
+
+ if (!hasJobId) {
+ pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
+ if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+ pw.println("No matching jobs found.");
+ }
+ } else {
+ pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
+ if (!cancelJob(pkgUid, jobId)) {
+ pw.println("No matching job found.");
+ }
+ }
+
+ return 0;
+ }
+
void setMonitorBattery(boolean enabled) {
synchronized (mLock) {
if (mBatteryController != null) {
diff --git a/com/android/server/job/JobSchedulerShellCommand.java b/com/android/server/job/JobSchedulerShellCommand.java
index a53c0885..d630aab6 100644
--- a/com/android/server/job/JobSchedulerShellCommand.java
+++ b/com/android/server/job/JobSchedulerShellCommand.java
@@ -48,6 +48,8 @@ public final class JobSchedulerShellCommand extends ShellCommand {
return runJob(pw);
case "timeout":
return timeout(pw);
+ case "cancel":
+ return cancelJob(pw);
case "monitor-battery":
return monitorBattery(pw);
case "get-battery-seq":
@@ -205,6 +207,42 @@ public final class JobSchedulerShellCommand extends ShellCommand {
}
}
+ private int cancelJob(PrintWriter pw) throws Exception {
+ checkPermission("cancel jobs");
+
+ int userId = UserHandle.USER_SYSTEM;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-u":
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+
+ default:
+ pw.println("Error: unknown option '" + opt + "'");
+ return -1;
+ }
+ }
+
+ if (userId < 0) {
+ pw.println("Error: must specify a concrete user ID");
+ return -1;
+ }
+
+ final String pkgName = getNextArg();
+ final String jobIdStr = getNextArg();
+ final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int monitorBattery(PrintWriter pw) throws Exception {
checkPermission("change battery monitoring");
String opt = getNextArgRequired();
@@ -315,6 +353,12 @@ public final class JobSchedulerShellCommand extends ShellCommand {
pw.println(" Options:");
pw.println(" -u or --user: specify which user's job is to be run; the default is");
pw.println(" all users");
+ pw.println(" cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
+ pw.println(" Cancel a scheduled job. If a job ID is not supplied, all jobs scheduled");
+ pw.println(" by that package will be canceled. USE WITH CAUTION.");
+ pw.println(" Options:");
+ pw.println(" -u or --user: specify which user's job is to be run; the default is");
+ pw.println(" the primary or system user");
pw.println(" monitor-battery [on|off]");
pw.println(" Control monitoring of all battery changes. Off by default. Turning");
pw.println(" on makes get-battery-seq useful.");
diff --git a/com/android/server/locksettings/LockSettingsService.java b/com/android/server/locksettings/LockSettingsService.java
index 11043bd1..a1a01061 100644
--- a/com/android/server/locksettings/LockSettingsService.java
+++ b/com/android/server/locksettings/LockSettingsService.java
@@ -376,7 +376,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
- return new SyntheticPasswordManager(storage, getUserManager());
+ return new SyntheticPasswordManager(getContext(), storage, getUserManager());
}
public int binderGetCallingUid() {
@@ -763,7 +763,8 @@ public class LockSettingsService extends ILockSettings.Stub {
private void migrateOldDataAfterSystemReady() {
try {
// Migrate the FRP credential to the persistent data block
- if (LockPatternUtils.frpCredentialEnabled() && !getBoolean("migrated_frp", false, 0)) {
+ if (LockPatternUtils.frpCredentialEnabled(mContext)
+ && !getBoolean("migrated_frp", false, 0)) {
migrateFrpCredential();
setBoolean("migrated_frp", true, 0);
Slog.i(TAG, "Migrated migrated_frp.");
@@ -784,7 +785,7 @@ public class LockSettingsService extends ILockSettings.Stub {
return;
}
for (UserInfo userInfo : mUserManager.getUsers()) {
- if (userOwnsFrpCredential(userInfo) && isUserSecure(userInfo.id)) {
+ if (userOwnsFrpCredential(mContext, userInfo) && isUserSecure(userInfo.id)) {
synchronized (mSpManager) {
if (isSyntheticPasswordBasedCredentialLocked(userInfo.id)) {
int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
@@ -2366,6 +2367,13 @@ public class LockSettingsService extends ILockSettings.Stub {
Slog.w(TAG, "Invalid escrow token supplied");
return false;
}
+ if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ // Most likely, an untrusted credential reset happened in the past which
+ // changed the synthetic password
+ Slog.e(TAG, "Obsolete token: synthetic password derived but it fails GK "
+ + "verification.");
+ return false;
+ }
// Update PASSWORD_TYPE_KEY since it's needed by notifyActivePasswordMetricsAvailable()
// called by setLockCredentialWithAuthTokenLocked().
// TODO: refactor usage of PASSWORD_TYPE_KEY b/65239740
@@ -2497,7 +2505,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
public void onSystemReady() {
- if (frpCredentialEnabled()) {
+ if (frpCredentialEnabled(mContext)) {
updateRegistration();
} else {
// If we don't intend to use frpCredentials and we're not provisioned yet, send
@@ -2526,7 +2534,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private void clearFrpCredentialIfOwnerNotSecure() {
List<UserInfo> users = mUserManager.getUsers();
for (UserInfo user : users) {
- if (userOwnsFrpCredential(user)) {
+ if (userOwnsFrpCredential(mContext, user)) {
if (!isUserSecure(user.id)) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
0, null);
diff --git a/com/android/server/locksettings/LockSettingsStrongAuth.java b/com/android/server/locksettings/LockSettingsStrongAuth.java
index 542b929d..c9c93293 100644
--- a/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -27,6 +27,7 @@ import android.app.AlarmManager.OnAlarmListener;
import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.DeadObjectException;
@@ -74,7 +75,10 @@ public class LockSettingsStrongAuth {
}
public void systemReady() {
- mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+ }
}
private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java
index 33a9a995..9440f171 100644
--- a/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -19,6 +19,7 @@ package com.android.server.locksettings;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.weaver.V1_0.IWeaver;
import android.hardware.weaver.V1_0.WeaverConfig;
@@ -255,13 +256,16 @@ public class SyntheticPasswordManager {
byte[] aggregatedSecret;
}
+ private final Context mContext;
private LockSettingsStorage mStorage;
private IWeaver mWeaver;
private WeaverConfig mWeaverConfig;
private final UserManager mUserManager;
- public SyntheticPasswordManager(LockSettingsStorage storage, UserManager userManager) {
+ public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
+ UserManager userManager) {
+ mContext = context;
mStorage = storage;
mUserManager = userManager;
}
@@ -645,7 +649,7 @@ public class SyntheticPasswordManager {
public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) {
if (mStorage.getPersistentDataBlock() != null
- && LockPatternUtils.userOwnsFrpCredential(userInfo)) {
+ && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle,
userInfo.id));
if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
@@ -662,7 +666,8 @@ public class SyntheticPasswordManager {
private void synchronizeFrpPassword(PasswordData pwd,
int requestedQuality, int userId) {
if (mStorage.getPersistentDataBlock() != null
- && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ && LockPatternUtils.userOwnsFrpCredential(mContext,
+ mUserManager.getUserInfo(userId))) {
if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
pwd.toBytes());
@@ -675,7 +680,8 @@ public class SyntheticPasswordManager {
private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
int weaverSlot) {
if (mStorage.getPersistentDataBlock() != null
- && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+ && LockPatternUtils.userOwnsFrpCredential(mContext,
+ mUserManager.getUserInfo(userId))) {
if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
requestedQuality, pwd.toBytes());
diff --git a/com/android/server/media/MediaRouterService.java b/com/android/server/media/MediaRouterService.java
index 3795b7f3..1cfd5f02 100644
--- a/com/android/server/media/MediaRouterService.java
+++ b/com/android/server/media/MediaRouterService.java
@@ -271,14 +271,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
- public boolean isGlobalBluetoothA2doOn() {
- synchronized (mLock) {
- return mGlobalBluetoothA2dpOn;
- }
- }
-
- // Binder call
- @Override
public void setDiscoveryRequest(IMediaRouterClient client,
int routeTypes, boolean activeScan) {
if (client == null) {
@@ -383,7 +375,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
synchronized (mLock) {
a2dpOn = mGlobalBluetoothA2dpOn;
}
- Slog.v(TAG, "restoreBluetoothA2dp( " + a2dpOn + ")");
+ Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
mAudioService.setBluetoothA2dpOn(a2dpOn);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
diff --git a/com/android/server/media/MediaSessionRecord.java b/com/android/server/media/MediaSessionRecord.java
index 89e10503..0b11479a 100644
--- a/com/android/server/media/MediaSessionRecord.java
+++ b/com/android/server/media/MediaSessionRecord.java
@@ -462,18 +462,25 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
mHandler.post(new Runnable() {
@Override
public void run() {
- if (useSuggested) {
- if (AudioSystem.isStreamActive(stream, 0)) {
- mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
- flags, packageName, uid);
+ try {
+ if (useSuggested) {
+ if (AudioSystem.isStreamActive(stream, 0)) {
+ mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream,
+ direction, flags, packageName, uid);
+ } else {
+ mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
+ AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
+ flags | previousFlagPlaySound, packageName, uid);
+ }
} else {
- mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
- AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
- flags | previousFlagPlaySound, packageName, uid);
+ mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
+ packageName, uid);
}
- } else {
- mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
- packageName, uid);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
+ + stream + ", flags=" + flags + ", packageName=" + packageName
+ + ", uid=" + uid + ", useSuggested=" + useSuggested
+ + ", previousFlagPlaySound=" + previousFlagPlaySound, e);
}
}
});
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index b77ed913..b9a2d184 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -1363,6 +1363,10 @@ public class MediaSessionService extends SystemService implements Monitor {
flags, packageName, TAG);
} catch (RemoteException e) {
Log.e(TAG, "Error adjusting default volume.", e);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Cannot adjust volume: direction=" + direction
+ + ", suggestedStream=" + suggestedStream + ", flags=" + flags,
+ e);
}
}
});
diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java
index 90dab2c3..b4056b33 100644
--- a/com/android/server/net/NetworkPolicyManagerService.java
+++ b/com/android/server/net/NetworkPolicyManagerService.java
@@ -331,7 +331,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
private static final int MSG_POLICIES_CHANGED = 13;
- private static final int MSG_SET_FIREWALL_RULES = 14;
private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
private static final int UID_MSG_STATE_CHANGED = 100;
@@ -3138,9 +3137,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
}
}
- setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
+ setUidFirewallRulesUL(chain, uidRules, CHAIN_TOGGLE_ENABLE);
} else {
- setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
+ setUidFirewallRulesUL(chain, null, CHAIN_TOGGLE_DISABLE);
}
}
@@ -3207,7 +3206,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
- setUidFirewallRulesAsync(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
+ setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
@@ -3906,18 +3905,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
removeInterfaceQuota((String) msg.obj);
return true;
}
- case MSG_SET_FIREWALL_RULES: {
- final int chain = msg.arg1;
- final int toggle = msg.arg2;
- final SparseIntArray uidRules = (SparseIntArray) msg.obj;
- if (uidRules != null) {
- setUidFirewallRules(chain, uidRules);
- }
- if (toggle != CHAIN_TOGGLE_NONE) {
- enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
- }
- return true;
- }
case MSG_RESET_FIREWALL_RULES_BY_UID: {
resetUidFirewallRules(msg.arg1);
return true;
@@ -4063,15 +4050,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
/**
* Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
- * {@link #enableFirewallChainUL(int, boolean)} asynchronously.
+ * {@link #enableFirewallChainUL(int, boolean)} synchronously.
*
* @param chain firewall chain.
* @param uidRules new UID rules; if {@code null}, only toggles chain state.
* @param toggle whether the chain should be enabled, disabled, or not changed.
*/
- private void setUidFirewallRulesAsync(int chain, @Nullable SparseIntArray uidRules,
+ private void setUidFirewallRulesUL(int chain, @Nullable SparseIntArray uidRules,
@ChainToggleType int toggle) {
- mHandler.obtainMessage(MSG_SET_FIREWALL_RULES, chain, toggle, uidRules).sendToTarget();
+ if (uidRules != null) {
+ setUidFirewallRulesUL(chain, uidRules);
+ }
+ if (toggle != CHAIN_TOGGLE_NONE) {
+ enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
+ }
}
/**
@@ -4079,7 +4071,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
* here to netd. It will clean up dead rules and make sure the target chain only contains rules
* specified here.
*/
- private void setUidFirewallRules(int chain, SparseIntArray uidRules) {
+ private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
try {
int size = uidRules.size();
int[] uids = new int[size];
diff --git a/com/android/server/notification/ConditionProviders.java b/com/android/server/notification/ConditionProviders.java
index 3444ef3e..c0fbfbb2 100644
--- a/com/android/server/notification/ConditionProviders.java
+++ b/com/android/server/notification/ConditionProviders.java
@@ -186,6 +186,11 @@ public class ConditionProviders extends ManagedServices {
super.onPackagesChanged(removingPackage, pkgList, uid);
}
+ @Override
+ protected boolean isValidEntry(String packageOrComponent, int userId) {
+ return true;
+ }
+
public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
synchronized(mMutex) {
return checkServiceTokenLocked(provider);
diff --git a/com/android/server/notification/ImportanceExtractor.java b/com/android/server/notification/ImportanceExtractor.java
index 46ec92b1..452121c0 100644
--- a/com/android/server/notification/ImportanceExtractor.java
+++ b/com/android/server/notification/ImportanceExtractor.java
@@ -22,7 +22,7 @@ import android.util.Slog;
* Determines the importance of the given notification.
*/
public class ImportanceExtractor implements NotificationSignalExtractor {
- private static final String TAG = "ImportantTopicExtractor";
+ private static final String TAG = "ImportanceExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
diff --git a/com/android/server/notification/ManagedServices.java b/com/android/server/notification/ManagedServices.java
index add4184f..019c7c2f 100644
--- a/com/android/server/notification/ManagedServices.java
+++ b/com/android/server/notification/ManagedServices.java
@@ -45,12 +45,16 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.notification.ManagedServiceInfoProto;
+import android.service.notification.ManagedServicesProto;
+import android.service.notification.ManagedServicesProto.ServiceProto;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.XmlUtils;
import com.android.server.notification.NotificationManagerService.DumpFilter;
@@ -214,6 +218,53 @@ abstract public class ManagedServices {
}
}
+ public void dump(ProtoOutputStream proto, DumpFilter filter) {
+ proto.write(ManagedServicesProto.CAPTION, getCaption());
+ final int N = mApproved.size();
+ for (int i = 0 ; i < N; i++) {
+ final int userId = mApproved.keyAt(i);
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+ if (approvedByType != null) {
+ final int M = approvedByType.size();
+ for (int j = 0; j < M; j++) {
+ final boolean isPrimary = approvedByType.keyAt(j);
+ final ArraySet<String> approved = approvedByType.valueAt(j);
+ if (approvedByType != null && approvedByType.size() > 0) {
+ final long sToken = proto.start(ManagedServicesProto.APPROVED);
+ for (String s : approved) {
+ proto.write(ServiceProto.NAME, s);
+ }
+ proto.write(ServiceProto.USER_ID, userId);
+ proto.write(ServiceProto.IS_PRIMARY, isPrimary);
+ proto.end(sToken);
+ }
+ }
+ }
+ }
+
+ for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+ if (filter != null && !filter.matches(cmpt)) continue;
+
+ final long cToken = proto.start(ManagedServicesProto.ENABLED);
+ cmpt.toProto(proto);
+ proto.end(cToken);
+ }
+
+ for (ManagedServiceInfo info : mServices) {
+ if (filter != null && !filter.matches(info.component)) continue;
+
+ final long lToken = proto.start(ManagedServicesProto.LIVE_SERVICES);
+ info.toProto(proto, this);
+ proto.end(lToken);
+ }
+
+ for (ComponentName name : mSnoozingForCurrentProfiles) {
+ final long cToken = proto.start(ManagedServicesProto.SNOOZED);
+ name.toProto(proto);
+ proto.end(cToken);
+ }
+ }
+
protected void onSettingRestored(String element, String value, int backupSdkInt, int userId) {
if (!mUseXml) {
Slog.d(TAG, "Restored managed service setting: " + element);
@@ -294,6 +345,7 @@ abstract public class ManagedServices {
}
if (type == XmlPullParser.START_TAG) {
if (TAG_MANAGED_SERVICES.equals(tag)) {
+ Slog.i(TAG, "Read " + mConfig.caption + " permissions from xml");
final String approved = XmlUtils.readStringAttribute(parser, ATT_APPROVED_LIST);
final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0);
final boolean isPrimary =
@@ -353,6 +405,8 @@ abstract public class ManagedServices {
protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
boolean isPrimary, boolean enabled) {
+ Slog.i(TAG,
+ (enabled ? " Allowing " : "Disallowing ") + mConfig.caption + " " + pkgOrComponent);
ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.get(userId);
if (allowedByType == null) {
allowedByType = new ArrayMap<>();
@@ -460,6 +514,7 @@ abstract public class ManagedServices {
}
public void onUserRemoved(int user) {
+ Slog.i(TAG, "Removing approved services for removed user " + user);
mApproved.remove(user);
rebindServices(true);
}
@@ -491,6 +546,17 @@ abstract public class ManagedServices {
return null;
}
+ protected boolean isServiceTokenValidLocked(IInterface service) {
+ if (service == null) {
+ return false;
+ }
+ ManagedServiceInfo info = getServiceFromTokenLocked(service);
+ if (info != null) {
+ return true;
+ }
+ return false;
+ }
+
protected ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
checkNotNull(service);
ManagedServiceInfo info = getServiceFromTokenLocked(service);
@@ -543,10 +609,8 @@ abstract public class ManagedServices {
}
// State changed
- if (DEBUG) {
- Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " +
- component.flattenToShortString());
- }
+ Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " +
+ component.flattenToShortString());
synchronized (mMutex) {
final int[] userIds = mUserProfiles.getCurrentProfileIds();
@@ -628,12 +692,10 @@ abstract public class ManagedServices {
int P = approved.size();
for (int k = P - 1; k >= 0; k--) {
final String approvedPackageOrComponent = approved.valueAt(k);
- if (!hasMatchingServices(approvedPackageOrComponent, userId)){
+ if (!isValidEntry(approvedPackageOrComponent, userId)){
approved.removeAt(k);
- if (DEBUG) {
- Slog.v(TAG, "Removing " + approvedPackageOrComponent
- + " from approved list; no matching services found");
- }
+ Slog.v(TAG, "Removing " + approvedPackageOrComponent
+ + " from approved list; no matching services found");
} else {
if (DEBUG) {
Slog.v(TAG, "Keeping " + approvedPackageOrComponent
@@ -678,6 +740,10 @@ abstract public class ManagedServices {
}
}
+ protected boolean isValidEntry(String packageOrComponent, int userId) {
+ return hasMatchingServices(packageOrComponent, userId);
+ }
+
private boolean hasMatchingServices(String packageOrComponent, int userId) {
if (!TextUtils.isEmpty(packageOrComponent)) {
final String packageName = getPackageName(packageOrComponent);
@@ -774,7 +840,12 @@ abstract public class ManagedServices {
ServiceInfo info = mPm.getServiceInfo(component,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
- if (info == null || !mConfig.bindPermission.equals(info.permission)) {
+ if (info == null) {
+ Slog.w(TAG, "Not binding " + getCaption() + " service " + component
+ + ": service not found");
+ continue;
+ }
+ if (!mConfig.bindPermission.equals(info.permission)) {
Slog.w(TAG, "Not binding " + getCaption() + " service " + component
+ ": it does not require the permission " + mConfig.bindPermission);
continue;
@@ -830,8 +901,7 @@ abstract public class ManagedServices {
if (name.equals(info.component)
&& info.userid == userid) {
// cut old connections
- if (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": "
- + info.service);
+ Slog.v(TAG, " disconnecting old " + getCaption() + ": " + info.service);
removeServiceLocked(i);
if (info.connection != null) {
mContext.unbindService(info.connection);
@@ -859,7 +929,7 @@ abstract public class ManagedServices {
appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
try {
- if (DEBUG) Slog.v(TAG, "binding: " + intent);
+ Slog.v(TAG, "binding: " + intent);
ServiceConnection serviceConnection = new ServiceConnection() {
IInterface mService;
@@ -917,8 +987,7 @@ abstract public class ManagedServices {
final int N = mServices.size();
for (int i = N - 1; i >= 0; i--) {
final ManagedServiceInfo info = mServices.get(i);
- if (name.equals(info.component)
- && info.userid == userid) {
+ if (name.equals(info.component) && info.userid == userid) {
removeServiceLocked(i);
if (info.connection != null) {
try {
@@ -945,9 +1014,8 @@ abstract public class ManagedServices {
final int N = mServices.size();
for (int i = N - 1; i >= 0; i--) {
final ManagedServiceInfo info = mServices.get(i);
- if (info.service.asBinder() == service.asBinder()
- && info.userid == userid) {
- if (DEBUG) Slog.d(TAG, "Removing active service " + info.component);
+ if (info.service.asBinder() == service.asBinder() && info.userid == userid) {
+ Slog.d(TAG, "Removing active service " + info.component);
serviceInfo = removeServiceLocked(i);
}
}
@@ -1035,6 +1103,16 @@ abstract public class ManagedServices {
.append(']').toString();
}
+ public void toProto(ProtoOutputStream proto, ManagedServices host) {
+ final long cToken = proto.start(ManagedServiceInfoProto.COMPONENT);
+ component.toProto(proto);
+ proto.end(cToken);
+ proto.write(ManagedServiceInfoProto.USER_ID, userid);
+ proto.write(ManagedServiceInfoProto.SERVICE, service.getClass().getName());
+ proto.write(ManagedServiceInfoProto.IS_SYSTEM, isSystem);
+ proto.write(ManagedServiceInfoProto.IS_GUEST, isGuest(host));
+ }
+
public boolean enabledAndUserMatches(int nid) {
if (!isEnabledForCurrentProfiles()) {
return false;
diff --git a/com/android/server/notification/NotificationAdjustmentExtractor.java b/com/android/server/notification/NotificationAdjustmentExtractor.java
index 7c828458..3bfd93fd 100644
--- a/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -22,7 +22,7 @@ import android.util.Slog;
* Applies adjustments from the group helper and notification assistant
*/
public class NotificationAdjustmentExtractor implements NotificationSignalExtractor {
- private static final String TAG = "BadgeExtractor";
+ private static final String TAG = "AdjustmentExtractor";
private static final boolean DBG = false;
@@ -35,7 +35,6 @@ public class NotificationAdjustmentExtractor implements NotificationSignalExtrac
if (DBG) Slog.d(TAG, "skipping empty notification");
return null;
}
-
record.applyAdjustments();
return null;
diff --git a/com/android/server/notification/NotificationChannelExtractor.java b/com/android/server/notification/NotificationChannelExtractor.java
index 46ab556f..11c7ab75 100644
--- a/com/android/server/notification/NotificationChannelExtractor.java
+++ b/com/android/server/notification/NotificationChannelExtractor.java
@@ -22,7 +22,7 @@ import android.util.Slog;
* Stores the latest notification channel information for this notification
*/
public class NotificationChannelExtractor implements NotificationSignalExtractor {
- private static final String TAG = "BadgeExtractor";
+ private static final String TAG = "ChannelExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
diff --git a/com/android/server/notification/NotificationDelegate.java b/com/android/server/notification/NotificationDelegate.java
index 6a1401c2..36bc0962 100644
--- a/com/android/server/notification/NotificationDelegate.java
+++ b/com/android/server/notification/NotificationDelegate.java
@@ -16,6 +16,8 @@
package com.android.server.notification;
+import android.service.notification.NotificationStats;
+
import com.android.internal.statusbar.NotificationVisibility;
public interface NotificationDelegate {
@@ -24,7 +26,8 @@ public interface NotificationDelegate {
void onNotificationClick(int callingUid, int callingPid, String key);
void onNotificationActionClick(int callingUid, int callingPid, String key, int actionIndex);
void onNotificationClear(int callingUid, int callingPid,
- String pkg, String tag, int id, int userId);
+ String pkg, String tag, int id, int userId, String key,
+ @NotificationStats.DismissalSurface int dismissalSurface);
void onNotificationError(int callingUid, int callingPid,
String pkg, String tag, int id,
int uid, int initialPid, String message, int userId);
@@ -35,4 +38,6 @@ public interface NotificationDelegate {
NotificationVisibility[] newlyVisibleKeys,
NotificationVisibility[] noLongerVisibleKeys);
void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
+ void onNotificationDirectReplied(String key);
+ void onNotificationSettingsViewed(String key);
}
diff --git a/com/android/server/notification/NotificationManagerInternal.java b/com/android/server/notification/NotificationManagerInternal.java
index 4923b06e..f1476b34 100644
--- a/com/android/server/notification/NotificationManagerInternal.java
+++ b/com/android/server/notification/NotificationManagerInternal.java
@@ -17,8 +17,10 @@
package com.android.server.notification;
import android.app.Notification;
+import android.app.NotificationChannel;
public interface NotificationManagerInternal {
+ NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int userId);
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index fe39fccb..14cd0555 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -16,8 +16,10 @@
package com.android.server.notification;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -125,12 +127,14 @@ import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
import android.service.notification.IStatusBarNotificationHolder;
+import android.service.notification.ListenersDisablingEffectsProto;
import android.service.notification.NotificationAssistantService;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationRankingUpdate;
import android.service.notification.NotificationRecordProto;
import android.service.notification.NotificationServiceDumpProto;
import android.service.notification.NotificationServiceProto;
+import android.service.notification.NotificationStats;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
@@ -675,7 +679,14 @@ public class NotificationManagerService extends SystemService {
@Override
public void onNotificationClear(int callingUid, int callingPid,
- String pkg, String tag, int id, int userId) {
+ String pkg, String tag, int id, int userId, String key,
+ @NotificationStats.DismissalSurface int dismissalSurface) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ r.recordDismissalSurface(dismissalSurface);
+ }
+ }
cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
true, userId, REASON_CANCEL, null);
@@ -761,12 +772,35 @@ public class NotificationManagerService extends SystemService {
.setType(expanded ? MetricsEvent.TYPE_DETAIL
: MetricsEvent.TYPE_COLLAPSE));
}
+ if (expanded) {
+ r.recordExpanded();
+ }
EventLogTags.writeNotificationExpansion(key,
userAction ? 1 : 0, expanded ? 1 : 0,
r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
}
}
}
+
+ @Override
+ public void onNotificationDirectReplied(String key) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ r.recordDirectReplied();
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationSettingsViewed(String key) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ r.recordViewedSettings();
+ }
+ }
+ }
};
@GuardedBy("mNotificationLock")
@@ -1142,6 +1176,12 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
+ NotificationRecord getNotificationRecord(String key) {
+ return mNotificationsByKey.get(key);
+ }
+
+
+ @VisibleForTesting
void setSystemReady(boolean systemReady) {
mSystemReady = systemReady;
}
@@ -1216,7 +1256,7 @@ public class NotificationManagerService extends SystemService {
mUsageStats = usageStats;
mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
mRankingHelper = new RankingHelper(getContext(),
- getContext().getPackageManager(),
+ mPackageManagerClient,
mRankingHandler,
mUsageStats,
extractorNames);
@@ -1269,13 +1309,11 @@ public class NotificationManagerService extends SystemService {
R.array.config_notificationFallbackVibePattern,
VIBRATE_PATTERN_MAXLEN,
DEFAULT_VIBRATE_PATTERN);
-
mInCallNotificationUri = Uri.parse("file://" +
resources.getString(R.string.config_inCallNotificationSound));
mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
- .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
.build();
mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
@@ -1476,7 +1514,7 @@ public class NotificationManagerService extends SystemService {
}
}
}
- mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+ mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
if (!fromListener) {
final NotificationChannel modifiedChannel =
@@ -3239,14 +3277,51 @@ public class NotificationManagerService extends SystemService {
}
}
proto.end(records);
- }
- long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
- mZenModeHelper.dump(proto);
- for (ComponentName suppressor : mEffectsSuppressors) {
- proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+ long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
+ mZenModeHelper.dump(proto);
+ for (ComponentName suppressor : mEffectsSuppressors) {
+ proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+ }
+ proto.end(zenLog);
+
+ long listenersToken = proto.start(NotificationServiceDumpProto.NOTIFICATION_LISTENERS);
+ mListeners.dump(proto, filter);
+ proto.end(listenersToken);
+
+ proto.write(NotificationServiceDumpProto.LISTENER_HINTS, mListenerHints);
+
+ for (int i = 0; i < mListenersDisablingEffects.size(); ++i) {
+ long effectsToken = proto.start(
+ NotificationServiceDumpProto.LISTENERS_DISABLING_EFFECTS);
+
+ proto.write(
+ ListenersDisablingEffectsProto.HINT, mListenersDisablingEffects.keyAt(i));
+ final ArraySet<ManagedServiceInfo> listeners =
+ mListenersDisablingEffects.valueAt(i);
+ for (int j = 0; j < listeners.size(); j++) {
+ final ManagedServiceInfo listener = listeners.valueAt(i);
+ listenersToken = proto.start(ListenersDisablingEffectsProto.LISTENERS);
+ listener.toProto(proto, null);
+ proto.end(listenersToken);
+ }
+
+ proto.end(effectsToken);
+ }
+
+ long assistantsToken = proto.start(
+ NotificationServiceDumpProto.NOTIFICATION_ASSISTANTS);
+ mAssistants.dump(proto, filter);
+ proto.end(assistantsToken);
+
+ long conditionsToken = proto.start(NotificationServiceDumpProto.CONDITION_PROVIDERS);
+ mConditionProviders.dump(proto, filter);
+ proto.end(conditionsToken);
+
+ long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG);
+ mRankingHelper.dump(proto, filter);
+ proto.end(rankingToken);
}
- proto.end(zenLog);
proto.flush();
}
@@ -3401,6 +3476,12 @@ public class NotificationManagerService extends SystemService {
*/
private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
@Override
+ public NotificationChannel getNotificationChannel(String pkg, int uid, String
+ channelId) {
+ return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+ }
+
+ @Override
public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int userId) {
enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
@@ -3519,6 +3600,21 @@ public class NotificationManagerService extends SystemService {
user, null, System.currentTimeMillis());
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
+ if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
+ && (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
+ && (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) {
+ // Increase the importance of foreground service notifications unless the user had an
+ // opinion otherwise
+ if (TextUtils.isEmpty(channelId)
+ || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
+ r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
+ } else {
+ channel.setImportance(IMPORTANCE_LOW);
+ mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
+ r.updateNotificationChannel(channel);
+ }
+ }
+
if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.sbn.getOverrideGroupKey() != null)) {
return;
@@ -3752,6 +3848,8 @@ public class NotificationManagerService extends SystemService {
MetricsLogger.action(r.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsEvent.TYPE_CLOSE)
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS,
+ mDuration)
.addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
mSnoozeCriterionId == null ? 0 : 1));
boolean wasPosted = removeFromNotificationListsLocked(r);
@@ -3763,6 +3861,7 @@ public class NotificationManagerService extends SystemService {
} else {
mSnoozeHelper.snooze(r, mDuration);
}
+ r.recordSnoozed();
savePolicyFile();
}
}
@@ -3902,7 +4001,7 @@ public class NotificationManagerService extends SystemService {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(n,
- NotificationListenerService.REASON_ERROR);
+ NotificationListenerService.REASON_ERROR, null);
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -4028,19 +4127,19 @@ public class NotificationManagerService extends SystemService {
if (mSystemReady && mAudioManager != null) {
Uri soundUri = record.getSound();
hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
-
long[] vibration = record.getVibration();
// Demote sound to vibration if vibration missing & phone in vibration mode.
if (vibration == null
&& hasValidSound
&& (mAudioManager.getRingerModeInternal()
- == AudioManager.RINGER_MODE_VIBRATE)) {
+ == AudioManager.RINGER_MODE_VIBRATE)
+ && mAudioManager.getStreamVolume(
+ AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
vibration = mFallbackVibrationPattern;
}
hasValidVibrate = vibration != null;
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
-
if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
if (DBG) Slog.v(TAG, "Interrupting!");
if (hasValidSound) {
@@ -4137,8 +4236,9 @@ public class NotificationManagerService extends SystemService {
boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
// do not play notifications if there is a user of exclusive audio focus
// or the device is in vibrate mode
- if (!mAudioManager.isAudioFocusExclusive() && mAudioManager.getRingerModeInternal()
- != AudioManager.RINGER_MODE_VIBRATE) {
+ if (!mAudioManager.isAudioFocusExclusive() && (mAudioManager.getRingerModeInternal()
+ != AudioManager.RINGER_MODE_VIBRATE || mAudioManager.getStreamVolume(
+ AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) {
final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
@@ -4394,6 +4494,7 @@ public class NotificationManagerService extends SystemService {
ArrayList<String> groupKeyBefore = new ArrayList<>(N);
ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
+ ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
@@ -4403,6 +4504,7 @@ public class NotificationManagerService extends SystemService {
groupKeyBefore.add(r.getGroupKey());
overridePeopleBefore.add(r.getPeopleOverride());
snoozeCriteriaBefore.add(r.getSnoozeCriteria());
+ userSentimentBefore.add(r.getUserSentiment());
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
@@ -4414,7 +4516,8 @@ public class NotificationManagerService extends SystemService {
|| !Objects.equals(channelBefore.get(i), r.getChannel())
|| !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
|| !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
- || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())) {
+ || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
+ || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())) {
mHandler.scheduleSendRankingUpdate();
return;
}
@@ -4607,6 +4710,10 @@ public class NotificationManagerService extends SystemService {
// Record caller.
recordCallerLocked(r);
+ if (r.getStats().getDismissalSurface() == NotificationStats.DISMISSAL_NOT_DISMISSED) {
+ r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER);
+ }
+
// tell the app
if (sendDelete) {
if (r.getNotification().deleteIntent != null) {
@@ -4627,7 +4734,7 @@ public class NotificationManagerService extends SystemService {
if (reason != REASON_SNOOZED) {
r.isCanceled = true;
}
- mListeners.notifyRemovedLocked(r.sbn, reason);
+ mListeners.notifyRemovedLocked(r.sbn, reason, r.getStats());
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -5221,6 +5328,7 @@ public class NotificationManagerService extends SystemService {
Bundle overridePeople = new Bundle();
Bundle snoozeCriteria = new Bundle();
Bundle showBadge = new Bundle();
+ Bundle userSentiment = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
@@ -5246,6 +5354,7 @@ public class NotificationManagerService extends SystemService {
overridePeople.putStringArrayList(key, record.getPeopleOverride());
snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
showBadge.putBoolean(key, record.canShowBadge());
+ userSentiment.putInt(key, record.getUserSentiment());
}
final int M = keys.size();
String[] keysAr = keys.toArray(new String[M]);
@@ -5256,7 +5365,7 @@ public class NotificationManagerService extends SystemService {
}
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
- channels, overridePeople, snoozeCriteria, showBadge);
+ channels, overridePeople, snoozeCriteria, showBadge, userSentiment);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
@@ -5345,7 +5454,7 @@ public class NotificationManagerService extends SystemService {
@Override
protected Config getConfig() {
Config c = new Config();
- c.caption = "notification assistant service";
+ c.caption = "notification assistant";
c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
@@ -5388,8 +5497,6 @@ public class NotificationManagerService extends SystemService {
continue;
}
- final int importance = r.getImportance();
- final boolean fromUser = r.isImportanceFromUser();
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
mHandler.post(new Runnable() {
@Override
@@ -5420,6 +5527,10 @@ public class NotificationManagerService extends SystemService {
final String snoozeCriterionId) {
TrimCache trimCache = new TrimCache(sbn);
for (final ManagedServiceInfo info : getServices()) {
+ boolean sbnVisible = isVisibleToListener(sbn, info);
+ if (!sbnVisible) {
+ continue;
+ }
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
mHandler.post(new Runnable() {
@Override
@@ -5541,7 +5652,8 @@ public class NotificationManagerService extends SystemService {
mHandler.post(new Runnable() {
@Override
public void run() {
- notifyRemoved(info, oldSbnLightClone, update, REASON_USER_STOPPED);
+ notifyRemoved(
+ info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
}
});
continue;
@@ -5561,7 +5673,8 @@ public class NotificationManagerService extends SystemService {
* asynchronously notify all listeners about a removed notification
*/
@GuardedBy("mNotificationLock")
- public void notifyRemovedLocked(StatusBarNotification sbn, int reason) {
+ public void notifyRemovedLocked(StatusBarNotification sbn, int reason,
+ NotificationStats notificationStats) {
// make a copy in case changes are made to the underlying Notification object
// NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
// notification
@@ -5570,11 +5683,14 @@ public class NotificationManagerService extends SystemService {
if (!isVisibleToListener(sbn, info)) {
continue;
}
+ // Only assistants can get stats
+ final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
+ ? notificationStats : null;
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
mHandler.post(new Runnable() {
@Override
public void run() {
- notifyRemoved(info, sbnLight, update, reason);
+ notifyRemoved(info, sbnLight, update, stats, reason);
}
});
}
@@ -5679,14 +5795,14 @@ public class NotificationManagerService extends SystemService {
}
private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
- NotificationRankingUpdate rankingUpdate, int reason) {
+ NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
if (!info.enabledAndUserMatches(sbn.getUserId())) {
return;
}
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
- listener.onNotificationRemoved(sbnHolder, rankingUpdate, reason);
+ listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
}
@@ -5772,10 +5888,9 @@ public class NotificationManagerService extends SystemService {
final DumpFilter filter = new DumpFilter();
for (int ai = 0; ai < args.length; ai++) {
final String a = args[ai];
- if ("--proto".equals(args[0])) {
+ if ("--proto".equals(a)) {
filter.proto = true;
- }
- if ("--noredact".equals(a) || "--reveal".equals(a)) {
+ } else if ("--noredact".equals(a) || "--reveal".equals(a)) {
filter.redact = false;
} else if ("p".equals(a) || "pkg".equals(a) || "--package".equals(a)) {
if (ai < args.length-1) {
@@ -5848,8 +5963,8 @@ public class NotificationManagerService extends SystemService {
private class ShellCmd extends ShellCommand {
public static final String USAGE = "help\n"
- + "allow_listener COMPONENT\n"
- + "disallow_listener COMPONENT\n"
+ + "allow_listener COMPONENT [user_id]\n"
+ + "disallow_listener COMPONENT [user_id]\n"
+ "set_assistant COMPONENT\n"
+ "remove_assistant COMPONENT\n"
+ "allow_dnd PACKAGE\n"
@@ -5880,7 +5995,13 @@ public class NotificationManagerService extends SystemService {
pw.println("Invalid listener - must be a ComponentName");
return -1;
}
- getBinderService().setNotificationListenerAccessGranted(cn, true);
+ String userId = getNextArg();
+ if (userId == null) {
+ getBinderService().setNotificationListenerAccessGranted(cn, true);
+ } else {
+ getBinderService().setNotificationListenerAccessGrantedForUser(
+ cn, Integer.parseInt(userId), true);
+ }
}
break;
case "disallow_listener": {
@@ -5889,7 +6010,13 @@ public class NotificationManagerService extends SystemService {
pw.println("Invalid listener - must be a ComponentName");
return -1;
}
- getBinderService().setNotificationListenerAccessGranted(cn, false);
+ String userId = getNextArg();
+ if (userId == null) {
+ getBinderService().setNotificationListenerAccessGranted(cn, false);
+ } else {
+ getBinderService().setNotificationListenerAccessGrantedForUser(
+ cn, Integer.parseInt(userId), false);
+ }
}
break;
case "allow_assistant": {
diff --git a/com/android/server/notification/NotificationRecord.java b/com/android/server/notification/NotificationRecord.java
index 77bf9e30..faa300f2 100644
--- a/com/android/server/notification/NotificationRecord.java
+++ b/com/android/server/notification/NotificationRecord.java
@@ -20,6 +20,8 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.service.notification.NotificationListenerService.Ranking
+ .USER_SENTIMENT_NEUTRAL;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -41,6 +43,7 @@ import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationStats;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
@@ -84,8 +87,6 @@ public final class NotificationRecord {
NotificationUsageStats.SingleNotificationStats stats;
boolean isCanceled;
- /** Whether the notification was seen by the user via one of the notification listeners. */
- boolean mIsSeen;
// These members are used by NotificationSignalExtractors
// to communicate with the ranking module.
@@ -136,6 +137,8 @@ public final class NotificationRecord {
private String mChannelIdLogTag;
private final List<Adjustment> mAdjustments;
+ private final NotificationStats mStats;
+ private int mUserSentiment;
@VisibleForTesting
public NotificationRecord(Context context, StatusBarNotification sbn,
@@ -156,6 +159,7 @@ public final class NotificationRecord {
mImportance = calculateImportance();
mLight = calculateLights();
mAdjustments = new ArrayList<>();
+ mStats = new NotificationStats();
}
private boolean isPreChannelsNotification() {
@@ -395,7 +399,7 @@ public final class NotificationRecord {
pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
pw.println(prefix + "pri=" + notification.priority);
pw.println(prefix + "key=" + sbn.getKey());
- pw.println(prefix + "seen=" + mIsSeen);
+ pw.println(prefix + "seen=" + mStats.hasSeen());
pw.println(prefix + "groupKey=" + getGroupKey());
pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
pw.println(prefix + "contentIntent=" + notification.contentIntent);
@@ -572,6 +576,10 @@ public final class NotificationRecord {
adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
setOverrideGroupKey(groupOverrideKey);
}
+ if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
+ setUserSentiment(adjustment.getSignals().getInt(
+ Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
+ }
}
}
}
@@ -740,6 +748,7 @@ public final class NotificationRecord {
.setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
.addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank));
if (visible) {
+ setSeen();
MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
}
EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
@@ -777,12 +786,12 @@ public final class NotificationRecord {
/** Check if any of the listeners have marked this notification as seen by the user. */
public boolean isSeen() {
- return mIsSeen;
+ return mStats.hasSeen();
}
/** Mark the notification as seen by the user. */
public void setSeen() {
- mIsSeen = true;
+ mStats.setSeen();
}
public void setAuthoritativeRank(int authoritativeRank) {
@@ -883,6 +892,38 @@ public final class NotificationRecord {
mSnoozeCriteria = snoozeCriteria;
}
+ private void setUserSentiment(int userSentiment) {
+ mUserSentiment = userSentiment;
+ }
+
+ public int getUserSentiment() {
+ return mUserSentiment;
+ }
+
+ public NotificationStats getStats() {
+ return mStats;
+ }
+
+ public void recordExpanded() {
+ mStats.setExpanded();
+ }
+
+ public void recordDirectReplied() {
+ mStats.setDirectReplied();
+ }
+
+ public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) {
+ mStats.setDismissalSurface(surface);
+ }
+
+ public void recordSnoozed() {
+ mStats.setSnoozed();
+ }
+
+ public void recordViewedSettings() {
+ mStats.setViewedSettings();
+ }
+
public LogMaker getLogMaker(long now) {
if (mLogMaker == null) {
// initialize fields that only change on update (so a new record)
diff --git a/com/android/server/notification/PriorityExtractor.java b/com/android/server/notification/PriorityExtractor.java
index 5d5d39de..7a287dba 100644
--- a/com/android/server/notification/PriorityExtractor.java
+++ b/com/android/server/notification/PriorityExtractor.java
@@ -23,7 +23,7 @@ import android.util.Slog;
* Determines if the given notification can bypass Do Not Disturb.
*/
public class PriorityExtractor implements NotificationSignalExtractor {
- private static final String TAG = "ImportantTopicExtractor";
+ private static final String TAG = "PriorityExtractor";
private static final boolean DBG = false;
private RankingConfig mConfig;
diff --git a/com/android/server/notification/RankingConfig.java b/com/android/server/notification/RankingConfig.java
index b5ef1c60..b9c0d907 100644
--- a/com/android/server/notification/RankingConfig.java
+++ b/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,7 @@ public interface RankingConfig {
int uid, boolean includeDeleted);
void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp);
- void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
+ void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java
index fc24581f..d7e9cf37 100644
--- a/com/android/server/notification/RankingHelper.java
+++ b/com/android/server/notification/RankingHelper.java
@@ -36,10 +36,13 @@ import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.RankingHelperProto;
+import android.service.notification.RankingHelperProto.RecordProto;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
import org.json.JSONArray;
import org.json.JSONException;
@@ -228,7 +231,11 @@ public class RankingHelper implements RankingConfig {
if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
NotificationChannel channel = new NotificationChannel(id,
channelName, channelImportance);
- channel.populateFromXml(parser);
+ if (forRestore) {
+ channel.populateFromXmlForRestore(parser, mContext);
+ } else {
+ channel.populateFromXml(parser);
+ }
r.channels.put(id, channel);
}
}
@@ -391,7 +398,11 @@ public class RankingHelper implements RankingConfig {
}
for (NotificationChannel channel : r.channels.values()) {
- if (!forBackup || (forBackup && !channel.isDeleted())) {
+ if (forBackup) {
+ if (!channel.isDeleted()) {
+ channel.writeXmlForBackup(out, mContext);
+ }
+ } else {
channel.writeXml(out);
}
}
@@ -613,7 +624,8 @@ public class RankingHelper implements RankingConfig {
}
@Override
- public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) {
+ public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
+ boolean fromUser) {
Preconditions.checkNotNull(updatedChannel);
Preconditions.checkNotNull(updatedChannel.getId());
Record r = getOrCreateRecord(pkg, uid);
@@ -627,7 +639,11 @@ public class RankingHelper implements RankingConfig {
if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
}
- lockFieldsForUpdate(channel, updatedChannel);
+ updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
+ updatedChannel.lockFields(channel.getUserLockedFields());
+ if (fromUser) {
+ lockFieldsForUpdate(channel, updatedChannel);
+ }
r.channels.put(updatedChannel.getId(), updatedChannel);
if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
@@ -874,8 +890,6 @@ public class RankingHelper implements RankingConfig {
@VisibleForTesting
void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
- update.unlockFields(update.getUserLockedFields());
- update.lockFields(original.getUserLockedFields());
if (original.canBypassDnd() != update.canBypassDnd()) {
update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
}
@@ -912,8 +926,7 @@ public class RankingHelper implements RankingConfig {
pw.print(" ");
pw.println(mSignalExtractors[i]);
}
- }
- if (filter == null) {
+
pw.print(prefix);
pw.println("per-package config:");
}
@@ -925,6 +938,52 @@ public class RankingHelper implements RankingConfig {
dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
}
+ public void dump(ProtoOutputStream proto, NotificationManagerService.DumpFilter filter) {
+ final int N = mSignalExtractors.length;
+ for (int i = 0; i < N; i++) {
+ proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
+ mSignalExtractors[i].getClass().getSimpleName());
+ }
+ synchronized (mRecords) {
+ dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
+ }
+ dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
+ mRestoredWithoutUids);
+ }
+
+ private static void dumpRecords(ProtoOutputStream proto, long fieldId,
+ NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+ final int N = records.size();
+ long fToken;
+ for (int i = 0; i < N; i++) {
+ final Record r = records.valueAt(i);
+ if (filter == null || filter.matches(r.pkg)) {
+ fToken = proto.start(fieldId);
+
+ proto.write(RecordProto.PACKAGE, r.pkg);
+ proto.write(RecordProto.UID, r.uid);
+ proto.write(RecordProto.IMPORTANCE, r.importance);
+ proto.write(RecordProto.PRIORITY, r.priority);
+ proto.write(RecordProto.VISIBILITY, r.visibility);
+ proto.write(RecordProto.SHOW_BADGE, r.showBadge);
+
+ long token;
+ for (NotificationChannel channel : r.channels.values()) {
+ token = proto.start(RecordProto.CHANNELS);
+ channel.toProto(proto);
+ proto.end(token);
+ }
+ for (NotificationChannelGroup group : r.groups.values()) {
+ token = proto.start(RecordProto.CHANNEL_GROUPS);
+ group.toProto(proto);
+ proto.end(token);
+ }
+
+ proto.end(fToken);
+ }
+ }
+ }
+
private static void dumpRecords(PrintWriter pw, String prefix,
NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
final int N = records.size();
diff --git a/com/android/server/notification/ScheduleCalendar.java b/com/android/server/notification/ScheduleCalendar.java
index 9e8b2e34..40230bd2 100644
--- a/com/android/server/notification/ScheduleCalendar.java
+++ b/com/android/server/notification/ScheduleCalendar.java
@@ -42,7 +42,8 @@ public class ScheduleCalendar {
public void maybeSetNextAlarm(long now, long nextAlarm) {
if (mSchedule != null) {
- if (mSchedule.exitAtAlarm && now > mSchedule.nextAlarm) {
+ if (mSchedule.exitAtAlarm
+ && (now > mSchedule.nextAlarm || nextAlarm < mSchedule.nextAlarm)) {
mSchedule.nextAlarm = nextAlarm;
}
}
diff --git a/com/android/server/notification/ZenModeHelper.java b/com/android/server/notification/ZenModeHelper.java
index ffdafc56..9fcc67df 100644
--- a/com/android/server/notification/ZenModeHelper.java
+++ b/com/android/server/notification/ZenModeHelper.java
@@ -567,7 +567,7 @@ public class ZenModeHelper {
proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, rule.toString());
}
}
- proto.write(ZenModeProto.POLICY, mConfig.toNotificationPolicy().toString());
+ mConfig.toNotificationPolicy().toProto(proto, ZenModeProto.POLICY);
proto.write(ZenModeProto.SUPPRESSED_EFFECTS, mSuppressedEffects);
}
}
diff --git a/com/android/server/oemlock/OemLockService.java b/com/android/server/oemlock/OemLockService.java
index 40c66394..5b3d1eca 100644
--- a/com/android/server/oemlock/OemLockService.java
+++ b/com/android/server/oemlock/OemLockService.java
@@ -31,6 +31,7 @@ import android.os.UserManager;
import android.os.UserManagerInternal;
import android.os.UserManagerInternal.UserRestrictionsListener;
import android.service.oemlock.IOemLockService;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Slog;
import com.android.server.LocalServices;
@@ -98,6 +99,7 @@ public class OemLockService extends SystemService {
!newRestrictions.getBoolean(UserManager.DISALLOW_FACTORY_RESET);
if (!unlockAllowedByAdmin) {
mOemLock.setOemUnlockAllowedByDevice(false);
+ setPersistentDataBlockOemUnlockAllowedBit(false);
}
}
}
@@ -158,6 +160,7 @@ public class OemLockService extends SystemService {
}
mOemLock.setOemUnlockAllowedByDevice(allowedByUser);
+ setPersistentDataBlockOemUnlockAllowedBit(allowedByUser);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -202,6 +205,20 @@ public class OemLockService extends SystemService {
}
};
+ /**
+ * Always synchronize the OemUnlockAllowed bit to the FRP partition, which
+ * is used to erase FRP information on a unlockable device.
+ */
+ private void setPersistentDataBlockOemUnlockAllowedBit(boolean allowed) {
+ final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
+ mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ // if mOemLock is PersistentDataBlockLock, then the bit should have already been set
+ if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)) {
+ Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed);
+ pdbm.setOemUnlockEnabled(allowed);
+ }
+ }
+
private boolean isOemUnlockAllowedByAdmin() {
return !UserManager.get(mContext)
.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM);
diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java
index 415c9a9c..6d8cac0c 100644
--- a/com/android/server/pm/BackgroundDexOptService.java
+++ b/com/android/server/pm/BackgroundDexOptService.java
@@ -342,8 +342,7 @@ public class BackgroundDexOptService extends JobService {
DexoptOptions.DEXOPT_BOOT_COMPLETE |
(downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0);
if (is_for_primary_dex) {
- int result = pm.performDexOptWithStatus(new DexoptOptions(pkg,
- PackageManagerService.REASON_BACKGROUND_DEXOPT,
+ int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
dexoptFlags));
success = result != PackageDexOptimizer.DEX_OPT_FAILED;
if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -351,8 +350,7 @@ public class BackgroundDexOptService extends JobService {
}
} else {
success = pm.performDexOpt(new DexoptOptions(pkg,
- PackageManagerService.REASON_BACKGROUND_DEXOPT,
- dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
+ reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
}
if (success) {
// Dexopt succeeded, remove package from the list of failing ones.
diff --git a/com/android/server/pm/BasePermission.java b/com/android/server/pm/BasePermission.java
deleted file mode 100644
index 30fda1e5..00000000
--- a/com/android/server/pm/BasePermission.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.content.pm.PackageParser;
-import android.content.pm.PermissionInfo;
-import android.os.UserHandle;
-
-final class BasePermission {
- final static int TYPE_NORMAL = 0;
-
- final static int TYPE_BUILTIN = 1;
-
- final static int TYPE_DYNAMIC = 2;
-
- final String name;
-
- String sourcePackage;
-
- PackageSettingBase packageSetting;
-
- final int type;
-
- int protectionLevel;
-
- PackageParser.Permission perm;
-
- PermissionInfo pendingInfo;
-
- /** UID that owns the definition of this permission */
- int uid;
-
- /** Additional GIDs given to apps granted this permission */
- private int[] gids;
-
- /**
- * Flag indicating that {@link #gids} should be adjusted based on the
- * {@link UserHandle} the granted app is running as.
- */
- private boolean perUser;
-
- BasePermission(String _name, String _sourcePackage, int _type) {
- name = _name;
- sourcePackage = _sourcePackage;
- type = _type;
- // Default to most conservative protection level.
- protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
- }
-
- @Override
- public String toString() {
- return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
- + "}";
- }
-
- public void setGids(int[] gids, boolean perUser) {
- this.gids = gids;
- this.perUser = perUser;
- }
-
- public int[] computeGids(int userId) {
- if (perUser) {
- final int[] userGids = new int[gids.length];
- for (int i = 0; i < gids.length; i++) {
- userGids[i] = UserHandle.getUid(userId, gids[i]);
- }
- return userGids;
- } else {
- return gids;
- }
- }
-
- public boolean isRuntime() {
- return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
- == PermissionInfo.PROTECTION_DANGEROUS;
- }
-
- public boolean isDevelopment() {
- return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
- == PermissionInfo.PROTECTION_SIGNATURE
- && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
- }
-
- public boolean isInstant() {
- return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
- }
-
- public boolean isRuntimeOnly() {
- return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
- }
-}
diff --git a/com/android/server/pm/DefaultPermissionGrantPolicy.java b/com/android/server/pm/DefaultPermissionGrantPolicy.java
deleted file mode 100644
index a3811baf..00000000
--- a/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ /dev/null
@@ -1,1244 +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 com.android.server.pm;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.DownloadManager;
-import android.app.admin.DevicePolicyManager;
-import android.companion.CompanionDeviceManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal.PackagesProvider;
-import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
-import android.content.pm.PackageParser;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.os.storage.StorageManager;
-import android.print.PrintManager;
-import android.provider.CalendarContract;
-import android.provider.ContactsContract;
-import android.provider.MediaStore;
-import android.provider.Telephony.Sms.Intents;
-import android.telephony.TelephonyManager;
-import android.security.Credentials;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-import com.android.internal.util.XmlUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static android.os.Process.FIRST_APPLICATION_UID;
-
-/**
- * This class is the policy for granting runtime permissions to
- * platform components and default handlers in the system such
- * that the device is usable out-of-the-box. For example, the
- * shell UID is a part of the system and the Phone app should
- * have phone related permission by default.
- */
-final class DefaultPermissionGrantPolicy {
- private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
- private static final boolean DEBUG = false;
-
- private static final int DEFAULT_FLAGS =
- PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_UNINSTALLED_PACKAGES;
-
- private static final String AUDIO_MIME_TYPE = "audio/mpeg";
-
- private static final String TAG_EXCEPTIONS = "exceptions";
- private static final String TAG_EXCEPTION = "exception";
- private static final String TAG_PERMISSION = "permission";
- private static final String ATTR_PACKAGE = "package";
- private static final String ATTR_NAME = "name";
- private static final String ATTR_FIXED = "fixed";
-
- private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
- static {
- PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
- PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
- PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
- PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
- PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
- PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
- PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
- }
-
- private static final Set<String> CONTACTS_PERMISSIONS = new ArraySet<>();
- static {
- CONTACTS_PERMISSIONS.add(Manifest.permission.READ_CONTACTS);
- CONTACTS_PERMISSIONS.add(Manifest.permission.WRITE_CONTACTS);
- CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
- }
-
- private static final Set<String> LOCATION_PERMISSIONS = new ArraySet<>();
- static {
- LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
- LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
- }
-
- private static final Set<String> CALENDAR_PERMISSIONS = new ArraySet<>();
- static {
- CALENDAR_PERMISSIONS.add(Manifest.permission.READ_CALENDAR);
- CALENDAR_PERMISSIONS.add(Manifest.permission.WRITE_CALENDAR);
- }
-
- private static final Set<String> SMS_PERMISSIONS = new ArraySet<>();
- static {
- SMS_PERMISSIONS.add(Manifest.permission.SEND_SMS);
- SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_SMS);
- SMS_PERMISSIONS.add(Manifest.permission.READ_SMS);
- SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_WAP_PUSH);
- SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_MMS);
- SMS_PERMISSIONS.add(Manifest.permission.READ_CELL_BROADCASTS);
- }
-
- private static final Set<String> MICROPHONE_PERMISSIONS = new ArraySet<>();
- static {
- MICROPHONE_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
- }
-
- private static final Set<String> CAMERA_PERMISSIONS = new ArraySet<>();
- static {
- CAMERA_PERMISSIONS.add(Manifest.permission.CAMERA);
- }
-
- private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
- static {
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
- }
-
- private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
- static {
- STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
- STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
- }
-
- private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
-
- private static final String ACTION_TRACK = "com.android.fitness.TRACK";
-
- private final PackageManagerService mService;
- private final Handler mHandler;
-
- private PackagesProvider mLocationPackagesProvider;
- private PackagesProvider mVoiceInteractionPackagesProvider;
- private PackagesProvider mSmsAppPackagesProvider;
- private PackagesProvider mDialerAppPackagesProvider;
- private PackagesProvider mSimCallManagerPackagesProvider;
- private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
-
- private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
-
- public DefaultPermissionGrantPolicy(PackageManagerService service) {
- mService = service;
- mHandler = new Handler(mService.mHandlerThread.getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) {
- synchronized (mService.mPackages) {
- if (mGrantExceptions == null) {
- mGrantExceptions = readDefaultPermissionExceptionsLPw();
- }
- }
- }
- }
- };
- }
-
- public void setLocationPackagesProviderLPw(PackagesProvider provider) {
- mLocationPackagesProvider = provider;
- }
-
- public void setVoiceInteractionPackagesProviderLPw(PackagesProvider provider) {
- mVoiceInteractionPackagesProvider = provider;
- }
-
- public void setSmsAppPackagesProviderLPw(PackagesProvider provider) {
- mSmsAppPackagesProvider = provider;
- }
-
- public void setDialerAppPackagesProviderLPw(PackagesProvider provider) {
- mDialerAppPackagesProvider = provider;
- }
-
- public void setSimCallManagerPackagesProviderLPw(PackagesProvider provider) {
- mSimCallManagerPackagesProvider = provider;
- }
-
- public void setSyncAdapterPackagesProviderLPw(SyncAdapterPackagesProvider provider) {
- mSyncAdapterPackagesProvider = provider;
- }
-
- public void grantDefaultPermissions(int userId) {
- if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
- grantAllRuntimePermissions(userId);
- } else {
- grantPermissionsToSysComponentsAndPrivApps(userId);
- grantDefaultSystemHandlerPermissions(userId);
- grantDefaultPermissionExceptions(userId);
- }
- }
-
- private void grantRuntimePermissionsForPackageLocked(int userId, PackageParser.Package pkg) {
- Set<String> permissions = new ArraySet<>();
- for (String permission : pkg.requestedPermissions) {
- BasePermission bp = mService.mSettings.mPermissions.get(permission);
- if (bp != null && bp.isRuntime()) {
- permissions.add(permission);
- }
- }
- if (!permissions.isEmpty()) {
- grantRuntimePermissionsLPw(pkg, permissions, true, userId);
- }
- }
-
- private void grantAllRuntimePermissions(int userId) {
- Log.i(TAG, "Granting all runtime permissions for user " + userId);
- synchronized (mService.mPackages) {
- for (PackageParser.Package pkg : mService.mPackages.values()) {
- grantRuntimePermissionsForPackageLocked(userId, pkg);
- }
- }
- }
-
- public void scheduleReadDefaultPermissionExceptions() {
- mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
- }
-
- private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
- Log.i(TAG, "Granting permissions to platform components for user " + userId);
-
- synchronized (mService.mPackages) {
- for (PackageParser.Package pkg : mService.mPackages.values()) {
- if (!isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg)
- || !doesPackageSupportRuntimePermissions(pkg)
- || pkg.requestedPermissions.isEmpty()) {
- continue;
- }
- grantRuntimePermissionsForPackageLocked(userId, pkg);
- }
- }
- }
-
- private void grantDefaultSystemHandlerPermissions(int userId) {
- Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
-
- final PackagesProvider locationPackagesProvider;
- final PackagesProvider voiceInteractionPackagesProvider;
- final PackagesProvider smsAppPackagesProvider;
- final PackagesProvider dialerAppPackagesProvider;
- final PackagesProvider simCallManagerPackagesProvider;
- final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
-
- synchronized (mService.mPackages) {
- locationPackagesProvider = mLocationPackagesProvider;
- voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
- smsAppPackagesProvider = mSmsAppPackagesProvider;
- dialerAppPackagesProvider = mDialerAppPackagesProvider;
- simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
- syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
- }
-
- String[] voiceInteractPackageNames = (voiceInteractionPackagesProvider != null)
- ? voiceInteractionPackagesProvider.getPackages(userId) : null;
- String[] locationPackageNames = (locationPackagesProvider != null)
- ? locationPackagesProvider.getPackages(userId) : null;
- String[] smsAppPackageNames = (smsAppPackagesProvider != null)
- ? smsAppPackagesProvider.getPackages(userId) : null;
- String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
- ? dialerAppPackagesProvider.getPackages(userId) : null;
- String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
- ? simCallManagerPackagesProvider.getPackages(userId) : null;
- String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
- syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
- String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
- syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null;
-
- synchronized (mService.mPackages) {
- // Installer
- PackageParser.Package installerPackage = getSystemPackageLPr(
- mService.mRequiredInstallerPackage);
- if (installerPackage != null
- && doesPackageSupportRuntimePermissions(installerPackage)) {
- grantRuntimePermissionsLPw(installerPackage, STORAGE_PERMISSIONS, true, userId);
- }
-
- // Verifier
- PackageParser.Package verifierPackage = getSystemPackageLPr(
- mService.mRequiredVerifierPackage);
- if (verifierPackage != null
- && doesPackageSupportRuntimePermissions(verifierPackage)) {
- grantRuntimePermissionsLPw(verifierPackage, STORAGE_PERMISSIONS, true, userId);
- grantRuntimePermissionsLPw(verifierPackage, PHONE_PERMISSIONS, false, userId);
- grantRuntimePermissionsLPw(verifierPackage, SMS_PERMISSIONS, false, userId);
- }
-
- // SetupWizard
- PackageParser.Package setupPackage = getSystemPackageLPr(
- mService.mSetupWizardPackage);
- if (setupPackage != null
- && doesPackageSupportRuntimePermissions(setupPackage)) {
- grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(setupPackage, CONTACTS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(setupPackage, LOCATION_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(setupPackage, CAMERA_PERMISSIONS, userId);
- }
-
- // Camera
- Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- PackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackageLPr(
- cameraIntent, userId);
- if (cameraPackage != null
- && doesPackageSupportRuntimePermissions(cameraPackage)) {
- grantRuntimePermissionsLPw(cameraPackage, CAMERA_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(cameraPackage, MICROPHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(cameraPackage, STORAGE_PERMISSIONS, userId);
- }
-
- // Media provider
- PackageParser.Package mediaStorePackage = getDefaultProviderAuthorityPackageLPr(
- MediaStore.AUTHORITY, userId);
- if (mediaStorePackage != null) {
- grantRuntimePermissionsLPw(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
- }
-
- // Downloads provider
- PackageParser.Package downloadsPackage = getDefaultProviderAuthorityPackageLPr(
- "downloads", userId);
- if (downloadsPackage != null) {
- grantRuntimePermissionsLPw(downloadsPackage, STORAGE_PERMISSIONS, true, userId);
- }
-
- // Downloads UI
- Intent downloadsUiIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
- PackageParser.Package downloadsUiPackage = getDefaultSystemHandlerActivityPackageLPr(
- downloadsUiIntent, userId);
- if (downloadsUiPackage != null
- && doesPackageSupportRuntimePermissions(downloadsUiPackage)) {
- grantRuntimePermissionsLPw(downloadsUiPackage, STORAGE_PERMISSIONS, true, userId);
- }
-
- // Storage provider
- PackageParser.Package storagePackage = getDefaultProviderAuthorityPackageLPr(
- "com.android.externalstorage.documents", userId);
- if (storagePackage != null) {
- grantRuntimePermissionsLPw(storagePackage, STORAGE_PERMISSIONS, true, userId);
- }
-
- // CertInstaller
- Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
- PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackageLPr(
- certInstallerIntent, userId);
- if (certInstallerPackage != null
- && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
- grantRuntimePermissionsLPw(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
- }
-
- // Dialer
- if (dialerAppPackageNames == null) {
- Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
- PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackageLPr(
- dialerIntent, userId);
- if (dialerPackage != null) {
- grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
- }
- } else {
- for (String dialerAppPackageName : dialerAppPackageNames) {
- PackageParser.Package dialerPackage = getSystemPackageLPr(dialerAppPackageName);
- if (dialerPackage != null) {
- grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
- }
- }
- }
-
- // Sim call manager
- if (simCallManagerPackageNames != null) {
- for (String simCallManagerPackageName : simCallManagerPackageNames) {
- PackageParser.Package simCallManagerPackage =
- getSystemPackageLPr(simCallManagerPackageName);
- if (simCallManagerPackage != null) {
- grantDefaultPermissionsToDefaultSimCallManagerLPr(simCallManagerPackage,
- userId);
- }
- }
- }
-
- // SMS
- if (smsAppPackageNames == null) {
- Intent smsIntent = new Intent(Intent.ACTION_MAIN);
- smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
- PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackageLPr(
- smsIntent, userId);
- if (smsPackage != null) {
- grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
- }
- } else {
- for (String smsPackageName : smsAppPackageNames) {
- PackageParser.Package smsPackage = getSystemPackageLPr(smsPackageName);
- if (smsPackage != null) {
- grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
- }
- }
- }
-
- // Cell Broadcast Receiver
- Intent cbrIntent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
- PackageParser.Package cbrPackage =
- getDefaultSystemHandlerActivityPackageLPr(cbrIntent, userId);
- if (cbrPackage != null && doesPackageSupportRuntimePermissions(cbrPackage)) {
- grantRuntimePermissionsLPw(cbrPackage, SMS_PERMISSIONS, userId);
- }
-
- // Carrier Provisioning Service
- Intent carrierProvIntent = new Intent(Intents.SMS_CARRIER_PROVISION_ACTION);
- PackageParser.Package carrierProvPackage =
- getDefaultSystemHandlerServicePackageLPr(carrierProvIntent, userId);
- if (carrierProvPackage != null && doesPackageSupportRuntimePermissions(carrierProvPackage)) {
- grantRuntimePermissionsLPw(carrierProvPackage, SMS_PERMISSIONS, false, userId);
- }
-
- // Calendar
- Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
- calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
- PackageParser.Package calendarPackage = getDefaultSystemHandlerActivityPackageLPr(
- calendarIntent, userId);
- if (calendarPackage != null
- && doesPackageSupportRuntimePermissions(calendarPackage)) {
- grantRuntimePermissionsLPw(calendarPackage, CALENDAR_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(calendarPackage, CONTACTS_PERMISSIONS, userId);
- }
-
- // Calendar provider
- PackageParser.Package calendarProviderPackage = getDefaultProviderAuthorityPackageLPr(
- CalendarContract.AUTHORITY, userId);
- if (calendarProviderPackage != null) {
- grantRuntimePermissionsLPw(calendarProviderPackage, CONTACTS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(calendarProviderPackage, CALENDAR_PERMISSIONS,
- true, userId);
- grantRuntimePermissionsLPw(calendarProviderPackage, STORAGE_PERMISSIONS, userId);
- }
-
- // Calendar provider sync adapters
- List<PackageParser.Package> calendarSyncAdapters = getHeadlessSyncAdapterPackagesLPr(
- calendarSyncAdapterPackages, userId);
- final int calendarSyncAdapterCount = calendarSyncAdapters.size();
- for (int i = 0; i < calendarSyncAdapterCount; i++) {
- PackageParser.Package calendarSyncAdapter = calendarSyncAdapters.get(i);
- if (doesPackageSupportRuntimePermissions(calendarSyncAdapter)) {
- grantRuntimePermissionsLPw(calendarSyncAdapter, CALENDAR_PERMISSIONS, userId);
- }
- }
-
- // Contacts
- Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
- contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
- PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackageLPr(
- contactsIntent, userId);
- if (contactsPackage != null
- && doesPackageSupportRuntimePermissions(contactsPackage)) {
- grantRuntimePermissionsLPw(contactsPackage, CONTACTS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(contactsPackage, PHONE_PERMISSIONS, userId);
- }
-
- // Contacts provider sync adapters
- List<PackageParser.Package> contactsSyncAdapters = getHeadlessSyncAdapterPackagesLPr(
- contactsSyncAdapterPackages, userId);
- final int contactsSyncAdapterCount = contactsSyncAdapters.size();
- for (int i = 0; i < contactsSyncAdapterCount; i++) {
- PackageParser.Package contactsSyncAdapter = contactsSyncAdapters.get(i);
- if (doesPackageSupportRuntimePermissions(contactsSyncAdapter)) {
- grantRuntimePermissionsLPw(contactsSyncAdapter, CONTACTS_PERMISSIONS, userId);
- }
- }
-
- // Contacts provider
- PackageParser.Package contactsProviderPackage = getDefaultProviderAuthorityPackageLPr(
- ContactsContract.AUTHORITY, userId);
- if (contactsProviderPackage != null) {
- grantRuntimePermissionsLPw(contactsProviderPackage, CONTACTS_PERMISSIONS,
- true, userId);
- grantRuntimePermissionsLPw(contactsProviderPackage, PHONE_PERMISSIONS,
- true, userId);
- grantRuntimePermissionsLPw(contactsProviderPackage, STORAGE_PERMISSIONS, userId);
- }
-
- // Device provisioning
- Intent deviceProvisionIntent = new Intent(
- DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE);
- PackageParser.Package deviceProvisionPackage =
- getDefaultSystemHandlerActivityPackageLPr(deviceProvisionIntent, userId);
- if (deviceProvisionPackage != null
- && doesPackageSupportRuntimePermissions(deviceProvisionPackage)) {
- grantRuntimePermissionsLPw(deviceProvisionPackage, CONTACTS_PERMISSIONS, userId);
- }
-
- // Maps
- Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
- mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
- PackageParser.Package mapsPackage = getDefaultSystemHandlerActivityPackageLPr(
- mapsIntent, userId);
- if (mapsPackage != null
- && doesPackageSupportRuntimePermissions(mapsPackage)) {
- grantRuntimePermissionsLPw(mapsPackage, LOCATION_PERMISSIONS, userId);
- }
-
- // Gallery
- Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
- galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
- PackageParser.Package galleryPackage = getDefaultSystemHandlerActivityPackageLPr(
- galleryIntent, userId);
- if (galleryPackage != null
- && doesPackageSupportRuntimePermissions(galleryPackage)) {
- grantRuntimePermissionsLPw(galleryPackage, STORAGE_PERMISSIONS, userId);
- }
-
- // Email
- Intent emailIntent = new Intent(Intent.ACTION_MAIN);
- emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
- PackageParser.Package emailPackage = getDefaultSystemHandlerActivityPackageLPr(
- emailIntent, userId);
- if (emailPackage != null
- && doesPackageSupportRuntimePermissions(emailPackage)) {
- grantRuntimePermissionsLPw(emailPackage, CONTACTS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(emailPackage, CALENDAR_PERMISSIONS, userId);
- }
-
- // Browser
- PackageParser.Package browserPackage = null;
- String defaultBrowserPackage = mService.getDefaultBrowserPackageName(userId);
- if (defaultBrowserPackage != null) {
- browserPackage = getPackageLPr(defaultBrowserPackage);
- }
- if (browserPackage == null) {
- Intent browserIntent = new Intent(Intent.ACTION_MAIN);
- browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
- browserPackage = getDefaultSystemHandlerActivityPackageLPr(
- browserIntent, userId);
- }
- if (browserPackage != null
- && doesPackageSupportRuntimePermissions(browserPackage)) {
- grantRuntimePermissionsLPw(browserPackage, LOCATION_PERMISSIONS, userId);
- }
-
- // Voice interaction
- if (voiceInteractPackageNames != null) {
- for (String voiceInteractPackageName : voiceInteractPackageNames) {
- PackageParser.Package voiceInteractPackage = getSystemPackageLPr(
- voiceInteractPackageName);
- if (voiceInteractPackage != null
- && doesPackageSupportRuntimePermissions(voiceInteractPackage)) {
- grantRuntimePermissionsLPw(voiceInteractPackage,
- CONTACTS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(voiceInteractPackage,
- CALENDAR_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(voiceInteractPackage,
- MICROPHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(voiceInteractPackage,
- PHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(voiceInteractPackage,
- SMS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(voiceInteractPackage,
- LOCATION_PERMISSIONS, userId);
- }
- }
- }
-
- if (ActivityManager.isLowRamDeviceStatic()) {
- // Allow voice search on low-ram devices
- Intent globalSearchIntent = new Intent("android.search.action.GLOBAL_SEARCH");
- PackageParser.Package globalSearchPickerPackage =
- getDefaultSystemHandlerActivityPackageLPr(globalSearchIntent, userId);
-
- if (globalSearchPickerPackage != null
- && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
- grantRuntimePermissionsLPw(globalSearchPickerPackage,
- MICROPHONE_PERMISSIONS, true, userId);
- grantRuntimePermissionsLPw(globalSearchPickerPackage,
- LOCATION_PERMISSIONS, true, userId);
- }
- }
-
- // Voice recognition
- Intent voiceRecoIntent = new Intent("android.speech.RecognitionService");
- voiceRecoIntent.addCategory(Intent.CATEGORY_DEFAULT);
- PackageParser.Package voiceRecoPackage = getDefaultSystemHandlerServicePackageLPr(
- voiceRecoIntent, userId);
- if (voiceRecoPackage != null
- && doesPackageSupportRuntimePermissions(voiceRecoPackage)) {
- grantRuntimePermissionsLPw(voiceRecoPackage, MICROPHONE_PERMISSIONS, userId);
- }
-
- // Location
- if (locationPackageNames != null) {
- for (String packageName : locationPackageNames) {
- PackageParser.Package locationPackage = getSystemPackageLPr(packageName);
- if (locationPackage != null
- && doesPackageSupportRuntimePermissions(locationPackage)) {
- grantRuntimePermissionsLPw(locationPackage, CONTACTS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(locationPackage, CALENDAR_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(locationPackage, MICROPHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(locationPackage, PHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(locationPackage, SMS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(locationPackage, LOCATION_PERMISSIONS,
- true, userId);
- grantRuntimePermissionsLPw(locationPackage, CAMERA_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(locationPackage, SENSORS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(locationPackage, STORAGE_PERMISSIONS, userId);
- }
- }
- }
-
- // Music
- Intent musicIntent = new Intent(Intent.ACTION_VIEW);
- musicIntent.addCategory(Intent.CATEGORY_DEFAULT);
- musicIntent.setDataAndType(Uri.fromFile(new File("foo.mp3")),
- AUDIO_MIME_TYPE);
- PackageParser.Package musicPackage = getDefaultSystemHandlerActivityPackageLPr(
- musicIntent, userId);
- if (musicPackage != null
- && doesPackageSupportRuntimePermissions(musicPackage)) {
- grantRuntimePermissionsLPw(musicPackage, STORAGE_PERMISSIONS, userId);
- }
-
- // Home
- Intent homeIntent = new Intent(Intent.ACTION_MAIN);
- homeIntent.addCategory(Intent.CATEGORY_HOME);
- homeIntent.addCategory(Intent.CATEGORY_LAUNCHER_APP);
- PackageParser.Package homePackage = getDefaultSystemHandlerActivityPackageLPr(
- homeIntent, userId);
- if (homePackage != null
- && doesPackageSupportRuntimePermissions(homePackage)) {
- grantRuntimePermissionsLPw(homePackage, LOCATION_PERMISSIONS, false, userId);
- }
-
- // Watches
- if (mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
- // Home application on watches
- Intent wearHomeIntent = new Intent(Intent.ACTION_MAIN);
- wearHomeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
-
- PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackageLPr(
- wearHomeIntent, userId);
-
- if (wearHomePackage != null
- && doesPackageSupportRuntimePermissions(wearHomePackage)) {
- grantRuntimePermissionsLPw(wearHomePackage, CONTACTS_PERMISSIONS, false,
- userId);
- grantRuntimePermissionsLPw(wearHomePackage, PHONE_PERMISSIONS, true, userId);
- grantRuntimePermissionsLPw(wearHomePackage, MICROPHONE_PERMISSIONS, false,
- userId);
- grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
- userId);
- }
-
- // Fitness tracking on watches
- Intent trackIntent = new Intent(ACTION_TRACK);
- PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackageLPr(
- trackIntent, userId);
- if (trackPackage != null
- && doesPackageSupportRuntimePermissions(trackPackage)) {
- grantRuntimePermissionsLPw(trackPackage, SENSORS_PERMISSIONS, false, userId);
- grantRuntimePermissionsLPw(trackPackage, LOCATION_PERMISSIONS, false, userId);
- }
- }
-
- // Print Spooler
- PackageParser.Package printSpoolerPackage = getSystemPackageLPr(
- PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
- if (printSpoolerPackage != null
- && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
- grantRuntimePermissionsLPw(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
- }
-
- // EmergencyInfo
- Intent emergencyInfoIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
- PackageParser.Package emergencyInfoPckg = getDefaultSystemHandlerActivityPackageLPr(
- emergencyInfoIntent, userId);
- if (emergencyInfoPckg != null
- && doesPackageSupportRuntimePermissions(emergencyInfoPckg)) {
- grantRuntimePermissionsLPw(emergencyInfoPckg, CONTACTS_PERMISSIONS, true, userId);
- grantRuntimePermissionsLPw(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
- }
-
- // NFC Tag viewer
- Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
- nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
- PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackageLPr(
- nfcTagIntent, userId);
- if (nfcTagPkg != null
- && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
- grantRuntimePermissionsLPw(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
- grantRuntimePermissionsLPw(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
- }
-
- // Storage Manager
- Intent storageManagerIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
- PackageParser.Package storageManagerPckg = getDefaultSystemHandlerActivityPackageLPr(
- storageManagerIntent, userId);
- if (storageManagerPckg != null
- && doesPackageSupportRuntimePermissions(storageManagerPckg)) {
- grantRuntimePermissionsLPw(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
- }
-
- // Companion devices
- PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackageLPr(
- CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
- if (companionDeviceDiscoveryPackage != null
- && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
- grantRuntimePermissionsLPw(companionDeviceDiscoveryPackage,
- LOCATION_PERMISSIONS, true, userId);
- }
-
- // Ringtone Picker
- Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
- PackageParser.Package ringtonePickerPackage =
- getDefaultSystemHandlerActivityPackageLPr(ringtonePickerIntent, userId);
- if (ringtonePickerPackage != null
- && doesPackageSupportRuntimePermissions(ringtonePickerPackage)) {
- grantRuntimePermissionsLPw(ringtonePickerPackage,
- STORAGE_PERMISSIONS, true, userId);
- }
-
- mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
- }
- }
-
- private void grantDefaultPermissionsToDefaultSystemDialerAppLPr(
- PackageParser.Package dialerPackage, int userId) {
- if (doesPackageSupportRuntimePermissions(dialerPackage)) {
- boolean isPhonePermFixed =
- mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
- grantRuntimePermissionsLPw(
- dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
- grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(dialerPackage, CAMERA_PERMISSIONS, userId);
- }
- }
-
- private void grantDefaultPermissionsToDefaultSystemSmsAppLPr(
- PackageParser.Package smsPackage, int userId) {
- if (doesPackageSupportRuntimePermissions(smsPackage)) {
- grantRuntimePermissionsLPw(smsPackage, PHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(smsPackage, CONTACTS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(smsPackage, SMS_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(smsPackage, STORAGE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(smsPackage, MICROPHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(smsPackage, CAMERA_PERMISSIONS, userId);
- }
- }
-
- public void grantDefaultPermissionsToDefaultSmsAppLPr(String packageName, int userId) {
- Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
- if (packageName == null) {
- return;
- }
- PackageParser.Package smsPackage = getPackageLPr(packageName);
- if (smsPackage != null && doesPackageSupportRuntimePermissions(smsPackage)) {
- grantRuntimePermissionsLPw(smsPackage, PHONE_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(smsPackage, CONTACTS_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(smsPackage, SMS_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(smsPackage, STORAGE_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(smsPackage, MICROPHONE_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(smsPackage, CAMERA_PERMISSIONS, false, true, userId);
- }
- }
-
- public void grantDefaultPermissionsToDefaultDialerAppLPr(String packageName, int userId) {
- Log.i(TAG, "Granting permissions to default dialer app for user:" + userId);
- if (packageName == null) {
- return;
- }
- PackageParser.Package dialerPackage = getPackageLPr(packageName);
- if (dialerPackage != null
- && doesPackageSupportRuntimePermissions(dialerPackage)) {
- grantRuntimePermissionsLPw(dialerPackage, PHONE_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, false, true, userId);
- grantRuntimePermissionsLPw(dialerPackage, CAMERA_PERMISSIONS, false, true, userId);
- }
- }
-
- private void grantDefaultPermissionsToDefaultSimCallManagerLPr(
- PackageParser.Package simCallManagerPackage, int userId) {
- Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
- if (doesPackageSupportRuntimePermissions(simCallManagerPackage)) {
- grantRuntimePermissionsLPw(simCallManagerPackage, PHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(simCallManagerPackage, MICROPHONE_PERMISSIONS, userId);
- }
- }
-
- public void grantDefaultPermissionsToDefaultSimCallManagerLPr(String packageName, int userId) {
- if (packageName == null) {
- return;
- }
- PackageParser.Package simCallManagerPackage = getPackageLPr(packageName);
- if (simCallManagerPackage != null) {
- grantDefaultPermissionsToDefaultSimCallManagerLPr(simCallManagerPackage, userId);
- }
- }
-
- public void grantDefaultPermissionsToEnabledCarrierAppsLPr(String[] packageNames, int userId) {
- Log.i(TAG, "Granting permissions to enabled carrier apps for user:" + userId);
- if (packageNames == null) {
- return;
- }
- for (String packageName : packageNames) {
- PackageParser.Package carrierPackage = getSystemPackageLPr(packageName);
- if (carrierPackage != null
- && doesPackageSupportRuntimePermissions(carrierPackage)) {
- grantRuntimePermissionsLPw(carrierPackage, PHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(carrierPackage, LOCATION_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(carrierPackage, SMS_PERMISSIONS, userId);
- }
- }
- }
-
- public void grantDefaultPermissionsToEnabledImsServicesLPr(String[] packageNames, int userId) {
- Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
- if (packageNames == null) {
- return;
- }
- for (String packageName : packageNames) {
- PackageParser.Package imsServicePackage = getSystemPackageLPr(packageName);
- if (imsServicePackage != null
- && doesPackageSupportRuntimePermissions(imsServicePackage)) {
- grantRuntimePermissionsLPw(imsServicePackage, PHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(imsServicePackage, LOCATION_PERMISSIONS, userId);
- grantRuntimePermissionsLPw(imsServicePackage, CAMERA_PERMISSIONS, userId);
- }
- }
- }
-
- public void grantDefaultPermissionsToDefaultBrowserLPr(String packageName, int userId) {
- Log.i(TAG, "Granting permissions to default browser for user:" + userId);
- if (packageName == null) {
- return;
- }
- PackageParser.Package browserPackage = getSystemPackageLPr(packageName);
- if (browserPackage != null
- && doesPackageSupportRuntimePermissions(browserPackage)) {
- grantRuntimePermissionsLPw(browserPackage, LOCATION_PERMISSIONS, false, false, userId);
- }
- }
-
- private PackageParser.Package getDefaultSystemHandlerActivityPackageLPr(
- Intent intent, int userId) {
- ResolveInfo handler = mService.resolveIntent(intent,
- intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
- if (handler == null || handler.activityInfo == null) {
- return null;
- }
- ActivityInfo activityInfo = handler.activityInfo;
- if (activityInfo.packageName.equals(mService.mResolveActivity.packageName)
- && activityInfo.name.equals(mService.mResolveActivity.name)) {
- return null;
- }
- return getSystemPackageLPr(handler.activityInfo.packageName);
- }
-
- private PackageParser.Package getDefaultSystemHandlerServicePackageLPr(
- Intent intent, int userId) {
- List<ResolveInfo> handlers = mService.queryIntentServices(intent,
- intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId)
- .getList();
- if (handlers == null) {
- return null;
- }
- final int handlerCount = handlers.size();
- for (int i = 0; i < handlerCount; i++) {
- ResolveInfo handler = handlers.get(i);
- PackageParser.Package handlerPackage = getSystemPackageLPr(
- handler.serviceInfo.packageName);
- if (handlerPackage != null) {
- return handlerPackage;
- }
- }
- return null;
- }
-
- private List<PackageParser.Package> getHeadlessSyncAdapterPackagesLPr(
- String[] syncAdapterPackageNames, int userId) {
- List<PackageParser.Package> syncAdapterPackages = new ArrayList<>();
-
- Intent homeIntent = new Intent(Intent.ACTION_MAIN);
- homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
- for (String syncAdapterPackageName : syncAdapterPackageNames) {
- homeIntent.setPackage(syncAdapterPackageName);
-
- ResolveInfo homeActivity = mService.resolveIntent(homeIntent,
- homeIntent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS,
- userId);
- if (homeActivity != null) {
- continue;
- }
-
- PackageParser.Package syncAdapterPackage = getSystemPackageLPr(syncAdapterPackageName);
- if (syncAdapterPackage != null) {
- syncAdapterPackages.add(syncAdapterPackage);
- }
- }
-
- return syncAdapterPackages;
- }
-
- private PackageParser.Package getDefaultProviderAuthorityPackageLPr(
- String authority, int userId) {
- ProviderInfo provider = mService.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
- if (provider != null) {
- return getSystemPackageLPr(provider.packageName);
- }
- return null;
- }
-
- private PackageParser.Package getPackageLPr(String packageName) {
- return mService.mPackages.get(packageName);
- }
-
- private PackageParser.Package getSystemPackageLPr(String packageName) {
- PackageParser.Package pkg = getPackageLPr(packageName);
- if (pkg != null && pkg.isSystemApp()) {
- return !isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg) ? pkg : null;
- }
- return null;
- }
-
- private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
- int userId) {
- grantRuntimePermissionsLPw(pkg, permissions, false, false, userId);
- }
-
- private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
- boolean systemFixed, int userId) {
- grantRuntimePermissionsLPw(pkg, permissions, systemFixed, false, userId);
- }
-
- private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
- boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
- if (pkg.requestedPermissions.isEmpty()) {
- return;
- }
-
- List<String> requestedPermissions = pkg.requestedPermissions;
- Set<String> grantablePermissions = null;
-
- // If this is the default Phone or SMS app we grant permissions regardless
- // whether the version on the system image declares the permission as used since
- // selecting the app as the default Phone or SMS the user makes a deliberate
- // choice to grant this app the permissions needed to function. For all other
- // apps, (default grants on first boot and user creation) we don't grant default
- // permissions if the version on the system image does not declare them.
- if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
- PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
- if (sysPs != null && sysPs.pkg != null) {
- if (sysPs.pkg.requestedPermissions.isEmpty()) {
- return;
- }
- if (!requestedPermissions.equals(sysPs.pkg.requestedPermissions)) {
- grantablePermissions = new ArraySet<>(requestedPermissions);
- requestedPermissions = sysPs.pkg.requestedPermissions;
- }
- }
- }
-
- final int grantablePermissionCount = requestedPermissions.size();
- for (int i = 0; i < grantablePermissionCount; i++) {
- String permission = requestedPermissions.get(i);
-
- // If there is a disabled system app it may request a permission the updated
- // version ot the data partition doesn't, In this case skip the permission.
- if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
- continue;
- }
-
- if (permissions.contains(permission)) {
- final int flags = mService.getPermissionFlags(permission, pkg.packageName, userId);
-
- // If any flags are set to the permission, then it is either set in
- // its current state by the system or device/profile owner or the user.
- // In all these cases we do not want to clobber the current state.
- // Unless the caller wants to override user choices. The override is
- // to make sure we can grant the needed permission to the default
- // sms and phone apps after the user chooses this in the UI.
- if (flags == 0 || isDefaultPhoneOrSms) {
- // Never clobber policy or system.
- final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
- | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
- if ((flags & fixedFlags) != 0) {
- continue;
- }
-
- mService.grantRuntimePermission(pkg.packageName, permission, userId);
- if (DEBUG) {
- Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
- + permission + " to default handler " + pkg.packageName);
- }
-
- int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
- if (systemFixed) {
- newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
- }
-
- mService.updatePermissionFlags(permission, pkg.packageName,
- newFlags, newFlags, userId);
- }
-
- // If a component gets a permission for being the default handler A
- // and also default handler B, we grant the weaker grant form.
- if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
- && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
- && !systemFixed) {
- if (DEBUG) {
- Log.i(TAG, "Granted not fixed " + permission + " to default handler "
- + pkg.packageName);
- }
- mService.updatePermissionFlags(permission, pkg.packageName,
- PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
- }
- }
- }
- }
-
- private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {
- if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
- return true;
- }
- if (!pkg.isPrivilegedApp()) {
- return false;
- }
- PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
- if (sysPkg != null && sysPkg.pkg != null) {
- if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
- return false;
- }
- } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
- return false;
- }
- return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
- pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
- }
-
- private void grantDefaultPermissionExceptions(int userId) {
- synchronized (mService.mPackages) {
- mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
-
- if (mGrantExceptions == null) {
- mGrantExceptions = readDefaultPermissionExceptionsLPw();
- }
-
- // mGrantExceptions is null only before the first read and then
- // it serves as a cache of the default grants that should be
- // performed for every user. If there is an entry then the app
- // is on the system image and supports runtime permissions.
- Set<String> permissions = null;
- final int exceptionCount = mGrantExceptions.size();
- for (int i = 0; i < exceptionCount; i++) {
- String packageName = mGrantExceptions.keyAt(i);
- PackageParser.Package pkg = getSystemPackageLPr(packageName);
- List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
- final int permissionGrantCount = permissionGrants.size();
- for (int j = 0; j < permissionGrantCount; j++) {
- DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
- if (permissions == null) {
- permissions = new ArraySet<>();
- } else {
- permissions.clear();
- }
- permissions.add(permissionGrant.name);
- grantRuntimePermissionsLPw(pkg, permissions,
- permissionGrant.fixed, userId);
- }
- }
- }
- }
-
- private File[] getDefaultPermissionFiles() {
- ArrayList<File> ret = new ArrayList<File>();
- File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
- if (dir.isDirectory() && dir.canRead()) {
- Collections.addAll(ret, dir.listFiles());
- }
- dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
- if (dir.isDirectory() && dir.canRead()) {
- Collections.addAll(ret, dir.listFiles());
- }
- return ret.isEmpty() ? null : ret.toArray(new File[0]);
- }
-
- private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
- readDefaultPermissionExceptionsLPw() {
- File[] files = getDefaultPermissionFiles();
- if (files == null) {
- return new ArrayMap<>(0);
- }
-
- ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
-
- // Iterate over the files in the directory and scan .xml files
- for (File file : files) {
- if (!file.getPath().endsWith(".xml")) {
- Slog.i(TAG, "Non-xml file " + file + " in " + file.getParent() + " directory, ignoring");
- continue;
- }
- if (!file.canRead()) {
- Slog.w(TAG, "Default permissions file " + file + " cannot be read");
- continue;
- }
- try (
- InputStream str = new BufferedInputStream(new FileInputStream(file))
- ) {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(str, null);
- parse(parser, grantExceptions);
- } catch (XmlPullParserException | IOException e) {
- Slog.w(TAG, "Error reading default permissions file " + file, e);
- }
- }
-
- return grantExceptions;
- }
-
- private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
- outGrantExceptions) throws IOException, XmlPullParserException {
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- if (TAG_EXCEPTIONS.equals(parser.getName())) {
- parseExceptions(parser, outGrantExceptions);
- } else {
- Log.e(TAG, "Unknown tag " + parser.getName());
- }
- }
- }
-
- private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
- outGrantExceptions) throws IOException, XmlPullParserException {
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- if (TAG_EXCEPTION.equals(parser.getName())) {
- String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
-
- List<DefaultPermissionGrant> packageExceptions =
- outGrantExceptions.get(packageName);
- if (packageExceptions == null) {
- // The package must be on the system image
- PackageParser.Package pkg = getSystemPackageLPr(packageName);
- if (pkg == null) {
- Log.w(TAG, "Unknown package:" + packageName);
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
-
- // The package must support runtime permissions
- if (!doesPackageSupportRuntimePermissions(pkg)) {
- Log.w(TAG, "Skipping non supporting runtime permissions package:"
- + packageName);
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- packageExceptions = new ArrayList<>();
- outGrantExceptions.put(packageName, packageExceptions);
- }
-
- parsePermission(parser, packageExceptions);
- } else {
- Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
- }
- }
- }
-
- private void parsePermission(XmlPullParser parser, List<DefaultPermissionGrant>
- outPackageExceptions) throws IOException, XmlPullParserException {
- final int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- if (TAG_PERMISSION.contains(parser.getName())) {
- String name = parser.getAttributeValue(null, ATTR_NAME);
- if (name == null) {
- Log.w(TAG, "Mandatory name attribute missing for permission tag");
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
-
- final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED);
-
- DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed);
- outPackageExceptions.add(exception);
- } else {
- Log.e(TAG, "Unknown tag " + parser.getName() + "under <exception>");
- }
- }
- }
-
- private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
- return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
- }
-
- private static final class DefaultPermissionGrant {
- final String name;
- final boolean fixed;
-
- public DefaultPermissionGrant(String name, boolean fixed) {
- this.name = name;
- this.fixed = fixed;
- }
- }
-}
diff --git a/com/android/server/pm/DumpState.java b/com/android/server/pm/DumpState.java
new file mode 100644
index 00000000..7ebef83a
--- /dev/null
+++ b/com/android/server/pm/DumpState.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 com.android.server.pm;
+
+public final class DumpState {
+ public static final int DUMP_LIBS = 1 << 0;
+ public static final int DUMP_FEATURES = 1 << 1;
+ public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
+ public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
+ public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
+ public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
+ public static final int DUMP_PERMISSIONS = 1 << 6;
+ public static final int DUMP_PACKAGES = 1 << 7;
+ public static final int DUMP_SHARED_USERS = 1 << 8;
+ public static final int DUMP_MESSAGES = 1 << 9;
+ public static final int DUMP_PROVIDERS = 1 << 10;
+ public static final int DUMP_VERIFIERS = 1 << 11;
+ public static final int DUMP_PREFERRED = 1 << 12;
+ public static final int DUMP_PREFERRED_XML = 1 << 13;
+ public static final int DUMP_KEYSETS = 1 << 14;
+ public static final int DUMP_VERSION = 1 << 15;
+ public static final int DUMP_INSTALLS = 1 << 16;
+ public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+ public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
+ public static final int DUMP_FROZEN = 1 << 19;
+ public static final int DUMP_DEXOPT = 1 << 20;
+ public static final int DUMP_COMPILER_STATS = 1 << 21;
+ public static final int DUMP_CHANGES = 1 << 22;
+ public static final int DUMP_VOLUMES = 1 << 23;
+
+ public static final int OPTION_SHOW_FILTERS = 1 << 0;
+
+ private int mTypes;
+
+ private int mOptions;
+
+ private boolean mTitlePrinted;
+
+ private SharedUserSetting mSharedUser;
+
+ public boolean isDumping(int type) {
+ if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
+ return true;
+ }
+
+ return (mTypes & type) != 0;
+ }
+
+ public void setDump(int type) {
+ mTypes |= type;
+ }
+
+ public boolean isOptionEnabled(int option) {
+ return (mOptions & option) != 0;
+ }
+
+ public void setOptionEnabled(int option) {
+ mOptions |= option;
+ }
+
+ public boolean onTitlePrinted() {
+ final boolean printed = mTitlePrinted;
+ mTitlePrinted = true;
+ return printed;
+ }
+
+ public boolean getTitlePrinted() {
+ return mTitlePrinted;
+ }
+
+ public void setTitlePrinted(boolean enabled) {
+ mTitlePrinted = enabled;
+ }
+
+ public SharedUserSetting getSharedUser() {
+ return mSharedUser;
+ }
+
+ public void setSharedUser(SharedUserSetting user) {
+ mSharedUser = user;
+ }
+} \ No newline at end of file
diff --git a/com/android/server/pm/InstantAppRegistry.java b/com/android/server/pm/InstantAppRegistry.java
index e1e5b355..c964f912 100644
--- a/com/android/server/pm/InstantAppRegistry.java
+++ b/com/android/server/pm/InstantAppRegistry.java
@@ -49,6 +49,8 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
+import com.android.server.pm.permission.BasePermission;
+
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -878,8 +880,9 @@ class InstantAppRegistry {
final long identity = Binder.clearCallingIdentity();
try {
for (String grantedPermission : appInfo.getGrantedPermissions()) {
- BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
- if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
+ final boolean propagatePermission =
+ mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission);
+ if (propagatePermission) {
mService.grantRuntimePermission(packageName, grantedPermission, userId);
}
}
diff --git a/com/android/server/pm/KeySetManagerService.java b/com/android/server/pm/KeySetManagerService.java
index 49d3c8bc..35744662 100644
--- a/com/android/server/pm/KeySetManagerService.java
+++ b/com/android/server/pm/KeySetManagerService.java
@@ -565,7 +565,7 @@ public class KeySetManagerService {
}
public void dumpLPr(PrintWriter pw, String packageName,
- PackageManagerService.DumpState dumpState) {
+ DumpState dumpState) {
boolean printedHeader = false;
for (ArrayMap.Entry<String, PackageSetting> e : mPackages.entrySet()) {
String keySetPackage = e.getKey();
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 0f580d81..8ebeeae2 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -23,6 +23,7 @@ import android.content.pm.PackageParser;
import android.os.FileUtils;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
@@ -103,7 +104,17 @@ public class PackageDexOptimizer {
}
static boolean canOptimizePackage(PackageParser.Package pkg) {
- return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+ // We do not dexopt a package with no code.
+ if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
+ return false;
+ }
+
+ // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+ if (pkg.isPrivilegedApp()) {
+ return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+ }
+
+ return true;
}
/**
@@ -354,18 +365,13 @@ public class PackageDexOptimizer {
+ " dexoptFlags=" + printDexoptFlags(dexoptFlags)
+ " target-filter=" + compilerFilter);
- String classLoaderContext;
- if (dexUseInfo.isUnknownClassLoaderContext() ||
- dexUseInfo.isUnsupportedClassLoaderContext() ||
- dexUseInfo.isVariableClassLoaderContext()) {
- // If we have an unknown (not yet set), unsupported (custom class loaders), or a
- // variable class loader chain, compile without a context and mark the oat file with
- // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation.
- // TODO(calin): We should just extract in this case.
- classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
- } else {
- classLoaderContext = dexUseInfo.getClassLoaderContext();
- }
+ // TODO(calin): b/64530081 b/66984396. Use SKIP_SHARED_LIBRARY_CHECK for the context
+ // (instead of dexUseInfo.getClassLoaderContext()) in order to compile secondary dex files
+ // in isolation (and avoid to extract/verify the main apk if it's in the class path).
+ // Note this trades correctness for performance since the resulting slow down is
+ // unacceptable in some cases until b/64530081 is fixed.
+ String classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
+
try {
for (String isa : dexUseInfo.getLoaderIsas()) {
// Reuse the same dexopt path as for the primary apks. We don't need all the
@@ -425,7 +431,7 @@ public class PackageDexOptimizer {
}
if (useInfo.isUsedByOtherApps(path)) {
- pw.println("used be other apps: " + useInfo.getLoadingPackages(path));
+ pw.println("used by other apps: " + useInfo.getLoadingPackages(path));
}
Map<String, PackageDexUsage.DexUseInfo> dexUseInfoMap = useInfo.getDexUseInfoMap();
@@ -438,19 +444,10 @@ public class PackageDexOptimizer {
PackageDexUsage.DexUseInfo dexUseInfo = e.getValue();
pw.println(dex);
pw.increaseIndent();
- for (String isa : dexUseInfo.getLoaderIsas()) {
- String status = null;
- try {
- status = DexFile.getDexFileStatus(path, isa);
- } catch (IOException ioe) {
- status = "[Exception]: " + ioe.getMessage();
- }
- pw.println(isa + ": " + status);
- }
-
+ // TODO(calin): get the status of the oat file (needs installd call)
pw.println("class loader context: " + dexUseInfo.getClassLoaderContext());
if (dexUseInfo.isUsedByOtherApps()) {
- pw.println("used be other apps: " + dexUseInfo.getLoadingPackages());
+ pw.println("used by other apps: " + dexUseInfo.getLoadingPackages());
}
pw.decreaseIndent();
}
@@ -474,8 +471,9 @@ public class PackageDexOptimizer {
}
if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
- // If the dex files is used by other apps, we cannot use profile-guided compilation.
- return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
+ // If the dex files is used by other apps, apply the shared filter.
+ return PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+ PackageManagerService.REASON_SHARED);
}
return targetCompilerFilter;
diff --git a/com/android/server/pm/PackageInstallerService.java b/com/android/server/pm/PackageInstallerService.java
index 1fa37b91..09f9cb8c 100644
--- a/com/android/server/pm/PackageInstallerService.java
+++ b/com/android/server/pm/PackageInstallerService.java
@@ -80,6 +80,8 @@ import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerInternal;
import libcore.io.IoUtils;
@@ -122,6 +124,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private final Context mContext;
private final PackageManagerService mPm;
+ private final PermissionManagerInternal mPermissionManager;
private AppOpsManager mAppOps;
@@ -177,6 +180,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
public PackageInstallerService(Context context, PackageManagerService pm) {
mContext = context;
mPm = pm;
+ mPermissionManager = LocalServices.getService(PermissionManagerInternal.class);
mInstallThread = new HandlerThread(TAG);
mInstallThread.start();
@@ -243,35 +247,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
}
- public void onSecureContainersAvailable() {
- synchronized (mSessions) {
- final ArraySet<String> unclaimed = new ArraySet<>();
- for (String cid : PackageHelper.getSecureContainerList()) {
- if (isStageName(cid)) {
- unclaimed.add(cid);
- }
- }
-
- // Ignore stages claimed by active sessions
- for (int i = 0; i < mSessions.size(); i++) {
- final PackageInstallerSession session = mSessions.valueAt(i);
- final String cid = session.stageCid;
-
- if (unclaimed.remove(cid)) {
- // Claimed by active session, mount it
- PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
- Process.SYSTEM_UID);
- }
- }
-
- // Clean up orphaned staging containers
- for (String cid : unclaimed) {
- Slog.w(TAG, "Deleting orphan container " + cid);
- PackageHelper.destroySdDir(cid);
- }
- }
- }
-
public static boolean isStageName(String name) {
final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
@@ -426,7 +401,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
throws IOException {
final int callingUid = Binder.getCallingUid();
- mPm.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");
+ mPermissionManager.enforceCrossUserPermission(
+ callingUid, userId, true, true, "createSession");
if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
throw new SecurityException("User restriction prevents installing");
@@ -671,13 +647,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
return "smdl" + sessionId + ".tmp";
}
- static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException {
- if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(),
- Process.SYSTEM_UID, true) == null) {
- throw new IOException("Failed to create session cid: " + stageCid);
- }
- }
-
@Override
public SessionInfo getSessionInfo(int sessionId) {
synchronized (mSessions) {
@@ -688,7 +657,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
@Override
public ParceledListSlice<SessionInfo> getAllSessions(int userId) {
- mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getAllSessions");
+ mPermissionManager.enforceCrossUserPermission(
+ Binder.getCallingUid(), userId, true, false, "getAllSessions");
final List<SessionInfo> result = new ArrayList<>();
synchronized (mSessions) {
@@ -704,7 +674,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
@Override
public ParceledListSlice<SessionInfo> getMySessions(String installerPackageName, int userId) {
- mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getMySessions");
+ mPermissionManager.enforceCrossUserPermission(
+ Binder.getCallingUid(), userId, true, false, "getMySessions");
mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
final List<SessionInfo> result = new ArrayList<>();
@@ -726,7 +697,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
IntentSender statusReceiver, int userId) throws RemoteException {
final int callingUid = Binder.getCallingUid();
- mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
mAppOps.checkPackage(callingUid, callerPackageName);
}
@@ -775,7 +746,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
@Override
public void registerCallback(IPackageInstallerCallback callback, int userId) {
- mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "registerCallback");
+ mPermissionManager.enforceCrossUserPermission(
+ Binder.getCallingUid(), userId, true, false, "registerCallback");
mCallbacks.register(callback, userId);
}
diff --git a/com/android/server/pm/PackageInstallerSession.java b/com/android/server/pm/PackageInstallerSession.java
index ff6e5b3b..d62f0934 100644
--- a/com/android/server/pm/PackageInstallerSession.java
+++ b/com/android/server/pm/PackageInstallerSession.java
@@ -36,7 +36,6 @@ import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.internal.util.XmlUtils.writeUriAttribute;
-import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
import android.Manifest;
@@ -481,12 +480,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (stageDir != null) {
mResolvedStageDir = stageDir;
} else {
- final String path = PackageHelper.getSdDir(stageCid);
- if (path != null) {
- mResolvedStageDir = new File(path);
- } else {
- throw new IOException("Failed to resolve path to container " + stageCid);
- }
+ throw new IOException("Missing stageDir");
}
}
return mResolvedStageDir;
@@ -880,14 +874,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return;
}
- if (stageCid != null) {
- // Figure out the final installed size and resize the container once
- // and for all. Internally the parser handles straddling between two
- // locations when inheriting.
- final long finalSize = calculateInstalledSize();
- resizeContainer(stageCid, finalSize);
- }
-
// Inherit any packages and native libraries from existing install that
// haven't been overridden.
if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
@@ -924,11 +910,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// Unpack native libraries
extractNativeLibraries(mResolvedStageDir, params.abiOverride);
- // Container is ready to go, let's seal it up!
- if (stageCid != null) {
- finalizeAndFixContainer(stageCid);
- }
-
// We've reached point of no return; call into PMS to install the stage.
// Regardless of success or failure we always destroy session.
final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@@ -953,7 +934,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
mRelinquished = true;
- mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
+ mPm.installStage(mPackageName, stageDir, localObserver, params,
mInstallerPackageName, mInstallerUid, user, mCertificates);
}
@@ -1212,11 +1193,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// straddled between the inherited and staged APKs.
final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null,
splitPaths.toArray(new String[splitPaths.size()]), null);
- final boolean isForwardLocked =
- (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
try {
- return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
+ return PackageHelper.calculateInstalledSize(pkg, params.abiOverride);
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Failed to calculate install size", e);
@@ -1345,52 +1324,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
- private static void resizeContainer(String cid, long targetSize)
- throws PackageManagerException {
- String path = PackageHelper.getSdDir(cid);
- if (path == null) {
- throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
- "Failed to find mounted " + cid);
- }
-
- final long currentSize = new File(path).getTotalSpace();
- if (currentSize > targetSize) {
- Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
- + targetSize + "; skipping resize");
- return;
- }
-
- if (!PackageHelper.unMountSdDir(cid)) {
- throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
- "Failed to unmount " + cid + " before resize");
- }
-
- if (!PackageHelper.resizeSdDir(targetSize, cid,
- PackageManagerService.getEncryptKey())) {
- throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
- "Failed to resize " + cid + " to " + targetSize + " bytes");
- }
-
- path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
- Process.SYSTEM_UID, false);
- if (path == null) {
- throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
- "Failed to mount " + cid + " after resize");
- }
- }
-
- private void finalizeAndFixContainer(String cid) throws PackageManagerException {
- if (!PackageHelper.finalizeSdDir(cid)) {
- throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
- "Failed to finalize container " + cid);
- }
-
- if (!PackageHelper.fixSdPermissions(cid, defaultContainerGid, null)) {
- throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
- "Failed to fix permissions on container " + cid);
- }
- }
-
void setPermissionsResult(boolean accepted) {
if (!mSealed) {
throw new SecurityException("Must be sealed to accept permissions");
@@ -1419,20 +1352,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (!mPrepared) {
if (stageDir != null) {
prepareStageDir(stageDir);
- } else if (stageCid != null) {
- final long identity = Binder.clearCallingIdentity();
- try {
- prepareExternalStageCid(stageCid, params.sizeBytes);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
-
- // TODO: deliver more granular progress for ASEC allocation
- mInternalProgress = 0.25f;
- computeProgressLocked(true);
} else {
- throw new IllegalArgumentException(
- "Exactly one of stageDir or stageCid stage must be set");
+ throw new IllegalArgumentException("stageDir must be set");
}
mPrepared = true;
@@ -1534,9 +1455,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
} catch (InstallerException ignored) {
}
}
- if (stageCid != null) {
- PackageHelper.destroySdDir(stageCid);
- }
}
void dump(IndentingPrintWriter pw) {
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index ff52e0eb..7d1a6470 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -55,7 +55,6 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK;
import static android.content.pm.PackageManager.INSTALL_INTERNAL;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
@@ -103,10 +102,9 @@ import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILURE;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
-
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
import android.Manifest;
@@ -160,10 +158,11 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.Package;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageStats;
@@ -230,7 +229,6 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Base64;
-import android.util.TimingsTraceLog;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.ExceptionUtils;
@@ -244,6 +242,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import android.util.TimingsTraceLog;
import android.util.Xml;
import android.util.jar.StrictJarFile;
import android.util.proto.ProtoOutputStream;
@@ -283,12 +282,19 @@ import com.android.server.SystemServerInitThreadPool;
import com.android.server.Watchdog;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.PermissionsState.PermissionState;
import com.android.server.pm.Settings.DatabaseVersion;
import com.android.server.pm.Settings.VersionInfo;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
+import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.pm.permission.PermissionManagerInternal;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
import com.android.server.storage.DeviceStorageMonitorInternal;
import dalvik.system.CloseGuard;
@@ -338,6 +344,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -385,7 +392,7 @@ import java.util.zip.GZIPInputStream;
public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
static final String TAG = "PackageManager";
- static final boolean DEBUG_SETTINGS = false;
+ public static final boolean DEBUG_SETTINGS = false;
static final boolean DEBUG_PREFERRED = false;
static final boolean DEBUG_UPGRADE = false;
static final boolean DEBUG_DOMAIN_VERIFICATION = false;
@@ -396,7 +403,7 @@ public class PackageManagerService extends IPackageManager.Stub
private static final boolean DEBUG_SHOW_INFO = false;
private static final boolean DEBUG_PACKAGE_INFO = false;
private static final boolean DEBUG_INTENT_MATCHING = false;
- private static final boolean DEBUG_PACKAGE_SCANNING = false;
+ public static final boolean DEBUG_PACKAGE_SCANNING = false;
private static final boolean DEBUG_VERIFY = false;
private static final boolean DEBUG_FILTERS = false;
private static final boolean DEBUG_PERMISSIONS = false;
@@ -427,9 +434,6 @@ public class PackageManagerService extends IPackageManager.Stub
private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID;
private static final int SHELL_UID = Process.SHELL_UID;
- // Cap the size of permission trees that 3rd party apps can define
- private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; // characters of text
-
// Suffix used during package installation when copying/moving
// package apks to install directory.
private static final String INSTALL_PACKAGE_SUFFIX = "-";
@@ -578,8 +582,9 @@ public class PackageManagerService extends IPackageManager.Stub
public static final int REASON_BACKGROUND_DEXOPT = 3;
public static final int REASON_AB_OTA = 4;
public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 5;
+ public static final int REASON_SHARED = 6;
- public static final int REASON_LAST = REASON_INACTIVE_PACKAGE_DOWNGRADE;
+ public static final int REASON_LAST = REASON_SHARED;
/** All dangerous permission names in the same order as the events in MetricsEvent */
private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
@@ -654,9 +659,6 @@ public class PackageManagerService extends IPackageManager.Stub
@GuardedBy("mPackages")
private boolean mDexOptDialogShown;
- /** The location for ASEC container files on internal storage. */
- final String mAsecInternalPath;
-
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
@GuardedBy("mInstallLock")
@@ -862,7 +864,7 @@ public class PackageManagerService extends IPackageManager.Stub
String targetPath) {
return getStaticOverlayPaths(targetPackageName, targetPath);
}
- };
+ }
class ParallelPackageParserCallback extends PackageParserCallback {
List<PackageParser.Package> mOverlayPackages = null;
@@ -1004,7 +1006,9 @@ public class PackageManagerService extends IPackageManager.Stub
final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
= new SparseArray<IntentFilterVerificationState>();
+ // TODO remove this and go through mPermissonManager directly
final DefaultPermissionGrantPolicy mDefaultPermissionPolicy;
+ private final PermissionManagerInternal mPermissionManager;
// List of packages names to keep cached, even if they are uninstalled for all users
private List<String> mKeepUninstalledPackages;
@@ -1315,7 +1319,6 @@ public class PackageManagerService extends IPackageManager.Stub
static final int POST_INSTALL = 9;
static final int MCS_RECONNECT = 10;
static final int MCS_GIVE_UP = 11;
- static final int UPDATED_MEDIA_STATUS = 12;
static final int WRITE_SETTINGS = 13;
static final int WRITE_PACKAGE_RESTRICTIONS = 14;
static final int PACKAGE_VERIFIED = 15;
@@ -1714,32 +1717,6 @@ public class PackageManagerService extends IPackageManager.Stub
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", msg.arg1);
} break;
- case UPDATED_MEDIA_STATUS: {
- if (DEBUG_SD_INSTALL) Log.i(TAG, "Got message UPDATED_MEDIA_STATUS");
- boolean reportStatus = msg.arg1 == 1;
- boolean doGc = msg.arg2 == 1;
- if (DEBUG_SD_INSTALL) Log.i(TAG, "reportStatus=" + reportStatus + ", doGc = " + doGc);
- if (doGc) {
- // Force a gc to clear up stale containers.
- Runtime.getRuntime().gc();
- }
- if (msg.obj != null) {
- @SuppressWarnings("unchecked")
- Set<AsecInstallArgs> args = (Set<AsecInstallArgs>) msg.obj;
- if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading all containers");
- // Unload containers
- unloadAllContainers(args);
- }
- if (reportStatus) {
- try {
- if (DEBUG_SD_INSTALL) Log.i(TAG,
- "Invoking StorageManagerService call back");
- PackageHelper.getStorageManager().finishMediaUpdate();
- } catch (RemoteException e) {
- Log.e(TAG, "StorageManagerService not running?");
- }
- }
- } break;
case WRITE_SETTINGS: {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
synchronized (mPackages) {
@@ -1910,6 +1887,69 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
+ private PermissionCallback mPermissionCallback = new PermissionCallback() {
+ @Override
+ public void onGidsChanged(int appId, int userId) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
+ }
+ });
+ }
+ @Override
+ public void onPermissionGranted(int uid, int userId) {
+ mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+ // Not critical; if this is lost, the application has to request again.
+ synchronized (mPackages) {
+ mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+ }
+ }
+ @Override
+ public void onInstallPermissionGranted() {
+ synchronized (mPackages) {
+ scheduleWriteSettingsLocked();
+ }
+ }
+ @Override
+ public void onPermissionRevoked(int uid, int userId) {
+ mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+ synchronized (mPackages) {
+ // Critical; after this call the application should never have the permission
+ mSettings.writeRuntimePermissionsForUserLPr(userId, true);
+ }
+
+ final int appId = UserHandle.getAppId(uid);
+ killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+ }
+ @Override
+ public void onInstallPermissionRevoked() {
+ synchronized (mPackages) {
+ scheduleWriteSettingsLocked();
+ }
+ }
+ @Override
+ public void onPermissionUpdated(int userId) {
+ synchronized (mPackages) {
+ mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+ }
+ }
+ @Override
+ public void onInstallPermissionUpdated() {
+ synchronized (mPackages) {
+ scheduleWriteSettingsLocked();
+ }
+ }
+ @Override
+ public void onPermissionRemoved() {
+ synchronized (mPackages) {
+ mSettings.writeLPr();
+ }
+ }
+ };
+
private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
boolean killApp, boolean virtualPreload, String[] grantedPermissions,
boolean launchedForRestore, String installerPackage,
@@ -1926,7 +1966,10 @@ public class PackageManagerService extends IPackageManager.Stub
// review flag which is used to emulate runtime permissions for
// legacy apps.
if (grantPermissions) {
- grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions);
+ final int callingUid = Binder.getCallingUid();
+ mPermissionManager.grantRequestedRuntimePermissions(
+ res.pkg, res.newUsers, grantedPermissions, callingUid,
+ mPermissionCallback);
}
final boolean update = res.removedInfo != null
@@ -1942,9 +1985,9 @@ public class PackageManagerService extends IPackageManager.Stub
// app that had no children, we grant requested runtime permissions to the new
// children if the parent on the system image had them already granted.
if (res.pkg.parentPackage != null) {
- synchronized (mPackages) {
- grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(res.pkg);
- }
+ final int callingUid = Binder.getCallingUid();
+ mPermissionManager.grantRuntimePermissionsGrantedToDisabledPackage(
+ res.pkg, callingUid, mPermissionCallback);
}
synchronized (mPackages) {
@@ -2109,39 +2152,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private void grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(
- PackageParser.Package pkg) {
- if (pkg.parentPackage == null) {
- return;
- }
- if (pkg.requestedPermissions == null) {
- return;
- }
- final PackageSetting disabledSysParentPs = mSettings
- .getDisabledSystemPkgLPr(pkg.parentPackage.packageName);
- if (disabledSysParentPs == null || disabledSysParentPs.pkg == null
- || !disabledSysParentPs.isPrivileged()
- || (disabledSysParentPs.childPackageNames != null
- && !disabledSysParentPs.childPackageNames.isEmpty())) {
- return;
- }
- final int[] allUserIds = sUserManager.getUserIds();
- final int permCount = pkg.requestedPermissions.size();
- for (int i = 0; i < permCount; i++) {
- String permission = pkg.requestedPermissions.get(i);
- BasePermission bp = mSettings.mPermissions.get(permission);
- if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) {
- continue;
- }
- for (int userId : allUserIds) {
- if (disabledSysParentPs.getPermissionsState().hasRuntimePermission(
- permission, userId)) {
- grantRuntimePermission(pkg.packageName, permission, userId);
- }
- }
- }
- }
-
private StorageEventListener mStorageListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
@@ -2164,14 +2174,6 @@ public class PackageManagerService extends IPackageManager.Stub
unloadPrivatePackages(vol);
}
}
-
- if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isPrimary()) {
- if (vol.state == VolumeInfo.STATE_MOUNTED) {
- updateExternalMediaStatus(true, false);
- } else if (vol.state == VolumeInfo.STATE_EJECTING) {
- updateExternalMediaStatus(false, false);
- }
- }
}
@Override
@@ -2202,58 +2204,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
};
- private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
- String[] grantedPermissions) {
- for (int userId : userIds) {
- grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions);
- }
- }
-
- private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
- String[] grantedPermissions) {
- PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null) {
- return;
- }
-
- PermissionsState permissionsState = ps.getPermissionsState();
-
- final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
- | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-
- final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.M;
-
- final boolean instantApp = isInstantApp(pkg.packageName, userId);
-
- for (String permission : pkg.requestedPermissions) {
- final BasePermission bp;
- synchronized (mPackages) {
- bp = mSettings.mPermissions.get(permission);
- }
- if (bp != null && (bp.isRuntime() || bp.isDevelopment())
- && (!instantApp || bp.isInstant())
- && (supportsRuntimePermissions || !bp.isRuntimeOnly())
- && (grantedPermissions == null
- || ArrayUtils.contains(grantedPermissions, permission))) {
- final int flags = permissionsState.getPermissionFlags(permission, userId);
- if (supportsRuntimePermissions) {
- // Installer cannot change immutable permissions.
- if ((flags & immutableFlags) == 0) {
- grantRuntimePermission(pkg.packageName, permission, userId);
- }
- } else if (mPermissionReviewRequired) {
- // In permission review mode we clear the review flag when we
- // are asked to install the app with all permissions granted.
- if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- updatePermissionFlags(permission, pkg.packageName,
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId);
- }
- }
- }
- }
- }
-
Bundle extrasForInstallResult(PackageInstalledInfo res) {
Bundle extras = null;
switch (res.returnCode) {
@@ -2422,7 +2372,29 @@ public class PackageManagerService extends IPackageManager.Stub
mFactoryTest = factoryTest;
mOnlyCore = onlyCore;
mMetrics = new DisplayMetrics();
- mSettings = new Settings(mPackages);
+ mInstaller = installer;
+
+ // Create sub-components that provide services / data. Order here is important.
+ synchronized (mInstallLock) {
+ synchronized (mPackages) {
+ // Expose private service for system components to use.
+ LocalServices.addService(
+ PackageManagerInternal.class, new PackageManagerInternalImpl());
+ sUserManager = new UserManagerService(context, this,
+ new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
+ mPermissionManager = PermissionManagerService.create(context,
+ new DefaultPermissionGrantedCallback() {
+ @Override
+ public void onDefaultRuntimePermissionsGranted(int userId) {
+ synchronized(mPackages) {
+ mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
+ }
+ }
+ }, mPackages /*externalLock*/);
+ mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
+ mSettings = new Settings(mPermissionManager.getPermissionSettings(), mPackages);
+ }
+ }
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
@@ -2453,7 +2425,6 @@ public class PackageManagerService extends IPackageManager.Stub
mSeparateProcesses = null;
}
- mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
@@ -2482,32 +2453,12 @@ public class PackageManagerService extends IPackageManager.Stub
mHandler = new PackageHandler(mHandlerThread.getLooper());
mProcessLoggingHandler = new ProcessLoggingHandler();
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
-
- mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);
mInstantAppRegistry = new InstantAppRegistry(this);
File dataDir = Environment.getDataDirectory();
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
- mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
- sUserManager = new UserManagerService(context, this,
- new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
-
- // Propagate permission configuration in to package manager.
- ArrayMap<String, SystemConfig.PermissionEntry> permConfig
- = systemConfig.getPermissions();
- for (int i=0; i<permConfig.size(); i++) {
- SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
- BasePermission bp = mSettings.mPermissions.get(perm.name);
- if (bp == null) {
- bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
- mSettings.mPermissions.put(perm.name, bp);
- }
- if (perm.gids != null) {
- bp.setGids(perm.gids, perm.perUser);
- }
- }
ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
final int builtInLibCount = libConfig.size();
@@ -3110,8 +3061,6 @@ public class PackageManagerService extends IPackageManager.Stub
// once we have a booted system.
mInstaller.setWarnIfHeld(mPackages);
- // Expose private service for system components to use.
- LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -3314,6 +3263,24 @@ public class PackageManagerService extends IPackageManager.Stub
removeCodePathLI(dstCodePath);
return null;
}
+
+ // If we have a profile for a compressed APK, copy it to the reference location.
+ // Since the package is the stub one, remove the stub suffix to get the normal package and
+ // APK name.
+ File profileFile = new File(getPrebuildProfilePath(pkg).replace(STUB_SUFFIX, ""));
+ if (profileFile.exists()) {
+ try {
+ // We could also do this lazily before calling dexopt in
+ // PackageDexOptimizer to prevent this happening on first boot. The issue
+ // is that we don't have a good way to say "do this only once".
+ if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
+ pkg.applicationInfo.uid, pkg.packageName)) {
+ Log.e(TAG, "decompressPackage failed to copy system profile!");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ", e);
+ }
+ }
return dstCodePath;
}
@@ -3909,7 +3876,7 @@ public class PackageManagerService extends IPackageManager.Stub
public boolean isPackageAvailable(String packageName, int userId) {
if (!sUserManager.exists(userId)) return false;
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/, "is package available");
synchronized (mPackages) {
PackageParser.Package p = mPackages.get(packageName);
@@ -3952,7 +3919,7 @@ public class PackageManagerService extends IPackageManager.Stub
int flags, int filterCallingUid, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForPackage(flags, userId, packageName);
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */, "get package info");
// reader
@@ -4214,7 +4181,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!sUserManager.exists(userId)) return -1;
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForPackage(flags, userId, packageName);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/, "getPackageUid");
// reader
@@ -4244,7 +4211,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!sUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForPackage(flags, userId, packageName);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/, "getPackageGids");
// reader
@@ -4271,116 +4238,23 @@ public class PackageManagerService extends IPackageManager.Stub
return null;
}
- static PermissionInfo generatePermissionInfo(BasePermission bp, int flags) {
- if (bp.perm != null) {
- return PackageParser.generatePermissionInfo(bp.perm, flags);
- }
- PermissionInfo pi = new PermissionInfo();
- pi.name = bp.name;
- pi.packageName = bp.sourcePackage;
- pi.nonLocalizedLabel = bp.name;
- pi.protectionLevel = bp.protectionLevel;
- return pi;
- }
-
@Override
public PermissionInfo getPermissionInfo(String name, String packageName, int flags) {
- final int callingUid = Binder.getCallingUid();
- if (getInstantAppPackageName(callingUid) != null) {
- return null;
- }
- // reader
- synchronized (mPackages) {
- final BasePermission p = mSettings.mPermissions.get(name);
- if (p == null) {
- return null;
- }
- // If the caller is an app that targets pre 26 SDK drop protection flags.
- PermissionInfo permissionInfo = generatePermissionInfo(p, flags);
- if (permissionInfo != null) {
- final int protectionLevel = adjustPermissionProtectionFlagsLPr(
- permissionInfo.protectionLevel, packageName, callingUid);
- if (permissionInfo.protectionLevel != protectionLevel) {
- // If we return different protection level, don't use the cached info
- if (p.perm != null && p.perm.info == permissionInfo) {
- permissionInfo = new PermissionInfo(permissionInfo);
- }
- permissionInfo.protectionLevel = protectionLevel;
- }
- }
- return permissionInfo;
- }
- }
-
- private int adjustPermissionProtectionFlagsLPr(int protectionLevel,
- String packageName, int uid) {
- // Signature permission flags area always reported
- final int protectionLevelMasked = protectionLevel
- & (PermissionInfo.PROTECTION_NORMAL
- | PermissionInfo.PROTECTION_DANGEROUS
- | PermissionInfo.PROTECTION_SIGNATURE);
- if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
- return protectionLevel;
- }
-
- // System sees all flags.
- final int appId = UserHandle.getAppId(uid);
- if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
- || appId == Process.SHELL_UID) {
- return protectionLevel;
- }
-
- // Normalize package name to handle renamed packages and static libs
- packageName = resolveInternalPackageNameLPr(packageName,
- PackageManager.VERSION_CODE_HIGHEST);
-
- // Apps that target O see flags for all protection levels.
- final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps == null) {
- return protectionLevel;
- }
- if (ps.appId != appId) {
- return protectionLevel;
- }
-
- final PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
- return protectionLevel;
- }
- if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
- return protectionLevelMasked;
- }
-
- return protectionLevel;
+ return mPermissionManager.getPermissionInfo(name, packageName, flags, getCallingUid());
}
@Override
- public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String group,
+ public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
int flags) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return null;
- }
- // reader
+ // TODO Move this to PermissionManager when mPermissionGroups is moved there
synchronized (mPackages) {
- if (group != null && !mPermissionGroups.containsKey(group)) {
+ if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
// This is thrown as NameNotFoundException
return null;
}
-
- ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
- for (BasePermission p : mSettings.mPermissions.values()) {
- if (group == null) {
- if (p.perm == null || p.perm.info.group == null) {
- out.add(generatePermissionInfo(p, flags));
- }
- } else {
- if (p.perm != null && group.equals(p.perm.info.group)) {
- out.add(PackageParser.generatePermissionInfo(p.perm, flags));
- }
- }
- }
- return new ParceledListSlice<>(out);
}
+ return new ParceledListSlice<>(
+ mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid()));
}
@Override
@@ -4455,7 +4329,7 @@ public class PackageManagerService extends IPackageManager.Stub
int filterCallingUid, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForApplication(flags, userId, packageName);
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */, "get application info");
// writer
@@ -4764,7 +4638,8 @@ public class PackageManagerService extends IPackageManager.Stub
triaged = false;
}
if ((flags & PackageManager.MATCH_ANY_USER) != 0) {
- enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
+ mPermissionManager.enforceCrossUserPermission(
+ Binder.getCallingUid(), userId, false, false,
"MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission at "
+ Debug.getCallers(5));
} else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0 && isCallerSystemUser
@@ -4893,7 +4768,7 @@ public class PackageManagerService extends IPackageManager.Stub
int filterCallingUid, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId, component);
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */, "get activity info");
synchronized (mPackages) {
PackageParser.Activity a = mActivities.mActivities.get(component);
@@ -4952,7 +4827,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!sUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForComponent(flags, userId, component);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /* requireFullPermission */, false /* checkShell */, "get receiver info");
synchronized (mPackages) {
PackageParser.Activity a = mReceivers.mActivities.get(component);
@@ -5089,7 +4964,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!sUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForComponent(flags, userId, component);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /* requireFullPermission */, false /* checkShell */, "get service info");
synchronized (mPackages) {
PackageParser.Service s = mServices.mServices.get(component);
@@ -5113,7 +4988,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!sUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForComponent(flags, userId, component);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /* requireFullPermission */, false /* checkShell */, "get provider info");
synchronized (mPackages) {
PackageParser.Provider p = mProviders.mProviders.get(component);
@@ -5276,39 +5151,7 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public int checkPermission(String permName, String pkgName, int userId) {
- if (!sUserManager.exists(userId)) {
- return PackageManager.PERMISSION_DENIED;
- }
- final int callingUid = Binder.getCallingUid();
-
- synchronized (mPackages) {
- final PackageParser.Package p = mPackages.get(pkgName);
- if (p != null && p.mExtras != null) {
- final PackageSetting ps = (PackageSetting) p.mExtras;
- if (filterAppAccessLPr(ps, callingUid, userId)) {
- return PackageManager.PERMISSION_DENIED;
- }
- final boolean instantApp = ps.getInstantApp(userId);
- final PermissionsState permissionsState = ps.getPermissionsState();
- if (permissionsState.hasPermission(permName, userId)) {
- if (instantApp) {
- BasePermission bp = mSettings.mPermissions.get(permName);
- if (bp != null && bp.isInstant()) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
- if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
- .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- }
- }
-
- return PackageManager.PERMISSION_DENIED;
+ return mPermissionManager.checkPermission(permName, pkgName, getCallingUid(), userId);
}
@Override
@@ -5339,8 +5182,7 @@ public class PackageManagerService extends IPackageManager.Stub
final PermissionsState permissionsState = settingBase.getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
- BasePermission bp = mSettings.mPermissions.get(permName);
- if (bp != null && bp.isInstant()) {
+ if (mPermissionManager.isPermissionInstant(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
@@ -5409,448 +5251,49 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- /**
- * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
- * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
- * @param checkShell whether to prevent shell from access if there's a debugging restriction
- * @param message the message to log on security exception
- */
- void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
- boolean checkShell, String message) {
- if (userId < 0) {
- throw new IllegalArgumentException("Invalid userId " + userId);
- }
- if (checkShell) {
- enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
- }
- if (userId == UserHandle.getUserId(callingUid)) return;
- if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
- if (requireFullPermission) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- } else {
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- } catch (SecurityException se) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS, message);
- }
- }
- }
- }
-
- void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
- if (callingUid == Process.SHELL_UID) {
- if (userHandle >= 0
- && sUserManager.hasUserRestriction(restriction, userHandle)) {
- throw new SecurityException("Shell does not have permission to access user "
- + userHandle);
- } else if (userHandle < 0) {
- Slog.e(TAG, "Unable to check shell permission for user " + userHandle + "\n\t"
- + Debug.getCallers(3));
- }
- }
- }
-
- private BasePermission findPermissionTreeLP(String permName) {
- for(BasePermission bp : mSettings.mPermissionTrees.values()) {
- if (permName.startsWith(bp.name) &&
- permName.length() > bp.name.length() &&
- permName.charAt(bp.name.length()) == '.') {
- return bp;
- }
- }
- return null;
- }
-
- private BasePermission checkPermissionTreeLP(String permName) {
- if (permName != null) {
- BasePermission bp = findPermissionTreeLP(permName);
- if (bp != null) {
- if (bp.uid == UserHandle.getAppId(Binder.getCallingUid())) {
- return bp;
- }
- throw new SecurityException("Calling uid "
- + Binder.getCallingUid()
- + " is not allowed to add to permission tree "
- + bp.name + " owned by uid " + bp.uid);
- }
- }
- throw new SecurityException("No permission tree found for " + permName);
- }
-
- static boolean compareStrings(CharSequence s1, CharSequence s2) {
- if (s1 == null) {
- return s2 == null;
- }
- if (s2 == null) {
- return false;
- }
- if (s1.getClass() != s2.getClass()) {
- return false;
- }
- return s1.equals(s2);
- }
-
- static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
- if (pi1.icon != pi2.icon) return false;
- if (pi1.logo != pi2.logo) return false;
- if (pi1.protectionLevel != pi2.protectionLevel) return false;
- if (!compareStrings(pi1.name, pi2.name)) return false;
- if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
- // We'll take care of setting this one.
- if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
- // These are not currently stored in settings.
- //if (!compareStrings(pi1.group, pi2.group)) return false;
- //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
- //if (pi1.labelRes != pi2.labelRes) return false;
- //if (pi1.descriptionRes != pi2.descriptionRes) return false;
- return true;
- }
-
- int permissionInfoFootprint(PermissionInfo info) {
- int size = info.name.length();
- if (info.nonLocalizedLabel != null) size += info.nonLocalizedLabel.length();
- if (info.nonLocalizedDescription != null) size += info.nonLocalizedDescription.length();
- return size;
- }
-
- int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
- int size = 0;
- for (BasePermission perm : mSettings.mPermissions.values()) {
- if (perm.uid == tree.uid) {
- size += perm.name.length() + permissionInfoFootprint(perm.perm.info);
- }
- }
- return size;
- }
-
- void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
- // We calculate the max size of permissions defined by this uid and throw
- // if that plus the size of 'info' would exceed our stated maximum.
- if (tree.uid != Process.SYSTEM_UID) {
- final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
- if (curTreeSize + permissionInfoFootprint(info) > MAX_PERMISSION_TREE_FOOTPRINT) {
- throw new SecurityException("Permission tree size cap exceeded");
- }
- }
- }
-
- boolean addPermissionLocked(PermissionInfo info, boolean async) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- throw new SecurityException("Instant apps can't add permissions");
- }
- if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
- throw new SecurityException("Label must be specified in permission");
- }
- BasePermission tree = checkPermissionTreeLP(info.name);
- BasePermission bp = mSettings.mPermissions.get(info.name);
- boolean added = bp == null;
- boolean changed = true;
- int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
- if (added) {
- enforcePermissionCapLocked(info, tree);
- bp = new BasePermission(info.name, tree.sourcePackage,
- BasePermission.TYPE_DYNAMIC);
- } else if (bp.type != BasePermission.TYPE_DYNAMIC) {
- throw new SecurityException(
- "Not allowed to modify non-dynamic permission "
- + info.name);
- } else {
- if (bp.protectionLevel == fixedLevel
- && bp.perm.owner.equals(tree.perm.owner)
- && bp.uid == tree.uid
- && comparePermissionInfos(bp.perm.info, info)) {
- changed = false;
- }
- }
- bp.protectionLevel = fixedLevel;
- info = new PermissionInfo(info);
- info.protectionLevel = fixedLevel;
- bp.perm = new PackageParser.Permission(tree.perm.owner, info);
- bp.perm.info.packageName = tree.perm.info.packageName;
- bp.uid = tree.uid;
- if (added) {
- mSettings.mPermissions.put(info.name, bp);
- }
- if (changed) {
- if (!async) {
- mSettings.writeLPr();
- } else {
- scheduleWriteSettingsLocked();
- }
- }
- return added;
+ boolean addPermission(PermissionInfo info, final boolean async) {
+ return mPermissionManager.addPermission(
+ info, async, getCallingUid(), new PermissionCallback() {
+ @Override
+ public void onPermissionChanged() {
+ if (!async) {
+ mSettings.writeLPr();
+ } else {
+ scheduleWriteSettingsLocked();
+ }
+ }
+ });
}
@Override
public boolean addPermission(PermissionInfo info) {
synchronized (mPackages) {
- return addPermissionLocked(info, false);
+ return addPermission(info, false);
}
}
@Override
public boolean addPermissionAsync(PermissionInfo info) {
synchronized (mPackages) {
- return addPermissionLocked(info, true);
+ return addPermission(info, true);
}
}
@Override
- public void removePermission(String name) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- throw new SecurityException("Instant applications don't have access to this method");
- }
- synchronized (mPackages) {
- checkPermissionTreeLP(name);
- BasePermission bp = mSettings.mPermissions.get(name);
- if (bp != null) {
- if (bp.type != BasePermission.TYPE_DYNAMIC) {
- throw new SecurityException(
- "Not allowed to modify non-dynamic permission "
- + name);
- }
- mSettings.mPermissions.remove(name);
- mSettings.writeLPr();
- }
- }
- }
-
- private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(
- PackageParser.Package pkg, BasePermission bp) {
- int index = pkg.requestedPermissions.indexOf(bp.name);
- if (index == -1) {
- throw new SecurityException("Package " + pkg.packageName
- + " has not requested permission " + bp.name);
- }
- if (!bp.isRuntime() && !bp.isDevelopment()) {
- throw new SecurityException("Permission " + bp.name
- + " is not a changeable permission type");
- }
+ public void removePermission(String permName) {
+ mPermissionManager.removePermission(permName, getCallingUid(), mPermissionCallback);
}
@Override
- public void grantRuntimePermission(String packageName, String name, final int userId) {
- grantRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
- }
-
- private void grantRuntimePermission(String packageName, String name, final int userId,
- boolean overridePolicy) {
- if (!sUserManager.exists(userId)) {
- Log.e(TAG, "No such user:" + userId);
- return;
- }
- final int callingUid = Binder.getCallingUid();
-
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
- "grantRuntimePermission");
-
- enforceCrossUserPermission(callingUid, userId,
- true /* requireFullPermission */, true /* checkShell */,
- "grantRuntimePermission");
-
- final int uid;
- final PackageSetting ps;
-
- synchronized (mPackages) {
- final PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- final BasePermission bp = mSettings.mPermissions.get(name);
- if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + name);
- }
- ps = (PackageSetting) pkg.mExtras;
- if (ps == null
- || filterAppAccessLPr(ps, callingUid, userId)) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
-
- enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
-
- // If a permission review is required for legacy apps we represent
- // their permissions as always granted runtime ones since we need
- // to keep the review required permission flag per user while an
- // install permission's state is shared across all users.
- if (mPermissionReviewRequired
- && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
- && bp.isRuntime()) {
- return;
- }
-
- uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
-
- final PermissionsState permissionsState = ps.getPermissionsState();
-
- final int flags = permissionsState.getPermissionFlags(name, userId);
- if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
- throw new SecurityException("Cannot grant system fixed permission "
- + name + " for package " + packageName);
- }
- if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
- throw new SecurityException("Cannot grant policy fixed permission "
- + name + " for package " + packageName);
- }
-
- if (bp.isDevelopment()) {
- // Development permissions must be handled specially, since they are not
- // normal runtime permissions. For now they apply to all users.
- if (permissionsState.grantInstallPermission(bp) !=
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- scheduleWriteSettingsLocked();
- }
- return;
- }
-
- if (ps.getInstantApp(userId) && !bp.isInstant()) {
- throw new SecurityException("Cannot grant non-ephemeral permission"
- + name + " for package " + packageName);
- }
-
- if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
- Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
- return;
- }
-
- final int result = permissionsState.grantRuntimePermission(bp, userId);
- switch (result) {
- case PermissionsState.PERMISSION_OPERATION_FAILURE: {
- return;
- }
-
- case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
- final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
- }
- });
- }
- break;
- }
-
- if (bp.isRuntime()) {
- logPermissionGranted(mContext, name, packageName);
- }
-
- mOnPermissionChangeListeners.onPermissionsChanged(uid);
-
- // Not critical if that is lost - app has to request again.
- mSettings.writeRuntimePermissionsForUserLPr(userId, false);
- }
-
- // Only need to do this if user is initialized. Otherwise it's a new user
- // and there are no processes running as the user yet and there's no need
- // to make an expensive call to remount processes for the changed permissions.
- if (READ_EXTERNAL_STORAGE.equals(name)
- || WRITE_EXTERNAL_STORAGE.equals(name)) {
- final long token = Binder.clearCallingIdentity();
- try {
- if (sUserManager.isInitialized(userId)) {
- StorageManagerInternal storageManagerInternal = LocalServices.getService(
- StorageManagerInternal.class);
- storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
+ public void grantRuntimePermission(String packageName, String permName, final int userId) {
+ mPermissionManager.grantRuntimePermission(permName, packageName, false /*overridePolicy*/,
+ getCallingUid(), userId, mPermissionCallback);
}
@Override
- public void revokeRuntimePermission(String packageName, String name, int userId) {
- revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
- }
-
- private void revokeRuntimePermission(String packageName, String name, int userId,
- boolean overridePolicy) {
- if (!sUserManager.exists(userId)) {
- Log.e(TAG, "No such user:" + userId);
- return;
- }
-
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- "revokeRuntimePermission");
-
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
- true /* requireFullPermission */, true /* checkShell */,
- "revokeRuntimePermission");
-
- final int appId;
-
- synchronized (mPackages) {
- final PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null
- || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- final BasePermission bp = mSettings.mPermissions.get(name);
- if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + name);
- }
-
- enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
-
- // If a permission review is required for legacy apps we represent
- // their permissions as always granted runtime ones since we need
- // to keep the review required permission flag per user while an
- // install permission's state is shared across all users.
- if (mPermissionReviewRequired
- && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
- && bp.isRuntime()) {
- return;
- }
-
- final PermissionsState permissionsState = ps.getPermissionsState();
-
- final int flags = permissionsState.getPermissionFlags(name, userId);
- if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
- throw new SecurityException("Cannot revoke system fixed permission "
- + name + " for package " + packageName);
- }
- if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
- throw new SecurityException("Cannot revoke policy fixed permission "
- + name + " for package " + packageName);
- }
-
- if (bp.isDevelopment()) {
- // Development permissions must be handled specially, since they are not
- // normal runtime permissions. For now they apply to all users.
- if (permissionsState.revokeInstallPermission(bp) !=
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- scheduleWriteSettingsLocked();
- }
- return;
- }
-
- if (permissionsState.revokeRuntimePermission(bp, userId) ==
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- return;
- }
-
- if (bp.isRuntime()) {
- logPermissionRevoked(mContext, name, packageName);
- }
-
- mOnPermissionChangeListeners.onPermissionsChanged(pkg.applicationInfo.uid);
-
- // Critical, after this call app should never have the permission.
- mSettings.writeRuntimePermissionsForUserLPr(userId, true);
-
- appId = UserHandle.getAppId(pkg.applicationInfo.uid);
- }
-
- killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+ public void revokeRuntimePermission(String packageName, String permName, int userId) {
+ mPermissionManager.revokeRuntimePermission(permName, packageName, false /*overridePolicy*/,
+ getCallingUid(), userId, mPermissionCallback);
}
/**
@@ -5943,91 +5386,16 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
- public int getPermissionFlags(String name, String packageName, int userId) {
- if (!sUserManager.exists(userId)) {
- return 0;
- }
-
- enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
-
- final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
- true /* requireFullPermission */, false /* checkShell */,
- "getPermissionFlags");
-
- synchronized (mPackages) {
- final PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
- return 0;
- }
- final BasePermission bp = mSettings.mPermissions.get(name);
- if (bp == null) {
- return 0;
- }
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null
- || filterAppAccessLPr(ps, callingUid, userId)) {
- return 0;
- }
- PermissionsState permissionsState = ps.getPermissionsState();
- return permissionsState.getPermissionFlags(name, userId);
- }
+ public int getPermissionFlags(String permName, String packageName, int userId) {
+ return mPermissionManager.getPermissionFlags(permName, packageName, getCallingUid(), userId);
}
@Override
- public void updatePermissionFlags(String name, String packageName, int flagMask,
+ public void updatePermissionFlags(String permName, String packageName, int flagMask,
int flagValues, int userId) {
- if (!sUserManager.exists(userId)) {
- return;
- }
-
- enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
-
- final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
- true /* requireFullPermission */, true /* checkShell */,
- "updatePermissionFlags");
-
- // Only the system can change these flags and nothing else.
- if (getCallingUid() != Process.SYSTEM_UID) {
- flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
- flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
- flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
- flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
- flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
- }
-
- synchronized (mPackages) {
- final PackageParser.Package pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null
- || filterAppAccessLPr(ps, callingUid, userId)) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
-
- final BasePermission bp = mSettings.mPermissions.get(name);
- if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + name);
- }
-
- PermissionsState permissionsState = ps.getPermissionsState();
-
- boolean hadState = permissionsState.getRuntimePermissionState(name, userId) != null;
-
- if (permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues)) {
- // Install and runtime permissions are stored in different places,
- // so figure out what permission changed and persist the change.
- if (permissionsState.getInstallPermissionState(name) != null) {
- scheduleWriteSettingsLocked();
- } else if (permissionsState.getRuntimePermissionState(name, userId) != null
- || hadState) {
- mSettings.writeRuntimePermissionsForUserLPr(userId, false);
- }
- }
- }
+ mPermissionManager.updatePermissionFlags(
+ permName, packageName, flagMask, flagValues, getCallingUid(), userId,
+ mPermissionCallback);
}
/**
@@ -6036,52 +5404,16 @@ public class PackageManagerService extends IPackageManager.Stub
*/
@Override
public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
- if (!sUserManager.exists(userId)) {
- return;
- }
-
- enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlagsForAllApps");
-
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
- true /* requireFullPermission */, true /* checkShell */,
- "updatePermissionFlagsForAllApps");
-
- // Only the system can change system fixed flags.
- if (getCallingUid() != Process.SYSTEM_UID) {
- flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
- flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
- }
-
synchronized (mPackages) {
- boolean changed = false;
- final int packageCount = mPackages.size();
- for (int pkgIndex = 0; pkgIndex < packageCount; pkgIndex++) {
- final PackageParser.Package pkg = mPackages.valueAt(pkgIndex);
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null) {
- continue;
- }
- PermissionsState permissionsState = ps.getPermissionsState();
- changed |= permissionsState.updatePermissionFlagsForAllPermissions(
- userId, flagMask, flagValues);
- }
+ final boolean changed = mPermissionManager.updatePermissionFlagsForAllApps(
+ flagMask, flagValues, getCallingUid(), userId, mPackages.values(),
+ mPermissionCallback);
if (changed) {
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
}
}
}
- private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
- != PackageManager.PERMISSION_GRANTED
- && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(message + " requires "
- + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
- + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
- }
- }
-
@Override
public boolean shouldShowRequestPermissionRationale(String permissionName,
String packageName, int userId) {
@@ -6273,7 +5605,7 @@ public class PackageManagerService extends IPackageManager.Stub
* <br />
* {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
*/
- static int compareSignatures(Signature[] s1, Signature[] s2) {
+ public static int compareSignatures(Signature[] s1, Signature[] s2) {
if (s1 == null) {
return s2 == null
? PackageManager.SIGNATURE_NEITHER_SIGNED
@@ -6627,9 +5959,14 @@ public class PackageManagerService extends IPackageManager.Stub
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
int flags, int userId) {
return resolveIntentInternal(
- intent, resolvedType, flags, userId, false /*includeInstantApps*/);
+ intent, resolvedType, flags, userId, false /*resolveForStart*/);
}
+ /**
+ * Normally instant apps can only be resolved when they're visible to the caller.
+ * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
+ * since we need to allow the system to start any installed application.
+ */
private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
int flags, int userId, boolean resolveForStart) {
try {
@@ -6638,7 +5975,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!sUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForResolve(flags, userId, intent, callingUid, resolveForStart);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
@@ -7219,7 +6556,7 @@ public class PackageManagerService extends IPackageManager.Stub
boolean resolveForStart, boolean allowDynamicSplits) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
false /* requireFullPermission */, false /* checkShell */,
"query intent activities");
final String pkgName = intent.getPackage();
@@ -7974,7 +7311,7 @@ public class PackageManagerService extends IPackageManager.Stub
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForResolve(flags, userId, intent, callingUid,
false /*includeInstantApps*/);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/,
"query intent activity options");
final String resultsAction = intent.getAction();
@@ -8155,7 +7492,7 @@ public class PackageManagerService extends IPackageManager.Stub
String resolvedType, int flags, int userId, boolean allowDynamicSplits) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/,
"query intent receivers");
final String instantAppPkgName = getInstantAppPackageName(callingUid);
@@ -8267,7 +7604,7 @@ public class PackageManagerService extends IPackageManager.Stub
String resolvedType, int flags, int userId, int callingUid,
boolean includeInstantApps) {
if (!sUserManager.exists(userId)) return Collections.emptyList();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/,
"query intent receivers");
final String instantAppPkgName = getInstantAppPackageName(callingUid);
@@ -8507,7 +7844,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForPackage(flags, userId, null);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, false /* checkShell */,
"get installed packages");
@@ -8594,7 +7931,7 @@ public class PackageManagerService extends IPackageManager.Stub
String[] permissions, int flags, int userId) {
if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
flags = updateFlagsForPackage(flags, userId, permissions);
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"get packages holding permissions");
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
@@ -8699,7 +8036,7 @@ public class PackageManagerService extends IPackageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
"getEphemeralApplications");
}
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getEphemeralApplications");
synchronized (mPackages) {
@@ -8714,7 +8051,7 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public boolean isInstantApp(String packageName, int userId) {
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"isInstantApp");
if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
@@ -8747,7 +8084,7 @@ public class PackageManagerService extends IPackageManager.Stub
return null;
}
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getInstantAppCookie");
if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
@@ -8765,7 +8102,7 @@ public class PackageManagerService extends IPackageManager.Stub
return true;
}
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, true /* checkShell */,
"setInstantAppCookie");
if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
@@ -8787,7 +8124,7 @@ public class PackageManagerService extends IPackageManager.Stub
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
"getInstantAppIcon");
}
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getInstantAppIcon");
@@ -8847,6 +8184,10 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+ return resolveContentProviderInternal(name, flags, userId);
+ }
+
+ private ProviderInfo resolveContentProviderInternal(String name, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
flags = updateFlagsForComponent(flags, userId, name);
final String instantAppPkgName = getInstantAppPackageName(Binder.getCallingUid());
@@ -9105,7 +8446,7 @@ public class PackageManagerService extends IPackageManager.Stub
return fname;
}
- static void reportSettingsProblem(int priority, String msg) {
+ public static void reportSettingsProblem(int priority, String msg) {
logCriticalInfo(priority, msg);
}
@@ -9738,7 +9079,7 @@ public class PackageManagerService extends IPackageManager.Stub
* and {@code numberOfPackagesFailed}.
*/
private int[] performDexOptUpgrade(List<PackageParser.Package> pkgs, boolean showDialog,
- String compilerFilter, boolean bootComplete) {
+ final String compilerFilter, boolean bootComplete) {
int numberOfPackagesVisited = 0;
int numberOfPackagesOptimized = 0;
@@ -9749,6 +9090,8 @@ public class PackageManagerService extends IPackageManager.Stub
for (PackageParser.Package pkg : pkgs) {
numberOfPackagesVisited++;
+ boolean useProfileForDexopt = false;
+
if ((isFirstBoot() || isUpgrade()) && isSystemApp(pkg)) {
// Copy over initial preopt profiles since we won't get any JIT samples for methods
// that are already compiled.
@@ -9762,11 +9105,30 @@ public class PackageManagerService extends IPackageManager.Stub
if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
pkg.applicationInfo.uid, pkg.packageName)) {
Log.e(TAG, "Installer failed to copy system profile!");
+ } else {
+ // Disabled as this causes speed-profile compilation during first boot
+ // even if things are already compiled.
+ // useProfileForDexopt = true;
}
} catch (Exception e) {
Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
e);
}
+ } else {
+ PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);
+ // Handle compressed APKs in this path. Only do this for stubs with profiles to
+ // minimize the number off apps being speed-profile compiled during first boot.
+ // The other paths will not change the filter.
+ if (disabledPs != null && disabledPs.pkg.isStub) {
+ // The package is the stub one, remove the stub suffix to get the normal
+ // package and APK names.
+ String systemProfilePath =
+ getPrebuildProfilePath(disabledPs.pkg).replace(STUB_SUFFIX, "");
+ File systemProfile = new File(systemProfilePath);
+ // Use the profile for compilation if there exists one for the same package
+ // in the system partition.
+ useProfileForDexopt = systemProfile.exists();
+ }
}
}
@@ -9795,17 +9157,13 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- // If the OTA updates a system app which was previously preopted to a non-preopted state
- // the app might end up being verified at runtime. That's because by default the apps
- // are verify-profile but for preopted apps there's no profile.
- // Do a hacky check to ensure that if we have no profiles (a reasonable indication
- // that before the OTA the app was preopted) the app gets compiled with a non-profile
- // filter (by default 'quicken').
- // Note that at this stage unused apps are already filtered.
- if (isSystemApp(pkg) &&
- DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
- !Environment.getReferenceProfile(pkg.packageName).exists()) {
- compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter);
+ String pkgCompilerFilter = compilerFilter;
+ if (useProfileForDexopt) {
+ // Use background dexopt mode to try and use the profile. Note that this does not
+ // guarantee usage of the profile.
+ pkgCompilerFilter =
+ PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+ PackageManagerService.REASON_BACKGROUND_DEXOPT);
}
// checkProfiles is false to avoid merging profiles during boot which
@@ -9816,22 +9174,9 @@ public class PackageManagerService extends IPackageManager.Stub
int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
int primaryDexOptStaus = performDexOptTraced(new DexoptOptions(
pkg.packageName,
- compilerFilter,
+ pkgCompilerFilter,
dexoptFlags));
- if (pkg.isSystemApp()) {
- // Only dexopt shared secondary dex files belonging to system apps to not slow down
- // too much boot after an OTA.
- int secondaryDexoptFlags = dexoptFlags |
- DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
- DexoptOptions.DEXOPT_ONLY_SHARED_DEX;
- mDexManager.dexoptSecondaryDex(new DexoptOptions(
- pkg.packageName,
- compilerFilter,
- secondaryDexoptFlags));
- }
-
- // TODO(shubhamajmera): Record secondary dexopt stats.
switch (primaryDexOptStaus) {
case PackageDexOptimizer.DEX_OPT_PERFORMED:
numberOfPackagesOptimized++;
@@ -10388,7 +9733,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private void addSharedLibraryLPr(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
+ private void addSharedLibraryLPr(Set<String> usesLibraryFiles,
+ SharedLibraryEntry file,
PackageParser.Package changingLib) {
if (file.path != null) {
usesLibraryFiles.add(file.path);
@@ -10417,7 +9763,10 @@ public class PackageManagerService extends IPackageManager.Stub
if (pkg == null) {
return;
}
- ArraySet<String> usesLibraryFiles = null;
+ // The collection used here must maintain the order of addition (so
+ // that libraries are searched in the correct order) and must have no
+ // duplicates.
+ Set<String> usesLibraryFiles = null;
if (pkg.usesLibraries != null) {
usesLibraryFiles = addSharedLibrariesLPw(pkg.usesLibraries,
null, null, pkg.packageName, changingLib, true,
@@ -10441,10 +9790,10 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private ArraySet<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
+ private Set<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
@Nullable int[] requiredVersions, @Nullable String[][] requiredCertDigests,
@NonNull String packageName, @Nullable PackageParser.Package changingLib,
- boolean required, int targetSdk, @Nullable ArraySet<String> outUsedLibraries)
+ boolean required, int targetSdk, @Nullable Set<String> outUsedLibraries)
throws PackageManagerException {
final int libCount = requestedLibraries.size();
for (int i = 0; i < libCount; i++) {
@@ -10510,7 +9859,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
if (outUsedLibraries == null) {
- outUsedLibraries = new ArraySet<>();
+ // Use LinkedHashSet to preserve the order of files added to
+ // usesLibraryFiles while eliminating duplicates.
+ outUsedLibraries = new LinkedHashSet<>();
}
addSharedLibraryLPr(outUsedLibraries, libEntry, changingLib);
}
@@ -10703,6 +10054,12 @@ public class PackageManagerService extends IPackageManager.Stub
assertPackageIsValid(pkg, policyFlags, scanFlags);
+ if (Build.IS_DEBUGGABLE &&
+ pkg.isPrivilegedApp() &&
+ !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+ PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
+ }
+
// Initialize package source and resource directories
final File scanFile = new File(pkg.codePath);
final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
@@ -11866,86 +11223,27 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- ArrayMap<String, BasePermission> permissionMap =
- p.tree ? mSettings.mPermissionTrees
- : mSettings.mPermissions;
- BasePermission bp = permissionMap.get(p.info.name);
-
- // Allow system apps to redefine non-system permissions
- if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) {
- final boolean currentOwnerIsSystem = (bp.perm != null
- && isSystemApp(bp.perm.owner));
- if (isSystemApp(p.owner)) {
- if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
- // It's a built-in permission and no owner, take ownership now
- bp.packageSetting = pkgSetting;
- bp.perm = p;
- bp.uid = pkg.applicationInfo.uid;
- bp.sourcePackage = p.info.packageName;
- p.info.flags |= PermissionInfo.FLAG_INSTALLED;
- } else if (!currentOwnerIsSystem) {
- String msg = "New decl " + p.owner + " of permission "
- + p.info.name + " is system; overriding " + bp.sourcePackage;
- reportSettingsProblem(Log.WARN, msg);
- bp = null;
- }
- }
- }
-
- if (bp == null) {
- bp = new BasePermission(p.info.name, p.info.packageName,
- BasePermission.TYPE_NORMAL);
+ // TODO Move to PermissionManager once mPermissionTrees moves there.
+// p.tree ? mSettings.mPermissionTrees
+// : mSettings.mPermissions;
+// final BasePermission bp = BasePermission.createOrUpdate(
+// permissionMap.get(p.info.name), p, pkg, mSettings.mPermissionTrees, chatty);
+// permissionMap.put(p.info.name, bp);
+ if (p.tree) {
+ final ArrayMap<String, BasePermission> permissionMap =
+ mSettings.mPermissionTrees;
+ final BasePermission bp = BasePermission.createOrUpdate(
+ permissionMap.get(p.info.name), p, pkg, mSettings.mPermissionTrees,
+ chatty);
permissionMap.put(p.info.name, bp);
- }
-
- if (bp.perm == null) {
- if (bp.sourcePackage == null
- || bp.sourcePackage.equals(p.info.packageName)) {
- BasePermission tree = findPermissionTreeLP(p.info.name);
- if (tree == null
- || tree.sourcePackage.equals(p.info.packageName)) {
- bp.packageSetting = pkgSetting;
- bp.perm = p;
- bp.uid = pkg.applicationInfo.uid;
- bp.sourcePackage = p.info.packageName;
- p.info.flags |= PermissionInfo.FLAG_INSTALLED;
- if (chatty) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- r.append(p.info.name);
- }
- } else {
- Slog.w(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName + " ignored: base tree "
- + tree.name + " is from package "
- + tree.sourcePackage);
- }
- } else {
- Slog.w(TAG, "Permission " + p.info.name + " from package "
- + p.info.packageName + " ignored: original from "
- + bp.sourcePackage);
- }
- } else if (chatty) {
- if (r == null) {
- r = new StringBuilder(256);
- } else {
- r.append(' ');
- }
- r.append("DUP:");
- r.append(p.info.name);
- }
- if (bp.perm == p) {
- bp.protectionLevel = p.info.protectionLevel;
+ } else {
+ final BasePermission bp = BasePermission.createOrUpdate(
+ (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name),
+ p, pkg, mSettings.mPermissionTrees, chatty);
+ mPermissionManager.putPermissionTEMP(p.info.name, bp);
}
}
- if (r != null) {
- if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permissions: " + r);
- }
-
N = pkg.instrumentation.size();
r = null;
for (i=0; i<N; i++) {
@@ -12673,12 +11971,12 @@ public class PackageManagerService extends IPackageManager.Stub
r = null;
for (i=0; i<N; i++) {
PackageParser.Permission p = pkg.permissions.get(i);
- BasePermission bp = mSettings.mPermissions.get(p.info.name);
+ BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name);
if (bp == null) {
bp = mSettings.mPermissionTrees.get(p.info.name);
}
- if (bp != null && bp.perm == p) {
- bp.perm = null;
+ if (bp != null && bp.isPermission(p)) {
+ bp.setPermission(null);
if (DEBUG_REMOVE && chatty) {
if (r == null) {
r = new StringBuilder(256);
@@ -12703,8 +12001,7 @@ public class PackageManagerService extends IPackageManager.Stub
r = null;
for (i=0; i<N; i++) {
String perm = pkg.requestedPermissions.get(i);
- BasePermission bp = mSettings.mPermissions.get(perm);
- if (bp != null && (bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
+ if (mPermissionManager.isPermissionAppOp(perm)) {
ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(perm);
if (appOpPkgs != null) {
appOpPkgs.remove(pkg.packageName);
@@ -12809,23 +12106,32 @@ public class PackageManagerService extends IPackageManager.Stub
private void updatePermissionsLPw(String changingPkg,
PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
+ // TODO: Most of the methods exposing BasePermission internals [source package name,
+ // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
+ // have package settings, we should make note of it elsewhere [map between
+ // source package name and BasePermission] and cycle through that here. Then we
+ // define a single method on BasePermission that takes a PackageSetting, changing
+ // package name and a package.
+ // NOTE: With this approach, we also don't need to tree trees differently than
+ // normal permissions. Today, we need two separate loops because these BasePermission
+ // objects are stored separately.
// Make sure there are no dangling permission trees.
Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
while (it.hasNext()) {
final BasePermission bp = it.next();
- if (bp.packageSetting == null) {
+ if (bp.getSourcePackageSetting() == null) {
// We may not yet have parsed the package, so just see if
// we still know about its settings.
- bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
+ bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
}
- if (bp.packageSetting == null) {
- Slog.w(TAG, "Removing dangling permission tree: " + bp.name
- + " from package " + bp.sourcePackage);
+ if (bp.getSourcePackageSetting() == null) {
+ Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
+ + " from package " + bp.getSourcePackageName());
it.remove();
- } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
- if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
- Slog.i(TAG, "Removing old permission tree: " + bp.name
- + " from package " + bp.sourcePackage);
+ } else if (changingPkg != null && changingPkg.equals(bp.getSourcePackageName())) {
+ if (pkgInfo == null || !hasPermission(pkgInfo, bp.getName())) {
+ Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+ + " from package " + bp.getSourcePackageName());
flags |= UPDATE_PERMISSIONS_ALL;
it.remove();
}
@@ -12834,40 +12140,28 @@ public class PackageManagerService extends IPackageManager.Stub
// Make sure all dynamic permissions have been assigned to a package,
// and make sure there are no dangling permissions.
- it = mSettings.mPermissions.values().iterator();
- while (it.hasNext()) {
- final BasePermission bp = it.next();
- if (bp.type == BasePermission.TYPE_DYNAMIC) {
- if (DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
- + bp.name + " pkg=" + bp.sourcePackage
- + " info=" + bp.pendingInfo);
- if (bp.packageSetting == null && bp.pendingInfo != null) {
- final BasePermission tree = findPermissionTreeLP(bp.name);
- if (tree != null && tree.perm != null) {
- bp.packageSetting = tree.packageSetting;
- bp.perm = new PackageParser.Permission(tree.perm.owner,
- new PermissionInfo(bp.pendingInfo));
- bp.perm.info.packageName = tree.perm.info.packageName;
- bp.perm.info.name = bp.name;
- bp.uid = tree.uid;
- }
- }
- }
- if (bp.packageSetting == null) {
+ final Iterator<BasePermission> permissionIter =
+ mPermissionManager.getPermissionIteratorTEMP();
+ while (permissionIter.hasNext()) {
+ final BasePermission bp = permissionIter.next();
+ if (bp.isDynamic()) {
+ bp.updateDynamicPermission(mSettings.mPermissionTrees);
+ }
+ if (bp.getSourcePackageSetting() == null) {
// We may not yet have parsed the package, so just see if
// we still know about its settings.
- bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
- }
- if (bp.packageSetting == null) {
- Slog.w(TAG, "Removing dangling permission: " + bp.name
- + " from package " + bp.sourcePackage);
- it.remove();
- } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
- if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
- Slog.i(TAG, "Removing old permission: " + bp.name
- + " from package " + bp.sourcePackage);
+ bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
+ }
+ if (bp.getSourcePackageSetting() == null) {
+ Slog.w(TAG, "Removing dangling permission: " + bp.getName()
+ + " from package " + bp.getSourcePackageName());
+ permissionIter.remove();
+ } else if (changingPkg != null && changingPkg.equals(bp.getSourcePackageName())) {
+ if (pkgInfo == null || !hasPermission(pkgInfo, bp.getName())) {
+ Slog.i(TAG, "Removing old permission: " + bp.getName()
+ + " from package " + bp.getSourcePackageName());
flags |= UPDATE_PERMISSIONS_ALL;
- it.remove();
+ permissionIter.remove();
}
}
}
@@ -12936,8 +12230,9 @@ public class PackageManagerService extends IPackageManager.Stub
// the runtime ones are written only if changed. The only cases of
// changed runtime permissions here are promotion of an install to
// runtime and revocation of a runtime from a shared user.
- changedRuntimePermissionUserIds = revokeUnusedSharedUserPermissionsLPw(
- ps.sharedUser, UserManagerService.getInstance().getUserIds());
+ changedRuntimePermissionUserIds =
+ mPermissionManager.revokeUnusedSharedUserPermissions(
+ ps.sharedUser, UserManagerService.getInstance().getUserIds());
if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
runtimePermissionsRevoked = true;
}
@@ -12949,7 +12244,7 @@ public class PackageManagerService extends IPackageManager.Stub
final int N = pkg.requestedPermissions.size();
for (int i=0; i<N; i++) {
final String name = pkg.requestedPermissions.get(i);
- final BasePermission bp = mSettings.mPermissions.get(name);
+ final BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(name);
final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.M;
@@ -12957,7 +12252,7 @@ public class PackageManagerService extends IPackageManager.Stub
Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
}
- if (bp == null || bp.packageSetting == null) {
+ if (bp == null || bp.getSourcePackageSetting() == null) {
if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
if (DEBUG_PERMISSIONS) {
Slog.i(TAG, "Unknown permission " + name
@@ -12971,7 +12266,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Limit ephemeral apps to ephemeral allowed permissions.
if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
if (DEBUG_PERMISSIONS) {
- Log.i(TAG, "Denying non-ephemeral permission " + bp.name + " for package "
+ Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + " for package "
+ pkg.packageName);
}
continue;
@@ -12979,64 +12274,57 @@ public class PackageManagerService extends IPackageManager.Stub
if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
if (DEBUG_PERMISSIONS) {
- Log.i(TAG, "Denying runtime-only permission " + bp.name + " for package "
+ Log.i(TAG, "Denying runtime-only permission " + bp.getName() + " for package "
+ pkg.packageName);
}
continue;
}
- final String perm = bp.name;
+ final String perm = bp.getName();
boolean allowedSig = false;
int grant = GRANT_DENIED;
// Keep track of app op permissions.
- if ((bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
- ArraySet<String> pkgs = mAppOpPermissionPackages.get(bp.name);
+ if (bp.isAppOp()) {
+ ArraySet<String> pkgs = mAppOpPermissionPackages.get(perm);
if (pkgs == null) {
pkgs = new ArraySet<>();
- mAppOpPermissionPackages.put(bp.name, pkgs);
+ mAppOpPermissionPackages.put(perm, pkgs);
}
pkgs.add(pkg.packageName);
}
- final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- switch (level) {
- case PermissionInfo.PROTECTION_NORMAL: {
- // For all apps normal permissions are install time ones.
+ if (bp.isNormal()) {
+ // For all apps normal permissions are install time ones.
+ grant = GRANT_INSTALL;
+ } else if (bp.isRuntime()) {
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
+ // For legacy apps dangerous permissions are install time ones.
grant = GRANT_INSTALL;
- } break;
-
- case PermissionInfo.PROTECTION_DANGEROUS: {
- // If a permission review is required for legacy apps we represent
- // their permissions as always granted runtime ones since we need
- // to keep the review required permission flag per user while an
- // install permission's state is shared across all users.
- if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
- // For legacy apps dangerous permissions are install time ones.
- grant = GRANT_INSTALL;
- } else if (origPermissions.hasInstallPermission(bp.name)) {
- // For legacy apps that became modern, install becomes runtime.
- grant = GRANT_UPGRADE;
- } else if (mPromoteSystemApps
- && isSystemApp(ps)
- && mExistingSystemPackages.contains(ps.name)) {
- // For legacy system apps, install becomes runtime.
- // We cannot check hasInstallPermission() for system apps since those
- // permissions were granted implicitly and not persisted pre-M.
- grant = GRANT_UPGRADE;
- } else {
- // For modern apps keep runtime permissions unchanged.
- grant = GRANT_RUNTIME;
- }
- } break;
-
- case PermissionInfo.PROTECTION_SIGNATURE: {
- // For all apps signature permissions are install time ones.
- allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
- if (allowedSig) {
- grant = GRANT_INSTALL;
- }
- } break;
+ } else if (origPermissions.hasInstallPermission(bp.getName())) {
+ // For legacy apps that became modern, install becomes runtime.
+ grant = GRANT_UPGRADE;
+ } else if (mPromoteSystemApps
+ && isSystemApp(ps)
+ && mExistingSystemPackages.contains(ps.name)) {
+ // For legacy system apps, install becomes runtime.
+ // We cannot check hasInstallPermission() for system apps since those
+ // permissions were granted implicitly and not persisted pre-M.
+ grant = GRANT_UPGRADE;
+ } else {
+ // For modern apps keep runtime permissions unchanged.
+ grant = GRANT_RUNTIME;
+ }
+ } else if (bp.isSignature()) {
+ // For all apps signature permissions are install time ones.
+ allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+ if (allowedSig) {
+ grant = GRANT_INSTALL;
+ }
}
if (DEBUG_PERMISSIONS) {
@@ -13065,7 +12353,7 @@ public class PackageManagerService extends IPackageManager.Stub
// for legacy apps
for (int userId : UserManagerService.getInstance().getUserIds()) {
if (origPermissions.getRuntimePermissionState(
- bp.name, userId) != null) {
+ perm, userId) != null) {
// Revoke the runtime permission and clear the flags.
origPermissions.revokeRuntimePermission(bp, userId);
origPermissions.updatePermissionFlags(bp, userId,
@@ -13086,10 +12374,10 @@ public class PackageManagerService extends IPackageManager.Stub
// Grant previously granted runtime permissions.
for (int userId : UserManagerService.getInstance().getUserIds()) {
PermissionState permissionState = origPermissions
- .getRuntimePermissionState(bp.name, userId);
+ .getRuntimePermissionState(perm, userId);
int flags = permissionState != null
? permissionState.getFlags() : 0;
- if (origPermissions.hasRuntimePermission(bp.name, userId)) {
+ if (origPermissions.hasRuntimePermission(perm, userId)) {
// Don't propagate the permission in a permission review mode if
// the former was revoked, i.e. marked to not propagate on upgrade.
// Note that in a permission review mode install permissions are
@@ -13132,7 +12420,7 @@ public class PackageManagerService extends IPackageManager.Stub
// permissions as these are the only ones the platform knows
// how to disable the API to simulate revocation as legacy
// apps don't expect to run with revoked permissions.
- if (PLATFORM_PACKAGE_NAME.equals(bp.sourcePackage)) {
+ if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
// We changed the flags, hence have to write.
@@ -13155,7 +12443,7 @@ public class PackageManagerService extends IPackageManager.Stub
case GRANT_UPGRADE: {
// Grant runtime permissions for a previously held install permission.
PermissionState permissionState = origPermissions
- .getInstallPermissionState(bp.name);
+ .getInstallPermissionState(perm);
final int flags = permissionState != null ? permissionState.getFlags() : 0;
if (origPermissions.revokeInstallPermission(bp)
@@ -13203,10 +12491,10 @@ public class PackageManagerService extends IPackageManager.Stub
changedInstallPermission = true;
Slog.i(TAG, "Un-granting permission " + perm
+ " from package " + pkg.packageName
- + " (protectionLevel=" + bp.protectionLevel
+ + " (protectionLevel=" + bp.getProtectionLevel()
+ " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+ ")");
- } else if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) == 0) {
+ } else if (bp.isAppOp()) {
// Don't print warning for app op permissions, since it is fine for them
// not to be granted, there is a UI for the user to decide.
if (DEBUG_PERMISSIONS
@@ -13214,7 +12502,7 @@ public class PackageManagerService extends IPackageManager.Stub
|| packageOfInterest.equals(pkg.packageName))) {
Slog.i(TAG, "Not granting permission " + perm
+ " to package " + pkg.packageName
- + " (protectionLevel=" + bp.protectionLevel
+ + " (protectionLevel=" + bp.getProtectionLevel()
+ " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+ ")");
}
@@ -13274,13 +12562,11 @@ public class PackageManagerService extends IPackageManager.Stub
private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
BasePermission bp, PermissionsState origPermissions) {
- boolean oemPermission = (bp.protectionLevel
- & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
- boolean privilegedPermission = (bp.protectionLevel
- & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
+ boolean oemPermission = bp.isOEM();
+ boolean privilegedPermission = bp.isPrivileged();
boolean privappPermissionsDisable =
RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
- boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.sourcePackage);
+ boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp()
&& !platformPackage && platformPermission) {
@@ -13309,7 +12595,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
boolean allowed = (compareSignatures(
- bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
+ bp.getSourcePackageSetting().signatures.mSignatures, pkg.mSignatures)
== PackageManager.SIGNATURE_MATCH)
|| (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
== PackageManager.SIGNATURE_MATCH);
@@ -13386,39 +12672,37 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
if (!allowed) {
- if (!allowed && (bp.protectionLevel
- & PermissionInfo.PROTECTION_FLAG_PRE23) != 0
+ if (!allowed
+ && bp.isPre23()
&& pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
// If this was a previously normal/dangerous permission that got moved
// to a system permission as part of the runtime permission redesign, then
// we still want to blindly grant it to old apps.
allowed = true;
}
- if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0
+ if (!allowed && bp.isInstaller()
&& pkg.packageName.equals(mRequiredInstallerPackage)) {
// If this permission is to be granted to the system installer and
// this app is an installer, then it gets the permission.
allowed = true;
}
- if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0
+ if (!allowed && bp.isVerifier()
&& pkg.packageName.equals(mRequiredVerifierPackage)) {
// If this permission is to be granted to the system verifier and
// this app is a verifier, then it gets the permission.
allowed = true;
}
- if (!allowed && (bp.protectionLevel
- & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0
+ if (!allowed && bp.isPreInstalled()
&& isSystemApp(pkg)) {
// Any pre-installed system app is allowed to get this permission.
allowed = true;
}
- if (!allowed && (bp.protectionLevel
- & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+ if (!allowed && bp.isDevelopment()) {
// For development permissions, a development permission
// is granted only if it was already granted.
allowed = origPermissions.hasInstallPermission(perm);
}
- if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0
+ if (!allowed && bp.isSetup()
&& pkg.packageName.equals(mSetupWizardPackage)) {
// If this permission is to be granted to the system setup wizard and
// this app is a setup wizard, then it gets the permission.
@@ -14719,7 +14003,7 @@ public class PackageManagerService extends IPackageManager.Stub
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */, "installPackageAsUser");
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
@@ -14847,7 +14131,7 @@ public class PackageManagerService extends IPackageManager.Stub
return installReason;
}
- void installStage(String packageName, File stagedDir, String stagedCid,
+ void installStage(String packageName, File stagedDir,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {
@@ -14860,12 +14144,7 @@ public class PackageManagerService extends IPackageManager.Stub
sessionParams.originatingUri, sessionParams.referrerUri,
sessionParams.originatingUid, installerUid);
- final OriginInfo origin;
- if (stagedDir != null) {
- origin = OriginInfo.fromStagedFile(stagedDir);
- } else {
- origin = OriginInfo.fromStagedContainer(stagedCid);
- }
+ final OriginInfo origin = OriginInfo.fromStagedFile(stagedDir);
final Message msg = mHandler.obtainMessage(INIT_COPY);
final int installReason = fixUpInstallReason(installerPackageName, installerUid,
@@ -14963,7 +14242,7 @@ public class PackageManagerService extends IPackageManager.Stub
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
PackageSetting pkgSetting;
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */,
"setApplicationHiddenSetting for user " + userId);
@@ -15065,7 +14344,7 @@ public class PackageManagerService extends IPackageManager.Stub
public boolean getApplicationHiddenSettingAsUser(String packageName, int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, false /* checkShell */,
"getApplicationHidden for user " + userId);
PackageSetting ps;
@@ -15097,7 +14376,7 @@ public class PackageManagerService extends IPackageManager.Stub
null);
PackageSetting pkgSetting;
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */,
"installExistingPackage for user " + userId);
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
@@ -15202,7 +14481,7 @@ public class PackageManagerService extends IPackageManager.Stub
int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */,
"setPackagesSuspended for user " + userId);
@@ -15263,7 +14542,7 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public boolean isPackageSuspendedForUser(String packageName, int userId) {
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, false /* checkShell */,
"isPackageSuspendedForUser for user " + userId);
synchronized (mPackages) {
@@ -15710,7 +14989,7 @@ public class PackageManagerService extends IPackageManager.Stub
synchronized (mPackages) {
boolean result = mSettings.setDefaultBrowserPackageNameLPw(packageName, userId);
if (packageName != null) {
- mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultBrowserLPr(
+ mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultBrowser(
packageName, userId);
}
return result;
@@ -16065,7 +15344,6 @@ public class PackageManagerService extends IPackageManager.Stub
* file, or a cluster directory. This location may be untrusted.
*/
final File file;
- final String cid;
/**
* Flag indicating that {@link #file} or {@link #cid} has already been
@@ -16084,35 +15362,27 @@ public class PackageManagerService extends IPackageManager.Stub
final File resolvedFile;
static OriginInfo fromNothing() {
- return new OriginInfo(null, null, false, false);
+ return new OriginInfo(null, false, false);
}
static OriginInfo fromUntrustedFile(File file) {
- return new OriginInfo(file, null, false, false);
+ return new OriginInfo(file, false, false);
}
static OriginInfo fromExistingFile(File file) {
- return new OriginInfo(file, null, false, true);
+ return new OriginInfo(file, false, true);
}
static OriginInfo fromStagedFile(File file) {
- return new OriginInfo(file, null, true, false);
+ return new OriginInfo(file, true, false);
}
- static OriginInfo fromStagedContainer(String cid) {
- return new OriginInfo(null, cid, true, false);
- }
-
- private OriginInfo(File file, String cid, boolean staged, boolean existing) {
+ private OriginInfo(File file, boolean staged, boolean existing) {
this.file = file;
- this.cid = cid;
this.staged = staged;
this.existing = existing;
- if (cid != null) {
- resolvedPath = PackageHelper.getSdDir(cid);
- resolvedFile = new File(resolvedPath);
- } else if (file != null) {
+ if (file != null) {
resolvedPath = file.getAbsolutePath();
resolvedFile = file;
} else {
@@ -16205,7 +15475,7 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public String toString() {
return "InstallParams{" + Integer.toHexString(System.identityHashCode(this))
- + " file=" + origin.file + " cid=" + origin.cid + "}";
+ + " file=" + origin.file + "}";
}
private int installLocationPolicy(PackageInfoLite pkgLite) {
@@ -16316,9 +15586,6 @@ public class PackageManagerService extends IPackageManager.Stub
if (origin.file != null) {
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
- } else if (origin.cid != null) {
- installFlags |= PackageManager.INSTALL_EXTERNAL;
- installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else {
throw new IllegalStateException("Invalid stage location");
}
@@ -16356,7 +15623,7 @@ public class PackageManagerService extends IPackageManager.Stub
Environment.getDataDirectory());
final long sizeBytes = mContainerService.calculateInstalledSize(
- origin.resolvedPath, isForwardLocked(), packageAbiOverride);
+ origin.resolvedPath, packageAbiOverride);
try {
mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
@@ -16593,43 +15860,11 @@ public class PackageManagerService extends IPackageManager.Stub
mArgs = createInstallArgs(this);
mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
}
-
- public boolean isForwardLocked() {
- return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
- }
- }
-
- /**
- * Used during creation of InstallArgs
- *
- * @param installFlags package installation flags
- * @return true if should be installed on external storage
- */
- private static boolean installOnExternalAsec(int installFlags) {
- if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
- return false;
- }
- if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
- return true;
- }
- return false;
- }
-
- /**
- * Used during creation of InstallArgs
- *
- * @param installFlags package installation flags
- * @return true if should be installed as forward locked
- */
- private static boolean installForwardLocked(int installFlags) {
- return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
}
private InstallArgs createInstallArgs(InstallParams params) {
if (params.move != null) {
return new MoveInstallArgs(params);
- } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
- return new AsecInstallArgs(params);
} else {
return new FileInstallArgs(params);
}
@@ -16641,27 +15876,7 @@ public class PackageManagerService extends IPackageManager.Stub
*/
private InstallArgs createInstallArgsForExisting(int installFlags, String codePath,
String resourcePath, String[] instructionSets) {
- final boolean isInAsec;
- if (installOnExternalAsec(installFlags)) {
- /* Apps on SD card are always in ASEC containers. */
- isInAsec = true;
- } else if (installForwardLocked(installFlags)
- && !codePath.startsWith(mDrmAppPrivateInstallDir.getAbsolutePath())) {
- /*
- * Forward-locked apps are only in ASEC containers if they're the
- * new style
- */
- isInAsec = true;
- } else {
- isInAsec = false;
- }
-
- if (isInAsec) {
- return new AsecInstallArgs(codePath, instructionSets,
- installOnExternalAsec(installFlags), installForwardLocked(installFlags));
- } else {
- return new FileInstallArgs(codePath, resourcePath, instructionSets);
- }
+ return new FileInstallArgs(codePath, resourcePath, instructionSets);
}
static abstract class InstallArgs {
@@ -16995,11 +16210,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private boolean isAsecExternal(String cid) {
- final String asecPath = PackageHelper.getSdFilesystem(cid);
- return !asecPath.startsWith(mAsecInternalPath);
- }
-
private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet) throws
PackageManagerException {
if (copyRet < 0) {
@@ -17022,308 +16232,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
/**
- * Logic to handle installation of ASEC applications, including copying and
- * renaming logic.
- */
- class AsecInstallArgs extends InstallArgs {
- static final String RES_FILE_NAME = "pkg.apk";
- static final String PUBLIC_RES_FILE_NAME = "res.zip";
-
- String cid;
- String packagePath;
- String resourcePath;
-
- /** New install */
- AsecInstallArgs(InstallParams params) {
- super(params.origin, params.move, params.observer, params.installFlags,
- params.installerPackageName, params.volumeUuid,
- params.getUser(), null /* instruction sets */, params.packageAbiOverride,
- params.grantedRuntimePermissions,
- params.traceMethod, params.traceCookie, params.certificates,
- params.installReason);
- }
-
- /** Existing install */
- AsecInstallArgs(String fullCodePath, String[] instructionSets,
- boolean isExternal, boolean isForwardLocked) {
- super(OriginInfo.fromNothing(), null, null, (isExternal ? INSTALL_EXTERNAL : 0)
- | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
- instructionSets, null, null, null, 0, null /*certificates*/,
- PackageManager.INSTALL_REASON_UNKNOWN);
- // Hackily pretend we're still looking at a full code path
- if (!fullCodePath.endsWith(RES_FILE_NAME)) {
- fullCodePath = new File(fullCodePath, RES_FILE_NAME).getAbsolutePath();
- }
-
- // Extract cid from fullCodePath
- int eidx = fullCodePath.lastIndexOf("/");
- String subStr1 = fullCodePath.substring(0, eidx);
- int sidx = subStr1.lastIndexOf("/");
- cid = subStr1.substring(sidx+1, eidx);
- setMountPath(subStr1);
- }
-
- AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) {
- super(OriginInfo.fromNothing(), null, null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0)
- | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
- instructionSets, null, null, null, 0, null /*certificates*/,
- PackageManager.INSTALL_REASON_UNKNOWN);
- this.cid = cid;
- setMountPath(PackageHelper.getSdDir(cid));
- }
-
- void createCopyFile() {
- cid = mInstallerService.allocateExternalStageCidLegacy();
- }
-
- int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
- if (origin.staged && origin.cid != null) {
- if (DEBUG_INSTALL) Slog.d(TAG, origin.cid + " already staged; skipping copy");
- cid = origin.cid;
- setMountPath(PackageHelper.getSdDir(cid));
- return PackageManager.INSTALL_SUCCEEDED;
- }
-
- if (temp) {
- createCopyFile();
- } else {
- /*
- * Pre-emptively destroy the container since it's destroyed if
- * copying fails due to it existing anyway.
- */
- PackageHelper.destroySdDir(cid);
- }
-
- final String newMountPath = imcs.copyPackageToContainer(
- origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternalAsec(),
- isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */));
-
- if (newMountPath != null) {
- setMountPath(newMountPath);
- return PackageManager.INSTALL_SUCCEEDED;
- } else {
- return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- }
- }
-
- @Override
- String getCodePath() {
- return packagePath;
- }
-
- @Override
- String getResourcePath() {
- return resourcePath;
- }
-
- int doPreInstall(int status) {
- if (status != PackageManager.INSTALL_SUCCEEDED) {
- // Destroy container
- PackageHelper.destroySdDir(cid);
- } else {
- boolean mounted = PackageHelper.isContainerMounted(cid);
- if (!mounted) {
- String newMountPath = PackageHelper.mountSdDir(cid, getEncryptKey(),
- Process.SYSTEM_UID);
- if (newMountPath != null) {
- setMountPath(newMountPath);
- } else {
- return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- }
- }
- }
- return status;
- }
-
- boolean doRename(int status, PackageParser.Package pkg, String oldCodePath) {
- String newCacheId = getNextCodePath(oldCodePath, pkg.packageName, "/" + RES_FILE_NAME);
- String newMountPath = null;
- if (PackageHelper.isContainerMounted(cid)) {
- // Unmount the container
- if (!PackageHelper.unMountSdDir(cid)) {
- Slog.i(TAG, "Failed to unmount " + cid + " before renaming");
- return false;
- }
- }
- if (!PackageHelper.renameSdDir(cid, newCacheId)) {
- Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId +
- " which might be stale. Will try to clean up.");
- // Clean up the stale container and proceed to recreate.
- if (!PackageHelper.destroySdDir(newCacheId)) {
- Slog.e(TAG, "Very strange. Cannot clean up stale container " + newCacheId);
- return false;
- }
- // Successfully cleaned up stale container. Try to rename again.
- if (!PackageHelper.renameSdDir(cid, newCacheId)) {
- Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId
- + " inspite of cleaning it up.");
- return false;
- }
- }
- if (!PackageHelper.isContainerMounted(newCacheId)) {
- Slog.w(TAG, "Mounting container " + newCacheId);
- newMountPath = PackageHelper.mountSdDir(newCacheId,
- getEncryptKey(), Process.SYSTEM_UID);
- } else {
- newMountPath = PackageHelper.getSdDir(newCacheId);
- }
- if (newMountPath == null) {
- Slog.w(TAG, "Failed to get cache path for " + newCacheId);
- return false;
- }
- Log.i(TAG, "Succesfully renamed " + cid +
- " to " + newCacheId +
- " at new path: " + newMountPath);
- cid = newCacheId;
-
- final File beforeCodeFile = new File(packagePath);
- setMountPath(newMountPath);
- final File afterCodeFile = new File(packagePath);
-
- // Reflect the rename in scanned details
- pkg.setCodePath(afterCodeFile.getAbsolutePath());
- pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
- afterCodeFile, pkg.baseCodePath));
- pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
- afterCodeFile, pkg.splitCodePaths));
-
- // Reflect the rename in app info
- pkg.setApplicationVolumeUuid(pkg.volumeUuid);
- pkg.setApplicationInfoCodePath(pkg.codePath);
- pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
- pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
- pkg.setApplicationInfoResourcePath(pkg.codePath);
- pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
- pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
-
- return true;
- }
-
- private void setMountPath(String mountPath) {
- final File mountFile = new File(mountPath);
-
- final File monolithicFile = new File(mountFile, RES_FILE_NAME);
- if (monolithicFile.exists()) {
- packagePath = monolithicFile.getAbsolutePath();
- if (isFwdLocked()) {
- resourcePath = new File(mountFile, PUBLIC_RES_FILE_NAME).getAbsolutePath();
- } else {
- resourcePath = packagePath;
- }
- } else {
- packagePath = mountFile.getAbsolutePath();
- resourcePath = packagePath;
- }
- }
-
- int doPostInstall(int status, int uid) {
- if (status != PackageManager.INSTALL_SUCCEEDED) {
- cleanUp();
- } else {
- final int groupOwner;
- final String protectedFile;
- if (isFwdLocked()) {
- groupOwner = UserHandle.getSharedAppGid(uid);
- protectedFile = RES_FILE_NAME;
- } else {
- groupOwner = -1;
- protectedFile = null;
- }
-
- if (uid < Process.FIRST_APPLICATION_UID
- || !PackageHelper.fixSdPermissions(cid, groupOwner, protectedFile)) {
- Slog.e(TAG, "Failed to finalize " + cid);
- PackageHelper.destroySdDir(cid);
- return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- }
-
- boolean mounted = PackageHelper.isContainerMounted(cid);
- if (!mounted) {
- PackageHelper.mountSdDir(cid, getEncryptKey(), Process.myUid());
- }
- }
- return status;
- }
-
- private void cleanUp() {
- if (DEBUG_SD_INSTALL) Slog.i(TAG, "cleanUp");
-
- // Destroy secure container
- PackageHelper.destroySdDir(cid);
- }
-
- private List<String> getAllCodePaths() {
- final File codeFile = new File(getCodePath());
- if (codeFile != null && codeFile.exists()) {
- try {
- final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0);
- return pkg.getAllCodePaths();
- } catch (PackageParserException e) {
- // Ignored; we tried our best
- }
- }
- return Collections.EMPTY_LIST;
- }
-
- void cleanUpResourcesLI() {
- // Enumerate all code paths before deleting
- cleanUpResourcesLI(getAllCodePaths());
- }
-
- private void cleanUpResourcesLI(List<String> allCodePaths) {
- cleanUp();
- removeDexFiles(allCodePaths, instructionSets);
- }
-
- String getPackageName() {
- return getAsecPackageName(cid);
- }
-
- boolean doPostDeleteLI(boolean delete) {
- if (DEBUG_SD_INSTALL) Slog.i(TAG, "doPostDeleteLI() del=" + delete);
- final List<String> allCodePaths = getAllCodePaths();
- boolean mounted = PackageHelper.isContainerMounted(cid);
- if (mounted) {
- // Unmount first
- if (PackageHelper.unMountSdDir(cid)) {
- mounted = false;
- }
- }
- if (!mounted && delete) {
- cleanUpResourcesLI(allCodePaths);
- }
- return !mounted;
- }
-
- @Override
- int doPreCopy() {
- if (isFwdLocked()) {
- if (!PackageHelper.fixSdPermissions(cid, getPackageUid(DEFAULT_CONTAINER_PACKAGE,
- MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM), RES_FILE_NAME)) {
- return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- }
- }
-
- return PackageManager.INSTALL_SUCCEEDED;
- }
-
- @Override
- int doPostCopy(int uid) {
- if (isFwdLocked()) {
- if (uid < Process.FIRST_APPLICATION_UID
- || !PackageHelper.fixSdPermissions(cid, UserHandle.getSharedAppGid(uid),
- RES_FILE_NAME)) {
- Slog.e(TAG, "Failed to finalize " + cid);
- PackageHelper.destroySdDir(cid);
- return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- }
- }
-
- return PackageManager.INSTALL_SUCCEEDED;
- }
- }
-
- /**
* Logic to handle movement of existing installed applications.
*/
class MoveInstallArgs extends InstallArgs {
@@ -17627,9 +16535,9 @@ public class PackageManagerService extends IPackageManager.Stub
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- private boolean shouldCheckUpgradeKeySetLP(PackageSetting oldPs, int scanFlags) {
+ private boolean shouldCheckUpgradeKeySetLP(PackageSettingBase oldPs, int scanFlags) {
// Can't rotate keys during boot or if sharedUser.
- if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.sharedUser != null
+ if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
|| !oldPs.keySetData.isUsingUpgradeKeySets()) {
return false;
}
@@ -17649,7 +16557,7 @@ public class PackageManagerService extends IPackageManager.Stub
return true;
}
- private boolean checkUpgradeKeySetLP(PackageSetting oldPS, PackageParser.Package newPkg) {
+ private boolean checkUpgradeKeySetLP(PackageSettingBase oldPS, PackageParser.Package newPkg) {
// Upgrade keysets are being used. Determine if new package has a superset of the
// required keys.
long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
@@ -18225,66 +17133,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private int[] revokeUnusedSharedUserPermissionsLPw(SharedUserSetting su, int[] allUserIds) {
- // Collect all used permissions in the UID
- ArraySet<String> usedPermissions = new ArraySet<>();
- final int packageCount = su.packages.size();
- for (int i = 0; i < packageCount; i++) {
- PackageSetting ps = su.packages.valueAt(i);
- if (ps.pkg == null) {
- continue;
- }
- final int requestedPermCount = ps.pkg.requestedPermissions.size();
- for (int j = 0; j < requestedPermCount; j++) {
- String permission = ps.pkg.requestedPermissions.get(j);
- BasePermission bp = mSettings.mPermissions.get(permission);
- if (bp != null) {
- usedPermissions.add(permission);
- }
- }
- }
-
- PermissionsState permissionsState = su.getPermissionsState();
- // Prune install permissions
- List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
- final int installPermCount = installPermStates.size();
- for (int i = installPermCount - 1; i >= 0; i--) {
- PermissionState permissionState = installPermStates.get(i);
- if (!usedPermissions.contains(permissionState.getName())) {
- BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
- if (bp != null) {
- permissionsState.revokeInstallPermission(bp);
- permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
- PackageManager.MASK_PERMISSION_FLAGS, 0);
- }
- }
- }
-
- int[] runtimePermissionChangedUserIds = EmptyArray.INT;
-
- // Prune runtime permissions
- for (int userId : allUserIds) {
- List<PermissionState> runtimePermStates = permissionsState
- .getRuntimePermissionStates(userId);
- final int runtimePermCount = runtimePermStates.size();
- for (int i = runtimePermCount - 1; i >= 0; i--) {
- PermissionState permissionState = runtimePermStates.get(i);
- if (!usedPermissions.contains(permissionState.getName())) {
- BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
- if (bp != null) {
- permissionsState.revokeRuntimePermission(bp, userId);
- permissionsState.updatePermissionFlags(bp, userId,
- PackageManager.MASK_PERMISSION_FLAGS, 0);
- runtimePermissionChangedUserIds = ArrayUtils.appendInt(
- runtimePermissionChangedUserIds, userId);
- }
- }
- }
- }
-
- return runtimePermissionChangedUserIds;
- }
-
private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
int[] allUsers, PackageInstalledInfo res, UserHandle user, int installReason) {
// Update the parent package setting
@@ -18685,8 +17533,9 @@ public class PackageManagerService extends IPackageManager.Stub
int N = pkg.permissions.size();
for (int i = N-1; i >= 0; i--) {
- PackageParser.Permission perm = pkg.permissions.get(i);
- BasePermission bp = mSettings.mPermissions.get(perm.info.name);
+ final PackageParser.Permission perm = pkg.permissions.get(i);
+ final BasePermission bp =
+ (BasePermission) mPermissionManager.getPermissionTEMP(perm.info.name);
// Don't allow anyone but the system to define ephemeral permissions.
if ((perm.info.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
@@ -18703,25 +17552,26 @@ public class PackageManagerService extends IPackageManager.Stub
// also includes the "updating the same package" case, of course.
// "updating same package" could also involve key-rotation.
final boolean sigsOk;
- if (bp.sourcePackage.equals(pkg.packageName)
- && (bp.packageSetting instanceof PackageSetting)
- && (shouldCheckUpgradeKeySetLP((PackageSetting) bp.packageSetting,
+ final String sourcePackageName = bp.getSourcePackageName();
+ final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
+ if (sourcePackageName.equals(pkg.packageName)
+ && (shouldCheckUpgradeKeySetLP(sourcePackageSetting,
scanFlags))) {
- sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg);
+ sigsOk = checkUpgradeKeySetLP(sourcePackageSetting, pkg);
} else {
- sigsOk = compareSignatures(bp.packageSetting.signatures.mSignatures,
+ sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
}
if (!sigsOk) {
// If the owning package is the system itself, we log but allow
// install to proceed; we fail the install on all other permission
// redefinitions.
- if (!bp.sourcePackage.equals("android")) {
+ if (!sourcePackageName.equals("android")) {
res.setError(INSTALL_FAILED_DUPLICATE_PERMISSION, "Package "
+ pkg.packageName + " attempting to redeclare permission "
- + perm.info.name + " already owned by " + bp.sourcePackage);
+ + perm.info.name + " already owned by " + sourcePackageName);
res.origPermission = perm.info.name;
- res.origPackage = bp.sourcePackage;
+ res.origPackage = sourcePackageName;
return;
} else {
Slog.w(TAG, "Package " + pkg.packageName
@@ -18740,7 +17590,7 @@ public class PackageManagerService extends IPackageManager.Stub
Slog.w(TAG, "Package " + pkg.packageName + " trying to change a "
+ "non-runtime permission " + perm.info.name
+ " to runtime; keeping old protection level");
- perm.info.protectionLevel = bp.protectionLevel;
+ perm.info.protectionLevel = bp.getProtectionLevel();
}
}
}
@@ -18855,7 +17705,13 @@ public class PackageManagerService extends IPackageManager.Stub
// TODO: Layering violation
BackgroundDexOptService.notifyPackageChanged(pkg.packageName);
- startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
+ if (!instantApp) {
+ startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
+ } else {
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.d(TAG, "Not verifying instant app install for app links: " + pkgName);
+ }
+ }
try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
"installPackageLI")) {
@@ -20440,7 +19296,7 @@ public class PackageManagerService extends IPackageManager.Stub
android.Manifest.permission.CLEAR_APP_USER_DATA, null);
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, false /* checkShell */, "clear application data");
final PackageSetting ps = mSettings.getPackageLPr(packageName);
@@ -20579,9 +19435,9 @@ public class PackageManagerService extends IPackageManager.Stub
final int permissionCount = ps.pkg.requestedPermissions.size();
for (int i = 0; i < permissionCount; i++) {
- String permission = ps.pkg.requestedPermissions.get(i);
-
- BasePermission bp = mSettings.mPermissions.get(permission);
+ final String permName = ps.pkg.requestedPermissions.get(i);
+ final BasePermission bp =
+ (BasePermission) mPermissionManager.getPermissionTEMP(permName);
if (bp == null) {
continue;
}
@@ -20593,7 +19449,7 @@ public class PackageManagerService extends IPackageManager.Stub
for (int j = 0; j < packageCount; j++) {
PackageSetting pkg = ps.sharedUser.packages.valueAt(j);
if (pkg.pkg != null && !pkg.pkg.packageName.equals(ps.pkg.packageName)
- && pkg.pkg.requestedPermissions.contains(permission)) {
+ && pkg.pkg.requestedPermissions.contains(permName)) {
used = true;
break;
}
@@ -20603,13 +19459,13 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- PermissionsState permissionsState = ps.getPermissionsState();
+ final PermissionsState permissionsState = ps.getPermissionsState();
- final int oldFlags = permissionsState.getPermissionFlags(bp.name, userId);
+ final int oldFlags = permissionsState.getPermissionFlags(permName, userId);
// Always clear the user settable flags.
- final boolean hasInstallState = permissionsState.getInstallPermissionState(
- bp.name) != null;
+ final boolean hasInstallState =
+ permissionsState.getInstallPermissionState(permName) != null;
// If permission review is enabled and this is a legacy app, mark the
// permission as requiring a review as this is the initial state.
int flags = 0;
@@ -20709,7 +19565,7 @@ public class PackageManagerService extends IPackageManager.Stub
final int callingUid = Binder.getCallingUid();
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DELETE_CACHE_FILES, null);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
/* requireFullPermission= */ true, /* checkShell= */ false,
"delete application cache files");
final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission(
@@ -20829,7 +19685,7 @@ public class PackageManagerService extends IPackageManager.Stub
String opname) {
// writer
int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, false /* checkShell */, "add preferred activity");
if (filter.countActions() == 0) {
Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
@@ -20894,7 +19750,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, false /* checkShell */,
"replace preferred activity");
synchronized (mPackages) {
@@ -21571,8 +20427,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
newFlagSet |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
}
if (DEBUG_BACKUP) {
- Slog.v(TAG, " + Restoring grant: pkg=" + pkgName + " perm=" + permName
- + " granted=" + isGranted + " bits=0x" + Integer.toHexString(newFlagSet));
+ Slog.v(TAG, " + Restoring grant:"
+ + " pkg=" + pkgName
+ + " perm=" + permName
+ + " granted=" + isGranted
+ + " bits=0x" + Integer.toHexString(newFlagSet));
}
final PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
@@ -21581,13 +20440,15 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
Slog.v(TAG, " + already installed; applying");
}
PermissionsState perms = ps.getPermissionsState();
- BasePermission bp = mSettings.mPermissions.get(permName);
+ BasePermission bp =
+ (BasePermission) mPermissionManager.getPermissionTEMP(permName);
if (bp != null) {
if (isGranted) {
perms.grantRuntimePermission(bp, userId);
}
if (newFlagSet != 0) {
- perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
+ perms.updatePermissionFlags(
+ bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
}
}
} else {
@@ -21616,7 +20477,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
int callingUid = Binder.getCallingUid();
enforceOwnerRights(ownerPackage, callingUid);
- enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+ PackageManagerServiceUtils.enforceShellRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
if (intentFilter.countActions() == 0) {
Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions");
return;
@@ -21647,7 +20509,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
final int callingUid = Binder.getCallingUid();
enforceOwnerRights(ownerPackage, callingUid);
- enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+ PackageManagerServiceUtils.enforceShellRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
synchronized (mPackages) {
CrossProfileIntentResolver resolver =
mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
@@ -21877,7 +20740,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
permission = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
}
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /* requireFullPermission */, true /* checkShell */, "set enabled");
final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
boolean sendNow = false;
@@ -22166,7 +21029,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
if (!sUserManager.exists(userId)) {
return;
}
- enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/,
false /* checkShell */, "flushPackageRestrictions");
synchronized (mPackages) {
mSettings.writePackageRestrictionsLPr(userId);
@@ -22208,7 +21071,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
final int permission = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, true /* checkShell */, "stop package");
// writer
synchronized (mPackages) {
@@ -22248,7 +21111,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
public int getApplicationEnabledSetting(String packageName, int userId) {
if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /* requireFullPermission */, false /* checkShell */, "get enabled");
// reader
synchronized (mPackages) {
@@ -22263,7 +21126,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
public int getComponentEnabledSetting(ComponentName component, int userId) {
if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
false /*requireFullPermission*/, false /*checkShell*/, "getComponentEnabled");
synchronized (mPackages) {
if (filterAppAccessLPr(mSettings.getPackageLPr(component.getPackageName()), callingUid,
@@ -22361,7 +21224,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
// If we upgraded grant all default permissions before kicking off.
for (int userId : grantPermissionsUserIds) {
- mDefaultPermissionPolicy.grantDefaultPermissions(userId);
+ mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
}
if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
@@ -22465,85 +21328,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
return buf.toString();
}
- static class DumpState {
- public static final int DUMP_LIBS = 1 << 0;
- public static final int DUMP_FEATURES = 1 << 1;
- public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
- public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
- public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
- public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
- public static final int DUMP_PERMISSIONS = 1 << 6;
- public static final int DUMP_PACKAGES = 1 << 7;
- public static final int DUMP_SHARED_USERS = 1 << 8;
- public static final int DUMP_MESSAGES = 1 << 9;
- public static final int DUMP_PROVIDERS = 1 << 10;
- public static final int DUMP_VERIFIERS = 1 << 11;
- public static final int DUMP_PREFERRED = 1 << 12;
- public static final int DUMP_PREFERRED_XML = 1 << 13;
- public static final int DUMP_KEYSETS = 1 << 14;
- public static final int DUMP_VERSION = 1 << 15;
- public static final int DUMP_INSTALLS = 1 << 16;
- public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
- public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
- public static final int DUMP_FROZEN = 1 << 19;
- public static final int DUMP_DEXOPT = 1 << 20;
- public static final int DUMP_COMPILER_STATS = 1 << 21;
- public static final int DUMP_CHANGES = 1 << 22;
- public static final int DUMP_VOLUMES = 1 << 23;
-
- public static final int OPTION_SHOW_FILTERS = 1 << 0;
-
- private int mTypes;
-
- private int mOptions;
-
- private boolean mTitlePrinted;
-
- private SharedUserSetting mSharedUser;
-
- public boolean isDumping(int type) {
- if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
- return true;
- }
-
- return (mTypes & type) != 0;
- }
-
- public void setDump(int type) {
- mTypes |= type;
- }
-
- public boolean isOptionEnabled(int option) {
- return (mOptions & option) != 0;
- }
-
- public void setOptionEnabled(int option) {
- mOptions |= option;
- }
-
- public boolean onTitlePrinted() {
- final boolean printed = mTitlePrinted;
- mTitlePrinted = true;
- return printed;
- }
-
- public boolean getTitlePrinted() {
- return mTitlePrinted;
- }
-
- public void setTitlePrinted(boolean enabled) {
- mTitlePrinted = enabled;
- }
-
- public SharedUserSetting getSharedUser() {
- return mSharedUser;
- }
-
- public void setSharedUser(SharedUserSetting user) {
- mSharedUser = user;
- }
- }
-
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -23401,135 +22185,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
}
- /*
- * Update media status on PackageManager.
- */
- @Override
- public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) {
- enforceSystemOrRoot("Media status can only be updated by the system");
- // reader; this apparently protects mMediaMounted, but should probably
- // be a different lock in that case.
- synchronized (mPackages) {
- Log.i(TAG, "Updating external media status from "
- + (mMediaMounted ? "mounted" : "unmounted") + " to "
- + (mediaStatus ? "mounted" : "unmounted"));
- if (DEBUG_SD_INSTALL)
- Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + mediaStatus
- + ", mMediaMounted=" + mMediaMounted);
- if (mediaStatus == mMediaMounted) {
- final Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1
- : 0, -1);
- mHandler.sendMessage(msg);
- return;
- }
- mMediaMounted = mediaStatus;
- }
- // Queue up an async operation since the package installation may take a
- // little while.
- mHandler.post(new Runnable() {
- public void run() {
- updateExternalMediaStatusInner(mediaStatus, reportStatus, true);
- }
- });
- }
-
- /**
- * Called by StorageManagerService when the initial ASECs to scan are available.
- * Should block until all the ASEC containers are finished being scanned.
- */
- public void scanAvailableAsecs() {
- updateExternalMediaStatusInner(true, false, false);
- }
-
- /*
- * Collect information of applications on external media, map them against
- * existing containers and update information based on current mount status.
- * Please note that we always have to report status if reportStatus has been
- * set to true especially when unloading packages.
- */
- private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus,
- boolean externalStorage) {
- ArrayMap<AsecInstallArgs, String> processCids = new ArrayMap<>();
- int[] uidArr = EmptyArray.INT;
-
- final String[] list = PackageHelper.getSecureContainerList();
- if (ArrayUtils.isEmpty(list)) {
- Log.i(TAG, "No secure containers found");
- } else {
- // Process list of secure containers and categorize them
- // as active or stale based on their package internal state.
-
- // reader
- synchronized (mPackages) {
- for (String cid : list) {
- // Leave stages untouched for now; installer service owns them
- if (PackageInstallerService.isStageName(cid)) continue;
-
- if (DEBUG_SD_INSTALL)
- Log.i(TAG, "Processing container " + cid);
- String pkgName = getAsecPackageName(cid);
- if (pkgName == null) {
- Slog.i(TAG, "Found stale container " + cid + " with no package name");
- continue;
- }
- if (DEBUG_SD_INSTALL)
- Log.i(TAG, "Looking for pkg : " + pkgName);
-
- final PackageSetting ps = mSettings.mPackages.get(pkgName);
- if (ps == null) {
- Slog.i(TAG, "Found stale container " + cid + " with no matching settings");
- continue;
- }
-
- /*
- * Skip packages that are not external if we're unmounting
- * external storage.
- */
- if (externalStorage && !isMounted && !isExternal(ps)) {
- continue;
- }
-
- final AsecInstallArgs args = new AsecInstallArgs(cid,
- getAppDexInstructionSets(ps), ps.isForwardLocked());
- // The package status is changed only if the code path
- // matches between settings and the container id.
- if (ps.codePathString != null
- && ps.codePathString.startsWith(args.getCodePath())) {
- if (DEBUG_SD_INSTALL) {
- Log.i(TAG, "Container : " + cid + " corresponds to pkg : " + pkgName
- + " at code path: " + ps.codePathString);
- }
-
- // We do have a valid package installed on sdcard
- processCids.put(args, ps.codePathString);
- final int uid = ps.appId;
- if (uid != -1) {
- uidArr = ArrayUtils.appendInt(uidArr, uid);
- }
- } else {
- Slog.i(TAG, "Found stale container " + cid + ": expected codePath="
- + ps.codePathString);
- }
- }
- }
-
- Arrays.sort(uidArr);
- }
-
- // Process packages with valid entries.
- if (isMounted) {
- if (DEBUG_SD_INSTALL)
- Log.i(TAG, "Loading packages");
- loadMediaPackages(processCids, uidArr, externalStorage);
- startCleaningPackages();
- mInstallerService.onSecureContainersAvailable();
- } else {
- if (DEBUG_SD_INSTALL)
- Log.i(TAG, "Unloading packages");
- unloadMediaPackages(processCids, uidArr, reportStatus);
- }
- }
-
private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
ArrayList<ApplicationInfo> infos, IIntentReceiver finishedReceiver) {
final int size = infos.size();
@@ -23569,193 +22224,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
}
- /*
- * Look at potentially valid container ids from processCids If package
- * information doesn't match the one on record or package scanning fails,
- * the cid is added to list of removeCids. We currently don't delete stale
- * containers.
- */
- private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr,
- boolean externalStorage) {
- ArrayList<String> pkgList = new ArrayList<String>();
- Set<AsecInstallArgs> keys = processCids.keySet();
-
- for (AsecInstallArgs args : keys) {
- String codePath = processCids.get(args);
- if (DEBUG_SD_INSTALL)
- Log.i(TAG, "Loading container : " + args.cid);
- int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
- try {
- // Make sure there are no container errors first.
- if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) {
- Slog.e(TAG, "Failed to mount cid : " + args.cid
- + " when installing from sdcard");
- continue;
- }
- // Check code path here.
- if (codePath == null || !codePath.startsWith(args.getCodePath())) {
- Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath()
- + " does not match one in settings " + codePath);
- continue;
- }
- // Parse package
- int parseFlags = mDefParseFlags;
- if (args.isExternalAsec()) {
- parseFlags |= PackageParser.PARSE_EXTERNAL_STORAGE;
- }
- if (args.isFwdLocked()) {
- parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
- }
-
- synchronized (mInstallLock) {
- PackageParser.Package pkg = null;
- try {
- // Sadly we don't know the package name yet to freeze it
- pkg = scanPackageTracedLI(new File(codePath), parseFlags,
- SCAN_IGNORE_FROZEN, 0, null);
- } catch (PackageManagerException e) {
- Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage());
- }
- // Scan the package
- if (pkg != null) {
- /*
- * TODO why is the lock being held? doPostInstall is
- * called in other places without the lock. This needs
- * to be straightened out.
- */
- // writer
- synchronized (mPackages) {
- retCode = PackageManager.INSTALL_SUCCEEDED;
- pkgList.add(pkg.packageName);
- // Post process args
- args.doPostInstall(PackageManager.INSTALL_SUCCEEDED,
- pkg.applicationInfo.uid);
- }
- } else {
- Slog.i(TAG, "Failed to install pkg from " + codePath + " from sdcard");
- }
- }
-
- } finally {
- if (retCode != PackageManager.INSTALL_SUCCEEDED) {
- Log.w(TAG, "Container " + args.cid + " is stale, retCode=" + retCode);
- }
- }
- }
- // writer
- synchronized (mPackages) {
- // If the platform SDK has changed since the last time we booted,
- // we need to re-grant app permission to catch any new ones that
- // appear. This is really a hack, and means that apps can in some
- // cases get permissions that the user didn't initially explicitly
- // allow... it would be nice to have some better way to handle
- // this situation.
- final VersionInfo ver = externalStorage ? mSettings.getExternalVersion()
- : mSettings.getInternalVersion();
- final String volumeUuid = externalStorage ? StorageManager.UUID_PRIMARY_PHYSICAL
- : StorageManager.UUID_PRIVATE_INTERNAL;
-
- int updateFlags = UPDATE_PERMISSIONS_ALL;
- if (ver.sdkVersion != mSdkVersion) {
- logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
- + mSdkVersion + "; regranting permissions for external");
- updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
- }
- updatePermissionsLPw(null, null, volumeUuid, updateFlags);
-
- // Yay, everything is now upgraded
- ver.forceCurrent();
-
- // can downgrade to reader
- // Persist settings
- mSettings.writeLPr();
- }
- // Send a broadcast to let everyone know we are done processing
- if (pkgList.size() > 0) {
- sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null);
- }
- }
-
- /*
- * Utility method to unload a list of specified containers
- */
- private void unloadAllContainers(Set<AsecInstallArgs> cidArgs) {
- // Just unmount all valid containers.
- for (AsecInstallArgs arg : cidArgs) {
- synchronized (mInstallLock) {
- arg.doPostDeleteLI(false);
- }
- }
- }
-
- /*
- * Unload packages mounted on external media. This involves deleting package
- * data from internal structures, sending broadcasts about disabled packages,
- * gc'ing to free up references, unmounting all secure containers
- * corresponding to packages on external media, and posting a
- * UPDATED_MEDIA_STATUS message if status has been requested. Please note
- * that we always have to post this message if status has been requested no
- * matter what.
- */
- private void unloadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int uidArr[],
- final boolean reportStatus) {
- if (DEBUG_SD_INSTALL)
- Log.i(TAG, "unloading media packages");
- ArrayList<String> pkgList = new ArrayList<String>();
- ArrayList<AsecInstallArgs> failedList = new ArrayList<AsecInstallArgs>();
- final Set<AsecInstallArgs> keys = processCids.keySet();
- for (AsecInstallArgs args : keys) {
- String pkgName = args.getPackageName();
- if (DEBUG_SD_INSTALL)
- Log.i(TAG, "Trying to unload pkg : " + pkgName);
- // Delete package internally
- PackageRemovedInfo outInfo = new PackageRemovedInfo(this);
- synchronized (mInstallLock) {
- final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
- final boolean res;
- try (PackageFreezer freezer = freezePackageForDelete(pkgName, deleteFlags,
- "unloadMediaPackages")) {
- res = deletePackageLIF(pkgName, null, false, null, deleteFlags, outInfo, false,
- null);
- }
- if (res) {
- pkgList.add(pkgName);
- } else {
- Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName);
- failedList.add(args);
- }
- }
- }
-
- // reader
- synchronized (mPackages) {
- // We didn't update the settings after removing each package;
- // write them now for all packages.
- mSettings.writeLPr();
- }
-
- // We have to absolutely send UPDATED_MEDIA_STATUS only
- // after confirming that all the receivers processed the ordered
- // broadcast when packages get disabled, force a gc to clean things up.
- // and unload all the containers.
- if (pkgList.size() > 0) {
- sendResourcesChangedBroadcast(false, false, pkgList, uidArr,
- new IIntentReceiver.Stub() {
- public void performReceive(Intent intent, int resultCode, String data,
- Bundle extras, boolean ordered, boolean sticky,
- int sendingUser) throws RemoteException {
- Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS,
- reportStatus ? 1 : 0, 1, keys);
- mHandler.sendMessage(msg);
- }
- });
- } else {
- Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 : 0, -1,
- keys);
- mHandler.sendMessage(msg);
- }
- }
-
private void loadPrivatePackages(final VolumeInfo vol) {
mHandler.post(new Runnable() {
@Override
@@ -24841,7 +23309,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
void onNewUserCreated(final int userId) {
- mDefaultPermissionPolicy.grantDefaultPermissions(userId);
+ synchronized(mPackages) {
+ mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
+ }
// If permission review for legacy apps is required, we represent
// dagerous permissions for such apps as always granted runtime
// permissions to keep per user flag state whether review is needed.
@@ -24873,7 +23343,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
synchronized (mPackages) {
if (mSettings.mReadExternalStorageEnforced == null
|| mSettings.mReadExternalStorageEnforced != enforced) {
- mSettings.mReadExternalStorageEnforced = enforced;
+ mSettings.mReadExternalStorageEnforced =
+ enforced ? Boolean.TRUE : Boolean.FALSE;
mSettings.writeLPr();
}
}
@@ -25245,74 +23716,164 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
return results;
}
+
+ // NB: this differentiates between preloads and sideloads
+ @Override
+ public String getInstallerForPackage(String packageName) throws RemoteException {
+ final String installerName = getInstallerPackageName(packageName);
+ if (!TextUtils.isEmpty(installerName)) {
+ return installerName;
+ }
+ // differentiate between preload and sideload
+ int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+ ApplicationInfo appInfo = getApplicationInfo(packageName,
+ /*flags*/ 0,
+ /*userId*/ callingUser);
+ if (appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return "preload";
+ }
+ return "";
+ }
+
+ @Override
+ public int getVersionCodeForPackage(String packageName) throws RemoteException {
+ try {
+ int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+ PackageInfo pInfo = getPackageInfo(packageName, 0, callingUser);
+ if (pInfo != null) {
+ return pInfo.versionCode;
+ }
+ } catch (Exception e) {
+ }
+ return 0;
+ }
}
private class PackageManagerInternalImpl extends PackageManagerInternal {
@Override
- public void setLocationPackagesProvider(PackagesProvider provider) {
+ public void updatePermissionFlagsTEMP(String permName, String packageName, int flagMask,
+ int flagValues, int userId) {
+ PackageManagerService.this.updatePermissionFlags(
+ permName, packageName, flagMask, flagValues, userId);
+ }
+
+ @Override
+ public int getPermissionFlagsTEMP(String permName, String packageName, int userId) {
+ return PackageManagerService.this.getPermissionFlags(permName, packageName, userId);
+ }
+
+ @Override
+ public Object enforcePermissionTreeTEMP(String permName, int callingUid) {
synchronized (mPackages) {
- mDefaultPermissionPolicy.setLocationPackagesProviderLPw(provider);
+ return BasePermission.enforcePermissionTreeLP(
+ mSettings.mPermissionTrees, permName, callingUid);
}
}
+ @Override
+ public boolean isInstantApp(String packageName, int userId) {
+ return PackageManagerService.this.isInstantApp(packageName, userId);
+ }
@Override
- public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
+ public String getInstantAppPackageName(int uid) {
+ return PackageManagerService.this.getInstantAppPackageName(uid);
+ }
+
+ @Override
+ public boolean filterAppAccess(PackageParser.Package pkg, int callingUid, int userId) {
synchronized (mPackages) {
- mDefaultPermissionPolicy.setVoiceInteractionPackagesProviderLPw(provider);
+ return PackageManagerService.this.filterAppAccessLPr(
+ (PackageSetting) pkg.mExtras, callingUid, userId);
}
}
@Override
- public void setSmsAppPackagesProvider(PackagesProvider provider) {
+ public PackageParser.Package getPackage(String packageName) {
synchronized (mPackages) {
- mDefaultPermissionPolicy.setSmsAppPackagesProviderLPw(provider);
+ packageName = resolveInternalPackageNameLPr(
+ packageName, PackageManager.VERSION_CODE_HIGHEST);
+ return mPackages.get(packageName);
}
}
@Override
- public void setDialerAppPackagesProvider(PackagesProvider provider) {
+ public PackageParser.Package getDisabledPackage(String packageName) {
synchronized (mPackages) {
- mDefaultPermissionPolicy.setDialerAppPackagesProviderLPw(provider);
+ final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
+ return (ps != null) ? ps.pkg : null;
}
}
@Override
- public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
- synchronized (mPackages) {
- mDefaultPermissionPolicy.setSimCallManagerPackagesProviderLPw(provider);
+ public String getKnownPackageName(int knownPackage, int userId) {
+ switch(knownPackage) {
+ case PackageManagerInternal.PACKAGE_BROWSER:
+ return getDefaultBrowserPackageName(userId);
+ case PackageManagerInternal.PACKAGE_INSTALLER:
+ return mRequiredInstallerPackage;
+ case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
+ return mSetupWizardPackage;
+ case PackageManagerInternal.PACKAGE_SYSTEM:
+ return "android";
+ case PackageManagerInternal.PACKAGE_VERIFIER:
+ return mRequiredVerifierPackage;
}
+ return null;
+ }
+
+ @Override
+ public boolean isResolveActivityComponent(ComponentInfo component) {
+ return mResolveActivity.packageName.equals(component.packageName)
+ && mResolveActivity.name.equals(component.name);
+ }
+
+ @Override
+ public void setLocationPackagesProvider(PackagesProvider provider) {
+ mDefaultPermissionPolicy.setLocationPackagesProvider(provider);
+ }
+
+ @Override
+ public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
+ mDefaultPermissionPolicy.setVoiceInteractionPackagesProvider(provider);
+ }
+
+ @Override
+ public void setSmsAppPackagesProvider(PackagesProvider provider) {
+ mDefaultPermissionPolicy.setSmsAppPackagesProvider(provider);
+ }
+
+ @Override
+ public void setDialerAppPackagesProvider(PackagesProvider provider) {
+ mDefaultPermissionPolicy.setDialerAppPackagesProvider(provider);
+ }
+
+ @Override
+ public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
+ mDefaultPermissionPolicy.setSimCallManagerPackagesProvider(provider);
}
@Override
public void setSyncAdapterPackagesprovider(SyncAdapterPackagesProvider provider) {
- synchronized (mPackages) {
- mDefaultPermissionPolicy.setSyncAdapterPackagesProviderLPw(provider);
- }
+ mDefaultPermissionPolicy.setSyncAdapterPackagesProvider(provider);
}
@Override
public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
- synchronized (mPackages) {
- mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSmsAppLPr(
- packageName, userId);
- }
+ mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSmsApp(packageName, userId);
}
@Override
public void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId) {
synchronized (mPackages) {
mSettings.setDefaultDialerPackageNameLPw(packageName, userId);
- mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultDialerAppLPr(
- packageName, userId);
}
+ mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultDialerApp(packageName, userId);
}
@Override
public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
- synchronized (mPackages) {
- mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSimCallManagerLPr(
- packageName, userId);
- }
+ mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSimCallManager(
+ packageName, userId);
}
@Override
@@ -25399,6 +23960,15 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
@Override
+ public List<ResolveInfo> queryIntentServices(
+ Intent intent, int flags, int callingUid, int userId) {
+ final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
+ return PackageManagerService.this
+ .queryIntentServicesInternal(intent, resolvedType, flags, userId, callingUid,
+ false);
+ }
+
+ @Override
public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
int userId) {
return PackageManagerService.this.getHomeActivitiesAsUser(allHomeCandidates, userId);
@@ -25433,17 +24003,19 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
@Override
- public void grantRuntimePermission(String packageName, String name, int userId,
+ public void grantRuntimePermission(String packageName, String permName, int userId,
boolean overridePolicy) {
- PackageManagerService.this.grantRuntimePermission(packageName, name, userId,
- overridePolicy);
+ PackageManagerService.this.mPermissionManager.grantRuntimePermission(
+ permName, packageName, overridePolicy, getCallingUid(), userId,
+ mPermissionCallback);
}
@Override
- public void revokeRuntimePermission(String packageName, String name, int userId,
+ public void revokeRuntimePermission(String packageName, String permName, int userId,
boolean overridePolicy) {
- PackageManagerService.this.revokeRuntimePermission(packageName, name, userId,
- overridePolicy);
+ mPermissionManager.revokeRuntimePermission(
+ permName, packageName, overridePolicy, getCallingUid(), userId,
+ mPermissionCallback);
}
@Override
@@ -25565,9 +24137,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
@Override
public ResolveInfo resolveIntent(Intent intent, String resolvedType,
- int flags, int userId) {
+ int flags, int userId, boolean resolveForStart) {
return resolveIntentInternal(
- intent, resolvedType, flags, userId, true /*resolveForStart*/);
+ intent, resolvedType, flags, userId, resolveForStart);
}
@Override
@@ -25577,6 +24149,12 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
}
@Override
+ public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+ return PackageManagerService.this.resolveContentProviderInternal(
+ name, flags, userId);
+ }
+
+ @Override
public void addIsolatedUid(int isolatedUid, int ownerUid) {
synchronized (mPackages) {
mIsolatedOwners.put(isolatedUid, ownerUid);
@@ -25623,7 +24201,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
synchronized (mPackages) {
final long identity = Binder.clearCallingIdentity();
try {
- mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledCarrierAppsLPr(
+ mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledCarrierApps(
packageNames, userId);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -25637,7 +24215,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
synchronized (mPackages) {
final long identity = Binder.clearCallingIdentity();
try {
- mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServicesLPr(
+ mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServices(
packageNames, userId);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -25712,7 +24290,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
@Override
public int getInstallReason(String packageName, int userId) {
final int callingUid = Binder.getCallingUid();
- enforceCrossUserPermission(callingUid, userId,
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
true /* requireFullPermission */, false /* checkShell */,
"get install reason");
synchronized (mPackages) {
@@ -25790,7 +24368,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
public String getInstantAppAndroidId(String packageName, int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_INSTANT_APPS,
"getInstantAppAndroidId");
- enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
true /* requireFullPermission */, false /* checkShell */,
"getInstantAppAndroidId");
// Make sure the target is an Instant App.
diff --git a/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 1a97a72c..19b0d9bc 100644
--- a/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -26,14 +26,19 @@ import dalvik.system.DexFile;
public class PackageManagerServiceCompilerMapping {
// Names for compilation reasons.
static final String REASON_STRINGS[] = {
- "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive"
+ "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive", "shared"
};
+ static final int REASON_SHARED_INDEX = 6;
+
// Static block to ensure the strings array is of the right length.
static {
if (PackageManagerService.REASON_LAST + 1 != REASON_STRINGS.length) {
throw new IllegalStateException("REASON_STRINGS not correct");
}
+ if (!"shared".equals(REASON_STRINGS[REASON_SHARED_INDEX])) {
+ throw new IllegalStateException("REASON_STRINGS not correct because of shared index");
+ }
}
private static String getSystemPropertyName(int reason) {
@@ -52,11 +57,18 @@ public class PackageManagerServiceCompilerMapping {
!DexFile.isValidCompilerFilter(sysPropValue)) {
throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
+ "(reason " + REASON_STRINGS[reason] + ")");
+ } else if (!isFilterAllowedForReason(reason, sysPropValue)) {
+ throw new IllegalStateException("Value \"" + sysPropValue +"\" not allowed "
+ + "(reason " + REASON_STRINGS[reason] + ")");
}
return sysPropValue;
}
+ private static boolean isFilterAllowedForReason(int reason, String filter) {
+ return reason != REASON_SHARED_INDEX || !DexFile.isProfileGuidedCompilerFilter(filter);
+ }
+
// Check that the properties are set and valid.
// Note: this is done in a separate method so this class can be statically initialized.
static void checkProperties() {
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 25fef0a0..8f7971e1 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -22,17 +22,23 @@ import com.android.server.pm.dex.PackageDexUsage;
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
import static com.android.server.pm.PackageManagerService.TAG;
+import com.android.internal.util.ArrayUtils;
+
import android.annotation.NonNull;
import android.app.AppGlobals;
import android.content.Intent;
import android.content.pm.PackageParser;
import android.content.pm.ResolveInfo;
import android.os.Build;
+import android.os.Debug;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.system.ErrnoException;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
+import android.util.jar.StrictJarFile;
import dalvik.system.VMRuntime;
import libcore.io.Libcore;
@@ -41,9 +47,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
/**
* Class containing helper methods for the PackageManagerService.
@@ -253,4 +261,73 @@ public class PackageManagerServiceUtils {
}
return false;
}
+
+ /**
+ * Checks that the archive located at {@code fileName} has uncompressed dex file and so
+ * files that can be direclty mapped.
+ */
+ public static void logApkHasUncompressedCode(String fileName) {
+ StrictJarFile jarFile = null;
+ try {
+ jarFile = new StrictJarFile(fileName,
+ false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
+ Iterator<ZipEntry> it = jarFile.iterator();
+ while (it.hasNext()) {
+ ZipEntry entry = it.next();
+ if (entry.getName().endsWith(".dex")) {
+ if (entry.getMethod() != ZipEntry.STORED) {
+ Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
+ entry.getName());
+ } else if ((entry.getDataOffset() & 0x3) != 0) {
+ Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
+ entry.getName());
+ }
+ } else if (entry.getName().endsWith(".so")) {
+ if (entry.getMethod() != ZipEntry.STORED) {
+ Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
+ entry.getName());
+ } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
+ Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
+ entry.getName());
+ }
+ }
+ }
+ } catch (IOException ignore) {
+ Slog.wtf(TAG, "Error when parsing APK " + fileName);
+ } finally {
+ try {
+ if (jarFile != null) {
+ jarFile.close();
+ }
+ } catch (IOException ignore) {}
+ }
+ return;
+ }
+
+ /**
+ * Checks that the APKs in the given package have uncompressed dex file and so
+ * files that can be direclty mapped.
+ */
+ public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
+ logApkHasUncompressedCode(pkg.baseCodePath);
+ if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+ for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+ logApkHasUncompressedCode(pkg.splitCodePaths[i]);
+ }
+ }
+ }
+
+ public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
+ if (callingUid == Process.SHELL_UID) {
+ if (userHandle >= 0
+ && PackageManagerService.sUserManager.hasUserRestriction(
+ restriction, userHandle)) {
+ throw new SecurityException("Shell does not have permission to access user "
+ + userHandle);
+ } else if (userHandle < 0) {
+ Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
+ + userHandle + "\n\t" + Debug.getCallers(3));
+ }
+ }
+ }
}
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index 930e4f09..1fea003a 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -183,7 +183,7 @@ class PackageManagerShellCommand extends ShellCommand {
PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
null, null);
params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
- pkgLite, false, params.sessionParams.abiOverride));
+ pkgLite, params.sessionParams.abiOverride));
} catch (PackageParserException | IOException e) {
pw.println("Error: Failed to parse APK file: " + file);
throw new IllegalArgumentException(
@@ -1441,7 +1441,7 @@ class PackageManagerShellCommand extends ShellCommand {
out = session.openWrite(splitName, 0, sizeBytes);
int total = 0;
- byte[] buffer = new byte[65536];
+ byte[] buffer = new byte[1024 * 1024];
int c;
while ((c = in.read(buffer)) != -1) {
total += c;
diff --git a/com/android/server/pm/PackageSetting.java b/com/android/server/pm/PackageSetting.java
index 52bf6410..83cb2db2 100644
--- a/com/android/server/pm/PackageSetting.java
+++ b/com/android/server/pm/PackageSetting.java
@@ -23,13 +23,15 @@ import android.content.pm.UserInfo;
import android.service.pm.PackageProto;
import android.util.proto.ProtoOutputStream;
+import com.android.server.pm.permission.PermissionsState;
+
import java.io.File;
import java.util.List;
/**
* Settings data for a particular package we know about.
*/
-final class PackageSetting extends PackageSettingBase {
+public final class PackageSetting extends PackageSettingBase {
int appId;
PackageParser.Package pkg;
/**
@@ -103,12 +105,21 @@ final class PackageSetting extends PackageSettingBase {
sharedUserId = orig.sharedUserId;
}
+ @Override
public PermissionsState getPermissionsState() {
return (sharedUser != null)
? sharedUser.getPermissionsState()
: super.getPermissionsState();
}
+ public PackageParser.Package getPackage() {
+ return pkg;
+ }
+
+ public int getAppId() {
+ return appId;
+ }
+
public boolean isPrivileged() {
return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
}
@@ -125,6 +136,7 @@ final class PackageSetting extends PackageSettingBase {
return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ @Override
public boolean isSharedUser() {
return sharedUser != null;
}
@@ -136,6 +148,10 @@ final class PackageSetting extends PackageSettingBase {
return true;
}
+ public boolean hasChildPackages() {
+ return childPackageNames != null && !childPackageNames.isEmpty();
+ }
+
public void writeToProto(ProtoOutputStream proto, long fieldId, List<UserInfo> users) {
final long packageToken = proto.start(fieldId);
proto.write(PackageProto.NAME, (realName != null ? realName : name));
diff --git a/com/android/server/pm/PackageSettingBase.java b/com/android/server/pm/PackageSettingBase.java
index d3ca1fda..e19e83fc 100644
--- a/com/android/server/pm/PackageSettingBase.java
+++ b/com/android/server/pm/PackageSettingBase.java
@@ -24,14 +24,12 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageUserState;
-import android.os.storage.VolumeInfo;
import android.service.pm.PackageProto;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import com.google.android.collect.Lists;
import java.io.File;
import java.util.ArrayList;
@@ -42,7 +40,7 @@ import java.util.Set;
/**
* Settings base class for pending and resolved classes.
*/
-abstract class PackageSettingBase extends SettingBase {
+public abstract class PackageSettingBase extends SettingBase {
private static final int[] EMPTY_INT_ARRAY = new int[0];
@@ -230,6 +228,9 @@ abstract class PackageSettingBase extends SettingBase {
return updateAvailable;
}
+ public boolean isSharedUser() {
+ return false;
+ }
/**
* Makes a shallow copy of the given package settings.
*
@@ -412,7 +413,7 @@ abstract class PackageSettingBase extends SettingBase {
modifyUserState(userId).suspended = suspended;
}
- boolean getInstantApp(int userId) {
+ public boolean getInstantApp(int userId) {
return readUserState(userId).instantApp;
}
diff --git a/com/android/server/pm/SettingBase.java b/com/android/server/pm/SettingBase.java
index e17cec02..c97f5e54 100644
--- a/com/android/server/pm/SettingBase.java
+++ b/com/android/server/pm/SettingBase.java
@@ -18,6 +18,8 @@ package com.android.server.pm;
import android.content.pm.ApplicationInfo;
+import com.android.server.pm.permission.PermissionsState;
+
abstract class SettingBase {
int pkgFlags;
int pkgPrivateFlags;
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 51d3e102..00844114 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -64,7 +63,6 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
import android.service.pm.PackageServiceDumpProto;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -87,10 +85,11 @@ import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
-import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.PackageManagerService.DumpState;
-import com.android.server.pm.PermissionsState.PermissionState;
+import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.PermissionSettings;
+import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
import libcore.io.IoUtils;
@@ -127,7 +126,7 @@ import java.util.Set;
/**
* Holds information about dynamic settings.
*/
-final class Settings {
+public final class Settings {
private static final String TAG = "PackageSettings";
/**
@@ -176,7 +175,7 @@ final class Settings {
private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage";
private static final String ATTR_ENFORCEMENT = "enforcement";
- private static final String TAG_ITEM = "item";
+ public static final String TAG_ITEM = "item";
private static final String TAG_DISABLED_COMPONENTS = "disabled-components";
private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
@@ -201,7 +200,8 @@ final class Settings {
private static final String TAG_DEFAULT_DIALER = "default-dialer";
private static final String TAG_VERSION = "version";
- private static final String ATTR_NAME = "name";
+ public static final String ATTR_NAME = "name";
+ public static final String ATTR_PACKAGE = "package";
private static final String ATTR_USER = "user";
private static final String ATTR_CODE = "code";
private static final String ATTR_GRANTED = "granted";
@@ -233,7 +233,6 @@ final class Settings {
private static final String ATTR_VOLUME_UUID = "volumeUuid";
private static final String ATTR_SDK_VERSION = "sdkVersion";
private static final String ATTR_DATABASE_VERSION = "databaseVersion";
- private static final String ATTR_DONE = "done";
// Bookkeeping for restored permission grants
private static final String TAG_RESTORED_RUNTIME_PERMISSIONS = "restored-perms";
@@ -379,10 +378,6 @@ final class Settings {
private final ArrayMap<Long, Integer> mKeySetRefs =
new ArrayMap<Long, Integer>();
- // Mapping from permission names to info about them.
- final ArrayMap<String, BasePermission> mPermissions =
- new ArrayMap<String, BasePermission>();
-
// Mapping from permission tree names to info about them.
final ArrayMap<String, BasePermission> mPermissionTrees =
new ArrayMap<String, BasePermission>();
@@ -420,14 +415,16 @@ final class Settings {
private final File mSystemDir;
public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
+ /** Settings and other information about permissions */
+ private final PermissionSettings mPermissions;
- Settings(Object lock) {
- this(Environment.getDataDirectory(), lock);
+ Settings(PermissionSettings permissions, Object lock) {
+ this(Environment.getDataDirectory(), permissions, lock);
}
- Settings(File dataDir, Object lock) {
+ Settings(File dataDir, PermissionSettings permission, Object lock) {
mLock = lock;
-
+ mPermissions = permission;
mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
mSystemDir = new File(dataDir, "system");
@@ -490,7 +487,7 @@ final class Settings {
final PermissionsState perms = ps.getPermissionsState();
for (RestoredPermissionGrant grant : grants) {
- BasePermission bp = mPermissions.get(grant.permissionName);
+ BasePermission bp = mPermissions.getPermission(grant.permissionName);
if (bp != null) {
if (grant.granted) {
perms.grantRuntimePermission(bp, userId);
@@ -507,6 +504,10 @@ final class Settings {
writeRuntimePermissionsForUserLPr(userId, false);
}
+ public boolean canPropagatePermissionToInstantApp(String permName) {
+ return mPermissions.canPropagatePermissionToInstantApp(permName);
+ }
+
void setInstallerPackageName(String pkgName, String installerPkgName) {
PackageSetting p = mPackages.get(pkgName);
if (p != null) {
@@ -664,29 +665,11 @@ final class Settings {
}
}
- // Transfer ownership of permissions from one package to another.
- void transferPermissionsLPw(String origPkg, String newPkg) {
- // Transfer ownership of permissions to the new package.
- for (int i=0; i<2; i++) {
- ArrayMap<String, BasePermission> permissions =
- i == 0 ? mPermissionTrees : mPermissions;
- for (BasePermission bp : permissions.values()) {
- if (origPkg.equals(bp.sourcePackage)) {
- if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG,
- "Moving permission " + bp.name
- + " from pkg " + bp.sourcePackage
- + " to " + newPkg);
- bp.sourcePackage = newPkg;
- bp.packageSetting = null;
- bp.perm = null;
- if (bp.pendingInfo != null) {
- bp.pendingInfo.packageName = newPkg;
- }
- bp.uid = 0;
- bp.setGids(null, false);
- }
- }
- }
+ /**
+ * Transfers ownership of permissions from one package to another.
+ */
+ void transferPermissionsLPw(String origPackageName, String newPackageName) {
+ mPermissions.transferPermissions(origPackageName, newPackageName, mPermissionTrees);
}
/**
@@ -1074,7 +1057,7 @@ final class Settings {
// Update permissions
for (String eachPerm : deletedPs.pkg.requestedPermissions) {
- BasePermission bp = mPermissions.get(eachPerm);
+ BasePermission bp = mPermissions.getPermission(eachPerm);
if (bp == null) {
continue;
}
@@ -2022,8 +2005,7 @@ final class Settings {
// Specifically for backup/restore
public void processRestoredPermissionGrantLPr(String pkgName, String permission,
- boolean isGranted, int restoredFlagSet, int userId)
- throws IOException, XmlPullParserException {
+ boolean isGranted, int restoredFlagSet, int userId) {
mRuntimePermissionsPersistence.rememberRestoredUserGrantLPr(
pkgName, permission, isGranted, restoredFlagSet, userId);
}
@@ -2225,7 +2207,7 @@ final class Settings {
if (tagName.equals(TAG_ITEM)) {
String name = parser.getAttributeValue(null, ATTR_NAME);
- BasePermission bp = mPermissions.get(name);
+ BasePermission bp = mPermissions.getPermission(name);
if (bp == null) {
Slog.w(PackageManagerService.TAG, "Unknown permission: " + name);
XmlUtils.skipCurrentTag(parser);
@@ -2520,9 +2502,7 @@ final class Settings {
serializer.endTag(null, "permission-trees");
serializer.startTag(null, "permissions");
- for (BasePermission bp : mPermissions.values()) {
- writePermissionLPr(serializer, bp);
- }
+ mPermissions.writePermissions(serializer);
serializer.endTag(null, "permissions");
for (final PackageSetting pkg : mPackages.values()) {
@@ -2605,9 +2585,6 @@ final class Settings {
writeAllRuntimePermissionsLPr();
return;
- } catch(XmlPullParserException e) {
- Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
- + "current changes will be lost at reboot", e);
} catch(java.io.IOException e) {
Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
+ "current changes will be lost at reboot", e);
@@ -2951,7 +2928,6 @@ final class Settings {
void writeUpgradeKeySetsLPr(XmlSerializer serializer,
PackageKeySetData data) throws IOException {
- long properSigning = data.getProperSigningKeySet();
if (data.isUsingUpgradeKeySets()) {
for (long id : data.getUpgradeKeySets()) {
serializer.startTag(null, "upgrade-keyset");
@@ -2971,32 +2947,8 @@ final class Settings {
}
}
- void writePermissionLPr(XmlSerializer serializer, BasePermission bp)
- throws XmlPullParserException, java.io.IOException {
- if (bp.sourcePackage != null) {
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, bp.name);
- serializer.attribute(null, "package", bp.sourcePackage);
- if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
- serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel));
- }
- if (PackageManagerService.DEBUG_SETTINGS)
- Log.v(PackageManagerService.TAG, "Writing perm: name=" + bp.name + " type="
- + bp.type);
- if (bp.type == BasePermission.TYPE_DYNAMIC) {
- final PermissionInfo pi = bp.perm != null ? bp.perm.info : bp.pendingInfo;
- if (pi != null) {
- serializer.attribute(null, "type", "dynamic");
- if (pi.icon != 0) {
- serializer.attribute(null, "icon", Integer.toString(pi.icon));
- }
- if (pi.nonLocalizedLabel != null) {
- serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
- }
- }
- }
- serializer.endTag(null, TAG_ITEM);
- }
+ void writePermissionLPr(XmlSerializer serializer, BasePermission bp) throws IOException {
+ bp.writeLPr(serializer);
}
ArrayList<PackageSetting> getListOfIncompleteInstallPackagesLPr() {
@@ -3088,9 +3040,9 @@ final class Settings {
if (tagName.equals("package")) {
readPackageLPw(parser);
} else if (tagName.equals("permissions")) {
- readPermissionsLPw(mPermissions, parser);
+ mPermissions.readPermissions(parser);
} else if (tagName.equals("permission-trees")) {
- readPermissionsLPw(mPermissionTrees, parser);
+ PermissionSettings.readPermissions(mPermissionTrees, parser);
} else if (tagName.equals("shared-user")) {
readSharedUserLPw(parser);
} else if (tagName.equals("preferred-packages")) {
@@ -3169,7 +3121,8 @@ final class Settings {
}
} else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
- mReadExternalStorageEnforced = "1".equals(enforcement);
+ mReadExternalStorageEnforced =
+ "1".equals(enforcement) ? Boolean.TRUE : Boolean.FALSE;
} else if (tagName.equals("keyset-settings")) {
mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs);
} else if (TAG_VERSION.equals(tagName)) {
@@ -3593,72 +3546,6 @@ final class Settings {
}
}
- private int readInt(XmlPullParser parser, String ns, String name, int defValue) {
- String v = parser.getAttributeValue(ns, name);
- try {
- if (v == null) {
- return defValue;
- }
- return Integer.parseInt(v);
- } catch (NumberFormatException e) {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Error in package manager settings: attribute " + name
- + " has bad integer value " + v + " at "
- + parser.getPositionDescription());
- }
- return defValue;
- }
-
- private void readPermissionsLPw(ArrayMap<String, BasePermission> out, XmlPullParser parser)
- throws IOException, XmlPullParserException {
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- final String tagName = parser.getName();
- if (tagName.equals(TAG_ITEM)) {
- final String name = parser.getAttributeValue(null, ATTR_NAME);
- final String sourcePackage = parser.getAttributeValue(null, "package");
- final String ptype = parser.getAttributeValue(null, "type");
- if (name != null && sourcePackage != null) {
- final boolean dynamic = "dynamic".equals(ptype);
- BasePermission bp = out.get(name);
- // If the permission is builtin, do not clobber it.
- if (bp == null || bp.type != BasePermission.TYPE_BUILTIN) {
- bp = new BasePermission(name.intern(), sourcePackage,
- dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL);
- }
- bp.protectionLevel = readInt(parser, null, "protection",
- PermissionInfo.PROTECTION_NORMAL);
- bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
- if (dynamic) {
- PermissionInfo pi = new PermissionInfo();
- pi.packageName = sourcePackage.intern();
- pi.name = name.intern();
- pi.icon = readInt(parser, null, "icon", 0);
- pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
- pi.protectionLevel = bp.protectionLevel;
- bp.pendingInfo = pi;
- }
- out.put(bp.name, bp);
- } else {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Error in package manager settings: permissions has" + " no name at "
- + parser.getPositionDescription());
- }
- } else {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Unknown element reading permissions: " + parser.getName() + " at "
- + parser.getPositionDescription());
- }
- XmlUtils.skipCurrentTag(parser);
- }
- }
-
private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException,
IOException {
String name = parser.getAttributeValue(null, ATTR_NAME);
@@ -4385,10 +4272,6 @@ final class Settings {
return ps;
}
- private String compToString(ArraySet<String> cmp) {
- return cmp != null ? Arrays.toString(cmp.toArray()) : "[]";
- }
-
boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, int flags, int userId) {
final PackageSetting ps = mPackages.get(componentInfo.packageName);
if (ps == null) return false;
@@ -5001,45 +4884,8 @@ final class Settings {
void dumpPermissionsLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
DumpState dumpState) {
- boolean printedSomething = false;
- for (BasePermission p : mPermissions.values()) {
- if (packageName != null && !packageName.equals(p.sourcePackage)) {
- continue;
- }
- if (permissionNames != null && !permissionNames.contains(p.name)) {
- continue;
- }
- if (!printedSomething) {
- if (dumpState.onTitlePrinted())
- pw.println();
- pw.println("Permissions:");
- printedSomething = true;
- }
- pw.print(" Permission ["); pw.print(p.name); pw.print("] (");
- pw.print(Integer.toHexString(System.identityHashCode(p)));
- pw.println("):");
- pw.print(" sourcePackage="); pw.println(p.sourcePackage);
- pw.print(" uid="); pw.print(p.uid);
- pw.print(" gids="); pw.print(Arrays.toString(
- p.computeGids(UserHandle.USER_SYSTEM)));
- pw.print(" type="); pw.print(p.type);
- pw.print(" prot=");
- pw.println(PermissionInfo.protectionToString(p.protectionLevel));
- if (p.perm != null) {
- pw.print(" perm="); pw.println(p.perm);
- if ((p.perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
- || (p.perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
- pw.print(" flags=0x"); pw.println(Integer.toHexString(p.perm.info.flags));
- }
- }
- if (p.packageSetting != null) {
- pw.print(" packageSetting="); pw.println(p.packageSetting);
- }
- if (READ_EXTERNAL_STORAGE.equals(p.name)) {
- pw.print(" enforced=");
- pw.println(mReadExternalStorageEnforced);
- }
- }
+ mPermissions.dumpPermissions(pw, packageName, permissionNames,
+ (mReadExternalStorageEnforced == Boolean.TRUE), dumpState);
}
void dumpSharedUsersLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
@@ -5248,7 +5094,7 @@ final class Settings {
private final Handler mHandler = new MyHandler();
- private final Object mLock;
+ private final Object mPersistenceLock;
@GuardedBy("mLock")
private final SparseBooleanArray mWriteScheduled = new SparseBooleanArray();
@@ -5265,8 +5111,8 @@ final class Settings {
// The mapping keys are user ids.
private final SparseBooleanArray mDefaultPermissionsGranted = new SparseBooleanArray();
- public RuntimePermissionPersistence(Object lock) {
- mLock = lock;
+ public RuntimePermissionPersistence(Object persistenceLock) {
+ mPersistenceLock = persistenceLock;
}
public boolean areDefaultRuntimPermissionsGrantedLPr(int userId) {
@@ -5321,7 +5167,7 @@ final class Settings {
ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();
- synchronized (mLock) {
+ synchronized (mPersistenceLock) {
mWriteScheduled.delete(userId);
final int packageCount = mPackages.size();
@@ -5470,7 +5316,7 @@ final class Settings {
PermissionsState permissionsState = sb.getPermissionsState();
for (PermissionState permissionState
: permissionsState.getRuntimePermissionStates(userId)) {
- BasePermission bp = mPermissions.get(permissionState.getName());
+ BasePermission bp = mPermissions.getPermission(permissionState.getName());
if (bp != null) {
permissionsState.revokeRuntimePermission(bp, userId);
permissionsState.updatePermissionFlags(bp, userId,
@@ -5631,7 +5477,7 @@ final class Settings {
switch (parser.getName()) {
case TAG_ITEM: {
String name = parser.getAttributeValue(null, ATTR_NAME);
- BasePermission bp = mPermissions.get(name);
+ BasePermission bp = mPermissions.getPermission(name);
if (bp == null) {
Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
XmlUtils.skipCurrentTag(parser);
diff --git a/com/android/server/pm/SharedUserSetting.java b/com/android/server/pm/SharedUserSetting.java
index 06e020a0..a0dadae3 100644
--- a/com/android/server/pm/SharedUserSetting.java
+++ b/com/android/server/pm/SharedUserSetting.java
@@ -16,12 +16,18 @@
package com.android.server.pm;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
import android.util.ArraySet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
/**
* Settings data for a particular shared user ID we know about.
*/
-final class SharedUserSetting extends SettingBase {
+public final class SharedUserSetting extends SettingBase {
final String name;
int userId;
@@ -73,4 +79,18 @@ final class SharedUserSetting extends SettingBase {
setPrivateFlags(this.pkgPrivateFlags | packageSetting.pkgPrivateFlags);
}
}
+
+ public @Nullable List<PackageParser.Package> getPackages() {
+ if (packages == null || packages.size() == 0) {
+ return null;
+ }
+ final ArrayList<PackageParser.Package> pkgList = new ArrayList<>(packages.size());
+ for (PackageSetting ps : packages) {
+ if (ps == null) {
+ continue;
+ }
+ pkgList.add(ps.pkg);
+ }
+ return pkgList;
+ }
}
diff --git a/com/android/server/pm/ShortcutLauncher.java b/com/android/server/pm/ShortcutLauncher.java
index 30608403..f922ad19 100644
--- a/com/android/server/pm/ShortcutLauncher.java
+++ b/com/android/server/pm/ShortcutLauncher.java
@@ -25,6 +25,7 @@ import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutUser.PackageWithUser;
import org.json.JSONException;
@@ -293,7 +294,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
return ret;
}
- public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
pw.println();
pw.print(prefix);
diff --git a/com/android/server/pm/ShortcutPackage.java b/com/android/server/pm/ShortcutPackage.java
index 6f70f4c8..6fc1e738 100644
--- a/com/android/server/pm/ShortcutPackage.java
+++ b/com/android/server/pm/ShortcutPackage.java
@@ -33,6 +33,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
+import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutService.ShortcutOperation;
import com.android.server.pm.ShortcutService.Stats;
@@ -1144,7 +1145,7 @@ class ShortcutPackage extends ShortcutPackageItem {
return false;
}
- public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
pw.println();
pw.print(prefix);
@@ -1186,9 +1187,7 @@ class ShortcutPackage extends ShortcutPackageItem {
final int size = shortcuts.size();
for (int i = 0; i < size; i++) {
final ShortcutInfo si = shortcuts.valueAt(i);
- pw.print(prefix);
- pw.print(" ");
- pw.println(si.toInsecureString());
+ pw.println(si.toDumpString(prefix + " "));
if (si.getBitmapPath() != null) {
final long len = new File(si.getBitmapPath()).length();
pw.print(prefix);
diff --git a/com/android/server/pm/ShortcutService.java b/com/android/server/pm/ShortcutService.java
index 0e572d82..27560c5f 100644
--- a/com/android/server/pm/ShortcutService.java
+++ b/com/android/server/pm/ShortcutService.java
@@ -40,8 +40,8 @@ import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -134,6 +134,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.regex.Pattern;
/**
* TODO:
@@ -484,12 +485,13 @@ public class ShortcutService extends IShortcutService.Stub {
final private IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq) {
- handleOnUidStateChanged(uid, procState);
+ injectPostToHandler(() -> handleOnUidStateChanged(uid, procState));
}
@Override
public void onUidGone(int uid, boolean disabled) {
- handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
+ injectPostToHandler(() ->
+ handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT));
}
@Override
@@ -3454,121 +3456,265 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final DumpFilter filter = parseDumpArgs(args);
- boolean dumpMain = true;
- boolean checkin = false;
- boolean clear = false;
- boolean dumpUid = false;
- boolean dumpFiles = false;
+ if (filter.shouldDumpCheckIn()) {
+ // Other flags are not supported for checkin.
+ dumpCheckin(pw, filter.shouldCheckInClear());
+ } else {
+ if (filter.shouldDumpMain()) {
+ dumpInner(pw, filter);
+ pw.println();
+ }
+ if (filter.shouldDumpUid()) {
+ dumpUid(pw);
+ pw.println();
+ }
+ if (filter.shouldDumpFiles()) {
+ dumpDumpFiles(pw);
+ pw.println();
+ }
+ }
+ }
- if (args != null) {
- for (String arg : args) {
- if ("-c".equals(arg)) {
- checkin = true;
+ private static DumpFilter parseDumpArgs(String[] args) {
+ final DumpFilter filter = new DumpFilter();
+ if (args == null) {
+ return filter;
+ }
- } else if ("--checkin".equals(arg)) {
- checkin = true;
- clear = true;
+ int argIndex = 0;
+ while (argIndex < args.length) {
+ final String arg = args[argIndex++];
- } else if ("-a".equals(arg) || "--all".equals(arg)) {
- dumpUid = true;
- dumpFiles = true;
+ if ("-c".equals(arg)) {
+ filter.setDumpCheckIn(true);
+ continue;
+ }
+ if ("--checkin".equals(arg)) {
+ filter.setDumpCheckIn(true);
+ filter.setCheckInClear(true);
+ continue;
+ }
+ if ("-a".equals(arg) || "--all".equals(arg)) {
+ filter.setDumpUid(true);
+ filter.setDumpFiles(true);
+ continue;
+ }
+ if ("-u".equals(arg) || "--uid".equals(arg)) {
+ filter.setDumpUid(true);
+ continue;
+ }
+ if ("-f".equals(arg) || "--files".equals(arg)) {
+ filter.setDumpFiles(true);
+ continue;
+ }
+ if ("-n".equals(arg) || "--no-main".equals(arg)) {
+ filter.setDumpMain(false);
+ continue;
+ }
+ if ("--user".equals(arg)) {
+ if (argIndex >= args.length) {
+ throw new IllegalArgumentException("Missing user ID for --user");
+ }
+ try {
+ filter.addUser(Integer.parseInt(args[argIndex++]));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid user ID", e);
+ }
+ continue;
+ }
+ if ("-p".equals(arg) || "--package".equals(arg)) {
+ if (argIndex >= args.length) {
+ throw new IllegalArgumentException("Missing package name for --package");
+ }
+ filter.addPackageRegex(args[argIndex++]);
+ filter.setDumpDetails(false);
+ continue;
+ }
+ if (arg.startsWith("-")) {
+ throw new IllegalArgumentException("Unknown option " + arg);
+ }
+ break;
+ }
+ while (argIndex < args.length) {
+ filter.addPackage(args[argIndex++]);
+ }
+ return filter;
+ }
- } else if ("-u".equals(arg) || "--uid".equals(arg)) {
- dumpUid = true;
+ static class DumpFilter {
+ private boolean mDumpCheckIn = false;
+ private boolean mCheckInClear = false;
- } else if ("-f".equals(arg) || "--files".equals(arg)) {
- dumpFiles = true;
+ private boolean mDumpMain = true;
+ private boolean mDumpUid = false;
+ private boolean mDumpFiles = false;
- } else if ("-n".equals(arg) || "--no-main".equals(arg)) {
- dumpMain = false;
+ private boolean mDumpDetails = true;
+ private List<Pattern> mPackagePatterns = new ArrayList<>();
+ private List<Integer> mUsers = new ArrayList<>();
+
+ void addPackageRegex(String regex) {
+ mPackagePatterns.add(Pattern.compile(regex));
+ }
+
+ public void addPackage(String packageName) {
+ addPackageRegex(Pattern.quote(packageName));
+ }
+
+ void addUser(int userId) {
+ mUsers.add(userId);
+ }
+
+ boolean isPackageMatch(String packageName) {
+ if (mPackagePatterns.size() == 0) {
+ return true;
+ }
+ for (int i = 0; i < mPackagePatterns.size(); i++) {
+ if (mPackagePatterns.get(i).matcher(packageName).find()) {
+ return true;
}
}
+ return false;
}
- if (checkin) {
- // Other flags are not supported for checkin.
- dumpCheckin(pw, clear);
- } else {
- if (dumpMain) {
- dumpInner(pw);
- pw.println();
- }
- if (dumpUid) {
- dumpUid(pw);
- pw.println();
+ boolean isUserMatch(int userId) {
+ if (mUsers.size() == 0) {
+ return true;
}
- if (dumpFiles) {
- dumpDumpFiles(pw);
- pw.println();
+ for (int i = 0; i < mUsers.size(); i++) {
+ if (mUsers.get(i) == userId) {
+ return true;
+ }
}
+ return false;
+ }
+
+ public boolean shouldDumpCheckIn() {
+ return mDumpCheckIn;
+ }
+
+ public void setDumpCheckIn(boolean dumpCheckIn) {
+ mDumpCheckIn = dumpCheckIn;
+ }
+
+ public boolean shouldCheckInClear() {
+ return mCheckInClear;
+ }
+
+ public void setCheckInClear(boolean checkInClear) {
+ mCheckInClear = checkInClear;
+ }
+
+ public boolean shouldDumpMain() {
+ return mDumpMain;
+ }
+
+ public void setDumpMain(boolean dumpMain) {
+ mDumpMain = dumpMain;
+ }
+
+ public boolean shouldDumpUid() {
+ return mDumpUid;
+ }
+
+ public void setDumpUid(boolean dumpUid) {
+ mDumpUid = dumpUid;
+ }
+
+ public boolean shouldDumpFiles() {
+ return mDumpFiles;
+ }
+
+ public void setDumpFiles(boolean dumpFiles) {
+ mDumpFiles = dumpFiles;
+ }
+
+ public boolean shouldDumpDetails() {
+ return mDumpDetails;
+ }
+
+ public void setDumpDetails(boolean dumpDetails) {
+ mDumpDetails = dumpDetails;
}
}
private void dumpInner(PrintWriter pw) {
+ dumpInner(pw, new DumpFilter());
+ }
+
+ private void dumpInner(PrintWriter pw, DumpFilter filter) {
synchronized (mLock) {
- final long now = injectCurrentTimeMillis();
- pw.print("Now: [");
- pw.print(now);
- pw.print("] ");
- pw.print(formatTime(now));
-
- pw.print(" Raw last reset: [");
- pw.print(mRawLastResetTime);
- pw.print("] ");
- pw.print(formatTime(mRawLastResetTime));
-
- final long last = getLastResetTimeLocked();
- pw.print(" Last reset: [");
- pw.print(last);
- pw.print("] ");
- pw.print(formatTime(last));
-
- final long next = getNextResetTimeLocked();
- pw.print(" Next reset: [");
- pw.print(next);
- pw.print("] ");
- pw.print(formatTime(next));
-
- pw.print(" Config:");
- pw.print(" Max icon dim: ");
- pw.println(mMaxIconDimension);
- pw.print(" Icon format: ");
- pw.println(mIconPersistFormat);
- pw.print(" Icon quality: ");
- pw.println(mIconPersistQuality);
- pw.print(" saveDelayMillis: ");
- pw.println(mSaveDelayMillis);
- pw.print(" resetInterval: ");
- pw.println(mResetInterval);
- pw.print(" maxUpdatesPerInterval: ");
- pw.println(mMaxUpdatesPerInterval);
- pw.print(" maxShortcutsPerActivity: ");
- pw.println(mMaxShortcuts);
- pw.println();
+ if (filter.shouldDumpDetails()) {
+ final long now = injectCurrentTimeMillis();
+ pw.print("Now: [");
+ pw.print(now);
+ pw.print("] ");
+ pw.print(formatTime(now));
+
+ pw.print(" Raw last reset: [");
+ pw.print(mRawLastResetTime);
+ pw.print("] ");
+ pw.print(formatTime(mRawLastResetTime));
+
+ final long last = getLastResetTimeLocked();
+ pw.print(" Last reset: [");
+ pw.print(last);
+ pw.print("] ");
+ pw.print(formatTime(last));
+
+ final long next = getNextResetTimeLocked();
+ pw.print(" Next reset: [");
+ pw.print(next);
+ pw.print("] ");
+ pw.print(formatTime(next));
+
+ pw.print(" Config:");
+ pw.print(" Max icon dim: ");
+ pw.println(mMaxIconDimension);
+ pw.print(" Icon format: ");
+ pw.println(mIconPersistFormat);
+ pw.print(" Icon quality: ");
+ pw.println(mIconPersistQuality);
+ pw.print(" saveDelayMillis: ");
+ pw.println(mSaveDelayMillis);
+ pw.print(" resetInterval: ");
+ pw.println(mResetInterval);
+ pw.print(" maxUpdatesPerInterval: ");
+ pw.println(mMaxUpdatesPerInterval);
+ pw.print(" maxShortcutsPerActivity: ");
+ pw.println(mMaxShortcuts);
+ pw.println();
- pw.println(" Stats:");
- synchronized (mStatLock) {
- for (int i = 0; i < Stats.COUNT; i++) {
- dumpStatLS(pw, " ", i);
+ pw.println(" Stats:");
+ synchronized (mStatLock) {
+ for (int i = 0; i < Stats.COUNT; i++) {
+ dumpStatLS(pw, " ", i);
+ }
}
- }
- pw.println();
- pw.print(" #Failures: ");
- pw.println(mWtfCount);
+ pw.println();
+ pw.print(" #Failures: ");
+ pw.println(mWtfCount);
- if (mLastWtfStacktrace != null) {
- pw.print(" Last failure stack trace: ");
- pw.println(Log.getStackTraceString(mLastWtfStacktrace));
- }
+ if (mLastWtfStacktrace != null) {
+ pw.print(" Last failure stack trace: ");
+ pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+ }
- pw.println();
- mShortcutBitmapSaver.dumpLocked(pw, " ");
+ pw.println();
+ mShortcutBitmapSaver.dumpLocked(pw, " ");
- for (int i = 0; i < mUsers.size(); i++) {
pw.println();
- mUsers.valueAt(i).dump(pw, " ");
+ }
+
+ for (int i = 0; i < mUsers.size(); i++) {
+ final ShortcutUser user = mUsers.valueAt(i);
+ if (filter.isUserMatch(user.getUserId())) {
+ user.dump(pw, " ", filter);
+ pw.println();
+ }
}
}
}
diff --git a/com/android/server/pm/ShortcutUser.java b/com/android/server/pm/ShortcutUser.java
index 2c388c4a..55e6d28a 100644
--- a/com/android/server/pm/ShortcutUser.java
+++ b/com/android/server/pm/ShortcutUser.java
@@ -28,6 +28,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.pm.ShortcutService.DumpFilter;
import com.android.server.pm.ShortcutService.InvalidFileFormatException;
import libcore.util.Objects;
@@ -531,44 +532,54 @@ class ShortcutUser {
+ " S=" + restoredShortcuts[0]);
}
- public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- pw.print(prefix);
- pw.print("User: ");
- pw.print(mUserId);
- pw.print(" Known locales: ");
- pw.print(mKnownLocales);
- pw.print(" Last app scan: [");
- pw.print(mLastAppScanTime);
- pw.print("] ");
- pw.print(ShortcutService.formatTime(mLastAppScanTime));
- pw.print(" Last app scan FP: ");
- pw.print(mLastAppScanOsFingerprint);
- pw.println();
-
- prefix += prefix + " ";
-
- pw.print(prefix);
- pw.print("Cached launcher: ");
- pw.print(mCachedLauncher);
- pw.println();
-
- pw.print(prefix);
- pw.print("Last known launcher: ");
- pw.print(mLastKnownLauncher);
- pw.println();
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
+ if (filter.shouldDumpDetails()) {
+ pw.print(prefix);
+ pw.print("User: ");
+ pw.print(mUserId);
+ pw.print(" Known locales: ");
+ pw.print(mKnownLocales);
+ pw.print(" Last app scan: [");
+ pw.print(mLastAppScanTime);
+ pw.print("] ");
+ pw.print(ShortcutService.formatTime(mLastAppScanTime));
+ pw.print(" Last app scan FP: ");
+ pw.print(mLastAppScanOsFingerprint);
+ pw.println();
+
+ prefix += prefix + " ";
+
+ pw.print(prefix);
+ pw.print("Cached launcher: ");
+ pw.print(mCachedLauncher);
+ pw.println();
+
+ pw.print(prefix);
+ pw.print("Last known launcher: ");
+ pw.print(mLastKnownLauncher);
+ pw.println();
+ }
for (int i = 0; i < mLaunchers.size(); i++) {
- mLaunchers.valueAt(i).dump(pw, prefix);
+ ShortcutLauncher launcher = mLaunchers.valueAt(i);
+ if (filter.isPackageMatch(launcher.getPackageName())) {
+ launcher.dump(pw, prefix, filter);
+ }
}
for (int i = 0; i < mPackages.size(); i++) {
- mPackages.valueAt(i).dump(pw, prefix);
+ ShortcutPackage pkg = mPackages.valueAt(i);
+ if (filter.isPackageMatch(pkg.getPackageName())) {
+ pkg.dump(pw, prefix, filter);
+ }
}
- pw.println();
- pw.print(prefix);
- pw.println("Bitmap directories: ");
- dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId));
+ if (filter.shouldDumpDetails()) {
+ pw.println();
+ pw.print(prefix);
+ pw.println("Bitmap directories: ");
+ dumpDirectorySize(pw, prefix + " ", mService.getUserBitmapFilePath(mUserId));
+ }
}
private void dumpDirectorySize(@NonNull PrintWriter pw,
diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java
index f2d527b2..1e5245cf 100644
--- a/com/android/server/pm/UserManagerService.java
+++ b/com/android/server/pm/UserManagerService.java
@@ -1055,7 +1055,7 @@ public class UserManagerService extends IUserManager.Stub {
/** Called by PackageManagerService */
public boolean exists(int userId) {
- return getUserInfoNoChecks(userId) != null;
+ return mLocalService.exists(userId);
}
@Override
@@ -3502,8 +3502,8 @@ public class UserManagerService extends IUserManager.Stub {
* @param userId
* @return whether the user has been initialized yet
*/
- boolean isInitialized(int userId) {
- return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+ boolean isUserInitialized(int userId) {
+ return mLocalService.isUserInitialized(userId);
}
private class LocalService extends UserManagerInternal {
@@ -3715,6 +3715,16 @@ public class UserManagerService extends IUserManager.Stub {
}
return state == UserState.STATE_RUNNING_UNLOCKED;
}
+
+ @Override
+ public boolean isUserInitialized(int userId) {
+ return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+ }
+
+ @Override
+ public boolean exists(int userId) {
+ return getUserInfoNoChecks(userId) != null;
+ }
}
/* Remove all the users except of the system one. */
diff --git a/com/android/server/pm/permission/BasePermission.java b/com/android/server/pm/permission/BasePermission.java
new file mode 100644
index 00000000..09a6e9c0
--- /dev/null
+++ b/com/android/server/pm/permission/BasePermission.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.content.pm.PermissionInfo.PROTECTION_NORMAL;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM;
+
+import static com.android.server.pm.Settings.ATTR_NAME;
+import static com.android.server.pm.Settings.ATTR_PACKAGE;
+import static com.android.server.pm.Settings.TAG_ITEM;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Permission;
+import android.content.pm.PermissionInfo;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSettingBase;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public final class BasePermission {
+ static final String TAG = "PackageManager";
+
+ public static final int TYPE_NORMAL = 0;
+ public static final int TYPE_BUILTIN = 1;
+ public static final int TYPE_DYNAMIC = 2;
+ @IntDef(value = {
+ TYPE_NORMAL,
+ TYPE_BUILTIN,
+ TYPE_DYNAMIC,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionType {}
+
+ @IntDef(value = {
+ PROTECTION_DANGEROUS,
+ PROTECTION_NORMAL,
+ PROTECTION_SIGNATURE,
+ PROTECTION_SIGNATURE_OR_SYSTEM,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProtectionLevel {}
+
+ final String name;
+
+ @PermissionType final int type;
+
+ String sourcePackageName;
+
+ // TODO: Can we get rid of this? Seems we only use some signature info from the setting
+ PackageSettingBase sourcePackageSetting;
+
+ int protectionLevel;
+
+ PackageParser.Permission perm;
+
+ PermissionInfo pendingPermissionInfo;
+
+ /** UID that owns the definition of this permission */
+ int uid;
+
+ /** Additional GIDs given to apps granted this permission */
+ private int[] gids;
+
+ /**
+ * Flag indicating that {@link #gids} should be adjusted based on the
+ * {@link UserHandle} the granted app is running as.
+ */
+ private boolean perUser;
+
+ public BasePermission(String _name, String _sourcePackageName, @PermissionType int _type) {
+ name = _name;
+ sourcePackageName = _sourcePackageName;
+ type = _type;
+ // Default to most conservative protection level.
+ protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
+ }
+
+ @Override
+ public String toString() {
+ return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
+ + "}";
+ }
+
+ public String getName() {
+ return name;
+ }
+ public int getProtectionLevel() {
+ return protectionLevel;
+ }
+ public String getSourcePackageName() {
+ return sourcePackageName;
+ }
+ public PackageSettingBase getSourcePackageSetting() {
+ return sourcePackageSetting;
+ }
+ public int getType() {
+ return type;
+ }
+ public int getUid() {
+ return uid;
+ }
+ public void setGids(int[] gids, boolean perUser) {
+ this.gids = gids;
+ this.perUser = perUser;
+ }
+ public void setPermission(@Nullable Permission perm) {
+ this.perm = perm;
+ }
+ public void setSourcePackageSetting(PackageSettingBase sourcePackageSetting) {
+ this.sourcePackageSetting = sourcePackageSetting;
+ }
+
+ public int[] computeGids(int userId) {
+ if (perUser) {
+ final int[] userGids = new int[gids.length];
+ for (int i = 0; i < gids.length; i++) {
+ userGids[i] = UserHandle.getUid(userId, gids[i]);
+ }
+ return userGids;
+ } else {
+ return gids;
+ }
+ }
+
+ public int calculateFootprint(BasePermission perm) {
+ if (uid == perm.uid) {
+ return perm.name.length() + perm.perm.info.calculateFootprint();
+ }
+ return 0;
+ }
+
+ public boolean isPermission(Permission perm) {
+ return this.perm == perm;
+ }
+
+ public boolean isDynamic() {
+ return type == TYPE_DYNAMIC;
+ }
+
+
+ public boolean isNormal() {
+ return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_NORMAL;
+ }
+ public boolean isRuntime() {
+ return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_DANGEROUS;
+ }
+ public boolean isSignature() {
+ return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) ==
+ PermissionInfo.PROTECTION_SIGNATURE;
+ }
+
+ public boolean isAppOp() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+ }
+ public boolean isDevelopment() {
+ return isSignature()
+ && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
+ }
+ public boolean isInstaller() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0;
+ }
+ public boolean isInstant() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
+ }
+ public boolean isOEM() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
+ }
+ public boolean isPre23() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRE23) != 0;
+ }
+ public boolean isPreInstalled() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0;
+ }
+ public boolean isPrivileged() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
+ }
+ public boolean isRuntimeOnly() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
+ }
+ public boolean isSetup() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0;
+ }
+ public boolean isVerifier() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0;
+ }
+
+ public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
+ if (!origPackageName.equals(sourcePackageName)) {
+ return;
+ }
+ sourcePackageName = newPackageName;
+ sourcePackageSetting = null;
+ perm = null;
+ if (pendingPermissionInfo != null) {
+ pendingPermissionInfo.packageName = newPackageName;
+ }
+ uid = 0;
+ setGids(null, false);
+ }
+
+ public boolean addToTree(@ProtectionLevel int protectionLevel,
+ @NonNull PermissionInfo info, @NonNull BasePermission tree) {
+ final boolean changed =
+ (this.protectionLevel != protectionLevel
+ || perm == null
+ || uid != tree.uid
+ || !perm.owner.equals(tree.perm.owner)
+ || !comparePermissionInfos(perm.info, info));
+ this.protectionLevel = protectionLevel;
+ info = new PermissionInfo(info);
+ info.protectionLevel = protectionLevel;
+ perm = new PackageParser.Permission(tree.perm.owner, info);
+ perm.info.packageName = tree.perm.info.packageName;
+ uid = tree.uid;
+ return changed;
+ }
+
+ public void updateDynamicPermission(Map<String, BasePermission> permissionTrees) {
+ if (PackageManagerService.DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
+ + getName() + " pkg=" + getSourcePackageName()
+ + " info=" + pendingPermissionInfo);
+ if (sourcePackageSetting == null && pendingPermissionInfo != null) {
+ final BasePermission tree = findPermissionTreeLP(permissionTrees, name);
+ if (tree != null && tree.perm != null) {
+ sourcePackageSetting = tree.sourcePackageSetting;
+ perm = new PackageParser.Permission(tree.perm.owner,
+ new PermissionInfo(pendingPermissionInfo));
+ perm.info.packageName = tree.perm.info.packageName;
+ perm.info.name = name;
+ uid = tree.uid;
+ }
+ }
+ }
+
+ public static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p,
+ @NonNull PackageParser.Package pkg, Map<String, BasePermission> permissionTrees,
+ boolean chatty) {
+ final PackageSettingBase pkgSetting = (PackageSettingBase) pkg.mExtras;
+ // Allow system apps to redefine non-system permissions
+ if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) {
+ final boolean currentOwnerIsSystem = (bp.perm != null
+ && bp.perm.owner.isSystemApp());
+ if (p.owner.isSystemApp()) {
+ if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
+ // It's a built-in permission and no owner, take ownership now
+ bp.sourcePackageSetting = pkgSetting;
+ bp.perm = p;
+ bp.uid = pkg.applicationInfo.uid;
+ bp.sourcePackageName = p.info.packageName;
+ p.info.flags |= PermissionInfo.FLAG_INSTALLED;
+ } else if (!currentOwnerIsSystem) {
+ String msg = "New decl " + p.owner + " of permission "
+ + p.info.name + " is system; overriding " + bp.sourcePackageName;
+ PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+ bp = null;
+ }
+ }
+ }
+ if (bp == null) {
+ bp = new BasePermission(p.info.name, p.info.packageName, TYPE_NORMAL);
+ }
+ StringBuilder r = null;
+ if (bp.perm == null) {
+ if (bp.sourcePackageName == null
+ || bp.sourcePackageName.equals(p.info.packageName)) {
+ final BasePermission tree = findPermissionTreeLP(permissionTrees, p.info.name);
+ if (tree == null
+ || tree.sourcePackageName.equals(p.info.packageName)) {
+ bp.sourcePackageSetting = pkgSetting;
+ bp.perm = p;
+ bp.uid = pkg.applicationInfo.uid;
+ bp.sourcePackageName = p.info.packageName;
+ p.info.flags |= PermissionInfo.FLAG_INSTALLED;
+ if (chatty) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append(p.info.name);
+ }
+ } else {
+ Slog.w(TAG, "Permission " + p.info.name + " from package "
+ + p.info.packageName + " ignored: base tree "
+ + tree.name + " is from package "
+ + tree.sourcePackageName);
+ }
+ } else {
+ Slog.w(TAG, "Permission " + p.info.name + " from package "
+ + p.info.packageName + " ignored: original from "
+ + bp.sourcePackageName);
+ }
+ } else if (chatty) {
+ if (r == null) {
+ r = new StringBuilder(256);
+ } else {
+ r.append(' ');
+ }
+ r.append("DUP:");
+ r.append(p.info.name);
+ }
+ if (bp.perm == p) {
+ bp.protectionLevel = p.info.protectionLevel;
+ }
+ if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
+ Log.d(TAG, " Permissions: " + r);
+ }
+ return bp;
+ }
+
+ public static BasePermission enforcePermissionTreeLP(
+ Map<String, BasePermission> permissionTrees, String permName, int callingUid) {
+ if (permName != null) {
+ BasePermission bp = findPermissionTreeLP(permissionTrees, permName);
+ if (bp != null) {
+ if (bp.uid == UserHandle.getAppId(callingUid)) {//UserHandle.getAppId(Binder.getCallingUid())) {
+ return bp;
+ }
+ throw new SecurityException("Calling uid " + callingUid
+ + " is not allowed to add to permission tree "
+ + bp.name + " owned by uid " + bp.uid);
+ }
+ }
+ throw new SecurityException("No permission tree found for " + permName);
+ }
+
+ public void enforceDeclaredUsedAndRuntimeOrDevelopment(PackageParser.Package pkg) {
+ int index = pkg.requestedPermissions.indexOf(name);
+ if (index == -1) {
+ throw new SecurityException("Package " + pkg.packageName
+ + " has not requested permission " + name);
+ }
+ if (!isRuntime() && !isDevelopment()) {
+ throw new SecurityException("Permission " + name
+ + " is not a changeable permission type");
+ }
+ }
+
+ private static BasePermission findPermissionTreeLP(
+ Map<String, BasePermission> permissionTrees, String permName) {
+ for (BasePermission bp : permissionTrees.values()) {
+ if (permName.startsWith(bp.name) &&
+ permName.length() > bp.name.length() &&
+ permName.charAt(bp.name.length()) == '.') {
+ return bp;
+ }
+ }
+ return null;
+ }
+
+ public @Nullable PermissionInfo generatePermissionInfo(@NonNull String groupName, int flags) {
+ if (groupName == null) {
+ if (perm == null || perm.info.group == null) {
+ return generatePermissionInfo(protectionLevel, flags);
+ }
+ } else {
+ if (perm != null && groupName.equals(perm.info.group)) {
+ return PackageParser.generatePermissionInfo(perm, flags);
+ }
+ }
+ return null;
+ }
+
+ public @NonNull PermissionInfo generatePermissionInfo(int adjustedProtectionLevel, int flags) {
+ final boolean protectionLevelChanged = protectionLevel != adjustedProtectionLevel;
+ // if we return different protection level, don't use the cached info
+ if (perm != null && !protectionLevelChanged) {
+ return PackageParser.generatePermissionInfo(perm, flags);
+ }
+ final PermissionInfo pi = new PermissionInfo();
+ pi.name = name;
+ pi.packageName = sourcePackageName;
+ pi.nonLocalizedLabel = name;
+ pi.protectionLevel = protectionLevelChanged ? adjustedProtectionLevel : protectionLevel;
+ return pi;
+ }
+
+ public static boolean readLPw(@NonNull Map<String, BasePermission> out,
+ @NonNull XmlPullParser parser) {
+ final String tagName = parser.getName();
+ if (!tagName.equals(TAG_ITEM)) {
+ return false;
+ }
+ final String name = parser.getAttributeValue(null, ATTR_NAME);
+ final String sourcePackage = parser.getAttributeValue(null, ATTR_PACKAGE);
+ final String ptype = parser.getAttributeValue(null, "type");
+ if (name == null || sourcePackage == null) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: permissions has" + " no name at "
+ + parser.getPositionDescription());
+ return false;
+ }
+ final boolean dynamic = "dynamic".equals(ptype);
+ BasePermission bp = out.get(name);
+ // If the permission is builtin, do not clobber it.
+ if (bp == null || bp.type != TYPE_BUILTIN) {
+ bp = new BasePermission(name.intern(), sourcePackage,
+ dynamic ? TYPE_DYNAMIC : TYPE_NORMAL);
+ }
+ bp.protectionLevel = readInt(parser, null, "protection",
+ PermissionInfo.PROTECTION_NORMAL);
+ bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
+ if (dynamic) {
+ final PermissionInfo pi = new PermissionInfo();
+ pi.packageName = sourcePackage.intern();
+ pi.name = name.intern();
+ pi.icon = readInt(parser, null, "icon", 0);
+ pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
+ pi.protectionLevel = bp.protectionLevel;
+ bp.pendingPermissionInfo = pi;
+ }
+ out.put(bp.name, bp);
+ return true;
+ }
+
+ private static int readInt(XmlPullParser parser, String ns, String name, int defValue) {
+ String v = parser.getAttributeValue(ns, name);
+ try {
+ if (v == null) {
+ return defValue;
+ }
+ return Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: attribute " + name
+ + " has bad integer value " + v + " at "
+ + parser.getPositionDescription());
+ }
+ return defValue;
+ }
+
+ public void writeLPr(@NonNull XmlSerializer serializer) throws IOException {
+ if (sourcePackageName == null) {
+ return;
+ }
+ serializer.startTag(null, TAG_ITEM);
+ serializer.attribute(null, ATTR_NAME, name);
+ serializer.attribute(null, ATTR_PACKAGE, sourcePackageName);
+ if (protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
+ serializer.attribute(null, "protection", Integer.toString(protectionLevel));
+ }
+ if (type == BasePermission.TYPE_DYNAMIC) {
+ final PermissionInfo pi = perm != null ? perm.info : pendingPermissionInfo;
+ if (pi != null) {
+ serializer.attribute(null, "type", "dynamic");
+ if (pi.icon != 0) {
+ serializer.attribute(null, "icon", Integer.toString(pi.icon));
+ }
+ if (pi.nonLocalizedLabel != null) {
+ serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
+ }
+ }
+ }
+ serializer.endTag(null, TAG_ITEM);
+ }
+
+ private static boolean compareStrings(CharSequence s1, CharSequence s2) {
+ if (s1 == null) {
+ return s2 == null;
+ }
+ if (s2 == null) {
+ return false;
+ }
+ if (s1.getClass() != s2.getClass()) {
+ return false;
+ }
+ return s1.equals(s2);
+ }
+
+ private static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
+ if (pi1.icon != pi2.icon) return false;
+ if (pi1.logo != pi2.logo) return false;
+ if (pi1.protectionLevel != pi2.protectionLevel) return false;
+ if (!compareStrings(pi1.name, pi2.name)) return false;
+ if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
+ // We'll take care of setting this one.
+ if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
+ // These are not currently stored in settings.
+ //if (!compareStrings(pi1.group, pi2.group)) return false;
+ //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
+ //if (pi1.labelRes != pi2.labelRes) return false;
+ //if (pi1.descriptionRes != pi2.descriptionRes) return false;
+ return true;
+ }
+
+ public boolean dumpPermissionsLPr(@NonNull PrintWriter pw, @NonNull String packageName,
+ @NonNull Set<String> permissionNames, boolean readEnforced,
+ boolean printedSomething, @NonNull DumpState dumpState) {
+ if (packageName != null && !packageName.equals(sourcePackageName)) {
+ return false;
+ }
+ if (permissionNames != null && !permissionNames.contains(name)) {
+ return false;
+ }
+ if (!printedSomething) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Permissions:");
+ printedSomething = true;
+ }
+ pw.print(" Permission ["); pw.print(name); pw.print("] (");
+ pw.print(Integer.toHexString(System.identityHashCode(this)));
+ pw.println("):");
+ pw.print(" sourcePackage="); pw.println(sourcePackageName);
+ pw.print(" uid="); pw.print(uid);
+ pw.print(" gids="); pw.print(Arrays.toString(
+ computeGids(UserHandle.USER_SYSTEM)));
+ pw.print(" type="); pw.print(type);
+ pw.print(" prot=");
+ pw.println(PermissionInfo.protectionToString(protectionLevel));
+ if (perm != null) {
+ pw.print(" perm="); pw.println(perm);
+ if ((perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
+ || (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
+ pw.print(" flags=0x"); pw.println(Integer.toHexString(perm.info.flags));
+ }
+ }
+ if (sourcePackageSetting != null) {
+ pw.print(" packageSetting="); pw.println(sourcePackageSetting);
+ }
+ if (READ_EXTERNAL_STORAGE.equals(name)) {
+ pw.print(" enforced=");
+ pw.println(readEnforced);
+ }
+ return true;
+ }
+}
diff --git a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
new file mode 100644
index 00000000..161efd38
--- /dev/null
+++ b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -0,0 +1,1296 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.DownloadManager;
+import android.app.admin.DevicePolicyManager;
+import android.companion.CompanionDeviceManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManagerInternal.PackagesProvider;
+import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.print.PrintManager;
+import android.provider.CalendarContract;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.TelephonyManager;
+import android.security.Credentials;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+import com.android.server.LocalServices;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSetting;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+/**
+ * This class is the policy for granting runtime permissions to
+ * platform components and default handlers in the system such
+ * that the device is usable out-of-the-box. For example, the
+ * shell UID is a part of the system and the Phone app should
+ * have phone related permission by default.
+ * <p>
+ * NOTE: This class is at the wrong abstraction level. It is a part of the package manager
+ * service but knows about lots of higher level subsystems. The correct way to do this is
+ * to have an interface defined in the package manager but have the impl next to other
+ * policy stuff like PhoneWindowManager
+ */
+public final class DefaultPermissionGrantPolicy {
+ private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
+ private static final boolean DEBUG = false;
+
+ private static final int DEFAULT_FLAGS =
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+
+ private static final String AUDIO_MIME_TYPE = "audio/mpeg";
+
+ private static final String TAG_EXCEPTIONS = "exceptions";
+ private static final String TAG_EXCEPTION = "exception";
+ private static final String TAG_PERMISSION = "permission";
+ private static final String ATTR_PACKAGE = "package";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_FIXED = "fixed";
+
+ private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
+ static {
+ PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
+ PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
+ PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
+ PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
+ PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
+ PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
+ PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
+ }
+
+ private static final Set<String> CONTACTS_PERMISSIONS = new ArraySet<>();
+ static {
+ CONTACTS_PERMISSIONS.add(Manifest.permission.READ_CONTACTS);
+ CONTACTS_PERMISSIONS.add(Manifest.permission.WRITE_CONTACTS);
+ CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
+ }
+
+ private static final Set<String> LOCATION_PERMISSIONS = new ArraySet<>();
+ static {
+ LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+ LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+ }
+
+ private static final Set<String> CALENDAR_PERMISSIONS = new ArraySet<>();
+ static {
+ CALENDAR_PERMISSIONS.add(Manifest.permission.READ_CALENDAR);
+ CALENDAR_PERMISSIONS.add(Manifest.permission.WRITE_CALENDAR);
+ }
+
+ private static final Set<String> SMS_PERMISSIONS = new ArraySet<>();
+ static {
+ SMS_PERMISSIONS.add(Manifest.permission.SEND_SMS);
+ SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_SMS);
+ SMS_PERMISSIONS.add(Manifest.permission.READ_SMS);
+ SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_WAP_PUSH);
+ SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_MMS);
+ SMS_PERMISSIONS.add(Manifest.permission.READ_CELL_BROADCASTS);
+ }
+
+ private static final Set<String> MICROPHONE_PERMISSIONS = new ArraySet<>();
+ static {
+ MICROPHONE_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
+ }
+
+ private static final Set<String> CAMERA_PERMISSIONS = new ArraySet<>();
+ static {
+ CAMERA_PERMISSIONS.add(Manifest.permission.CAMERA);
+ }
+
+ private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
+ static {
+ SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+ }
+
+ private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
+ static {
+ STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+ STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+
+ private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
+
+ private static final String ACTION_TRACK = "com.android.fitness.TRACK";
+
+ private final Handler mHandler;
+
+ private PackagesProvider mLocationPackagesProvider;
+ private PackagesProvider mVoiceInteractionPackagesProvider;
+ private PackagesProvider mSmsAppPackagesProvider;
+ private PackagesProvider mDialerAppPackagesProvider;
+ private PackagesProvider mSimCallManagerPackagesProvider;
+ private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
+
+ private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
+ private final Context mContext;
+ private final Object mLock = new Object();
+ private final PackageManagerInternal mServiceInternal;
+ private final PermissionManagerService mPermissionManager;
+ private final DefaultPermissionGrantedCallback mPermissionGrantedCallback;
+ public interface DefaultPermissionGrantedCallback {
+ /** Callback when permissions have been granted */
+ public void onDefaultRuntimePermissionsGranted(int userId);
+ }
+
+ public DefaultPermissionGrantPolicy(Context context, Looper looper,
+ @Nullable DefaultPermissionGrantedCallback callback,
+ @NonNull PermissionManagerService permissionManager) {
+ mContext = context;
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) {
+ synchronized (mLock) {
+ if (mGrantExceptions == null) {
+ mGrantExceptions = readDefaultPermissionExceptionsLocked();
+ }
+ }
+ }
+ }
+ };
+ mPermissionGrantedCallback = callback;
+ mPermissionManager = permissionManager;
+ mServiceInternal = LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ public void setLocationPackagesProvider(PackagesProvider provider) {
+ synchronized (mLock) {
+ mLocationPackagesProvider = provider;
+ }
+ }
+
+ public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
+ synchronized (mLock) {
+ mVoiceInteractionPackagesProvider = provider;
+ }
+ }
+
+ public void setSmsAppPackagesProvider(PackagesProvider provider) {
+ synchronized (mLock) {
+ mSmsAppPackagesProvider = provider;
+ }
+ }
+
+ public void setDialerAppPackagesProvider(PackagesProvider provider) {
+ synchronized (mLock) {
+ mDialerAppPackagesProvider = provider;
+ }
+ }
+
+ public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
+ synchronized (mLock) {
+ mSimCallManagerPackagesProvider = provider;
+ }
+ }
+
+ public void setSyncAdapterPackagesProvider(SyncAdapterPackagesProvider provider) {
+ synchronized (mLock) {
+ mSyncAdapterPackagesProvider = provider;
+ }
+ }
+
+ public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) {
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
+ grantAllRuntimePermissions(packages, userId);
+ } else {
+ grantPermissionsToSysComponentsAndPrivApps(packages, userId);
+ grantDefaultSystemHandlerPermissions(userId);
+ grantDefaultPermissionExceptions(userId);
+ }
+ }
+
+ private void grantRuntimePermissionsForPackage(int userId, PackageParser.Package pkg) {
+ Set<String> permissions = new ArraySet<>();
+ for (String permission : pkg.requestedPermissions) {
+ final BasePermission bp = mPermissionManager.getPermission(permission);
+ if (bp == null) {
+ continue;
+ }
+ if (bp.isRuntime()) {
+ permissions.add(permission);
+ }
+ }
+ if (!permissions.isEmpty()) {
+ grantRuntimePermissions(pkg, permissions, true, userId);
+ }
+ }
+
+ private void grantAllRuntimePermissions(
+ Collection<PackageParser.Package> packages, int userId) {
+ Log.i(TAG, "Granting all runtime permissions for user " + userId);
+ for (PackageParser.Package pkg : packages) {
+ grantRuntimePermissionsForPackage(userId, pkg);
+ }
+ }
+
+ public void scheduleReadDefaultPermissionExceptions() {
+ mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
+ }
+
+ private void grantPermissionsToSysComponentsAndPrivApps(
+ Collection<PackageParser.Package> packages, int userId) {
+ Log.i(TAG, "Granting permissions to platform components for user " + userId);
+ for (PackageParser.Package pkg : packages) {
+ if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
+ || !doesPackageSupportRuntimePermissions(pkg)
+ || pkg.requestedPermissions.isEmpty()) {
+ continue;
+ }
+ grantRuntimePermissionsForPackage(userId, pkg);
+ }
+ }
+
+ private void grantDefaultSystemHandlerPermissions(int userId) {
+ Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
+
+ final PackagesProvider locationPackagesProvider;
+ final PackagesProvider voiceInteractionPackagesProvider;
+ final PackagesProvider smsAppPackagesProvider;
+ final PackagesProvider dialerAppPackagesProvider;
+ final PackagesProvider simCallManagerPackagesProvider;
+ final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
+
+ synchronized (mLock) {
+ locationPackagesProvider = mLocationPackagesProvider;
+ voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
+ smsAppPackagesProvider = mSmsAppPackagesProvider;
+ dialerAppPackagesProvider = mDialerAppPackagesProvider;
+ simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
+ syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
+ }
+
+ String[] voiceInteractPackageNames = (voiceInteractionPackagesProvider != null)
+ ? voiceInteractionPackagesProvider.getPackages(userId) : null;
+ String[] locationPackageNames = (locationPackagesProvider != null)
+ ? locationPackagesProvider.getPackages(userId) : null;
+ String[] smsAppPackageNames = (smsAppPackagesProvider != null)
+ ? smsAppPackagesProvider.getPackages(userId) : null;
+ String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
+ ? dialerAppPackagesProvider.getPackages(userId) : null;
+ String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
+ ? simCallManagerPackagesProvider.getPackages(userId) : null;
+ String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
+ syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
+ String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
+ syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null;
+
+ // Installer
+ final String installerPackageName = mServiceInternal.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_INSTALLER, userId);
+ PackageParser.Package installerPackage = getSystemPackage(installerPackageName);
+ if (installerPackage != null
+ && doesPackageSupportRuntimePermissions(installerPackage)) {
+ grantRuntimePermissions(installerPackage, STORAGE_PERMISSIONS, true, userId);
+ }
+
+ // Verifier
+ final String verifierPackageName = mServiceInternal.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_VERIFIER, userId);
+ PackageParser.Package verifierPackage = getSystemPackage(verifierPackageName);
+ if (verifierPackage != null
+ && doesPackageSupportRuntimePermissions(verifierPackage)) {
+ grantRuntimePermissions(verifierPackage, STORAGE_PERMISSIONS, true, userId);
+ grantRuntimePermissions(verifierPackage, PHONE_PERMISSIONS, false, userId);
+ grantRuntimePermissions(verifierPackage, SMS_PERMISSIONS, false, userId);
+ }
+
+ // SetupWizard
+ final String setupWizardPackageName = mServiceInternal.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId);
+ PackageParser.Package setupPackage = getSystemPackage(setupWizardPackageName);
+ if (setupPackage != null
+ && doesPackageSupportRuntimePermissions(setupPackage)) {
+ grantRuntimePermissions(setupPackage, PHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(setupPackage, CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissions(setupPackage, LOCATION_PERMISSIONS, userId);
+ grantRuntimePermissions(setupPackage, CAMERA_PERMISSIONS, userId);
+ }
+
+ // Camera
+ Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ PackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackage(
+ cameraIntent, userId);
+ if (cameraPackage != null
+ && doesPackageSupportRuntimePermissions(cameraPackage)) {
+ grantRuntimePermissions(cameraPackage, CAMERA_PERMISSIONS, userId);
+ grantRuntimePermissions(cameraPackage, MICROPHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(cameraPackage, STORAGE_PERMISSIONS, userId);
+ }
+
+ // Media provider
+ PackageParser.Package mediaStorePackage = getDefaultProviderAuthorityPackage(
+ MediaStore.AUTHORITY, userId);
+ if (mediaStorePackage != null) {
+ grantRuntimePermissions(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
+ }
+
+ // Downloads provider
+ PackageParser.Package downloadsPackage = getDefaultProviderAuthorityPackage(
+ "downloads", userId);
+ if (downloadsPackage != null) {
+ grantRuntimePermissions(downloadsPackage, STORAGE_PERMISSIONS, true, userId);
+ }
+
+ // Downloads UI
+ Intent downloadsUiIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+ PackageParser.Package downloadsUiPackage = getDefaultSystemHandlerActivityPackage(
+ downloadsUiIntent, userId);
+ if (downloadsUiPackage != null
+ && doesPackageSupportRuntimePermissions(downloadsUiPackage)) {
+ grantRuntimePermissions(downloadsUiPackage, STORAGE_PERMISSIONS, true, userId);
+ }
+
+ // Storage provider
+ PackageParser.Package storagePackage = getDefaultProviderAuthorityPackage(
+ "com.android.externalstorage.documents", userId);
+ if (storagePackage != null) {
+ grantRuntimePermissions(storagePackage, STORAGE_PERMISSIONS, true, userId);
+ }
+
+ // CertInstaller
+ Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
+ PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackage(
+ certInstallerIntent, userId);
+ if (certInstallerPackage != null
+ && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
+ grantRuntimePermissions(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
+ }
+
+ // Dialer
+ if (dialerAppPackageNames == null) {
+ Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
+ PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackage(
+ dialerIntent, userId);
+ if (dialerPackage != null) {
+ grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
+ }
+ } else {
+ for (String dialerAppPackageName : dialerAppPackageNames) {
+ PackageParser.Package dialerPackage = getSystemPackage(dialerAppPackageName);
+ if (dialerPackage != null) {
+ grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
+ }
+ }
+ }
+
+ // Sim call manager
+ if (simCallManagerPackageNames != null) {
+ for (String simCallManagerPackageName : simCallManagerPackageNames) {
+ PackageParser.Package simCallManagerPackage =
+ getSystemPackage(simCallManagerPackageName);
+ if (simCallManagerPackage != null) {
+ grantDefaultPermissionsToDefaultSimCallManager(simCallManagerPackage,
+ userId);
+ }
+ }
+ }
+
+ // SMS
+ if (smsAppPackageNames == null) {
+ Intent smsIntent = new Intent(Intent.ACTION_MAIN);
+ smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
+ PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackage(
+ smsIntent, userId);
+ if (smsPackage != null) {
+ grantDefaultPermissionsToDefaultSystemSmsApp(smsPackage, userId);
+ }
+ } else {
+ for (String smsPackageName : smsAppPackageNames) {
+ PackageParser.Package smsPackage = getSystemPackage(smsPackageName);
+ if (smsPackage != null) {
+ grantDefaultPermissionsToDefaultSystemSmsApp(smsPackage, userId);
+ }
+ }
+ }
+
+ // Cell Broadcast Receiver
+ Intent cbrIntent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
+ PackageParser.Package cbrPackage =
+ getDefaultSystemHandlerActivityPackage(cbrIntent, userId);
+ if (cbrPackage != null && doesPackageSupportRuntimePermissions(cbrPackage)) {
+ grantRuntimePermissions(cbrPackage, SMS_PERMISSIONS, userId);
+ }
+
+ // Carrier Provisioning Service
+ Intent carrierProvIntent = new Intent(Intents.SMS_CARRIER_PROVISION_ACTION);
+ PackageParser.Package carrierProvPackage =
+ getDefaultSystemHandlerServicePackage(carrierProvIntent, userId);
+ if (carrierProvPackage != null
+ && doesPackageSupportRuntimePermissions(carrierProvPackage)) {
+ grantRuntimePermissions(carrierProvPackage, SMS_PERMISSIONS, false, userId);
+ }
+
+ // Calendar
+ Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
+ calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
+ PackageParser.Package calendarPackage = getDefaultSystemHandlerActivityPackage(
+ calendarIntent, userId);
+ if (calendarPackage != null
+ && doesPackageSupportRuntimePermissions(calendarPackage)) {
+ grantRuntimePermissions(calendarPackage, CALENDAR_PERMISSIONS, userId);
+ grantRuntimePermissions(calendarPackage, CONTACTS_PERMISSIONS, userId);
+ }
+
+ // Calendar provider
+ PackageParser.Package calendarProviderPackage = getDefaultProviderAuthorityPackage(
+ CalendarContract.AUTHORITY, userId);
+ if (calendarProviderPackage != null) {
+ grantRuntimePermissions(calendarProviderPackage, CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissions(calendarProviderPackage, CALENDAR_PERMISSIONS,
+ true, userId);
+ grantRuntimePermissions(calendarProviderPackage, STORAGE_PERMISSIONS, userId);
+ }
+
+ // Calendar provider sync adapters
+ List<PackageParser.Package> calendarSyncAdapters = getHeadlessSyncAdapterPackages(
+ calendarSyncAdapterPackages, userId);
+ final int calendarSyncAdapterCount = calendarSyncAdapters.size();
+ for (int i = 0; i < calendarSyncAdapterCount; i++) {
+ PackageParser.Package calendarSyncAdapter = calendarSyncAdapters.get(i);
+ if (doesPackageSupportRuntimePermissions(calendarSyncAdapter)) {
+ grantRuntimePermissions(calendarSyncAdapter, CALENDAR_PERMISSIONS, userId);
+ }
+ }
+
+ // Contacts
+ Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
+ contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
+ PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackage(
+ contactsIntent, userId);
+ if (contactsPackage != null
+ && doesPackageSupportRuntimePermissions(contactsPackage)) {
+ grantRuntimePermissions(contactsPackage, CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissions(contactsPackage, PHONE_PERMISSIONS, userId);
+ }
+
+ // Contacts provider sync adapters
+ List<PackageParser.Package> contactsSyncAdapters = getHeadlessSyncAdapterPackages(
+ contactsSyncAdapterPackages, userId);
+ final int contactsSyncAdapterCount = contactsSyncAdapters.size();
+ for (int i = 0; i < contactsSyncAdapterCount; i++) {
+ PackageParser.Package contactsSyncAdapter = contactsSyncAdapters.get(i);
+ if (doesPackageSupportRuntimePermissions(contactsSyncAdapter)) {
+ grantRuntimePermissions(contactsSyncAdapter, CONTACTS_PERMISSIONS, userId);
+ }
+ }
+
+ // Contacts provider
+ PackageParser.Package contactsProviderPackage = getDefaultProviderAuthorityPackage(
+ ContactsContract.AUTHORITY, userId);
+ if (contactsProviderPackage != null) {
+ grantRuntimePermissions(contactsProviderPackage, CONTACTS_PERMISSIONS,
+ true, userId);
+ grantRuntimePermissions(contactsProviderPackage, PHONE_PERMISSIONS,
+ true, userId);
+ grantRuntimePermissions(contactsProviderPackage, STORAGE_PERMISSIONS, userId);
+ }
+
+ // Device provisioning
+ Intent deviceProvisionIntent = new Intent(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE);
+ PackageParser.Package deviceProvisionPackage =
+ getDefaultSystemHandlerActivityPackage(deviceProvisionIntent, userId);
+ if (deviceProvisionPackage != null
+ && doesPackageSupportRuntimePermissions(deviceProvisionPackage)) {
+ grantRuntimePermissions(deviceProvisionPackage, CONTACTS_PERMISSIONS, userId);
+ }
+
+ // Maps
+ Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
+ mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
+ PackageParser.Package mapsPackage = getDefaultSystemHandlerActivityPackage(
+ mapsIntent, userId);
+ if (mapsPackage != null
+ && doesPackageSupportRuntimePermissions(mapsPackage)) {
+ grantRuntimePermissions(mapsPackage, LOCATION_PERMISSIONS, userId);
+ }
+
+ // Gallery
+ Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
+ galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
+ PackageParser.Package galleryPackage = getDefaultSystemHandlerActivityPackage(
+ galleryIntent, userId);
+ if (galleryPackage != null
+ && doesPackageSupportRuntimePermissions(galleryPackage)) {
+ grantRuntimePermissions(galleryPackage, STORAGE_PERMISSIONS, userId);
+ }
+
+ // Email
+ Intent emailIntent = new Intent(Intent.ACTION_MAIN);
+ emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
+ PackageParser.Package emailPackage = getDefaultSystemHandlerActivityPackage(
+ emailIntent, userId);
+ if (emailPackage != null
+ && doesPackageSupportRuntimePermissions(emailPackage)) {
+ grantRuntimePermissions(emailPackage, CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissions(emailPackage, CALENDAR_PERMISSIONS, userId);
+ }
+
+ // Browser
+ PackageParser.Package browserPackage = null;
+ String defaultBrowserPackage = mServiceInternal.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_BROWSER, userId);
+ if (defaultBrowserPackage != null) {
+ browserPackage = getPackage(defaultBrowserPackage);
+ }
+ if (browserPackage == null) {
+ Intent browserIntent = new Intent(Intent.ACTION_MAIN);
+ browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
+ browserPackage = getDefaultSystemHandlerActivityPackage(
+ browserIntent, userId);
+ }
+ if (browserPackage != null
+ && doesPackageSupportRuntimePermissions(browserPackage)) {
+ grantRuntimePermissions(browserPackage, LOCATION_PERMISSIONS, userId);
+ }
+
+ // Voice interaction
+ if (voiceInteractPackageNames != null) {
+ for (String voiceInteractPackageName : voiceInteractPackageNames) {
+ PackageParser.Package voiceInteractPackage = getSystemPackage(
+ voiceInteractPackageName);
+ if (voiceInteractPackage != null
+ && doesPackageSupportRuntimePermissions(voiceInteractPackage)) {
+ grantRuntimePermissions(voiceInteractPackage,
+ CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissions(voiceInteractPackage,
+ CALENDAR_PERMISSIONS, userId);
+ grantRuntimePermissions(voiceInteractPackage,
+ MICROPHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(voiceInteractPackage,
+ PHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(voiceInteractPackage,
+ SMS_PERMISSIONS, userId);
+ grantRuntimePermissions(voiceInteractPackage,
+ LOCATION_PERMISSIONS, userId);
+ }
+ }
+ }
+
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ // Allow voice search on low-ram devices
+ Intent globalSearchIntent = new Intent("android.search.action.GLOBAL_SEARCH");
+ PackageParser.Package globalSearchPickerPackage =
+ getDefaultSystemHandlerActivityPackage(globalSearchIntent, userId);
+
+ if (globalSearchPickerPackage != null
+ && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
+ grantRuntimePermissions(globalSearchPickerPackage,
+ MICROPHONE_PERMISSIONS, true, userId);
+ grantRuntimePermissions(globalSearchPickerPackage,
+ LOCATION_PERMISSIONS, true, userId);
+ }
+ }
+
+ // Voice recognition
+ Intent voiceRecoIntent = new Intent("android.speech.RecognitionService");
+ voiceRecoIntent.addCategory(Intent.CATEGORY_DEFAULT);
+ PackageParser.Package voiceRecoPackage = getDefaultSystemHandlerServicePackage(
+ voiceRecoIntent, userId);
+ if (voiceRecoPackage != null
+ && doesPackageSupportRuntimePermissions(voiceRecoPackage)) {
+ grantRuntimePermissions(voiceRecoPackage, MICROPHONE_PERMISSIONS, userId);
+ }
+
+ // Location
+ if (locationPackageNames != null) {
+ for (String packageName : locationPackageNames) {
+ PackageParser.Package locationPackage = getSystemPackage(packageName);
+ if (locationPackage != null
+ && doesPackageSupportRuntimePermissions(locationPackage)) {
+ grantRuntimePermissions(locationPackage, CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissions(locationPackage, CALENDAR_PERMISSIONS, userId);
+ grantRuntimePermissions(locationPackage, MICROPHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(locationPackage, PHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(locationPackage, SMS_PERMISSIONS, userId);
+ grantRuntimePermissions(locationPackage, LOCATION_PERMISSIONS,
+ true, userId);
+ grantRuntimePermissions(locationPackage, CAMERA_PERMISSIONS, userId);
+ grantRuntimePermissions(locationPackage, SENSORS_PERMISSIONS, userId);
+ grantRuntimePermissions(locationPackage, STORAGE_PERMISSIONS, userId);
+ }
+ }
+ }
+
+ // Music
+ Intent musicIntent = new Intent(Intent.ACTION_VIEW);
+ musicIntent.addCategory(Intent.CATEGORY_DEFAULT);
+ musicIntent.setDataAndType(Uri.fromFile(new File("foo.mp3")),
+ AUDIO_MIME_TYPE);
+ PackageParser.Package musicPackage = getDefaultSystemHandlerActivityPackage(
+ musicIntent, userId);
+ if (musicPackage != null
+ && doesPackageSupportRuntimePermissions(musicPackage)) {
+ grantRuntimePermissions(musicPackage, STORAGE_PERMISSIONS, userId);
+ }
+
+ // Home
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+ homeIntent.addCategory(Intent.CATEGORY_LAUNCHER_APP);
+ PackageParser.Package homePackage = getDefaultSystemHandlerActivityPackage(
+ homeIntent, userId);
+ if (homePackage != null
+ && doesPackageSupportRuntimePermissions(homePackage)) {
+ grantRuntimePermissions(homePackage, LOCATION_PERMISSIONS, false, userId);
+ }
+
+ // Watches
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
+ // Home application on watches
+ Intent wearHomeIntent = new Intent(Intent.ACTION_MAIN);
+ wearHomeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
+
+ PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackage(
+ wearHomeIntent, userId);
+
+ if (wearHomePackage != null
+ && doesPackageSupportRuntimePermissions(wearHomePackage)) {
+ grantRuntimePermissions(wearHomePackage, CONTACTS_PERMISSIONS, false,
+ userId);
+ grantRuntimePermissions(wearHomePackage, PHONE_PERMISSIONS, true, userId);
+ grantRuntimePermissions(wearHomePackage, MICROPHONE_PERMISSIONS, false,
+ userId);
+ grantRuntimePermissions(wearHomePackage, LOCATION_PERMISSIONS, false,
+ userId);
+ }
+
+ // Fitness tracking on watches
+ Intent trackIntent = new Intent(ACTION_TRACK);
+ PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackage(
+ trackIntent, userId);
+ if (trackPackage != null
+ && doesPackageSupportRuntimePermissions(trackPackage)) {
+ grantRuntimePermissions(trackPackage, SENSORS_PERMISSIONS, false, userId);
+ grantRuntimePermissions(trackPackage, LOCATION_PERMISSIONS, false, userId);
+ }
+ }
+
+ // Print Spooler
+ PackageParser.Package printSpoolerPackage = getSystemPackage(
+ PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
+ if (printSpoolerPackage != null
+ && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
+ grantRuntimePermissions(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
+ }
+
+ // EmergencyInfo
+ Intent emergencyInfoIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
+ PackageParser.Package emergencyInfoPckg = getDefaultSystemHandlerActivityPackage(
+ emergencyInfoIntent, userId);
+ if (emergencyInfoPckg != null
+ && doesPackageSupportRuntimePermissions(emergencyInfoPckg)) {
+ grantRuntimePermissions(emergencyInfoPckg, CONTACTS_PERMISSIONS, true, userId);
+ grantRuntimePermissions(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
+ }
+
+ // NFC Tag viewer
+ Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
+ nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
+ PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackage(
+ nfcTagIntent, userId);
+ if (nfcTagPkg != null
+ && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
+ grantRuntimePermissions(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
+ grantRuntimePermissions(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
+ }
+
+ // Storage Manager
+ Intent storageManagerIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
+ PackageParser.Package storageManagerPckg = getDefaultSystemHandlerActivityPackage(
+ storageManagerIntent, userId);
+ if (storageManagerPckg != null
+ && doesPackageSupportRuntimePermissions(storageManagerPckg)) {
+ grantRuntimePermissions(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
+ }
+
+ // Companion devices
+ PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackage(
+ CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
+ if (companionDeviceDiscoveryPackage != null
+ && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
+ grantRuntimePermissions(companionDeviceDiscoveryPackage,
+ LOCATION_PERMISSIONS, true, userId);
+ }
+
+ // Ringtone Picker
+ Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+ PackageParser.Package ringtonePickerPackage =
+ getDefaultSystemHandlerActivityPackage(ringtonePickerIntent, userId);
+ if (ringtonePickerPackage != null
+ && doesPackageSupportRuntimePermissions(ringtonePickerPackage)) {
+ grantRuntimePermissions(ringtonePickerPackage,
+ STORAGE_PERMISSIONS, true, userId);
+ }
+
+ if (mPermissionGrantedCallback != null) {
+ mPermissionGrantedCallback.onDefaultRuntimePermissionsGranted(userId);
+ }
+ }
+
+ private void grantDefaultPermissionsToDefaultSystemDialerApp(
+ PackageParser.Package dialerPackage, int userId) {
+ if (doesPackageSupportRuntimePermissions(dialerPackage)) {
+ boolean isPhonePermFixed =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
+ grantRuntimePermissions(
+ dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
+ grantRuntimePermissions(dialerPackage, CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissions(dialerPackage, SMS_PERMISSIONS, userId);
+ grantRuntimePermissions(dialerPackage, MICROPHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(dialerPackage, CAMERA_PERMISSIONS, userId);
+ }
+ }
+
+ private void grantDefaultPermissionsToDefaultSystemSmsApp(
+ PackageParser.Package smsPackage, int userId) {
+ if (doesPackageSupportRuntimePermissions(smsPackage)) {
+ grantRuntimePermissions(smsPackage, PHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(smsPackage, CONTACTS_PERMISSIONS, userId);
+ grantRuntimePermissions(smsPackage, SMS_PERMISSIONS, userId);
+ grantRuntimePermissions(smsPackage, STORAGE_PERMISSIONS, userId);
+ grantRuntimePermissions(smsPackage, MICROPHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(smsPackage, CAMERA_PERMISSIONS, userId);
+ }
+ }
+
+ public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
+ Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
+ if (packageName == null) {
+ return;
+ }
+ PackageParser.Package smsPackage = getPackage(packageName);
+ if (smsPackage != null && doesPackageSupportRuntimePermissions(smsPackage)) {
+ grantRuntimePermissions(smsPackage, PHONE_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(smsPackage, CONTACTS_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(smsPackage, SMS_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(smsPackage, STORAGE_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(smsPackage, MICROPHONE_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(smsPackage, CAMERA_PERMISSIONS, false, true, userId);
+ }
+ }
+
+ public void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId) {
+ Log.i(TAG, "Granting permissions to default dialer app for user:" + userId);
+ if (packageName == null) {
+ return;
+ }
+ PackageParser.Package dialerPackage = getPackage(packageName);
+ if (dialerPackage != null
+ && doesPackageSupportRuntimePermissions(dialerPackage)) {
+ grantRuntimePermissions(dialerPackage, PHONE_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(dialerPackage, CONTACTS_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(dialerPackage, SMS_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(dialerPackage, MICROPHONE_PERMISSIONS, false, true, userId);
+ grantRuntimePermissions(dialerPackage, CAMERA_PERMISSIONS, false, true, userId);
+ }
+ }
+
+ private void grantDefaultPermissionsToDefaultSimCallManager(
+ PackageParser.Package simCallManagerPackage, int userId) {
+ Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
+ if (doesPackageSupportRuntimePermissions(simCallManagerPackage)) {
+ grantRuntimePermissions(simCallManagerPackage, PHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(simCallManagerPackage, MICROPHONE_PERMISSIONS, userId);
+ }
+ }
+
+ public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
+ if (packageName == null) {
+ return;
+ }
+ PackageParser.Package simCallManagerPackage = getPackage(packageName);
+ if (simCallManagerPackage != null) {
+ grantDefaultPermissionsToDefaultSimCallManager(simCallManagerPackage, userId);
+ }
+ }
+
+ public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId) {
+ Log.i(TAG, "Granting permissions to enabled carrier apps for user:" + userId);
+ if (packageNames == null) {
+ return;
+ }
+ for (String packageName : packageNames) {
+ PackageParser.Package carrierPackage = getSystemPackage(packageName);
+ if (carrierPackage != null
+ && doesPackageSupportRuntimePermissions(carrierPackage)) {
+ grantRuntimePermissions(carrierPackage, PHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(carrierPackage, LOCATION_PERMISSIONS, userId);
+ grantRuntimePermissions(carrierPackage, SMS_PERMISSIONS, userId);
+ }
+ }
+ }
+
+ public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId) {
+ Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
+ if (packageNames == null) {
+ return;
+ }
+ for (String packageName : packageNames) {
+ PackageParser.Package imsServicePackage = getSystemPackage(packageName);
+ if (imsServicePackage != null
+ && doesPackageSupportRuntimePermissions(imsServicePackage)) {
+ grantRuntimePermissions(imsServicePackage, PHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
+ grantRuntimePermissions(imsServicePackage, LOCATION_PERMISSIONS, userId);
+ grantRuntimePermissions(imsServicePackage, CAMERA_PERMISSIONS, userId);
+ }
+ }
+ }
+
+ public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) {
+ Log.i(TAG, "Granting permissions to default browser for user:" + userId);
+ if (packageName == null) {
+ return;
+ }
+ PackageParser.Package browserPackage = getSystemPackage(packageName);
+ if (browserPackage != null
+ && doesPackageSupportRuntimePermissions(browserPackage)) {
+ grantRuntimePermissions(browserPackage, LOCATION_PERMISSIONS, false, false, userId);
+ }
+ }
+
+ private PackageParser.Package getDefaultSystemHandlerActivityPackage(
+ Intent intent, int userId) {
+ ResolveInfo handler = mServiceInternal.resolveIntent(intent,
+ intent.resolveType(mContext.getContentResolver()), DEFAULT_FLAGS, userId, false);
+ if (handler == null || handler.activityInfo == null) {
+ return null;
+ }
+ if (mServiceInternal.isResolveActivityComponent(handler.activityInfo)) {
+ return null;
+ }
+ return getSystemPackage(handler.activityInfo.packageName);
+ }
+
+ private PackageParser.Package getDefaultSystemHandlerServicePackage(
+ Intent intent, int userId) {
+ List<ResolveInfo> handlers = mServiceInternal.queryIntentServices(
+ intent, DEFAULT_FLAGS, Binder.getCallingUid(), userId);
+ if (handlers == null) {
+ return null;
+ }
+ final int handlerCount = handlers.size();
+ for (int i = 0; i < handlerCount; i++) {
+ ResolveInfo handler = handlers.get(i);
+ PackageParser.Package handlerPackage = getSystemPackage(
+ handler.serviceInfo.packageName);
+ if (handlerPackage != null) {
+ return handlerPackage;
+ }
+ }
+ return null;
+ }
+
+ private List<PackageParser.Package> getHeadlessSyncAdapterPackages(
+ String[] syncAdapterPackageNames, int userId) {
+ List<PackageParser.Package> syncAdapterPackages = new ArrayList<>();
+
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+ for (String syncAdapterPackageName : syncAdapterPackageNames) {
+ homeIntent.setPackage(syncAdapterPackageName);
+
+ ResolveInfo homeActivity = mServiceInternal.resolveIntent(homeIntent,
+ homeIntent.resolveType(mContext.getContentResolver()), DEFAULT_FLAGS,
+ userId, false);
+ if (homeActivity != null) {
+ continue;
+ }
+
+ PackageParser.Package syncAdapterPackage = getSystemPackage(syncAdapterPackageName);
+ if (syncAdapterPackage != null) {
+ syncAdapterPackages.add(syncAdapterPackage);
+ }
+ }
+
+ return syncAdapterPackages;
+ }
+
+ private PackageParser.Package getDefaultProviderAuthorityPackage(
+ String authority, int userId) {
+ ProviderInfo provider =
+ mServiceInternal.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
+ if (provider != null) {
+ return getSystemPackage(provider.packageName);
+ }
+ return null;
+ }
+
+ private PackageParser.Package getPackage(String packageName) {
+ return mServiceInternal.getPackage(packageName);
+ }
+
+ private PackageParser.Package getSystemPackage(String packageName) {
+ PackageParser.Package pkg = getPackage(packageName);
+ if (pkg != null && pkg.isSystemApp()) {
+ return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null;
+ }
+ return null;
+ }
+
+ private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+ int userId) {
+ grantRuntimePermissions(pkg, permissions, false, false, userId);
+ }
+
+ private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+ boolean systemFixed, int userId) {
+ grantRuntimePermissions(pkg, permissions, systemFixed, false, userId);
+ }
+
+ private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+ boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
+ if (pkg.requestedPermissions.isEmpty()) {
+ return;
+ }
+
+ List<String> requestedPermissions = pkg.requestedPermissions;
+ Set<String> grantablePermissions = null;
+
+ // If this is the default Phone or SMS app we grant permissions regardless
+ // whether the version on the system image declares the permission as used since
+ // selecting the app as the default Phone or SMS the user makes a deliberate
+ // choice to grant this app the permissions needed to function. For all other
+ // apps, (default grants on first boot and user creation) we don't grant default
+ // permissions if the version on the system image does not declare them.
+ if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
+ final PackageParser.Package disabledPkg =
+ mServiceInternal.getDisabledPackage(pkg.packageName);
+ if (disabledPkg != null) {
+ if (disabledPkg.requestedPermissions.isEmpty()) {
+ return;
+ }
+ if (!requestedPermissions.equals(disabledPkg.requestedPermissions)) {
+ grantablePermissions = new ArraySet<>(requestedPermissions);
+ requestedPermissions = disabledPkg.requestedPermissions;
+ }
+ }
+ }
+
+ final int grantablePermissionCount = requestedPermissions.size();
+ for (int i = 0; i < grantablePermissionCount; i++) {
+ String permission = requestedPermissions.get(i);
+
+ // If there is a disabled system app it may request a permission the updated
+ // version ot the data partition doesn't, In this case skip the permission.
+ if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
+ continue;
+ }
+
+ if (permissions.contains(permission)) {
+ final int flags = mServiceInternal.getPermissionFlagsTEMP(
+ permission, pkg.packageName, userId);
+
+ // If any flags are set to the permission, then it is either set in
+ // its current state by the system or device/profile owner or the user.
+ // In all these cases we do not want to clobber the current state.
+ // Unless the caller wants to override user choices. The override is
+ // to make sure we can grant the needed permission to the default
+ // sms and phone apps after the user chooses this in the UI.
+ if (flags == 0 || isDefaultPhoneOrSms) {
+ // Never clobber policy or system.
+ final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+ | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+ if ((flags & fixedFlags) != 0) {
+ continue;
+ }
+
+ mServiceInternal.grantRuntimePermission(
+ pkg.packageName, permission, userId, false);
+ if (DEBUG) {
+ Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
+ + permission + " to default handler " + pkg.packageName);
+ }
+
+ int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+ if (systemFixed) {
+ newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+ }
+
+ mServiceInternal.updatePermissionFlagsTEMP(permission, pkg.packageName,
+ newFlags, newFlags, userId);
+ }
+
+ // If a component gets a permission for being the default handler A
+ // and also default handler B, we grant the weaker grant form.
+ if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
+ && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+ && !systemFixed) {
+ if (DEBUG) {
+ Log.i(TAG, "Granted not fixed " + permission + " to default handler "
+ + pkg.packageName);
+ }
+ mServiceInternal.updatePermissionFlagsTEMP(permission, pkg.packageName,
+ PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
+ }
+ }
+ }
+ }
+
+ private boolean isSysComponentOrPersistentPlatformSignedPrivApp(PackageParser.Package pkg) {
+ if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
+ return true;
+ }
+ if (!pkg.isPrivilegedApp()) {
+ return false;
+ }
+ final PackageParser.Package disabledPkg =
+ mServiceInternal.getDisabledPackage(pkg.packageName);
+ if (disabledPkg != null) {
+ if ((disabledPkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
+ return false;
+ }
+ } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
+ return false;
+ }
+ final String systemPackageName = mServiceInternal.getKnownPackageName(
+ PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+ final PackageParser.Package systemPackage = getPackage(systemPackageName);
+ return PackageManagerService.compareSignatures(systemPackage.mSignatures,
+ pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+ }
+
+ private void grantDefaultPermissionExceptions(int userId) {
+ mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
+
+ synchronized (mLock) {
+ // mGrantExceptions is null only before the first read and then
+ // it serves as a cache of the default grants that should be
+ // performed for every user. If there is an entry then the app
+ // is on the system image and supports runtime permissions.
+ if (mGrantExceptions == null) {
+ mGrantExceptions = readDefaultPermissionExceptionsLocked();
+ }
+ }
+
+ Set<String> permissions = null;
+ final int exceptionCount = mGrantExceptions.size();
+ for (int i = 0; i < exceptionCount; i++) {
+ String packageName = mGrantExceptions.keyAt(i);
+ PackageParser.Package pkg = getSystemPackage(packageName);
+ List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
+ final int permissionGrantCount = permissionGrants.size();
+ for (int j = 0; j < permissionGrantCount; j++) {
+ DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
+ if (permissions == null) {
+ permissions = new ArraySet<>();
+ } else {
+ permissions.clear();
+ }
+ permissions.add(permissionGrant.name);
+ grantRuntimePermissions(pkg, permissions,
+ permissionGrant.fixed, userId);
+ }
+ }
+ }
+
+ private File[] getDefaultPermissionFiles() {
+ ArrayList<File> ret = new ArrayList<File>();
+ File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
+ if (dir.isDirectory() && dir.canRead()) {
+ Collections.addAll(ret, dir.listFiles());
+ }
+ dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
+ if (dir.isDirectory() && dir.canRead()) {
+ Collections.addAll(ret, dir.listFiles());
+ }
+ return ret.isEmpty() ? null : ret.toArray(new File[0]);
+ }
+
+ private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
+ readDefaultPermissionExceptionsLocked() {
+ File[] files = getDefaultPermissionFiles();
+ if (files == null) {
+ return new ArrayMap<>(0);
+ }
+
+ ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
+
+ // Iterate over the files in the directory and scan .xml files
+ for (File file : files) {
+ if (!file.getPath().endsWith(".xml")) {
+ Slog.i(TAG, "Non-xml file " + file
+ + " in " + file.getParent() + " directory, ignoring");
+ continue;
+ }
+ if (!file.canRead()) {
+ Slog.w(TAG, "Default permissions file " + file + " cannot be read");
+ continue;
+ }
+ try (
+ InputStream str = new BufferedInputStream(new FileInputStream(file))
+ ) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(str, null);
+ parse(parser, grantExceptions);
+ } catch (XmlPullParserException | IOException e) {
+ Slog.w(TAG, "Error reading default permissions file " + file, e);
+ }
+ }
+
+ return grantExceptions;
+ }
+
+ private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
+ outGrantExceptions) throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (TAG_EXCEPTIONS.equals(parser.getName())) {
+ parseExceptions(parser, outGrantExceptions);
+ } else {
+ Log.e(TAG, "Unknown tag " + parser.getName());
+ }
+ }
+ }
+
+ private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
+ outGrantExceptions) throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (TAG_EXCEPTION.equals(parser.getName())) {
+ String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+
+ List<DefaultPermissionGrant> packageExceptions =
+ outGrantExceptions.get(packageName);
+ if (packageExceptions == null) {
+ // The package must be on the system image
+ PackageParser.Package pkg = getSystemPackage(packageName);
+ if (pkg == null) {
+ Log.w(TAG, "Unknown package:" + packageName);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
+ // The package must support runtime permissions
+ if (!doesPackageSupportRuntimePermissions(pkg)) {
+ Log.w(TAG, "Skipping non supporting runtime permissions package:"
+ + packageName);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ packageExceptions = new ArrayList<>();
+ outGrantExceptions.put(packageName, packageExceptions);
+ }
+
+ parsePermission(parser, packageExceptions);
+ } else {
+ Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
+ }
+ }
+ }
+
+ private void parsePermission(XmlPullParser parser, List<DefaultPermissionGrant>
+ outPackageExceptions) throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (TAG_PERMISSION.contains(parser.getName())) {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ if (name == null) {
+ Log.w(TAG, "Mandatory name attribute missing for permission tag");
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
+ final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED);
+
+ DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed);
+ outPackageExceptions.add(exception);
+ } else {
+ Log.e(TAG, "Unknown tag " + parser.getName() + "under <exception>");
+ }
+ }
+ }
+
+ private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
+ return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
+ }
+
+ private static final class DefaultPermissionGrant {
+ final String name;
+ final boolean fixed;
+
+ public DefaultPermissionGrant(String name, boolean fixed) {
+ this.name = name;
+ this.fixed = fixed;
+ }
+ }
+}
diff --git a/com/android/server/pm/permission/PermissionManagerInternal.java b/com/android/server/pm/permission/PermissionManagerInternal.java
new file mode 100644
index 00000000..3b20b42b
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -0,0 +1,140 @@
+/*
+ * 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 com.android.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PermissionInfoFlags;
+import android.content.pm.PackageParser.Permission;
+
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Internal interfaces to be used by other components within the system server.
+ */
+public abstract class PermissionManagerInternal {
+ /**
+ * Callbacks invoked when interesting actions have been taken on a permission.
+ * <p>
+ * NOTE: The current arguments are merely to support the existing use cases. This
+ * needs to be properly thought out with appropriate arguments for each of the
+ * callback methods.
+ */
+ public static class PermissionCallback {
+ public void onGidsChanged(int appId, int userId) {
+ }
+ public void onPermissionChanged() {
+ }
+ public void onPermissionGranted(int uid, int userId) {
+ }
+ public void onInstallPermissionGranted() {
+ }
+ public void onPermissionRevoked(int uid, int userId) {
+ }
+ public void onInstallPermissionRevoked() {
+ }
+ public void onPermissionUpdated(int userId) {
+ }
+ public void onPermissionRemoved() {
+ }
+ public void onInstallPermissionUpdated() {
+ }
+ }
+
+ public abstract void grantRuntimePermission(
+ @NonNull String permName, @NonNull String packageName, boolean overridePolicy,
+ int callingUid, int userId, @Nullable PermissionCallback callback);
+ public abstract void grantRuntimePermissionsGrantedToDisabledPackage(
+ @NonNull PackageParser.Package pkg, int callingUid,
+ @Nullable PermissionCallback callback);
+ public abstract void grantRequestedRuntimePermissions(
+ @NonNull PackageParser.Package pkg, @NonNull int[] userIds,
+ @NonNull String[] grantedPermissions, int callingUid,
+ @Nullable PermissionCallback callback);
+ public abstract void revokeRuntimePermission(@NonNull String permName,
+ @NonNull String packageName, boolean overridePolicy, int callingUid, int userId,
+ @Nullable PermissionCallback callback);
+ public abstract int[] revokeUnusedSharedUserPermissions(@NonNull SharedUserSetting suSetting,
+ @NonNull int[] allUserIds);
+
+
+ public abstract boolean addPermission(@NonNull PermissionInfo info, boolean async,
+ int callingUid, @Nullable PermissionCallback callback);
+ public abstract void removePermission(@NonNull String permName, int callingUid,
+ @Nullable PermissionCallback callback);
+
+ public abstract int getPermissionFlags(@NonNull String permName,
+ @NonNull String packageName, int callingUid, int userId);
+ /**
+ * Retrieve all of the information we know about a particular permission.
+ */
+ public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName,
+ @NonNull String packageName, @PermissionInfoFlags int flags, int callingUid);
+ /**
+ * Retrieve all of the permissions associated with a particular group.
+ */
+ public abstract @Nullable List<PermissionInfo> getPermissionInfoByGroup(@NonNull String group,
+ @PermissionInfoFlags int flags, int callingUid);
+ public abstract boolean isPermissionAppOp(@NonNull String permName);
+ public abstract boolean isPermissionInstant(@NonNull String permName);
+
+ /**
+ * Updates the flags associated with a permission by replacing the flags in
+ * the specified mask with the provided flag values.
+ */
+ public abstract void updatePermissionFlags(@NonNull String permName,
+ @NonNull String packageName, int flagMask, int flagValues, int callingUid, int userId,
+ @Nullable PermissionCallback callback);
+ /**
+ * Updates the flags for all applications by replacing the flags in the specified mask
+ * with the provided flag values.
+ */
+ public abstract boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues,
+ int callingUid, int userId, @NonNull Collection<PackageParser.Package> packages,
+ @Nullable PermissionCallback callback);
+
+ public abstract int checkPermission(@NonNull String permName, @NonNull String packageName,
+ int callingUid, int userId);
+
+ /**
+ * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
+ * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userid} is not for the caller.
+ * @param checkShell whether to prevent shell from access if there's a debugging restriction
+ * @param message the message to log on security exception
+ */
+ public abstract void enforceCrossUserPermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell, @NonNull String message);
+ public abstract void enforceGrantRevokeRuntimePermissionPermissions(@NonNull String message);
+
+ public abstract @NonNull PermissionSettings getPermissionSettings();
+ public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy();
+
+ /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
+ public abstract Iterator<BasePermission> getPermissionIteratorTEMP();
+ public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
+ public abstract void putPermissionTEMP(@NonNull String permName,
+ @NonNull BasePermission permission);
+} \ No newline at end of file
diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java
new file mode 100644
index 00000000..6c031a6a
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionManagerService.java
@@ -0,0 +1,1081 @@
+/*
+ * 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 com.android.server.pm.permission;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageParser.Package;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.os.storage.StorageManagerInternal;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
+import com.android.server.Watchdog;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.ProcessLoggingHandler;
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
+
+import libcore.util.EmptyArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Manages all permissions and handles permissions related tasks.
+ */
+public class PermissionManagerService {
+ private static final String TAG = "PackageManager";
+
+ /** All dangerous permission names in the same order as the events in MetricsEvent */
+ private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
+ Manifest.permission.READ_CALENDAR,
+ Manifest.permission.WRITE_CALENDAR,
+ Manifest.permission.CAMERA,
+ Manifest.permission.READ_CONTACTS,
+ Manifest.permission.WRITE_CONTACTS,
+ Manifest.permission.GET_ACCOUNTS,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.READ_PHONE_STATE,
+ Manifest.permission.CALL_PHONE,
+ Manifest.permission.READ_CALL_LOG,
+ Manifest.permission.WRITE_CALL_LOG,
+ Manifest.permission.ADD_VOICEMAIL,
+ Manifest.permission.USE_SIP,
+ Manifest.permission.PROCESS_OUTGOING_CALLS,
+ Manifest.permission.READ_CELL_BROADCASTS,
+ Manifest.permission.BODY_SENSORS,
+ Manifest.permission.SEND_SMS,
+ Manifest.permission.RECEIVE_SMS,
+ Manifest.permission.READ_SMS,
+ Manifest.permission.RECEIVE_WAP_PUSH,
+ Manifest.permission.RECEIVE_MMS,
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_PHONE_NUMBERS,
+ Manifest.permission.ANSWER_PHONE_CALLS);
+
+ /** Cap the size of permission trees that 3rd party apps can define */
+ private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768; // characters of text
+
+ /** Lock to protect internal data access */
+ private final Object mLock;
+
+ /** Internal connection to the package manager */
+ private final PackageManagerInternal mPackageManagerInt;
+
+ /** Internal connection to the user manager */
+ private final UserManagerInternal mUserManagerInt;
+
+ /** Default permission policy to provide proper behaviour out-of-the-box */
+ private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
+
+ /** Internal storage for permissions and related settings */
+ private final PermissionSettings mSettings;
+
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
+ private final Context mContext;
+
+ PermissionManagerService(Context context,
+ @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
+ @NonNull Object externalLock) {
+ mContext = context;
+ mLock = externalLock;
+ mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
+ mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
+ mSettings = new PermissionSettings(context, mLock);
+
+ mHandlerThread = new ServiceThread(TAG,
+ Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ Watchdog.getInstance().addThread(mHandler);
+
+ mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy(
+ context, mHandlerThread.getLooper(), defaultGrantCallback, this);
+
+ // propagate permission configuration
+ final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
+ SystemConfig.getInstance().getPermissions();
+ synchronized (mLock) {
+ for (int i=0; i<permConfig.size(); i++) {
+ final SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
+ BasePermission bp = mSettings.getPermissionLocked(perm.name);
+ if (bp == null) {
+ bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
+ mSettings.putPermissionLocked(perm.name, bp);
+ }
+ if (perm.gids != null) {
+ bp.setGids(perm.gids, perm.perUser);
+ }
+ }
+ }
+
+ LocalServices.addService(
+ PermissionManagerInternal.class, new PermissionManagerInternalImpl());
+ }
+
+ /**
+ * Creates and returns an initialized, internal service for use by other components.
+ * <p>
+ * The object returned is identical to the one returned by the LocalServices class using:
+ * {@code LocalServices.getService(PermissionManagerInternal.class);}
+ * <p>
+ * NOTE: The external lock is temporary and should be removed. This needs to be a
+ * lock created by the permission manager itself.
+ */
+ public static PermissionManagerInternal create(Context context,
+ @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
+ @NonNull Object externalLock) {
+ final PermissionManagerInternal permMgrInt =
+ LocalServices.getService(PermissionManagerInternal.class);
+ if (permMgrInt != null) {
+ return permMgrInt;
+ }
+ new PermissionManagerService(context, defaultGrantCallback, externalLock);
+ return LocalServices.getService(PermissionManagerInternal.class);
+ }
+
+ @Nullable BasePermission getPermission(String permName) {
+ synchronized (mLock) {
+ return mSettings.getPermissionLocked(permName);
+ }
+ }
+
+ private int checkPermission(String permName, String pkgName, int callingUid, int userId) {
+ if (!mUserManagerInt.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+ if (pkg != null && pkg.mExtras != null) {
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ final boolean instantApp = ps.getInstantApp(userId);
+ final PermissionsState permissionsState = ps.getPermissionsState();
+ if (permissionsState.hasPermission(permName, userId)) {
+ if (instantApp) {
+ synchronized (mLock) {
+ BasePermission bp = mSettings.getPermissionLocked(permName);
+ if (bp != null && bp.isInstant()) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ } else {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+ if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+ .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ private PermissionInfo getPermissionInfo(String name, String packageName, int flags,
+ int callingUid) {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ return null;
+ }
+ // reader
+ synchronized (mLock) {
+ final BasePermission bp = mSettings.getPermissionLocked(name);
+ if (bp == null) {
+ return null;
+ }
+ final int adjustedProtectionLevel = adjustPermissionProtectionFlagsLocked(
+ bp.getProtectionLevel(), packageName, callingUid);
+ return bp.generatePermissionInfo(adjustedProtectionLevel, flags);
+ }
+ }
+
+ private List<PermissionInfo> getPermissionInfoByGroup(
+ String groupName, int flags, int callingUid) {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ return null;
+ }
+ // reader
+ synchronized (mLock) {
+ // TODO Uncomment when mPermissionGroups moves to this class
+// if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
+// // This is thrown as NameNotFoundException
+// return null;
+// }
+
+ final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
+ for (BasePermission bp : mSettings.getAllPermissionsLocked()) {
+ final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
+ if (pi != null) {
+ out.add(pi);
+ }
+ }
+ return out;
+ }
+ }
+
+ private int adjustPermissionProtectionFlagsLocked(
+ int protectionLevel, String packageName, int uid) {
+ // Signature permission flags area always reported
+ final int protectionLevelMasked = protectionLevel
+ & (PermissionInfo.PROTECTION_NORMAL
+ | PermissionInfo.PROTECTION_DANGEROUS
+ | PermissionInfo.PROTECTION_SIGNATURE);
+ if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
+ return protectionLevel;
+ }
+ // System sees all flags.
+ final int appId = UserHandle.getAppId(uid);
+ if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
+ || appId == Process.SHELL_UID) {
+ return protectionLevel;
+ }
+ // Normalize package name to handle renamed packages and static libs
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+ if (pkg == null) {
+ return protectionLevel;
+ }
+ if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ return protectionLevelMasked;
+ }
+ // Apps that target O see flags for all protection levels.
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
+ return protectionLevel;
+ }
+ if (ps.getAppId() != appId) {
+ return protectionLevel;
+ }
+ return protectionLevel;
+ }
+
+ private boolean addPermission(
+ PermissionInfo info, int callingUid, PermissionCallback callback) {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ throw new SecurityException("Instant apps can't add permissions");
+ }
+ if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
+ throw new SecurityException("Label must be specified in permission");
+ }
+ final BasePermission tree = (BasePermission) mPackageManagerInt.enforcePermissionTreeTEMP(
+ info.name, callingUid);
+ final boolean added;
+ final boolean changed;
+ synchronized (mLock) {
+ BasePermission bp = mSettings.getPermissionLocked(info.name);
+ added = bp == null;
+ int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+ if (added) {
+ enforcePermissionCapLocked(info, tree);
+ bp = new BasePermission(info.name, tree.getSourcePackageName(),
+ BasePermission.TYPE_DYNAMIC);
+ } else if (bp.isDynamic()) {
+ throw new SecurityException(
+ "Not allowed to modify non-dynamic permission "
+ + info.name);
+ }
+ changed = bp.addToTree(fixedLevel, info, tree);
+ if (added) {
+ mSettings.putPermissionLocked(info.name, bp);
+ }
+ }
+ if (changed && callback != null) {
+ callback.onPermissionChanged();
+ }
+ return added;
+ }
+
+ private void removePermission(
+ String permName, int callingUid, PermissionCallback callback) {
+ if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+ throw new SecurityException("Instant applications don't have access to this method");
+ }
+ final BasePermission tree = (BasePermission) mPackageManagerInt.enforcePermissionTreeTEMP(
+ permName, callingUid);
+ synchronized (mLock) {
+ final BasePermission bp = mSettings.getPermissionLocked(permName);
+ if (bp == null) {
+ return;
+ }
+ if (bp.isDynamic()) {
+ throw new SecurityException(
+ "Not allowed to modify non-dynamic permission "
+ + permName);
+ }
+ mSettings.removePermissionLocked(permName);
+ if (callback != null) {
+ callback.onPermissionRemoved();
+ }
+ }
+ }
+
+ private void grantRuntimePermissionsGrantedToDisabledPackageLocked(
+ PackageParser.Package pkg, int callingUid, PermissionCallback callback) {
+ if (pkg.parentPackage == null) {
+ return;
+ }
+ if (pkg.requestedPermissions == null) {
+ return;
+ }
+ final PackageParser.Package disabledPkg =
+ mPackageManagerInt.getDisabledPackage(pkg.parentPackage.packageName);
+ if (disabledPkg == null || disabledPkg.mExtras == null) {
+ return;
+ }
+ final PackageSetting disabledPs = (PackageSetting) disabledPkg.mExtras;
+ if (!disabledPs.isPrivileged() || disabledPs.hasChildPackages()) {
+ return;
+ }
+ final int permCount = pkg.requestedPermissions.size();
+ for (int i = 0; i < permCount; i++) {
+ String permission = pkg.requestedPermissions.get(i);
+ BasePermission bp = mSettings.getPermissionLocked(permission);
+ if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) {
+ continue;
+ }
+ for (int userId : mUserManagerInt.getUserIds()) {
+ if (disabledPs.getPermissionsState().hasRuntimePermission(permission, userId)) {
+ grantRuntimePermission(
+ permission, pkg.packageName, false, callingUid, userId, callback);
+ }
+ }
+ }
+ }
+
+ private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
+ String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+ for (int userId : userIds) {
+ grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions, callingUid,
+ callback);
+ }
+ }
+
+ private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
+ String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+ PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
+ return;
+ }
+
+ PermissionsState permissionsState = ps.getPermissionsState();
+
+ final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+ | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+
+ final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
+ >= Build.VERSION_CODES.M;
+
+ final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.packageName, userId);
+
+ for (String permission : pkg.requestedPermissions) {
+ final BasePermission bp;
+ synchronized (mLock) {
+ bp = mSettings.getPermissionLocked(permission);
+ }
+ if (bp != null && (bp.isRuntime() || bp.isDevelopment())
+ && (!instantApp || bp.isInstant())
+ && (supportsRuntimePermissions || !bp.isRuntimeOnly())
+ && (grantedPermissions == null
+ || ArrayUtils.contains(grantedPermissions, permission))) {
+ final int flags = permissionsState.getPermissionFlags(permission, userId);
+ if (supportsRuntimePermissions) {
+ // Installer cannot change immutable permissions.
+ if ((flags & immutableFlags) == 0) {
+ grantRuntimePermission(permission, pkg.packageName, false, callingUid,
+ userId, callback);
+ }
+ } else if (mSettings.mPermissionReviewRequired) {
+ // In permission review mode we clear the review flag when we
+ // are asked to install the app with all permissions granted.
+ if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ updatePermissionFlags(permission, pkg.packageName,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, callingUid,
+ userId, callback);
+ }
+ }
+ }
+ }
+ }
+
+ private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
+ int callingUid, final int userId, PermissionCallback callback) {
+ if (!mUserManagerInt.exists(userId)) {
+ Log.e(TAG, "No such user:" + userId);
+ return;
+ }
+
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ "grantRuntimePermission");
+
+ enforceCrossUserPermission(callingUid, userId,
+ true /* requireFullPermission */, true /* checkShell */,
+ "grantRuntimePermission");
+
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+ if (pkg == null || pkg.mExtras == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ final BasePermission bp;
+ synchronized(mLock) {
+ bp = mSettings.getPermissionLocked(permName);
+ }
+ if (bp == null) {
+ throw new IllegalArgumentException("Unknown permission: " + permName);
+ }
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+
+ bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (mSettings.mPermissionReviewRequired
+ && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+ && bp.isRuntime()) {
+ return;
+ }
+
+ final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ final PermissionsState permissionsState = ps.getPermissionsState();
+
+ final int flags = permissionsState.getPermissionFlags(permName, userId);
+ if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+ throw new SecurityException("Cannot grant system fixed permission "
+ + permName + " for package " + packageName);
+ }
+ if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ throw new SecurityException("Cannot grant policy fixed permission "
+ + permName + " for package " + packageName);
+ }
+
+ if (bp.isDevelopment()) {
+ // Development permissions must be handled specially, since they are not
+ // normal runtime permissions. For now they apply to all users.
+ if (permissionsState.grantInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ if (callback != null) {
+ callback.onInstallPermissionGranted();
+ }
+ }
+ return;
+ }
+
+ if (ps.getInstantApp(userId) && !bp.isInstant()) {
+ throw new SecurityException("Cannot grant non-ephemeral permission"
+ + permName + " for package " + packageName);
+ }
+
+ if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+ Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+ return;
+ }
+
+ final int result = permissionsState.grantRuntimePermission(bp, userId);
+ switch (result) {
+ case PermissionsState.PERMISSION_OPERATION_FAILURE: {
+ return;
+ }
+
+ case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+ if (callback != null) {
+ callback.onGidsChanged(UserHandle.getAppId(pkg.applicationInfo.uid), userId);
+ }
+ }
+ break;
+ }
+
+ if (bp.isRuntime()) {
+ logPermissionGranted(mContext, permName, packageName);
+ }
+
+ if (callback != null) {
+ callback.onPermissionGranted(uid, userId);
+ }
+
+ // Only need to do this if user is initialized. Otherwise it's a new user
+ // and there are no processes running as the user yet and there's no need
+ // to make an expensive call to remount processes for the changed permissions.
+ if (READ_EXTERNAL_STORAGE.equals(permName)
+ || WRITE_EXTERNAL_STORAGE.equals(permName)) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (mUserManagerInt.isUserInitialized(userId)) {
+ StorageManagerInternal storageManagerInternal = LocalServices.getService(
+ StorageManagerInternal.class);
+ storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ }
+
+ private void revokeRuntimePermission(String permName, String packageName,
+ boolean overridePolicy, int callingUid, int userId, PermissionCallback callback) {
+ if (!mUserManagerInt.exists(userId)) {
+ Log.e(TAG, "No such user:" + userId);
+ return;
+ }
+
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ "revokeRuntimePermission");
+
+ enforceCrossUserPermission(Binder.getCallingUid(), userId,
+ true /* requireFullPermission */, true /* checkShell */,
+ "revokeRuntimePermission");
+
+ final int appId;
+
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+ if (pkg == null || pkg.mExtras == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ if (mPackageManagerInt.filterAppAccess(pkg, Binder.getCallingUid(), userId)) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ final BasePermission bp = mSettings.getPermissionLocked(permName);
+ if (bp == null) {
+ throw new IllegalArgumentException("Unknown permission: " + permName);
+ }
+
+ bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (mSettings.mPermissionReviewRequired
+ && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+ && bp.isRuntime()) {
+ return;
+ }
+
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ final PermissionsState permissionsState = ps.getPermissionsState();
+
+ final int flags = permissionsState.getPermissionFlags(permName, userId);
+ if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+ throw new SecurityException("Cannot revoke system fixed permission "
+ + permName + " for package " + packageName);
+ }
+ if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ throw new SecurityException("Cannot revoke policy fixed permission "
+ + permName + " for package " + packageName);
+ }
+
+ if (bp.isDevelopment()) {
+ // Development permissions must be handled specially, since they are not
+ // normal runtime permissions. For now they apply to all users.
+ if (permissionsState.revokeInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ if (callback != null) {
+ callback.onInstallPermissionRevoked();
+ }
+ }
+ return;
+ }
+
+ if (permissionsState.revokeRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ return;
+ }
+
+ if (bp.isRuntime()) {
+ logPermissionRevoked(mContext, permName, packageName);
+ }
+
+ if (callback != null) {
+ final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+ callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
+ }
+ }
+
+ private int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, int[] allUserIds) {
+ // Collect all used permissions in the UID
+ final ArraySet<String> usedPermissions = new ArraySet<>();
+ final List<PackageParser.Package> pkgList = suSetting.getPackages();
+ if (pkgList == null || pkgList.size() == 0) {
+ return EmptyArray.INT;
+ }
+ for (PackageParser.Package pkg : pkgList) {
+ final int requestedPermCount = pkg.requestedPermissions.size();
+ for (int j = 0; j < requestedPermCount; j++) {
+ String permission = pkg.requestedPermissions.get(j);
+ BasePermission bp = mSettings.getPermissionLocked(permission);
+ if (bp != null) {
+ usedPermissions.add(permission);
+ }
+ }
+ }
+
+ PermissionsState permissionsState = suSetting.getPermissionsState();
+ // Prune install permissions
+ List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
+ final int installPermCount = installPermStates.size();
+ for (int i = installPermCount - 1; i >= 0; i--) {
+ PermissionState permissionState = installPermStates.get(i);
+ if (!usedPermissions.contains(permissionState.getName())) {
+ BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
+ if (bp != null) {
+ permissionsState.revokeInstallPermission(bp);
+ permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+ PackageManager.MASK_PERMISSION_FLAGS, 0);
+ }
+ }
+ }
+
+ int[] runtimePermissionChangedUserIds = EmptyArray.INT;
+
+ // Prune runtime permissions
+ for (int userId : allUserIds) {
+ List<PermissionState> runtimePermStates = permissionsState
+ .getRuntimePermissionStates(userId);
+ final int runtimePermCount = runtimePermStates.size();
+ for (int i = runtimePermCount - 1; i >= 0; i--) {
+ PermissionState permissionState = runtimePermStates.get(i);
+ if (!usedPermissions.contains(permissionState.getName())) {
+ BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
+ if (bp != null) {
+ permissionsState.revokeRuntimePermission(bp, userId);
+ permissionsState.updatePermissionFlags(bp, userId,
+ PackageManager.MASK_PERMISSION_FLAGS, 0);
+ runtimePermissionChangedUserIds = ArrayUtils.appendInt(
+ runtimePermissionChangedUserIds, userId);
+ }
+ }
+ }
+ }
+
+ return runtimePermissionChangedUserIds;
+ }
+
+ private int getPermissionFlags(String permName, String packageName, int callingUid, int userId) {
+ if (!mUserManagerInt.exists(userId)) {
+ return 0;
+ }
+
+ enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
+
+ enforceCrossUserPermission(callingUid, userId,
+ true /* requireFullPermission */, false /* checkShell */,
+ "getPermissionFlags");
+
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+ if (pkg == null || pkg.mExtras == null) {
+ return 0;
+ }
+ synchronized (mLock) {
+ if (mSettings.getPermissionLocked(permName) == null) {
+ return 0;
+ }
+ }
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+ return 0;
+ }
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ PermissionsState permissionsState = ps.getPermissionsState();
+ return permissionsState.getPermissionFlags(permName, userId);
+ }
+
+ private void updatePermissionFlags(String permName, String packageName, int flagMask,
+ int flagValues, int callingUid, int userId, PermissionCallback callback) {
+ if (!mUserManagerInt.exists(userId)) {
+ return;
+ }
+
+ enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
+
+ enforceCrossUserPermission(callingUid, userId,
+ true /* requireFullPermission */, true /* checkShell */,
+ "updatePermissionFlags");
+
+ // Only the system can change these flags and nothing else.
+ if (callingUid != Process.SYSTEM_UID) {
+ flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+ flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+ flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+ flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+ flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+ }
+
+ final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+ if (pkg == null || pkg.mExtras == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+
+ final BasePermission bp;
+ synchronized (mLock) {
+ bp = mSettings.getPermissionLocked(permName);
+ }
+ if (bp == null) {
+ throw new IllegalArgumentException("Unknown permission: " + permName);
+ }
+
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ final PermissionsState permissionsState = ps.getPermissionsState();
+ final boolean hadState =
+ permissionsState.getRuntimePermissionState(permName, userId) != null;
+ final boolean permissionUpdated =
+ permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
+ if (permissionUpdated && callback != null) {
+ // Install and runtime permissions are stored in different places,
+ // so figure out what permission changed and persist the change.
+ if (permissionsState.getInstallPermissionState(permName) != null) {
+ callback.onInstallPermissionUpdated();
+ } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
+ || hadState) {
+ callback.onPermissionUpdated(userId);
+ }
+ }
+ }
+
+ private boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
+ int userId, Collection<Package> packages, PermissionCallback callback) {
+ if (!mUserManagerInt.exists(userId)) {
+ return false;
+ }
+
+ enforceGrantRevokeRuntimePermissionPermissions(
+ "updatePermissionFlagsForAllApps");
+ enforceCrossUserPermission(callingUid, userId,
+ true /* requireFullPermission */, true /* checkShell */,
+ "updatePermissionFlagsForAllApps");
+
+ // Only the system can change system fixed flags.
+ if (callingUid != Process.SYSTEM_UID) {
+ flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+ flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+ }
+
+ boolean changed = false;
+ for (PackageParser.Package pkg : packages) {
+ final PackageSetting ps = (PackageSetting) pkg.mExtras;
+ if (ps == null) {
+ continue;
+ }
+ PermissionsState permissionsState = ps.getPermissionsState();
+ changed |= permissionsState.updatePermissionFlagsForAllPermissions(
+ userId, flagMask, flagValues);
+ }
+ return changed;
+ }
+
+ private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+ != PackageManager.PERMISSION_GRANTED
+ && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(message + " requires "
+ + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
+ + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
+ }
+ }
+
+ /**
+ * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
+ * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
+ * @param checkShell whether to prevent shell from access if there's a debugging restriction
+ * @param message the message to log on security exception
+ */
+ private void enforceCrossUserPermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell, String message) {
+ if (userId < 0) {
+ throw new IllegalArgumentException("Invalid userId " + userId);
+ }
+ if (checkShell) {
+ PackageManagerServiceUtils.enforceShellRestriction(
+ UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+ }
+ if (userId == UserHandle.getUserId(callingUid)) return;
+ if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+ if (requireFullPermission) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+ } else {
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+ } catch (SecurityException se) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS, message);
+ }
+ }
+ }
+ }
+
+ private int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
+ int size = 0;
+ for (BasePermission perm : mSettings.getAllPermissionsLocked()) {
+ size += tree.calculateFootprint(perm);
+ }
+ return size;
+ }
+
+ private void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
+ // We calculate the max size of permissions defined by this uid and throw
+ // if that plus the size of 'info' would exceed our stated maximum.
+ if (tree.getUid() != Process.SYSTEM_UID) {
+ final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
+ if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) {
+ throw new SecurityException("Permission tree size cap exceeded");
+ }
+ }
+ }
+
+ /**
+ * Get the first event id for the permission.
+ *
+ * <p>There are four events for each permission: <ul>
+ * <li>Request permission: first id + 0</li>
+ * <li>Grant permission: first id + 1</li>
+ * <li>Request for permission denied: first id + 2</li>
+ * <li>Revoke permission: first id + 3</li>
+ * </ul></p>
+ *
+ * @param name name of the permission
+ *
+ * @return The first event id for the permission
+ */
+ private static int getBaseEventId(@NonNull String name) {
+ int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
+
+ if (eventIdIndex == -1) {
+ if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
+ || Build.IS_USER) {
+ Log.i(TAG, "Unknown permission " + name);
+
+ return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
+ } else {
+ // Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
+ //
+ // Also update
+ // - EventLogger#ALL_DANGEROUS_PERMISSIONS
+ // - metrics_constants.proto
+ throw new IllegalStateException("Unknown permission " + name);
+ }
+ }
+
+ return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
+ }
+
+ /**
+ * Log that a permission was revoked.
+ *
+ * @param context Context of the caller
+ * @param name name of the permission
+ * @param packageName package permission if for
+ */
+ private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
+ @NonNull String packageName) {
+ MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
+ }
+
+ /**
+ * Log that a permission request was granted.
+ *
+ * @param context Context of the caller
+ * @param name name of the permission
+ * @param packageName package permission if for
+ */
+ private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
+ @NonNull String packageName) {
+ MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
+ }
+
+ private class PermissionManagerInternalImpl extends PermissionManagerInternal {
+ @Override
+ public boolean addPermission(PermissionInfo info, boolean async, int callingUid,
+ PermissionCallback callback) {
+ return PermissionManagerService.this.addPermission(info, callingUid, callback);
+ }
+ @Override
+ public void removePermission(String permName, int callingUid,
+ PermissionCallback callback) {
+ PermissionManagerService.this.removePermission(permName, callingUid, callback);
+ }
+ @Override
+ public void grantRuntimePermission(String permName, String packageName,
+ boolean overridePolicy, int callingUid, int userId,
+ PermissionCallback callback) {
+ PermissionManagerService.this.grantRuntimePermission(
+ permName, packageName, overridePolicy, callingUid, userId, callback);
+ }
+ @Override
+ public void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
+ String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+ PermissionManagerService.this.grantRequestedRuntimePermissions(
+ pkg, userIds, grantedPermissions, callingUid, callback);
+ }
+ @Override
+ public void grantRuntimePermissionsGrantedToDisabledPackage(PackageParser.Package pkg,
+ int callingUid, PermissionCallback callback) {
+ PermissionManagerService.this.grantRuntimePermissionsGrantedToDisabledPackageLocked(
+ pkg, callingUid, callback);
+ }
+ @Override
+ public void revokeRuntimePermission(String permName, String packageName,
+ boolean overridePolicy, int callingUid, int userId,
+ PermissionCallback callback) {
+ PermissionManagerService.this.revokeRuntimePermission(permName, packageName,
+ overridePolicy, callingUid, userId, callback);
+ }
+ @Override
+ public int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting,
+ int[] allUserIds) {
+ return PermissionManagerService.this.revokeUnusedSharedUserPermissions(
+ (SharedUserSetting) suSetting, allUserIds);
+ }
+ @Override
+ public int getPermissionFlags(String permName, String packageName, int callingUid,
+ int userId) {
+ return PermissionManagerService.this.getPermissionFlags(permName, packageName,
+ callingUid, userId);
+ }
+ @Override
+ public void updatePermissionFlags(String permName, String packageName, int flagMask,
+ int flagValues, int callingUid, int userId, PermissionCallback callback) {
+ PermissionManagerService.this.updatePermissionFlags(
+ permName, packageName, flagMask, flagValues, callingUid, userId, callback);
+ }
+ @Override
+ public boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
+ int userId, Collection<Package> packages, PermissionCallback callback) {
+ return PermissionManagerService.this.updatePermissionFlagsForAllApps(
+ flagMask, flagValues, callingUid, userId, packages, callback);
+ }
+ @Override
+ public void enforceCrossUserPermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell, String message) {
+ PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
+ requireFullPermission, checkShell, message);
+ }
+ @Override
+ public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+ PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
+ }
+ @Override
+ public int checkPermission(String permName, String packageName, int callingUid,
+ int userId) {
+ return PermissionManagerService.this.checkPermission(
+ permName, packageName, callingUid, userId);
+ }
+ @Override
+ public PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
+ int callingUid) {
+ return PermissionManagerService.this.getPermissionInfo(
+ permName, packageName, flags, callingUid);
+ }
+ @Override
+ public List<PermissionInfo> getPermissionInfoByGroup(String group, int flags,
+ int callingUid) {
+ return PermissionManagerService.this.getPermissionInfoByGroup(group, flags, callingUid);
+ }
+ @Override
+ public boolean isPermissionInstant(String permName) {
+ synchronized (PermissionManagerService.this.mLock) {
+ final BasePermission bp = mSettings.getPermissionLocked(permName);
+ return (bp != null && bp.isInstant());
+ }
+ }
+ @Override
+ public boolean isPermissionAppOp(String permName) {
+ synchronized (PermissionManagerService.this.mLock) {
+ final BasePermission bp = mSettings.getPermissionLocked(permName);
+ return (bp != null && bp.isAppOp());
+ }
+ }
+ @Override
+ public PermissionSettings getPermissionSettings() {
+ return mSettings;
+ }
+ @Override
+ public DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy() {
+ return mDefaultPermissionGrantPolicy;
+ }
+ @Override
+ public BasePermission getPermissionTEMP(String permName) {
+ synchronized (PermissionManagerService.this.mLock) {
+ return mSettings.getPermissionLocked(permName);
+ }
+ }
+ @Override
+ public void putPermissionTEMP(String permName, BasePermission permission) {
+ synchronized (PermissionManagerService.this.mLock) {
+ mSettings.putPermissionLocked(permName, (BasePermission) permission);
+ }
+ }
+ @Override
+ public Iterator<BasePermission> getPermissionIteratorTEMP() {
+ synchronized (PermissionManagerService.this.mLock) {
+ return mSettings.getAllPermissionsLocked().iterator();
+ }
+ }
+ }
+}
diff --git a/com/android/server/pm/permission/PermissionSettings.java b/com/android/server/pm/permission/PermissionSettings.java
new file mode 100644
index 00000000..7a2e5ecc
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionSettings.java
@@ -0,0 +1,149 @@
+/*
+ * 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 com.android.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.util.XmlUtils;
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerService;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+
+/**
+ * Permissions and other related data. This class is not meant for
+ * direct access outside of the permission package with the sole exception
+ * of package settings. Instead, it should be reference either from the
+ * permission manager or package settings.
+ */
+public class PermissionSettings {
+
+ final boolean mPermissionReviewRequired;
+ /**
+ * All of the permissions known to the system. The mapping is from permission
+ * name to permission object.
+ */
+ private final ArrayMap<String, BasePermission> mPermissions =
+ new ArrayMap<String, BasePermission>();
+ private final Object mLock;
+
+ PermissionSettings(@NonNull Context context, @NonNull Object lock) {
+ mPermissionReviewRequired =
+ context.getResources().getBoolean(R.bool.config_permissionReviewRequired);
+ mLock = lock;
+ }
+
+ public @Nullable BasePermission getPermission(@NonNull String permName) {
+ synchronized (mLock) {
+ return getPermissionLocked(permName);
+ }
+ }
+
+ /**
+ * Transfers ownership of permissions from one package to another.
+ */
+ public void transferPermissions(String origPackageName, String newPackageName,
+ ArrayMap<String, BasePermission> permissionTrees) {
+ synchronized (mLock) {
+ for (int i=0; i<2; i++) {
+ ArrayMap<String, BasePermission> permissions =
+ i == 0 ? permissionTrees : mPermissions;
+ for (BasePermission bp : permissions.values()) {
+ bp.transfer(origPackageName, newPackageName);
+ }
+ }
+ }
+ }
+
+ public boolean canPropagatePermissionToInstantApp(String permName) {
+ synchronized (mLock) {
+ final BasePermission bp = mPermissions.get(permName);
+ return (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant());
+ }
+ }
+
+ public void readPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
+ synchronized (mLock) {
+ readPermissions(mPermissions, parser);
+ }
+ }
+
+ public void writePermissions(XmlSerializer serializer) throws IOException {
+ for (BasePermission bp : mPermissions.values()) {
+ bp.writeLPr(serializer);
+ }
+ }
+
+ public static void readPermissions(ArrayMap<String, BasePermission> out, XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (!BasePermission.readLPw(out, parser)) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Unknown element reading permissions: " + parser.getName() + " at "
+ + parser.getPositionDescription());
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ public void dumpPermissions(PrintWriter pw, String packageName,
+ ArraySet<String> permissionNames, boolean externalStorageEnforced,
+ DumpState dumpState) {
+ synchronized (mLock) {
+ boolean printedSomething = false;
+ for (BasePermission bp : mPermissions.values()) {
+ printedSomething = bp.dumpPermissionsLPr(pw, packageName, permissionNames,
+ externalStorageEnforced, printedSomething, dumpState);
+ }
+ }
+ }
+
+ @Nullable BasePermission getPermissionLocked(@NonNull String permName) {
+ return mPermissions.get(permName);
+ }
+
+ void putPermissionLocked(@NonNull String permName, @NonNull BasePermission permission) {
+ mPermissions.put(permName, permission);
+ }
+
+ void removePermissionLocked(@NonNull String permName) {
+ mPermissions.remove(permName);
+ }
+
+ Collection<BasePermission> getAllPermissionsLocked() {
+ return mPermissions.values();
+ }
+}
diff --git a/com/android/server/pm/PermissionsState.java b/com/android/server/pm/permission/PermissionsState.java
index f4d2ad2c..11df3804 100644
--- a/com/android/server/pm/PermissionsState.java
+++ b/com/android/server/pm/permission/PermissionsState.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.pm;
+package com.android.server.pm.permission;
import android.content.pm.PackageManager;
import android.os.UserHandle;
@@ -406,7 +406,7 @@ public final class PermissionsState {
ensurePermissionData(permission);
}
- PermissionData permissionData = mPermissions.get(permission.name);
+ PermissionData permissionData = mPermissions.get(permission.getName());
if (permissionData == null) {
if (!mayChangeFlags) {
return false;
@@ -557,7 +557,7 @@ public final class PermissionsState {
}
private int grantPermission(BasePermission permission, int userId) {
- if (hasPermission(permission.name, userId)) {
+ if (hasPermission(permission.getName(), userId)) {
return PERMISSION_OPERATION_FAILURE;
}
@@ -581,21 +581,22 @@ public final class PermissionsState {
}
private int revokePermission(BasePermission permission, int userId) {
- if (!hasPermission(permission.name, userId)) {
+ final String permName = permission.getName();
+ if (!hasPermission(permName, userId)) {
return PERMISSION_OPERATION_FAILURE;
}
final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
- PermissionData permissionData = mPermissions.get(permission.name);
+ PermissionData permissionData = mPermissions.get(permName);
if (!permissionData.revoke(userId)) {
return PERMISSION_OPERATION_FAILURE;
}
if (permissionData.isDefault()) {
- ensureNoPermissionData(permission.name);
+ ensureNoPermissionData(permName);
}
if (hasGids) {
@@ -625,13 +626,14 @@ public final class PermissionsState {
}
private PermissionData ensurePermissionData(BasePermission permission) {
+ final String permName = permission.getName();
if (mPermissions == null) {
mPermissions = new ArrayMap<>();
}
- PermissionData permissionData = mPermissions.get(permission.name);
+ PermissionData permissionData = mPermissions.get(permName);
if (permissionData == null) {
permissionData = new PermissionData(permission);
- mPermissions.put(permission.name, permissionData);
+ mPermissions.put(permName, permissionData);
}
return permissionData;
}
@@ -692,7 +694,7 @@ public final class PermissionsState {
PermissionState userState = mUserStates.get(userId);
if (userState == null) {
- userState = new PermissionState(mPerm.name);
+ userState = new PermissionState(mPerm.getName());
mUserStates.put(userId, userState);
}
@@ -760,7 +762,7 @@ public final class PermissionsState {
}
return userState.mFlags != oldFlags;
} else if (newFlags != 0) {
- userState = new PermissionState(mPerm.name);
+ userState = new PermissionState(mPerm.getName());
userState.mFlags = newFlags;
mUserStates.put(userId, userState);
return true;
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index a806af46..db7817ec 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -20,9 +20,12 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.AppOpsManager.OP_TOAST_WINDOW;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+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_UNDEFINED;
import static android.content.Context.CONTEXT_RESTRICTED;
import static android.content.Context.DISPLAY_SERVICE;
import static android.content.Context.WINDOW_SERVICE;
@@ -198,6 +201,7 @@ import android.util.EventLog;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.MutableBoolean;
+import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -296,13 +300,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static final int LONG_PRESS_POWER_SHUT_OFF = 2;
static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
- static final int LONG_PRESS_BACK_NOTHING = 0;
- static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1;
-
static final int MULTI_PRESS_POWER_NOTHING = 0;
static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2;
+ static final int LONG_PRESS_BACK_NOTHING = 0;
+ static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1;
+
// Number of presses needed before we induce panic press behavior on the back button
static final int PANIC_PRESS_BACK_COUNT = 4;
static final int PANIC_PRESS_BACK_NOTHING = 0;
@@ -565,7 +569,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mLongPressOnBackBehavior;
int mPanicPressOnBackBehavior;
int mShortPressOnSleepBehavior;
- int mShortPressWindowBehavior;
+ int mShortPressOnWindowBehavior;
volatile boolean mAwake;
boolean mScreenOnEarly;
boolean mScreenOnFully;
@@ -2180,9 +2184,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mDoubleTapOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
}
- mShortPressWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
+ mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
if (mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
- mShortPressWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
+ mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
}
mNavBarOpacityMode = res.getInteger(
@@ -2626,18 +2630,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
}
- if (ActivityManager.isHighEndGfx()) {
- if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
- attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- }
- final boolean forceWindowDrawsStatusBarBackground =
- (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND)
- != 0;
- if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
- || forceWindowDrawsStatusBarBackground
- && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
- attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
- }
+ if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+ attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ }
+ final boolean forceWindowDrawsStatusBarBackground =
+ (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
+ if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+ || forceWindowDrawsStatusBarBackground
+ && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
+ attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
}
}
@@ -3627,10 +3628,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return -1;
}
- // If the device is in Vr mode, drop the volume keys and don't
- // forward it to the application/dispatch the audio event.
+ // If the device is in VR mode and keys are "internal" (e.g. on the side of the
+ // device), then drop the volume keys and don't forward it to the application/dispatch
+ // the audio event.
if (mPersistentVrModeEnabled) {
- return -1;
+ final InputDevice d = event.getDevice();
+ if (d != null && !d.isExternal()) {
+ return -1;
+ }
}
} else if (keyCode == KeyEvent.KEYCODE_TAB && event.isMetaPressed()) {
// Pass through keyboard navigation keys.
@@ -6272,7 +6277,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
break;
}
case KeyEvent.KEYCODE_WINDOW: {
- if (mShortPressWindowBehavior == SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE) {
+ if (mShortPressOnWindowBehavior == SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE) {
if (mPictureInPictureVisible) {
// Consumes the key only if picture-in-picture is visible to show
// picture-in-picture control menu. This gives a chance to the foreground
@@ -7915,8 +7920,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
- mWindowManagerFuncs.getStackBounds(HOME_STACK_ID, mNonDockedStackBounds);
- mWindowManagerFuncs.getStackBounds(DOCKED_STACK_ID, mDockedStackBounds);
+ mWindowManagerFuncs.getStackBounds(
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
+ mWindowManagerFuncs.getStackBounds(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
final int diff = visibility ^ mLastSystemUiFlags;
final int fullscreenDiff = fullscreenVisibility ^ mLastFullscreenStackSysUiFlags;
@@ -8319,9 +8326,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode);
pw.print(" mSystemReady="); pw.print(mSystemReady);
pw.print(" mSystemBooted="); pw.println(mSystemBooted);
- pw.print(prefix); pw.print("mLidState="); pw.print(mLidState);
- pw.print(" mLidOpenRotation="); pw.print(mLidOpenRotation);
- pw.print(" mCameraLensCoverState="); pw.print(mCameraLensCoverState);
+ pw.print(prefix); pw.print("mLidState=");
+ pw.print(WindowManagerFuncs.lidStateToString(mLidState));
+ pw.print(" mLidOpenRotation=");
+ pw.println(Surface.rotationToString(mLidOpenRotation));
+ pw.print(prefix); pw.print("mCameraLensCoverState=");
+ pw.print(WindowManagerFuncs.cameraLensStateToString(mCameraLensCoverState));
pw.print(" mHdmiPlugged="); pw.println(mHdmiPlugged);
if (mLastSystemUiFlags != 0 || mResettingSystemUiFlags != 0
|| mForceClearedSystemUiFlags != 0) {
@@ -8339,16 +8349,24 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print(prefix); pw.print("mWakeGestureEnabledSetting=");
pw.println(mWakeGestureEnabledSetting);
- pw.print(prefix); pw.print("mSupportAutoRotation="); pw.println(mSupportAutoRotation);
- pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode);
- pw.print(" mDockMode="); pw.print(mDockMode);
- pw.print(" mEnableCarDockHomeCapture="); pw.print(mEnableCarDockHomeCapture);
- pw.print(" mCarDockRotation="); pw.print(mCarDockRotation);
- pw.print(" mDeskDockRotation="); pw.println(mDeskDockRotation);
- pw.print(prefix); pw.print("mUserRotationMode="); pw.print(mUserRotationMode);
- pw.print(" mUserRotation="); pw.print(mUserRotation);
- pw.print(" mAllowAllRotations="); pw.println(mAllowAllRotations);
- pw.print(prefix); pw.print("mCurrentAppOrientation="); pw.println(mCurrentAppOrientation);
+ pw.print(prefix);
+ pw.print("mSupportAutoRotation="); pw.print(mSupportAutoRotation);
+ pw.print(" mOrientationSensorEnabled="); pw.println(mOrientationSensorEnabled);
+ pw.print(prefix); pw.print("mUiMode="); pw.print(Configuration.uiModeToString(mUiMode));
+ pw.print(" mDockMode="); pw.println(Intent.dockStateToString(mDockMode));
+ pw.print(prefix); pw.print("mEnableCarDockHomeCapture=");
+ pw.print(mEnableCarDockHomeCapture);
+ pw.print(" mCarDockRotation=");
+ pw.print(Surface.rotationToString(mCarDockRotation));
+ pw.print(" mDeskDockRotation=");
+ pw.println(Surface.rotationToString(mDeskDockRotation));
+ pw.print(prefix); pw.print("mUserRotationMode=");
+ pw.print(WindowManagerPolicy.userRotationModeToString(mUserRotationMode));
+ pw.print(" mUserRotation="); pw.print(Surface.rotationToString(mUserRotation));
+ pw.print(" mAllowAllRotations=");
+ pw.println(allowAllRotationsToString(mAllowAllRotations));
+ pw.print(prefix); pw.print("mCurrentAppOrientation=");
+ pw.println(ActivityInfo.screenOrientationToString(mCurrentAppOrientation));
pw.print(prefix); pw.print("mCarDockEnablesAccelerometer=");
pw.print(mCarDockEnablesAccelerometer);
pw.print(" mDeskDockEnablesAccelerometer=");
@@ -8357,23 +8375,54 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print(mLidKeyboardAccessibility);
pw.print(" mLidNavigationAccessibility="); pw.print(mLidNavigationAccessibility);
pw.print(" mLidControlsScreenLock="); pw.println(mLidControlsScreenLock);
- pw.print(" mLidControlsSleep="); pw.println(mLidControlsSleep);
+ pw.print(prefix); pw.print("mLidControlsSleep="); pw.println(mLidControlsSleep);
pw.print(prefix);
- pw.print(" mLongPressOnBackBehavior="); pw.println(mLongPressOnBackBehavior);
+ pw.print("mLongPressOnBackBehavior=");
+ pw.println(longPressOnBackBehaviorToString(mLongPressOnBackBehavior));
pw.print(prefix);
- pw.print("mShortPressOnPowerBehavior="); pw.print(mShortPressOnPowerBehavior);
- pw.print(" mLongPressOnPowerBehavior="); pw.println(mLongPressOnPowerBehavior);
+ pw.print("mPanicPressOnBackBehavior=");
+ pw.println(panicPressOnBackBehaviorToString(mPanicPressOnBackBehavior));
pw.print(prefix);
- pw.print("mDoublePressOnPowerBehavior="); pw.print(mDoublePressOnPowerBehavior);
- pw.print(" mTriplePressOnPowerBehavior="); pw.println(mTriplePressOnPowerBehavior);
- pw.print(prefix); pw.print("mHasSoftInput="); pw.println(mHasSoftInput);
- pw.print(prefix); pw.print("mAwake="); pw.println(mAwake);
- pw.print(prefix); pw.print("mScreenOnEarly="); pw.print(mScreenOnEarly);
+ pw.print("mLongPressOnHomeBehavior=");
+ pw.println(longPressOnHomeBehaviorToString(mLongPressOnHomeBehavior));
+ pw.print(prefix);
+ pw.print("mDoubleTapOnHomeBehavior=");
+ pw.println(doubleTapOnHomeBehaviorToString(mDoubleTapOnHomeBehavior));
+ pw.print(prefix);
+ pw.print("mShortPressOnPowerBehavior=");
+ pw.println(shortPressOnPowerBehaviorToString(mShortPressOnPowerBehavior));
+ pw.print(prefix);
+ pw.print("mLongPressOnPowerBehavior=");
+ pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
+ pw.print(prefix);
+ pw.print("mDoublePressOnPowerBehavior=");
+ pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior));
+ pw.print(prefix);
+ pw.print("mTriplePressOnPowerBehavior=");
+ pw.println(multiPressOnPowerBehaviorToString(mTriplePressOnPowerBehavior));
+ pw.print(prefix);
+ pw.print("mShortPressOnSleepBehavior=");
+ pw.println(shortPressOnSleepBehaviorToString(mShortPressOnSleepBehavior));
+ pw.print(prefix);
+ pw.print("mShortPressOnWindowBehavior=");
+ pw.println(shortPressOnWindowBehaviorToString(mShortPressOnWindowBehavior));
+ pw.print(prefix);
+ pw.print("mHasSoftInput="); pw.print(mHasSoftInput);
+ pw.print(" mDismissImeOnBackKeyPressed="); pw.println(mDismissImeOnBackKeyPressed);
+ pw.print(prefix);
+ pw.print("mIncallPowerBehavior=");
+ pw.print(incallPowerBehaviorToString(mIncallPowerBehavior));
+ pw.print(" mIncallBackBehavior=");
+ pw.print(incallBackBehaviorToString(mIncallBackBehavior));
+ pw.print(" mEndcallBehavior=");
+ pw.println(endcallBehaviorToString(mEndcallBehavior));
+ pw.print(prefix); pw.print("mHomePressed="); pw.println(mHomePressed);
+ pw.print(prefix);
+ pw.print("mAwake="); pw.print(mAwake);
+ pw.print("mScreenOnEarly="); pw.print(mScreenOnEarly);
pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
- pw.print(prefix); pw.print("mOrientationSensorEnabled=");
- pw.println(mOrientationSensorEnabled);
pw.print(prefix); pw.print("mOverscanScreen=("); pw.print(mOverscanScreenLeft);
pw.print(","); pw.print(mOverscanScreenTop);
pw.print(") "); pw.print(mOverscanScreenWidth);
@@ -8439,8 +8488,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print(prefix); pw.print("mLastInputMethodTargetWindow=");
pw.println(mLastInputMethodTargetWindow);
}
- pw.print(prefix); pw.print("mDismissImeOnBackKeyPressed=");
- pw.println(mDismissImeOnBackKeyPressed);
if (mStatusBar != null) {
pw.print(prefix); pw.print("mStatusBar=");
pw.print(mStatusBar); pw.print(" isStatusBarKeyguard=");
@@ -8473,26 +8520,28 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
pw.print(prefix); pw.print("mTopIsFullscreen="); pw.print(mTopIsFullscreen);
pw.print(" mKeyguardOccluded="); pw.println(mKeyguardOccluded);
- pw.print(" mKeyguardOccludedChanged="); pw.println(mKeyguardOccludedChanged);
+ pw.print(prefix);
+ pw.print("mKeyguardOccludedChanged="); pw.print(mKeyguardOccludedChanged);
pw.print(" mPendingKeyguardOccluded="); pw.println(mPendingKeyguardOccluded);
pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
pw.print(" mForceStatusBarFromKeyguard=");
pw.println(mForceStatusBarFromKeyguard);
- pw.print(prefix); pw.print("mHomePressed="); pw.println(mHomePressed);
pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.print(mAllowLockscreenWhenOn);
pw.print(" mLockScreenTimeout="); pw.print(mLockScreenTimeout);
pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
- pw.print(prefix); pw.print("mEndcallBehavior="); pw.print(mEndcallBehavior);
- pw.print(" mIncallPowerBehavior="); pw.print(mIncallPowerBehavior);
- pw.print(" mIncallBackBehavior="); pw.print(mIncallBackBehavior);
- pw.print(" mLongPressOnHomeBehavior="); pw.println(mLongPressOnHomeBehavior);
- pw.print(prefix); pw.print("mLandscapeRotation="); pw.print(mLandscapeRotation);
- pw.print(" mSeascapeRotation="); pw.println(mSeascapeRotation);
- pw.print(prefix); pw.print("mPortraitRotation="); pw.print(mPortraitRotation);
- pw.print(" mUpsideDownRotation="); pw.println(mUpsideDownRotation);
- pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation);
+ pw.print(prefix); pw.print("mLandscapeRotation=");
+ pw.print(Surface.rotationToString(mLandscapeRotation));
+ pw.print(" mSeascapeRotation=");
+ pw.println(Surface.rotationToString(mSeascapeRotation));
+ pw.print(prefix); pw.print("mPortraitRotation=");
+ pw.print(Surface.rotationToString(mPortraitRotation));
+ pw.print(" mUpsideDownRotation=");
+ pw.println(Surface.rotationToString(mUpsideDownRotation));
+ pw.print(prefix); pw.print("mDemoHdmiRotation=");
+ pw.print(Surface.rotationToString(mDemoHdmiRotation));
pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock);
- pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation);
+ pw.print(prefix); pw.print("mUndockedHdmiRotation=");
+ pw.println(Surface.rotationToString(mUndockedHdmiRotation));
if (mHasFeatureLeanback) {
pw.print(prefix);
pw.print("mAccessibilityTvKey1Pressed="); pw.println(mAccessibilityTvKey1Pressed);
@@ -8519,5 +8568,169 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mKeyguardDelegate != null) {
mKeyguardDelegate.dump(prefix, pw);
}
+
+ pw.print(prefix); pw.println("Looper state:");
+ mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
+ }
+
+ private static String allowAllRotationsToString(int allowAll) {
+ switch (allowAll) {
+ case -1:
+ return "unknown";
+ case 0:
+ return "false";
+ case 1:
+ return "true";
+ default:
+ return Integer.toString(allowAll);
+ }
+ }
+
+ private static String endcallBehaviorToString(int behavior) {
+ StringBuilder sb = new StringBuilder();
+ if ((behavior & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0 ) {
+ sb.append("home|");
+ }
+ if ((behavior & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) {
+ sb.append("sleep|");
+ }
+
+ final int N = sb.length();
+ if (N == 0) {
+ return "<nothing>";
+ } else {
+ // Chop off the trailing '|'
+ return sb.substring(0, N - 1);
+ }
+ }
+
+ private static String incallPowerBehaviorToString(int behavior) {
+ if ((behavior & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0) {
+ return "hangup";
+ } else {
+ return "sleep";
+ }
+ }
+
+ private static String incallBackBehaviorToString(int behavior) {
+ if ((behavior & Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR_HANGUP) != 0) {
+ return "hangup";
+ } else {
+ return "<nothing>";
+ }
+ }
+
+ private static String longPressOnBackBehaviorToString(int behavior) {
+ switch (behavior) {
+ case LONG_PRESS_BACK_NOTHING:
+ return "LONG_PRESS_BACK_NOTHING";
+ case LONG_PRESS_BACK_GO_TO_VOICE_ASSIST:
+ return "LONG_PRESS_BACK_GO_TO_VOICE_ASSIST";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String panicPressOnBackBehaviorToString(int behavior) {
+ switch (behavior) {
+ case PANIC_PRESS_BACK_NOTHING:
+ return "PANIC_PRESS_BACK_NOTHING";
+ case PANIC_PRESS_BACK_HOME:
+ return "PANIC_PRESS_BACK_HOME";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String longPressOnHomeBehaviorToString(int behavior) {
+ switch (behavior) {
+ case LONG_PRESS_HOME_NOTHING:
+ return "LONG_PRESS_HOME_NOTHING";
+ case LONG_PRESS_HOME_ALL_APPS:
+ return "LONG_PRESS_HOME_ALL_APPS";
+ case LONG_PRESS_HOME_ASSIST:
+ return "LONG_PRESS_HOME_ASSIST";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String doubleTapOnHomeBehaviorToString(int behavior) {
+ switch (behavior) {
+ case DOUBLE_TAP_HOME_NOTHING:
+ return "DOUBLE_TAP_HOME_NOTHING";
+ case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
+ return "DOUBLE_TAP_HOME_RECENT_SYSTEM_UI";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String shortPressOnPowerBehaviorToString(int behavior) {
+ switch (behavior) {
+ case SHORT_PRESS_POWER_NOTHING:
+ return "SHORT_PRESS_POWER_NOTHING";
+ case SHORT_PRESS_POWER_GO_TO_SLEEP:
+ return "SHORT_PRESS_POWER_GO_TO_SLEEP";
+ case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
+ return "SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP";
+ case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:
+ return "SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME";
+ case SHORT_PRESS_POWER_GO_HOME:
+ return "SHORT_PRESS_POWER_GO_HOME";
+ case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME:
+ return "SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String longPressOnPowerBehaviorToString(int behavior) {
+ switch (behavior) {
+ case LONG_PRESS_POWER_NOTHING:
+ return "LONG_PRESS_POWER_NOTHING";
+ case LONG_PRESS_POWER_GLOBAL_ACTIONS:
+ return "LONG_PRESS_POWER_GLOBAL_ACTIONS";
+ case LONG_PRESS_POWER_SHUT_OFF:
+ return "LONG_PRESS_POWER_SHUT_OFF";
+ case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
+ return "LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+ private static String multiPressOnPowerBehaviorToString(int behavior) {
+ switch (behavior) {
+ case MULTI_PRESS_POWER_NOTHING:
+ return "MULTI_PRESS_POWER_NOTHING";
+ case MULTI_PRESS_POWER_THEATER_MODE:
+ return "MULTI_PRESS_POWER_THEATER_MODE";
+ case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
+ return "MULTI_PRESS_POWER_BRIGHTNESS_BOOST";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String shortPressOnSleepBehaviorToString(int behavior) {
+ switch (behavior) {
+ case SHORT_PRESS_SLEEP_GO_TO_SLEEP:
+ return "SHORT_PRESS_SLEEP_GO_TO_SLEEP";
+ case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
+ return "SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME";
+ default:
+ return Integer.toString(behavior);
+ }
+ }
+
+ private static String shortPressOnWindowBehaviorToString(int behavior) {
+ switch (behavior) {
+ case SHORT_PRESS_WINDOW_NOTHING:
+ return "SHORT_PRESS_WINDOW_NOTHING";
+ case SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE:
+ return "SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE";
+ default:
+ return Integer.toString(behavior);
+ }
}
}
diff --git a/com/android/server/policy/WindowOrientationListener.java b/com/android/server/policy/WindowOrientationListener.java
index 64f64c0d..169fd278 100644
--- a/com/android/server/policy/WindowOrientationListener.java
+++ b/com/android/server/policy/WindowOrientationListener.java
@@ -26,6 +26,7 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Slog;
+import android.view.Surface;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -236,7 +237,7 @@ public abstract class WindowOrientationListener {
pw.println(prefix + TAG);
prefix += " ";
pw.println(prefix + "mEnabled=" + mEnabled);
- pw.println(prefix + "mCurrentRotation=" + mCurrentRotation);
+ pw.println(prefix + "mCurrentRotation=" + Surface.rotationToString(mCurrentRotation));
pw.println(prefix + "mSensorType=" + mSensorType);
pw.println(prefix + "mSensor=" + mSensor);
pw.println(prefix + "mRate=" + mRate);
@@ -1026,8 +1027,9 @@ public abstract class WindowOrientationListener {
public void dumpLocked(PrintWriter pw, String prefix) {
pw.println(prefix + "OrientationSensorJudge");
prefix += " ";
- pw.println(prefix + "mDesiredRotation=" + mDesiredRotation);
- pw.println(prefix + "mProposedRotation=" + mProposedRotation);
+ pw.println(prefix + "mDesiredRotation=" + Surface.rotationToString(mDesiredRotation));
+ pw.println(prefix + "mProposedRotation="
+ + Surface.rotationToString(mProposedRotation));
pw.println(prefix + "mTouching=" + mTouching);
pw.println(prefix + "mTouchEndedTimestampNanos=" + mTouchEndedTimestampNanos);
}
diff --git a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 50e5e7bd..70cd54ff 100644
--- a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -1,5 +1,7 @@
package com.android.server.policy.keyguard;
+import static android.view.Display.INVALID_DISPLAY;
+
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -13,6 +15,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import android.view.WindowManagerPolicy;
import android.view.WindowManagerPolicy.OnKeyguardExitResult;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -201,7 +204,10 @@ public class KeyguardServiceDelegate {
mKeyguardState.reset();
mHandler.post(() -> {
try {
- ActivityManager.getService().setLockScreenShown(true);
+ // There are no longer any keyguard windows on secondary displays, so pass
+ // INVALID_DISPLAY. All that means is that showWhenLocked activities on
+ // secondary displays now get to show.
+ ActivityManager.getService().setLockScreenShown(true, INVALID_DISPLAY);
} catch (RemoteException e) {
// Local call.
}
@@ -412,13 +418,45 @@ public class KeyguardServiceDelegate {
pw.println(prefix + "systemIsReady=" + mKeyguardState.systemIsReady);
pw.println(prefix + "deviceHasKeyguard=" + mKeyguardState.deviceHasKeyguard);
pw.println(prefix + "enabled=" + mKeyguardState.enabled);
- pw.println(prefix + "offReason=" + mKeyguardState.offReason);
+ pw.println(prefix + "offReason=" +
+ WindowManagerPolicy.offReasonToString(mKeyguardState.offReason));
pw.println(prefix + "currentUser=" + mKeyguardState.currentUser);
pw.println(prefix + "bootCompleted=" + mKeyguardState.bootCompleted);
- pw.println(prefix + "screenState=" + mKeyguardState.screenState);
- pw.println(prefix + "interactiveState=" + mKeyguardState.interactiveState);
+ pw.println(prefix + "screenState=" + screenStateToString(mKeyguardState.screenState));
+ pw.println(prefix + "interactiveState=" +
+ interactiveStateToString(mKeyguardState.interactiveState));
if (mKeyguardService != null) {
mKeyguardService.dump(prefix, pw);
}
}
+
+ private static String screenStateToString(int screen) {
+ switch (screen) {
+ case SCREEN_STATE_OFF:
+ return "SCREEN_STATE_OFF";
+ case SCREEN_STATE_TURNING_ON:
+ return "SCREEN_STATE_TURNING_ON";
+ case SCREEN_STATE_ON:
+ return "SCREEN_STATE_ON";
+ case SCREEN_STATE_TURNING_OFF:
+ return "SCREEN_STATE_TURNING_OFF";
+ default:
+ return Integer.toString(screen);
+ }
+ }
+
+ private static String interactiveStateToString(int interactive) {
+ switch (interactive) {
+ case INTERACTIVE_STATE_SLEEP:
+ return "INTERACTIVE_STATE_SLEEP";
+ case INTERACTIVE_STATE_WAKING:
+ return "INTERACTIVE_STATE_WAKING";
+ case INTERACTIVE_STATE_AWAKE:
+ return "INTERACTIVE_STATE_AWAKE";
+ case INTERACTIVE_STATE_GOING_TO_SLEEP:
+ return "INTERACTIVE_STATE_GOING_TO_SLEEP";
+ default:
+ return Integer.toString(interactive);
+ }
+ }
}
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index 3b701302..b917dae2 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -206,6 +206,8 @@ public final class PowerManagerService extends SystemService
private static final String REASON_REBOOT = "reboot";
private static final String REASON_USERREQUESTED = "shutdown,userrequested";
private static final String REASON_THERMAL_SHUTDOWN = "shutdown,thermal";
+ private static final String REASON_LOW_BATTERY = "shutdown,battery";
+ private static final String REASON_BATTERY_THERMAL_STATE = "shutdown,thermal,battery";
private static final String TRACE_SCREEN_ON = "Screen turning on";
@@ -1569,12 +1571,15 @@ public final class PowerManagerService extends SystemService
return true;
}
- private void setWakefulnessLocked(int wakefulness, int reason) {
+ @VisibleForTesting
+ void setWakefulnessLocked(int wakefulness, int reason) {
if (mWakefulness != wakefulness) {
mWakefulness = wakefulness;
mWakefulnessChanging = true;
mDirty |= DIRTY_WAKEFULNESS;
- mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
+ if (mNotifier != null) {
+ mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
+ }
}
}
@@ -2432,11 +2437,8 @@ public final class PowerManagerService extends SystemService
return value >= -1.0f && value <= 1.0f;
}
- private int getDesiredScreenPolicyLocked() {
- if (mIsVrModeEnabled) {
- return DisplayPowerRequest.POLICY_VR;
- }
-
+ @VisibleForTesting
+ int getDesiredScreenPolicyLocked() {
if (mWakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
return DisplayPowerRequest.POLICY_OFF;
}
@@ -2452,6 +2454,13 @@ public final class PowerManagerService extends SystemService
// doze after screen off. This causes the screen off transition to be skipped.
}
+ // It is important that POLICY_VR check happens after the wakefulness checks above so
+ // that VR-mode does not prevent displays from transitioning to the correct state when
+ // dozing or sleeping.
+ if (mIsVrModeEnabled) {
+ return DisplayPowerRequest.POLICY_VR;
+ }
+
if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
|| (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
|| !mBootCompleted
@@ -3113,6 +3122,11 @@ public final class PowerManagerService extends SystemService
}
}
+ @VisibleForTesting
+ void setVrModeEnabled(boolean enabled) {
+ mIsVrModeEnabled = enabled;
+ }
+
private void powerHintInternal(int hintId, int data) {
nativeSendPowerHint(hintId, data);
}
@@ -3810,7 +3824,7 @@ public final class PowerManagerService extends SystemService
synchronized (mLock) {
if (mIsVrModeEnabled != enabled) {
- mIsVrModeEnabled = enabled;
+ setVrModeEnabled(enabled);
mDirty |= DIRTY_VR_MODE_CHANGED;
updatePowerStateLocked();
}
@@ -4639,6 +4653,10 @@ public final class PowerManagerService extends SystemService
return PowerManager.SHUTDOWN_REASON_USER_REQUESTED;
case REASON_THERMAL_SHUTDOWN:
return PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN;
+ case REASON_LOW_BATTERY:
+ return PowerManager.SHUTDOWN_REASON_LOW_BATTERY;
+ case REASON_BATTERY_THERMAL_STATE:
+ return PowerManager.SHUTDOWN_REASON_BATTERY_THERMAL;
default:
return PowerManager.SHUTDOWN_REASON_UNKNOWN;
}
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 853e1b26..515fa399 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -457,8 +457,7 @@ public final class ShutdownThread extends Thread {
// First send the high-level shut down broadcast.
mActionDone = false;
Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
- intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendOrderedBroadcastAsUser(intent,
UserHandle.ALL, null, br, mHandler, 0, null, null);
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
new file mode 100644
index 00000000..f1fb3e7b
--- /dev/null
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -0,0 +1,301 @@
+/*
+ * 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 com.android.server.stats;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IStatsCompanionService;
+import android.os.IStatsManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.server.SystemService;
+
+import java.util.Map;
+
+/**
+ * Helper service for statsd (the native stats management service in cmds/statsd/).
+ * Used for registering and receiving alarms on behalf of statsd.
+ * @hide
+ */
+public class StatsCompanionService extends IStatsCompanionService.Stub {
+ static final String TAG = "StatsCompanionService";
+ static final boolean DEBUG = true;
+
+ private final Context mContext;
+ private final AlarmManager mAlarmManager;
+ @GuardedBy("sStatsdLock")
+ private static IStatsManager sStatsd;
+ private static final Object sStatsdLock = new Object();
+
+ private final PendingIntent mAnomalyAlarmIntent;
+ private final PendingIntent mPollingAlarmIntent;
+
+ public StatsCompanionService(Context context) {
+ super();
+ mContext = context;
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+ mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(mContext, AnomalyAlarmReceiver.class), 0);
+ mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
+ new Intent(mContext, PollingAlarmReceiver.class), 0);
+ }
+
+ public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+ return;
+ }
+ try {
+ // Two-way call to statsd to retain AlarmManager wakelock
+ sStatsd.informAnomalyAlarmFired();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e);
+ }
+ }
+ // AlarmManager releases its own wakelock here.
+ }
+ };
+
+ public final static class PollingAlarmReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Slog.d(TAG, "Time to poll something.");
+ synchronized (sStatsdLock) {
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing");
+ return;
+ }
+ try {
+ // Two-way call to statsd to retain AlarmManager wakelock
+ sStatsd.informPollAlarmFired();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e);
+ }
+ }
+ // AlarmManager releases its own wakelock here.
+ }
+ };
+
+ @Override // Binder call
+ public void setAnomalyAlarm(long timestampMs) {
+ enforceCallingPermission();
+ if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+ // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+ // AlarmManager will automatically cancel any previous mAnomalyAlarmIntent alarm.
+ mAlarmManager.set(AlarmManager.RTC, timestampMs, mAnomalyAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ }
+
+ @Override // Binder call
+ public void cancelAnomalyAlarm() {
+ enforceCallingPermission();
+ if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.cancel(mAnomalyAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ }
+
+ @Override // Binder call
+ public void setPollingAlarms(long timestampMs, long intervalMs) {
+ enforceCallingPermission();
+ if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
+ + " every " + intervalMs + "ms");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+ // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+ // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+ mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
+ mPollingAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ }
+
+ @Override // Binder call
+ public void cancelPollingAlarms() {
+ enforceCallingPermission();
+ if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ mAlarmManager.cancel(mPollingAlarmIntent);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ }
+
+ // These values must be kept in sync with cmd/statsd/StatsPullerManager.h.
+ // TODO: pull the constant from stats_events.proto instead
+ private static final int PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+ @Override // Binder call
+ public String pullData(int pullCode) {
+ enforceCallingPermission();
+ if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+
+ StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+ switch (pullCode) {
+ case PULL_CODE_KERNEL_WAKELOCKS:
+ final KernelWakelockStats wakelockStats =
+ mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+
+ for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+ String name = ent.getKey();
+ KernelWakelockStats.Entry kws = ent.getValue();
+ s.append("Wakelock ")
+ .append(name)
+ .append(", time=")
+ .append(kws.mTotalTime)
+ .append(", count=")
+ .append(kws.mCount)
+ .append('\n');
+ }
+ break;
+ default:
+ Slog.w(TAG, "No such pollable data as " + pullCode);
+ return null;
+ }
+ return s.toString();
+ }
+
+ @Override // Binder call
+ public void statsdReady() {
+ enforceCallingPermission();
+ if (DEBUG) Slog.d(TAG, "learned that statsdReady");
+ sayHiToStatsd(); // tell statsd that we're ready too and link to it
+ }
+
+ private void enforceCallingPermission() {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
+ }
+
+ // Lifecycle and related code
+
+ /** Fetches the statsd IBinder service */
+ private static IStatsManager fetchStatsdService() {
+ return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+ }
+
+ public static final class Lifecycle extends SystemService {
+ private StatsCompanionService mStatsCompanionService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mStatsCompanionService = new StatsCompanionService(getContext());
+ try {
+ publishBinderService(Context.STATS_COMPANION_SERVICE, mStatsCompanionService);
+ if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to publishBinderService", e);
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+ if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+ mStatsCompanionService.systemReady();
+ }
+ }
+ }
+
+ /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */
+ private void systemReady() {
+ if (DEBUG) Slog.d(TAG, "Learned that systemReady");
+ sayHiToStatsd();
+ }
+
+ /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */
+ private void sayHiToStatsd() {
+ synchronized (sStatsdLock) {
+ if (sStatsd != null) {
+ Slog.e(TAG, "Trying to fetch statsd, but it was already fetched",
+ new IllegalStateException("sStatsd is not null when being fetched"));
+ return;
+ }
+ sStatsd = fetchStatsdService();
+ if (sStatsd == null) {
+ Slog.w(TAG, "Could not access statsd");
+ return;
+ }
+ if (DEBUG) Slog.d(TAG, "Saying hi to statsd");
+ try {
+ sStatsd.statsCompanionReady();
+ // If the statsCompanionReady two-way binder call returns, link to statsd.
+ try {
+ sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
+ forgetEverything();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
+ forgetEverything();
+ }
+ }
+ }
+
+ private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+ @Override
+ public void binderDied() {
+ Slog.i(TAG, "Statsd is dead - erase all my knowledge.");
+ forgetEverything();
+ }
+ }
+
+ private void forgetEverything() {
+ synchronized (sStatsdLock) {
+ sStatsd = null;
+ cancelAnomalyAlarm();
+ cancelPollingAlarms();
+ }
+ }
+
+}
diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java
index 38dc33fa..bdfbe481 100644
--- a/com/android/server/statusbar/StatusBarManagerService.java
+++ b/com/android/server/statusbar/StatusBarManagerService.java
@@ -31,6 +31,7 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
+import android.service.notification.NotificationStats;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
@@ -97,12 +98,58 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
int what2;
IBinder token;
+ public DisableRecord(int userId, IBinder token) {
+ this.userId = userId;
+ this.token = token;
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException re) {
+ // Give up
+ }
+ }
+
+ @Override
public void binderDied() {
Slog.i(TAG, "binder died for pkg=" + pkg);
disableForUser(0, token, pkg, userId);
disable2ForUser(0, token, pkg, userId);
token.unlinkToDeath(this, 0);
}
+
+ public void setFlags(int what, int which, String pkg) {
+ switch (which) {
+ case 1:
+ what1 = what;
+ return;
+ case 2:
+ what2 = what;
+ return;
+ default:
+ Slog.w(TAG, "Can't set unsupported disable flag " + which
+ + ": 0x" + Integer.toHexString(what));
+ }
+ this.pkg = pkg;
+ }
+
+ public int getFlags(int which) {
+ switch (which) {
+ case 1: return what1;
+ case 2: return what2;
+ default:
+ Slog.w(TAG, "Can't get unsupported disable flag " + which);
+ return 0;
+ }
+ }
+
+ public boolean isEmpty() {
+ return what1 == 0 && what2 == 0;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s",
+ userId, what1, what2, pkg, token);
+ }
}
/**
@@ -483,7 +530,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
*/
@Override
public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
- enforceStatusBar();
+ enforceStatusBarService();
synchronized (mLock) {
disableLocked(userId, what, token, pkg, 2);
@@ -897,13 +944,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
@Override
- public void onNotificationClear(String pkg, String tag, int id, int userId) {
+ public void onNotificationClear(String pkg, String tag, int id, int userId, String key,
+ @NotificationStats.DismissalSurface int dismissalSurface) {
enforceStatusBarService();
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
long identity = Binder.clearCallingIdentity();
try {
- mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId);
+ mNotificationDelegate.onNotificationClear(
+ callingUid, callingPid, pkg, tag, id, userId, key, dismissalSurface);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -937,6 +986,28 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
}
@Override
+ public void onNotificationDirectReplied(String key) throws RemoteException {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationDirectReplied(key);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onNotificationSettingsViewed(String key) throws RemoteException {
+ enforceStatusBarService();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.onNotificationSettingsViewed(key);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void onClearAllNotifications(int userId) {
enforceStatusBarService();
final int callingUid = Binder.getCallingUid();
@@ -970,42 +1041,42 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
Slog.d(TAG, "manageDisableList userId=" + userId
+ " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
}
- // update the list
+
+ // Find matching record, if any
final int N = mDisableRecords.size();
- DisableRecord tok = null;
+ DisableRecord record = null;
int i;
- for (i=0; i<N; i++) {
- DisableRecord t = mDisableRecords.get(i);
- if (t.token == token && t.userId == userId) {
- tok = t;
+ for (i = 0; i < N; i++) {
+ DisableRecord r = mDisableRecords.get(i);
+ if (r.token == token && r.userId == userId) {
+ record = r;
break;
}
}
- if (what == 0 || !token.isBinderAlive()) {
- if (tok != null) {
+
+ // Remove record if binder is already dead
+ if (!token.isBinderAlive()) {
+ if (record != null) {
mDisableRecords.remove(i);
- tok.token.unlinkToDeath(tok, 0);
- }
- } else {
- if (tok == null) {
- tok = new DisableRecord();
- tok.userId = userId;
- try {
- token.linkToDeath(tok, 0);
- }
- catch (RemoteException ex) {
- return; // give up
- }
- mDisableRecords.add(tok);
+ record.token.unlinkToDeath(record, 0);
}
- if (which == 1) {
- tok.what1 = what;
- } else {
- tok.what2 = what;
+ return;
+ }
+
+ // Update existing record
+ if (record != null) {
+ record.setFlags(what, which, pkg);
+ if (record.isEmpty()) {
+ mDisableRecords.remove(i);
+ record.token.unlinkToDeath(record, 0);
}
- tok.token = token;
- tok.pkg = pkg;
+ return;
}
+
+ // Record doesn't exist, so we create a new one
+ record = new DisableRecord(userId, token);
+ record.setFlags(what, which, pkg);
+ mDisableRecords.add(record);
}
// lock on mDisableRecords
@@ -1016,7 +1087,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
for (int i=0; i<N; i++) {
final DisableRecord rec = mDisableRecords.get(i);
if (rec.userId == userId) {
- net |= (which == 1) ? rec.what1 : rec.what2;
+ net |= rec.getFlags(which);
}
}
return net;
@@ -1036,11 +1107,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub {
pw.println(" mDisableRecords.size=" + N);
for (int i=0; i<N; i++) {
DisableRecord tok = mDisableRecords.get(i);
- pw.println(" [" + i + "] userId=" + tok.userId
- + " what1=0x" + Integer.toHexString(tok.what1)
- + " what2=0x" + Integer.toHexString(tok.what2)
- + " pkg=" + tok.pkg
- + " token=" + tok.token);
+ pw.println(" [" + i + "] " + tok);
}
pw.println(" mCurrentUserId=" + mCurrentUserId);
pw.println(" mIcons=");
diff --git a/com/android/server/storage/AppCollector.java b/com/android/server/storage/AppCollector.java
index 03b754f7..0b51f9cc 100644
--- a/com/android/server/storage/AppCollector.java
+++ b/com/android/server/storage/AppCollector.java
@@ -135,7 +135,7 @@ public class AppCollector {
PackageStats packageStats = new PackageStats(app.packageName,
user.id);
packageStats.cacheSize = storageStats.getCacheBytes();
- packageStats.codeSize = storageStats.getCodeBytes();
+ packageStats.codeSize = storageStats.getAppBytes();
packageStats.dataSize = storageStats.getDataBytes();
stats.add(packageStats);
} catch (NameNotFoundException | IOException e) {
diff --git a/com/android/server/storage/DiskStatsFileLogger.java b/com/android/server/storage/DiskStatsFileLogger.java
index 0094ab55..1db3ec4c 100644
--- a/com/android/server/storage/DiskStatsFileLogger.java
+++ b/com/android/server/storage/DiskStatsFileLogger.java
@@ -56,10 +56,12 @@ public class DiskStatsFileLogger {
public static final String SYSTEM_KEY = "systemSize";
public static final String MISC_KEY = "otherSize";
public static final String APP_SIZE_AGG_KEY = "appSize";
+ public static final String APP_DATA_SIZE_AGG_KEY = "appDataSize";
public static final String APP_CACHE_AGG_KEY = "cacheSize";
public static final String PACKAGE_NAMES_KEY = "packageNames";
public static final String APP_SIZES_KEY = "appSizes";
public static final String APP_CACHES_KEY = "cacheSizes";
+ public static final String APP_DATA_KEY = "appDataSizes";
public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
private MeasurementResult mResult;
@@ -114,31 +116,39 @@ public class DiskStatsFileLogger {
private void addAppsToJson(JSONObject json) throws JSONException {
JSONArray names = new JSONArray();
JSONArray appSizeList = new JSONArray();
+ JSONArray appDataSizeList = new JSONArray();
JSONArray cacheSizeList = new JSONArray();
long appSizeSum = 0L;
+ long appDataSizeSum = 0L;
long cacheSizeSum = 0L;
boolean isExternal = Environment.isExternalStorageEmulated();
for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
PackageStats stat = entry.getValue();
- long appSize = stat.codeSize + stat.dataSize;
+ long appSize = stat.codeSize;
+ long appDataSize = stat.dataSize;
long cacheSize = stat.cacheSize;
if (isExternal) {
- appSize += stat.externalCodeSize + stat.externalDataSize;
+ appSize += stat.externalCodeSize;
+ appDataSize += stat.externalDataSize;
cacheSize += stat.externalCacheSize;
}
appSizeSum += appSize;
+ appDataSizeSum += appDataSize;
cacheSizeSum += cacheSize;
names.put(stat.packageName);
appSizeList.put(appSize);
+ appDataSizeList.put(appDataSize);
cacheSizeList.put(cacheSize);
}
json.put(PACKAGE_NAMES_KEY, names);
json.put(APP_SIZES_KEY, appSizeList);
json.put(APP_CACHES_KEY, cacheSizeList);
+ json.put(APP_DATA_KEY, appDataSizeList);
json.put(APP_SIZE_AGG_KEY, appSizeSum);
json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
+ json.put(APP_DATA_SIZE_AGG_KEY, appDataSizeSum);
}
/**
diff --git a/com/android/server/timezone/IntentHelper.java b/com/android/server/timezone/IntentHelper.java
index 0cb90657..5de54321 100644
--- a/com/android/server/timezone/IntentHelper.java
+++ b/com/android/server/timezone/IntentHelper.java
@@ -23,15 +23,22 @@ package com.android.server.timezone;
*/
interface IntentHelper {
- void initialize(String updateAppPackageName, String dataAppPackageName, Listener listener);
+ void initialize(String updateAppPackageName, String dataAppPackageName,
+ PackageTracker packageTracker);
void sendTriggerUpdateCheck(CheckToken checkToken);
- void enableReliabilityTriggering();
+ /**
+ * Schedule a "reliability trigger" after at least minimumDelayMillis, replacing any existing
+ * scheduled one. A reliability trigger ensures that the {@link PackageTracker} can pick up
+ * reliably if a previous update check did not complete for some reason. It can happen when
+ * the device is idle. The trigger is expected to call
+ * {@link PackageTracker#triggerUpdateIfNeeded(boolean)} with a {@code false} value.
+ */
+ void scheduleReliabilityTrigger(long minimumDelayMillis);
- void disableReliabilityTriggering();
-
- interface Listener {
- void triggerUpdateIfNeeded(boolean packageUpdated);
- }
+ /**
+ * Make sure there is no reliability trigger scheduled. No-op if there wasn't one.
+ */
+ void unscheduleReliabilityTrigger();
}
diff --git a/com/android/server/timezone/IntentHelperImpl.java b/com/android/server/timezone/IntentHelperImpl.java
index 6db70cd8..6e6259d9 100644
--- a/com/android/server/timezone/IntentHelperImpl.java
+++ b/com/android/server/timezone/IntentHelperImpl.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PatternMatcher;
+import android.os.UserHandle;
import android.util.Slog;
/**
@@ -36,16 +37,13 @@ final class IntentHelperImpl implements IntentHelper {
private final Context mContext;
private String mUpdaterAppPackageName;
- private boolean mReliabilityReceiverEnabled;
- private Receiver mReliabilityReceiver;
-
IntentHelperImpl(Context context) {
mContext = context;
}
@Override
- public void initialize(
- String updaterAppPackageName, String dataAppPackageName, Listener listener) {
+ public void initialize(String updaterAppPackageName, String dataAppPackageName,
+ PackageTracker packageTracker) {
mUpdaterAppPackageName = updaterAppPackageName;
// Register for events of interest.
@@ -78,10 +76,10 @@ final class IntentHelperImpl implements IntentHelper {
// We do not register for ACTION_PACKAGE_DATA_CLEARED because the updater / data apps are
// not expected to need local data.
- Receiver packageUpdateReceiver = new Receiver(listener, true /* packageUpdated */);
- mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
-
- mReliabilityReceiver = new Receiver(listener, false /* packageUpdated */);
+ Receiver packageUpdateReceiver = new Receiver(packageTracker);
+ mContext.registerReceiverAsUser(
+ packageUpdateReceiver, UserHandle.SYSTEM, packageIntentFilter,
+ null /* broadcastPermission */, null /* default handler */);
}
/** Sends an intent to trigger an update check. */
@@ -93,39 +91,26 @@ final class IntentHelperImpl implements IntentHelper {
}
@Override
- public synchronized void enableReliabilityTriggering() {
- if (!mReliabilityReceiverEnabled) {
- // The intent filter that exists to make updates reliable in the event of failures /
- // reboots.
- IntentFilter reliabilityIntentFilter = new IntentFilter();
- reliabilityIntentFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
- mContext.registerReceiver(mReliabilityReceiver, reliabilityIntentFilter);
- mReliabilityReceiverEnabled = true;
- }
+ public synchronized void scheduleReliabilityTrigger(long minimumDelayMillis) {
+ TimeZoneUpdateIdler.schedule(mContext, minimumDelayMillis);
}
@Override
- public synchronized void disableReliabilityTriggering() {
- if (mReliabilityReceiverEnabled) {
- mContext.unregisterReceiver(mReliabilityReceiver);
- mReliabilityReceiverEnabled = false;
- }
+ public synchronized void unscheduleReliabilityTrigger() {
+ TimeZoneUpdateIdler.unschedule(mContext);
}
private static class Receiver extends BroadcastReceiver {
- private final Listener mListener;
- private final boolean mPackageUpdated;
+ private final PackageTracker mPackageTracker;
- private Receiver(Listener listener, boolean packageUpdated) {
- mListener = listener;
- mPackageUpdated = packageUpdated;
+ private Receiver(PackageTracker packageTracker) {
+ mPackageTracker = packageTracker;
}
@Override
public void onReceive(Context context, Intent intent) {
Slog.d(TAG, "Received intent: " + intent.toString());
- mListener.triggerUpdateIfNeeded(mPackageUpdated);
+ mPackageTracker.triggerUpdateIfNeeded(true /* packageChanged */);
}
}
-
}
diff --git a/com/android/server/timezone/PackageTracker.java b/com/android/server/timezone/PackageTracker.java
index 24e0fe48..f0306b9b 100644
--- a/com/android/server/timezone/PackageTracker.java
+++ b/com/android/server/timezone/PackageTracker.java
@@ -51,7 +51,7 @@ import java.io.PrintWriter;
*/
// Also made non-final so it can be mocked.
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class PackageTracker implements IntentHelper.Listener {
+public class PackageTracker {
private static final String TAG = "timezone.PackageTracker";
private final PackageManagerHelper mPackageManagerHelper;
@@ -72,6 +72,13 @@ public class PackageTracker implements IntentHelper.Listener {
// The number of failed checks in a row before reliability checks should stop happening.
private long mFailedCheckRetryCount;
+ /*
+ * The minimum delay between a successive reliability triggers / other operations. Should to be
+ * larger than mCheckTimeAllowedMillis to avoid reliability triggers happening during package
+ * update checks.
+ */
+ private int mDelayBeforeReliabilityCheckMillis;
+
// Reliability check state: If a check was triggered but not acknowledged within
// mCheckTimeAllowedMillis then another one can be triggered.
private Long mLastTriggerTimestamp = null;
@@ -122,6 +129,7 @@ public class PackageTracker implements IntentHelper.Listener {
mDataAppPackageName = mConfigHelper.getDataAppPackageName();
mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis();
mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount();
+ mDelayBeforeReliabilityCheckMillis = mCheckTimeAllowedMillis + (60 * 1000);
// Validate the device configuration including the application packages.
// The manifest entries in the apps themselves are not validated until use as they can
@@ -135,9 +143,10 @@ public class PackageTracker implements IntentHelper.Listener {
// Initialize the intent helper.
mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this);
- // Enable the reliability triggering so we will have at least one reliability trigger if
- // a package isn't updated.
- mIntentHelper.enableReliabilityTriggering();
+ // Schedule a reliability trigger so we will have at least one after boot. This will allow
+ // us to catch if a package updated wasn't handled to completion. There's no hurry: it's ok
+ // to delay for a while before doing this even if idle.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
Slog.i(TAG, "Time zone updater / data package tracking enabled");
}
@@ -195,7 +204,6 @@ public class PackageTracker implements IntentHelper.Listener {
* @param packageChanged true if this method was called because a known packaged definitely
* changed, false if the cause is a reliability trigger
*/
- @Override
public synchronized void triggerUpdateIfNeeded(boolean packageChanged) {
if (!mTrackingEnabled) {
throw new IllegalStateException("Unexpected call. Tracking is disabled.");
@@ -212,8 +220,8 @@ public class PackageTracker implements IntentHelper.Listener {
+ " updaterApp=" + updaterAppManifestValid
+ ", dataApp=" + dataAppManifestValid);
- // There's no point in doing reliability checks if the current packages are bad.
- mIntentHelper.disableReliabilityTriggering();
+ // There's no point in doing any reliability triggers if the current packages are bad.
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
}
@@ -238,7 +246,8 @@ public class PackageTracker implements IntentHelper.Listener {
Slog.d(TAG,
"triggerUpdateIfNeeded: checkComplete call is not yet overdue."
+ " Not triggering.");
- // Not doing any work, but also not disabling future reliability triggers.
+ // Don't do any work now but we do schedule a future reliability trigger.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
return;
}
} else if (mCheckFailureCount > mFailedCheckRetryCount) {
@@ -247,13 +256,13 @@ public class PackageTracker implements IntentHelper.Listener {
Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures"
+ " exceeded. Stopping reliability triggers until next reboot or package"
+ " update.");
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
} else if (mCheckFailureCount == 0) {
// Case 4.
Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was"
+ " successful.");
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
}
}
@@ -263,7 +272,7 @@ public class PackageTracker implements IntentHelper.Listener {
if (currentInstalledVersions == null) {
// This should not happen if the device is configured in a valid way.
Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null");
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
}
@@ -288,7 +297,7 @@ public class PackageTracker implements IntentHelper.Listener {
// The last check succeeded and nothing has changed. Do nothing and disable
// reliability checks.
Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger.");
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
}
}
@@ -299,6 +308,8 @@ public class PackageTracker implements IntentHelper.Listener {
if (checkToken == null) {
Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token."
+ " Not sending check request.");
+ // Trigger again later: perhaps we'll have better luck.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
return;
}
@@ -309,9 +320,9 @@ public class PackageTracker implements IntentHelper.Listener {
// Update the reliability check state in case the update fails.
setCheckInProgress();
- // Enable reliability triggering in case the check doesn't succeed and there is no
- // response at all. Enabling reliability triggering is idempotent.
- mIntentHelper.enableReliabilityTriggering();
+ // Schedule a reliability trigger in case the update check doesn't succeed and there is no
+ // response at all. It will be cancelled if the check is successful in recordCheckResult.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
}
/**
@@ -370,9 +381,9 @@ public class PackageTracker implements IntentHelper.Listener {
+ " storage state.");
mPackageStatusStorage.resetCheckState();
- // Enable reliability triggering and reset the failure count so we know that the
+ // Schedule a reliability trigger and reset the failure count so we know that the
// next reliability trigger will do something.
- mIntentHelper.enableReliabilityTriggering();
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount = 0;
} else {
// This is the expected case when tracking is enabled: a check was triggered and it has
@@ -385,13 +396,13 @@ public class PackageTracker implements IntentHelper.Listener {
setCheckComplete();
if (success) {
- // Since the check was successful, no more reliability checks are required until
+ // Since the check was successful, no reliability trigger is required until
// there is a package change.
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
mCheckFailureCount = 0;
} else {
- // Enable reliability triggering to potentially check again in future.
- mIntentHelper.enableReliabilityTriggering();
+ // Enable schedule a reliability trigger to check again in future.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount++;
}
} else {
@@ -400,8 +411,8 @@ public class PackageTracker implements IntentHelper.Listener {
Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken
+ " with success=" + success + ". Optimistic lock failure");
- // Enable reliability triggering to potentially try again in future.
- mIntentHelper.enableReliabilityTriggering();
+ // Schedule a reliability trigger to potentially try again in future.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount++;
}
}
@@ -515,6 +526,7 @@ public class PackageTracker implements IntentHelper.Listener {
", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' +
", mDataAppPackageName='" + mDataAppPackageName + '\'' +
", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis +
+ ", mDelayBeforeReliabilityCheckMillis=" + mDelayBeforeReliabilityCheckMillis +
", mFailedCheckRetryCount=" + mFailedCheckRetryCount +
", mLastTriggerTimestamp=" + mLastTriggerTimestamp +
", mCheckTriggered=" + mCheckTriggered +
diff --git a/com/android/server/timezone/PackageTrackerHelperImpl.java b/com/android/server/timezone/PackageTrackerHelperImpl.java
index 2e0c21bf..b89dd383 100644
--- a/com/android/server/timezone/PackageTrackerHelperImpl.java
+++ b/com/android/server/timezone/PackageTrackerHelperImpl.java
@@ -26,6 +26,7 @@ import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Slog;
import java.util.List;
@@ -114,8 +115,8 @@ final class PackageTrackerHelperImpl implements ClockHelper, ConfigHelper, Packa
@Override
public boolean contentProviderRegistered(String authority, String requiredPackageName) {
int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
- ProviderInfo providerInfo =
- mPackageManager.resolveContentProvider(authority, flags);
+ ProviderInfo providerInfo = mPackageManager.resolveContentProviderAsUser(
+ authority, flags, UserHandle.SYSTEM.getIdentifier());
if (providerInfo == null) {
Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority="
+ authority);
@@ -136,7 +137,8 @@ final class PackageTrackerHelperImpl implements ClockHelper, ConfigHelper, Packa
throws PackageManager.NameNotFoundException {
int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
- List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceivers(intent, flags);
+ List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceiversAsUser(
+ intent, flags, UserHandle.SYSTEM);
if (resolveInfo.size() != 1) {
Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for"
+ " intent=" + intent + ", found=" + resolveInfo);
diff --git a/com/android/server/timezone/RulesManagerService.java b/com/android/server/timezone/RulesManagerService.java
index 3ad4419c..52b49baf 100644
--- a/com/android/server/timezone/RulesManagerService.java
+++ b/com/android/server/timezone/RulesManagerService.java
@@ -47,6 +47,7 @@ import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import libcore.icu.ICU;
+import libcore.util.TimeZoneFinder;
import libcore.util.ZoneInfoDB;
import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED;
@@ -69,18 +70,22 @@ public final class RulesManagerService extends IRulesManager.Stub {
DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
public static class Lifecycle extends SystemService {
- private RulesManagerService mService;
-
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
- mService = RulesManagerService.create(getContext());
- mService.start();
+ RulesManagerService service = RulesManagerService.create(getContext());
+ service.start();
+
+ // Publish the binder service so it can be accessed from other (appropriately
+ // permissioned) processes.
+ publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, service);
- publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
+ // Publish the service instance locally so we can use it directly from within the system
+ // server from TimeZoneUpdateIdler.
+ publishLocalService(RulesManagerService.class, service);
}
}
@@ -475,9 +480,10 @@ public final class RulesManagerService extends IRulesManager.Stub {
case 'a': {
// Report the active rules version (i.e. the rules in use by the current
// process).
- pw.println("Active rules version (ICU, libcore): "
+ pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
+ ICU.getTZDataVersion() + ","
- + ZoneInfoDB.getInstance().getVersion());
+ + ZoneInfoDB.getInstance().getVersion() + ","
+ + TimeZoneFinder.getInstance().getIanaVersion());
break;
}
default: {
@@ -490,12 +496,24 @@ public final class RulesManagerService extends IRulesManager.Stub {
}
pw.println("RulesManagerService state: " + toString());
- pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + ","
- + ZoneInfoDB.getInstance().getVersion());
+ pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
+ + ICU.getTZDataVersion() + ","
+ + ZoneInfoDB.getInstance().getVersion() + ","
+ + TimeZoneFinder.getInstance().getIanaVersion());
pw.println("Distro state: " + rulesState.toString());
mPackageTracker.dump(pw);
}
+ /**
+ * Called when the device is considered idle.
+ */
+ void notifyIdle() {
+ // No package has changed: we are just triggering because the device is idle and there
+ // *might* be work to do.
+ final boolean packageChanged = false;
+ mPackageTracker.triggerUpdateIfNeeded(packageChanged);
+ }
+
@Override
public String toString() {
return "RulesManagerService{" +
diff --git a/com/android/server/timezone/TimeZoneUpdateIdler.java b/com/android/server/timezone/TimeZoneUpdateIdler.java
new file mode 100644
index 00000000..a7767a4f
--- /dev/null
+++ b/com/android/server/timezone/TimeZoneUpdateIdler.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 com.android.server.timezone;
+
+import com.android.server.LocalServices;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+/**
+ * A JobService used to trigger time zone rules update work when a device falls idle.
+ */
+public final class TimeZoneUpdateIdler extends JobService {
+
+ private static final String TAG = "timezone.TimeZoneUpdateIdler";
+
+ /** The static job ID used to handle on-idle work. */
+ // Must be unique within UID (system service)
+ private static final int TIME_ZONE_UPDATE_IDLE_JOB_ID = 27042305;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ RulesManagerService rulesManagerService =
+ LocalServices.getService(RulesManagerService.class);
+
+ Slog.d(TAG, "onStartJob() called");
+
+ // Note: notifyIdle() explicitly handles canceling / re-scheduling so no need to reschedule
+ // here.
+ rulesManagerService.notifyIdle();
+
+ // Everything is handled synchronously. We are done.
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // Reschedule if stopped unless it was cancelled due to unschedule().
+ boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED;
+ Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
+ return reschedule;
+ }
+
+ /**
+ * Schedules the TimeZoneUpdateIdler job service to run once.
+ *
+ * @param context Context to use to get a job scheduler.
+ */
+ public static void schedule(Context context, long minimumDelayMillis) {
+ // Request that the JobScheduler tell us when the device falls idle.
+ JobScheduler jobScheduler =
+ (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+ // The TimeZoneUpdateIdler will send an intent that will trigger the Receiver.
+ ComponentName idlerJobServiceName =
+ new ComponentName(context, TimeZoneUpdateIdler.class);
+
+ // We require the device is idle, but also that it is charging to be as non-invasive as
+ // we can.
+ JobInfo.Builder jobInfoBuilder =
+ new JobInfo.Builder(TIME_ZONE_UPDATE_IDLE_JOB_ID, idlerJobServiceName)
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setMinimumLatency(minimumDelayMillis);
+
+ Slog.d(TAG, "schedule() called: minimumDelayMillis=" + minimumDelayMillis);
+ jobScheduler.schedule(jobInfoBuilder.build());
+ }
+
+ /**
+ * Unschedules the TimeZoneUpdateIdler job service.
+ *
+ * @param context Context to use to get a job scheduler.
+ */
+ public static void unschedule(Context context) {
+ JobScheduler jobScheduler =
+ (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ Slog.d(TAG, "unschedule() called");
+ jobScheduler.cancel(TIME_ZONE_UPDATE_IDLE_JOB_ID);
+ }
+}
diff --git a/com/android/server/twilight/TwilightState.java b/com/android/server/twilight/TwilightState.java
index 30a8cccb..71304a7a 100644
--- a/com/android/server/twilight/TwilightState.java
+++ b/com/android/server/twilight/TwilightState.java
@@ -18,7 +18,10 @@ package com.android.server.twilight;
import android.text.format.DateFormat;
-import java.util.Calendar;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.TimeZone;
/**
* The twilight state, consisting of the sunrise and sunset times (in millis) for the current
@@ -45,12 +48,11 @@ public final class TwilightState {
}
/**
- * Returns a new {@link Calendar} instance initialized to {@link #sunriseTimeMillis()}.
+ * Returns a new {@link LocalDateTime} instance initialized to {@link #sunriseTimeMillis()}.
*/
- public Calendar sunrise() {
- final Calendar sunrise = Calendar.getInstance();
- sunrise.setTimeInMillis(mSunriseTimeMillis);
- return sunrise;
+ public LocalDateTime sunrise() {
+ final ZoneId zoneId = TimeZone.getDefault().toZoneId();
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(mSunriseTimeMillis), zoneId);
}
/**
@@ -62,12 +64,11 @@ public final class TwilightState {
}
/**
- * Returns a new {@link Calendar} instance initialized to {@link #sunsetTimeMillis()}.
+ * Returns a new {@link LocalDateTime} instance initialized to {@link #sunsetTimeMillis()}.
*/
- public Calendar sunset() {
- final Calendar sunset = Calendar.getInstance();
- sunset.setTimeInMillis(mSunsetTimeMillis);
- return sunset;
+ public LocalDateTime sunset() {
+ final ZoneId zoneId = TimeZone.getDefault().toZoneId();
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(mSunsetTimeMillis), zoneId);
}
/**
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
new file mode 100644
index 00000000..b2446ba7
--- /dev/null
+++ b/com/android/server/usage/AppStandbyController.java
@@ -0,0 +1,987 @@
+/**
+ * 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 com.android.server.usage;
+
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import static com.android.server.usage.UsageStatsService.MSG_REPORT_EVENT;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
+import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.IDeviceIdleController;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the standby state of an app, listening to various events.
+ */
+public class AppStandbyController {
+
+ private static final String TAG = "AppStandbyController";
+ private static final boolean DEBUG = false;
+
+ static final boolean COMPRESS_TIME = false;
+ private static final long ONE_MINUTE = 60 * 1000;
+
+ // To name the lock for stack traces
+ static class Lock {}
+
+ /** Lock to protect the app's standby state. Required for calls into AppIdleHistory */
+ private final Object mAppIdleLock = new Lock();
+
+ /** Keeps the history and state for each app. */
+ @GuardedBy("mAppIdleLock")
+ private AppIdleHistory mAppIdleHistory;
+
+ @GuardedBy("mAppIdleLock")
+ private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
+ mPackageAccessListeners = new ArrayList<>();
+
+ /** Whether we've queried the list of carrier privileged apps. */
+ @GuardedBy("mAppIdleLock")
+ private boolean mHaveCarrierPrivilegedApps;
+
+ /** List of carrier-privileged apps that should be excluded from standby */
+ @GuardedBy("mAppIdleLock")
+ private List<String> mCarrierPrivilegedApps;
+
+ // Messages for the handler
+ static final int MSG_INFORM_LISTENERS = 3;
+ static final int MSG_FORCE_IDLE_STATE = 4;
+ static final int MSG_CHECK_IDLE_STATES = 5;
+ static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
+ static final int MSG_PAROLE_END_TIMEOUT = 7;
+ static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
+ static final int MSG_PAROLE_STATE_CHANGED = 9;
+ static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
+
+ long mAppIdleScreenThresholdMillis;
+ long mCheckIdleIntervalMillis;
+ long mAppIdleWallclockThresholdMillis;
+ long mAppIdleParoleIntervalMillis;
+ long mAppIdleParoleDurationMillis;
+ boolean mAppIdleEnabled;
+ boolean mAppIdleTempParoled;
+ boolean mCharging;
+ private long mLastAppIdleParoledTime;
+ private boolean mSystemServicesReady = false;
+
+ private volatile boolean mPendingOneTimeCheckIdleStates;
+
+ private final Handler mHandler;
+ private final Context mContext;
+
+ private DisplayManager mDisplayManager;
+ private IDeviceIdleController mDeviceIdleController;
+ private AppWidgetManager mAppWidgetManager;
+ private IBatteryStats mBatteryStats;
+ private PowerManager mPowerManager;
+ private PackageManager mPackageManager;
+ private PackageManagerInternal mPackageManagerInternal;
+
+ AppStandbyController(Context context, Looper looper) {
+ mContext = context;
+ mHandler = new AppStandbyHandler(looper);
+ mPackageManager = mContext.getPackageManager();
+ mAppIdleEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableAutoPowerModes);
+ if (mAppIdleEnabled) {
+ IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+ deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
+ }
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
+ }
+
+ IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme("package");
+
+ mContext.registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
+ null, mHandler);
+ }
+
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ // Observe changes to the threshold
+ SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+ settingsObserver.registerObserver();
+ settingsObserver.updateSettings();
+
+ mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
+ mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ mBatteryStats = IBatteryStats.Stub.asInterface(
+ ServiceManager.getService(BatteryStats.SERVICE_NAME));
+ mDisplayManager = (DisplayManager) mContext.getSystemService(
+ Context.DISPLAY_SERVICE);
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+
+ mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
+ }
+
+ if (mPendingOneTimeCheckIdleStates) {
+ postOneTimeCheckIdleStates();
+ }
+
+ mSystemServicesReady = true;
+ } else if (phase == PHASE_BOOT_COMPLETED) {
+ setChargingState(mContext.getSystemService(BatteryManager.class).isCharging());
+ }
+ }
+
+ void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
+ // Get sync adapters for the authority
+ String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
+ authority, userId);
+ for (String packageName: packages) {
+ // Only force the sync adapters to active if the provider is not in the same package and
+ // the sync adapter is a system package.
+ try {
+ PackageInfo pi = mPackageManager.getPackageInfoAsUser(
+ packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
+ if (pi == null || pi.applicationInfo == null) {
+ continue;
+ }
+ if (!packageName.equals(providerPkgName)) {
+ setAppIdleAsync(packageName, false, userId);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Shouldn't happen
+ }
+ }
+ }
+
+ void setChargingState(boolean charging) {
+ synchronized (mAppIdleLock) {
+ if (mCharging != charging) {
+ mCharging = charging;
+ postParoleStateChanged();
+ }
+ }
+ }
+
+ /** Paroled here means temporary pardon from being inactive */
+ void setAppIdleParoled(boolean paroled) {
+ synchronized (mAppIdleLock) {
+ final long now = System.currentTimeMillis();
+ if (mAppIdleTempParoled != paroled) {
+ mAppIdleTempParoled = paroled;
+ if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
+ if (paroled) {
+ postParoleEndTimeout();
+ } else {
+ mLastAppIdleParoledTime = now;
+ postNextParoleTimeout(now);
+ }
+ postParoleStateChanged();
+ }
+ }
+ }
+
+ boolean isParoledOrCharging() {
+ synchronized (mAppIdleLock) {
+ return mAppIdleTempParoled || mCharging;
+ }
+ }
+
+ private void postNextParoleTimeout(long now) {
+ if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
+ mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
+ // Compute when the next parole needs to happen. We check more frequently than necessary
+ // since the message handler delays are based on elapsedRealTime and not wallclock time.
+ // The comparison is done in wallclock time.
+ long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
+ if (timeLeft < 0) {
+ timeLeft = 0;
+ }
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
+ }
+
+ private void postParoleEndTimeout() {
+ if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
+ mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
+ }
+
+ private void postParoleStateChanged() {
+ if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
+ mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
+ mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
+ }
+
+ void postCheckIdleStates(int userId) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
+ }
+
+ /**
+ * We send a different message to check idle states once, otherwise we would end up
+ * scheduling a series of repeating checkIdleStates each time we fired off one.
+ */
+ void postOneTimeCheckIdleStates() {
+ if (mDeviceIdleController == null) {
+ // Not booted yet; wait for it!
+ mPendingOneTimeCheckIdleStates = true;
+ } else {
+ mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
+ mPendingOneTimeCheckIdleStates = false;
+ }
+ }
+
+ /**
+ * Check all running users' or specified user's apps to see if they enter an idle state.
+ * @return Returns whether checking should continue periodically.
+ */
+ boolean checkIdleStates(int checkUserId) {
+ if (!mAppIdleEnabled) {
+ return false;
+ }
+
+ final int[] runningUserIds;
+ try {
+ runningUserIds = ActivityManager.getService().getRunningUserIds();
+ if (checkUserId != UserHandle.USER_ALL
+ && !ArrayUtils.contains(runningUserIds, checkUserId)) {
+ return false;
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ for (int i = 0; i < runningUserIds.length; i++) {
+ final int userId = runningUserIds[i];
+ if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
+ continue;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Checking idle state for user " + userId);
+ }
+ List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+ PackageManager.MATCH_DISABLED_COMPONENTS,
+ userId);
+ final int packageCount = packages.size();
+ for (int p = 0; p < packageCount; p++) {
+ final PackageInfo pi = packages.get(p);
+ final String packageName = pi.packageName;
+ final boolean isIdle = isAppIdleFiltered(packageName,
+ UserHandle.getAppId(pi.applicationInfo.uid),
+ userId, elapsedRealtime);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+ userId, isIdle ? 1 : 0, packageName));
+ if (isIdle) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
+ }
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "checkIdleStates took "
+ + (SystemClock.elapsedRealtime() - elapsedRealtime));
+ }
+ return true;
+ }
+
+ /** Check if it's been a while since last parole and let idle apps do some work */
+ void checkParoleTimeout() {
+ boolean setParoled = false;
+ synchronized (mAppIdleLock) {
+ final long now = System.currentTimeMillis();
+ if (!mAppIdleTempParoled) {
+ final long timeSinceLastParole = now - mLastAppIdleParoledTime;
+ if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
+ if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
+ setParoled = true;
+ } else {
+ if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
+ postNextParoleTimeout(now);
+ }
+ }
+ }
+ if (setParoled) {
+ setAppIdleParoled(true);
+ }
+ }
+
+ private void notifyBatteryStats(String packageName, int userId, boolean idle) {
+ try {
+ final int uid = mPackageManager.getPackageUidAsUser(packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+ if (idle) {
+ mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
+ packageName, uid);
+ } else {
+ mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
+ packageName, uid);
+ }
+ } catch (PackageManager.NameNotFoundException | RemoteException e) {
+ }
+ }
+
+ void onDeviceIdleModeChanged() {
+ final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
+ if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
+ boolean paroled = false;
+ synchronized (mAppIdleLock) {
+ final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
+ if (!deviceIdle
+ && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
+ if (DEBUG) {
+ Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
+ }
+ paroled = true;
+ } else if (deviceIdle) {
+ if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
+ paroled = false;
+ } else {
+ return;
+ }
+ }
+ setAppIdleParoled(paroled);
+ }
+
+ void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
+ synchronized (mAppIdleLock) {
+ // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
+ // about apps that are on some kind of whitelist anyway.
+ final boolean previouslyIdle = mAppIdleHistory.isIdle(
+ event.mPackage, userId, elapsedRealtime);
+ // Inform listeners if necessary
+ if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
+ || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
+ || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
+ || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
+ mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
+ if (previouslyIdle) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+ /* idle = */ 0, event.mPackage));
+ notifyBatteryStats(event.mPackage, userId, false);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
+ * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
+ * the threshold for idle.
+ *
+ * This method is always called from the handler thread, so not much synchronization is
+ * required.
+ */
+ void forceIdleState(String packageName, int userId, boolean idle) {
+ final int appId = getAppId(packageName);
+ if (appId < 0) return;
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+
+ final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
+ userId, elapsedRealtime);
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
+ }
+ final boolean stillIdle = isAppIdleFiltered(packageName, appId,
+ userId, elapsedRealtime);
+ // Inform listeners if necessary
+ if (previouslyIdle != stillIdle) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+ /* idle = */ stillIdle ? 1 : 0, packageName));
+ if (!stillIdle) {
+ notifyBatteryStats(packageName, userId, idle);
+ }
+ }
+ }
+
+ public void onUserRemoved(int userId) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.onUserRemoved(userId);
+ }
+ }
+
+ private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
+ synchronized (mAppIdleLock) {
+ return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
+ }
+ }
+
+ void addListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+ synchronized (mAppIdleLock) {
+ if (!mPackageAccessListeners.contains(listener)) {
+ mPackageAccessListeners.add(listener);
+ }
+ }
+ }
+
+ void removeListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+ synchronized (mAppIdleLock) {
+ mPackageAccessListeners.remove(listener);
+ }
+ }
+
+ int getAppId(String packageName) {
+ try {
+ ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
+ PackageManager.MATCH_ANY_USER
+ | PackageManager.MATCH_DISABLED_COMPONENTS);
+ return ai.uid;
+ } catch (PackageManager.NameNotFoundException re) {
+ return -1;
+ }
+ }
+
+ boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
+ boolean shouldObfuscateInstantApps) {
+ if (isParoledOrCharging()) {
+ return false;
+ }
+ if (shouldObfuscateInstantApps &&
+ mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
+ return false;
+ }
+ return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
+ }
+
+ /**
+ * Checks if an app has been idle for a while and filters out apps that are excluded.
+ * It returns false if the current system state allows all apps to be considered active.
+ * This happens if the device is plugged in or temporarily allowed to make exceptions.
+ * Called by interface impls.
+ */
+ boolean isAppIdleFiltered(String packageName, int appId, int userId,
+ long elapsedRealtime) {
+ if (packageName == null) return false;
+ // If not enabled at all, of course nobody is ever idle.
+ if (!mAppIdleEnabled) {
+ return false;
+ }
+ if (appId < Process.FIRST_APPLICATION_UID) {
+ // System uids never go idle.
+ return false;
+ }
+ if (packageName.equals("android")) {
+ // Nor does the framework (which should be redundant with the above, but for MR1 we will
+ // retain this for safety).
+ return false;
+ }
+ if (mSystemServicesReady) {
+ try {
+ // We allow all whitelisted apps, including those that don't want to be whitelisted
+ // for idle mode, because app idle (aka app standby) is really not as big an issue
+ // for controlling who participates vs. doze mode.
+ if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+ return false;
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+
+ if (isActiveDeviceAdmin(packageName, userId)) {
+ return false;
+ }
+
+ if (isActiveNetworkScorer(packageName)) {
+ return false;
+ }
+
+ if (mAppWidgetManager != null
+ && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
+ return false;
+ }
+
+ if (isDeviceProvisioningPackage(packageName)) {
+ return false;
+ }
+ }
+
+ if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
+ return false;
+ }
+
+ // Check this last, as it is the most expensive check
+ // TODO: Optimize this by fetching the carrier privileged apps ahead of time
+ if (isCarrierApp(packageName)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ int[] getIdleUidsForUser(int userId) {
+ if (!mAppIdleEnabled) {
+ return new int[0];
+ }
+
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+
+ List<ApplicationInfo> apps;
+ try {
+ ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
+ .getInstalledApplications(/* flags= */ 0, userId);
+ if (slice == null) {
+ return new int[0];
+ }
+ apps = slice.getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ // State of each uid. Key is the uid. Value lower 16 bits is the number of apps
+ // associated with that uid, upper 16 bits is the number of those apps that is idle.
+ SparseIntArray uidStates = new SparseIntArray();
+
+ // Now resolve all app state. Iterating over all apps, keeping track of how many
+ // we find for each uid and how many of those are idle.
+ for (int i = apps.size() - 1; i >= 0; i--) {
+ ApplicationInfo ai = apps.get(i);
+
+ // Check whether this app is idle.
+ boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
+ userId, elapsedRealtime);
+
+ int index = uidStates.indexOfKey(ai.uid);
+ if (index < 0) {
+ uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
+ } else {
+ int value = uidStates.valueAt(index);
+ uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+ }
+ int numIdle = 0;
+ for (int i = uidStates.size() - 1; i >= 0; i--) {
+ int value = uidStates.valueAt(i);
+ if ((value&0x7fff) == (value>>16)) {
+ numIdle++;
+ }
+ }
+
+ int[] res = new int[numIdle];
+ numIdle = 0;
+ for (int i = uidStates.size() - 1; i >= 0; i--) {
+ int value = uidStates.valueAt(i);
+ if ((value&0x7fff) == (value>>16)) {
+ res[numIdle] = uidStates.keyAt(i);
+ numIdle++;
+ }
+ }
+
+ return res;
+ }
+
+ void setAppIdleAsync(String packageName, boolean idle, int userId) {
+ if (packageName == null) return;
+
+ mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
+ .sendToTarget();
+ }
+
+ private boolean isActiveDeviceAdmin(String packageName, int userId) {
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) return false;
+ return dpm.packageHasActiveAdmins(packageName, userId);
+ }
+
+ /**
+ * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
+ * returns {@code false}.
+ */
+ private boolean isDeviceProvisioningPackage(String packageName) {
+ String deviceProvisioningPackage = mContext.getResources().getString(
+ com.android.internal.R.string.config_deviceProvisioningPackage);
+ return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
+ }
+
+ private boolean isCarrierApp(String packageName) {
+ synchronized (mAppIdleLock) {
+ if (!mHaveCarrierPrivilegedApps) {
+ fetchCarrierPrivilegedAppsLA();
+ }
+ if (mCarrierPrivilegedApps != null) {
+ return mCarrierPrivilegedApps.contains(packageName);
+ }
+ return false;
+ }
+ }
+
+ void clearCarrierPrivilegedApps() {
+ if (DEBUG) {
+ Slog.i(TAG, "Clearing carrier privileged apps list");
+ }
+ synchronized (mAppIdleLock) {
+ mHaveCarrierPrivilegedApps = false;
+ mCarrierPrivilegedApps = null; // Need to be refetched.
+ }
+ }
+
+ @GuardedBy("mAppIdleLock")
+ private void fetchCarrierPrivilegedAppsLA() {
+ TelephonyManager telephonyManager =
+ mContext.getSystemService(TelephonyManager.class);
+ mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
+ mHaveCarrierPrivilegedApps = true;
+ if (DEBUG) {
+ Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
+ }
+ }
+
+ private boolean isActiveNetworkScorer(String packageName) {
+ NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
+ Context.NETWORK_SCORE_SERVICE);
+ return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
+ }
+
+ void informListeners(String packageName, int userId, boolean isIdle) {
+ for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+ listener.onAppIdleStateChanged(packageName, userId, isIdle);
+ }
+ }
+
+ void informParoleStateChanged() {
+ final boolean paroled = isParoledOrCharging();
+ for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+ listener.onParoleStateChanged(paroled);
+ }
+ }
+
+ void flushToDisk(int userId) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.writeAppIdleTimes(userId);
+ }
+ }
+
+ void flushDurationsToDisk() {
+ // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
+ // considered not-idle, which is the safest outcome in such an event.
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.writeAppIdleDurations();
+ }
+ }
+
+ boolean isDisplayOn() {
+ return mDisplayManager
+ .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+ }
+
+ void clearAppIdleForPackage(String packageName, int userId) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.clearUsage(packageName, userId);
+ }
+ }
+
+ private class PackageReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+ || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ clearCarrierPrivilegedApps();
+ }
+ if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
+ Intent.ACTION_PACKAGE_ADDED.equals(action))
+ && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
+ getSendingUserId());
+ }
+ }
+ }
+
+ void initializeDefaultsForSystemApps(int userId) {
+ Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+ PackageManager.MATCH_DISABLED_COMPONENTS,
+ userId);
+ final int packageCount = packages.size();
+ synchronized (mAppIdleLock) {
+ for (int i = 0; i < packageCount; i++) {
+ final PackageInfo pi = packages.get(i);
+ String packageName = pi.packageName;
+ if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
+ mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
+ }
+ }
+ }
+ }
+
+ void postReportContentProviderUsage(String name, String packageName, int userId) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = name;
+ args.arg2 = packageName;
+ args.arg3 = userId;
+ mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
+ .sendToTarget();
+ }
+
+ void dumpHistory(IndentingPrintWriter idpw, int userId) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.dumpHistory(idpw, userId);
+ }
+ }
+
+ void dumpUser(IndentingPrintWriter idpw, int userId) {
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.dump(idpw, userId);
+ }
+ }
+
+ void dumpState(String[] args, PrintWriter pw) {
+ synchronized (mAppIdleLock) {
+ pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
+ + "): " + mCarrierPrivilegedApps);
+ }
+
+ pw.println();
+ pw.println("Settings:");
+
+ pw.print(" mAppIdleDurationMillis=");
+ TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
+ pw.println();
+
+ pw.print(" mAppIdleWallclockThresholdMillis=");
+ TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
+ pw.println();
+
+ pw.print(" mCheckIdleIntervalMillis=");
+ TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
+ pw.println();
+
+ pw.print(" mAppIdleParoleIntervalMillis=");
+ TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
+ pw.println();
+
+ pw.print(" mAppIdleParoleDurationMillis=");
+ TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
+ pw.println();
+
+ pw.println();
+ pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+ pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
+ pw.print(" mCharging="); pw.print(mCharging);
+ pw.print(" mLastAppIdleParoledTime=");
+ TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
+ pw.println();
+ }
+
+ class AppStandbyHandler extends Handler {
+
+ AppStandbyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_FORCE_IDLE_STATE:
+ forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
+ break;
+
+ case MSG_CHECK_IDLE_STATES:
+ if (checkIdleStates(msg.arg1)) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MSG_CHECK_IDLE_STATES, msg.arg1, 0),
+ mCheckIdleIntervalMillis);
+ }
+ break;
+
+ case MSG_ONE_TIME_CHECK_IDLE_STATES:
+ mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
+ checkIdleStates(UserHandle.USER_ALL);
+ break;
+
+ case MSG_CHECK_PAROLE_TIMEOUT:
+ checkParoleTimeout();
+ break;
+
+ case MSG_PAROLE_END_TIMEOUT:
+ if (DEBUG) Slog.d(TAG, "Ending parole");
+ setAppIdleParoled(false);
+ break;
+
+ case MSG_REPORT_CONTENT_PROVIDER_USAGE:
+ SomeArgs args = (SomeArgs) msg.obj;
+ reportContentProviderUsage((String) args.arg1, // authority name
+ (String) args.arg2, // package name
+ (int) args.arg3); // userId
+ args.recycle();
+ break;
+
+ case MSG_PAROLE_STATE_CHANGED:
+ if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
+ + ", Charging state:" + mCharging);
+ informParoleStateChanged();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
+
+ }
+ }
+ };
+
+ private class DeviceStateReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ setChargingState(intent.getIntExtra("plugged", 0) != 0);
+ } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+ onDeviceIdleModeChanged();
+ }
+ }
+ }
+
+ private final DisplayManager.DisplayListener mDisplayListener
+ = new DisplayManager.DisplayListener() {
+
+ @Override public void onDisplayAdded(int displayId) {
+ }
+
+ @Override public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override public void onDisplayChanged(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ final boolean displayOn = isDisplayOn();
+ synchronized (mAppIdleLock) {
+ mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
+ }
+ }
+ }
+ };
+
+ /**
+ * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
+ */
+ private class SettingsObserver extends ContentObserver {
+ /**
+ * This flag has been used to disable app idle on older builds with bug b/26355386.
+ */
+ @Deprecated
+ private static final String KEY_IDLE_DURATION_OLD = "idle_duration";
+
+ private static final String KEY_IDLE_DURATION = "idle_duration2";
+ private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
+ private static final String KEY_PAROLE_INTERVAL = "parole_interval";
+ private static final String KEY_PAROLE_DURATION = "parole_duration";
+
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void registerObserver() {
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.APP_IDLE_CONSTANTS), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSettings();
+ postOneTimeCheckIdleStates();
+ }
+
+ void updateSettings() {
+ synchronized (mAppIdleLock) {
+ // Look at global settings for this.
+ // TODO: Maybe apply different thresholds for different users.
+ try {
+ mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.APP_IDLE_CONSTANTS));
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
+ // fallthrough, mParser is empty and all defaults will be returned.
+ }
+
+ // Default: 12 hours of screen-on time sans dream-time
+ mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
+ COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
+
+ mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
+ COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
+
+ mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
+ COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
+
+ // Default: 24 hours between paroles
+ mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
+ COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
+
+ mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
+ COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
+ mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
+ mAppIdleScreenThresholdMillis);
+ }
+ }
+ }
+
+}
+
diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java
index 25e471cb..afafea19 100644
--- a/com/android/server/usage/UsageStatsService.java
+++ b/com/android/server/usage/UsageStatsService.java
@@ -18,37 +18,24 @@ package com.android.server.usage;
import android.Manifest;
import android.app.ActivityManager;
-import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUidObserver;
-import android.app.admin.DevicePolicyManager;
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
-import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
-import android.database.ContentObserver;
-import android.hardware.display.DisplayManager;
-import android.net.NetworkScoreManager;
-import android.os.BatteryManager;
-import android.os.BatteryStats;
import android.os.Binder;
import android.os.Environment;
import android.os.FileUtils;
@@ -56,7 +43,6 @@ import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
-import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -64,21 +50,12 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
import android.util.ArraySet;
-import android.util.KeyValueListParser;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.view.Display;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IBatteryStats;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
@@ -88,7 +65,6 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -107,7 +83,6 @@ public class UsageStatsService extends SystemService implements
static final boolean COMPRESS_TIME = false;
private static final long TEN_SECONDS = 10 * 1000;
- private static final long ONE_MINUTE = 60 * 1000;
private static final long TWENTY_MINUTES = 20 * 60 * 1000;
private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
@@ -115,24 +90,10 @@ public class UsageStatsService extends SystemService implements
private static final boolean ENABLE_KERNEL_UPDATES = true;
private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
- long mAppIdleScreenThresholdMillis;
- long mCheckIdleIntervalMillis;
- long mAppIdleWallclockThresholdMillis;
- long mAppIdleParoleIntervalMillis;
- long mAppIdleParoleDurationMillis;
-
// Handler message types.
static final int MSG_REPORT_EVENT = 0;
static final int MSG_FLUSH_TO_DISK = 1;
static final int MSG_REMOVE_USER = 2;
- static final int MSG_INFORM_LISTENERS = 3;
- static final int MSG_FORCE_IDLE_STATE = 4;
- static final int MSG_CHECK_IDLE_STATES = 5;
- static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
- static final int MSG_PAROLE_END_TIMEOUT = 7;
- static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
- static final int MSG_PAROLE_STATE_CHANGED = 9;
- static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
private final Object mLock = new Object();
Handler mHandler;
@@ -140,11 +101,7 @@ public class UsageStatsService extends SystemService implements
UserManager mUserManager;
PackageManager mPackageManager;
PackageManagerInternal mPackageManagerInternal;
- AppWidgetManager mAppWidgetManager;
IDeviceIdleController mDeviceIdleController;
- private DisplayManager mDisplayManager;
- private PowerManager mPowerManager;
- private IBatteryStats mBatteryStats;
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
@@ -152,26 +109,7 @@ public class UsageStatsService extends SystemService implements
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
- boolean mAppIdleEnabled;
- boolean mAppIdleTempParoled;
- boolean mCharging;
- private long mLastAppIdleParoledTime;
-
- private volatile boolean mPendingOneTimeCheckIdleStates;
- private boolean mSystemServicesReady = false;
-
- private final Object mAppIdleLock = new Object();
- @GuardedBy("mAppIdleLock")
- private AppIdleHistory mAppIdleHistory;
-
- @GuardedBy("mAppIdleLock")
- private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
- mPackageAccessListeners = new ArrayList<>();
-
- @GuardedBy("mAppIdleLock")
- private boolean mHaveCarrierPrivilegedApps;
- @GuardedBy("mAppIdleLock")
- private List<String> mCarrierPrivilegedApps;
+ AppStandbyController mAppStandby;
public UsageStatsService(Context context) {
super(context);
@@ -185,6 +123,8 @@ public class UsageStatsService extends SystemService implements
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mHandler = new H(BackgroundThread.get().getLooper());
+ mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
+
File systemDataDir = new File(Environment.getDataDirectory(), "system");
mUsageStatsDir = new File(systemDataDir, "usagestats");
mUsageStatsDir.mkdirs();
@@ -198,30 +138,9 @@ public class UsageStatsService extends SystemService implements
getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
null, mHandler);
- IntentFilter packageFilter = new IntentFilter();
- packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageFilter.addDataScheme("package");
-
- getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
- null, mHandler);
-
- mAppIdleEnabled = getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_enableAutoPowerModes);
- if (mAppIdleEnabled) {
- IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
- deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
- deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
- getContext().registerReceiver(new DeviceStateReceiver(), deviceStates);
- }
-
synchronized (mLock) {
cleanUpRemovedUsersLocked();
}
- synchronized (mAppIdleLock) {
- mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
- }
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
@@ -233,28 +152,10 @@ public class UsageStatsService extends SystemService implements
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
- // Observe changes to the threshold
- SettingsObserver settingsObserver = new SettingsObserver(mHandler);
- settingsObserver.registerObserver();
- settingsObserver.updateSettings();
+ mAppStandby.onBootPhase(phase);
- mAppWidgetManager = getContext().getSystemService(AppWidgetManager.class);
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- mBatteryStats = IBatteryStats.Stub.asInterface(
- ServiceManager.getService(BatteryStats.SERVICE_NAME));
- mDisplayManager = (DisplayManager) getContext().getSystemService(
- Context.DISPLAY_SERVICE);
- mPowerManager = getContext().getSystemService(PowerManager.class);
-
- mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
- synchronized (mAppIdleLock) {
- mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
- }
-
- if (mPendingOneTimeCheckIdleStates) {
- postOneTimeCheckIdleStates();
- }
if (ENABLE_KERNEL_UPDATES && KERNEL_COUNTER_FILE.exists()) {
try {
@@ -268,18 +169,9 @@ public class UsageStatsService extends SystemService implements
} else {
Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
}
-
- mSystemServicesReady = true;
- } else if (phase == PHASE_BOOT_COMPLETED) {
- setChargingState(getContext().getSystemService(BatteryManager.class).isCharging());
}
}
- private boolean isDisplayOn() {
- return mDisplayManager
- .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
- }
-
private class UserActionsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@@ -291,60 +183,12 @@ public class UsageStatsService extends SystemService implements
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
if (userId >=0) {
- postCheckIdleStates(userId);
+ mAppStandby.postCheckIdleStates(userId);
}
}
}
}
- private class PackageReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_PACKAGE_ADDED.equals(action)
- || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
- clearCarrierPrivilegedApps();
- }
- if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
- Intent.ACTION_PACKAGE_ADDED.equals(action))
- && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
- clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
- getSendingUserId());
- }
- }
- }
-
- private class DeviceStateReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
- setChargingState(intent.getIntExtra("plugged", 0) != 0);
- } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
- onDeviceIdleModeChanged();
- }
- }
- }
-
- private final DisplayManager.DisplayListener mDisplayListener
- = new DisplayManager.DisplayListener() {
-
- @Override public void onDisplayAdded(int displayId) {
- }
-
- @Override public void onDisplayRemoved(int displayId) {
- }
-
- @Override public void onDisplayChanged(int displayId) {
- if (displayId == Display.DEFAULT_DISPLAY) {
- final boolean displayOn = isDisplayOn();
- synchronized (UsageStatsService.this.mAppIdleLock) {
- mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
- }
- }
- }
- };
-
private final IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq) {
@@ -388,42 +232,18 @@ public class UsageStatsService extends SystemService implements
@Override
public void onStatsReloaded() {
- postOneTimeCheckIdleStates();
+ mAppStandby.postOneTimeCheckIdleStates();
}
@Override
public void onNewUpdate(int userId) {
- initializeDefaultsForSystemApps(userId);
- }
-
- private void initializeDefaultsForSystemApps(int userId) {
- Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
- PackageManager.MATCH_DISABLED_COMPONENTS,
- userId);
- final int packageCount = packages.size();
- synchronized (mAppIdleLock) {
- for (int i = 0; i < packageCount; i++) {
- final PackageInfo pi = packages.get(i);
- String packageName = pi.packageName;
- if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
- mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
- }
- }
- }
+ mAppStandby.initializeDefaultsForSystemApps(userId);
}
private boolean shouldObfuscateInstantAppsForCaller(int callingUid, int userId) {
return !mPackageManagerInternal.canAccessInstantApps(callingUid, userId);
}
- void clearAppIdleForPackage(String packageName, int userId) {
- synchronized (mAppIdleLock) {
- mAppIdleHistory.clearUsage(packageName, userId);
- }
- }
-
private void cleanUpRemovedUsersLocked() {
final List<UserInfo> users = mUserManager.getUsers(true);
if (users == null || users.size() == 0) {
@@ -451,195 +271,6 @@ public class UsageStatsService extends SystemService implements
}
}
- void setChargingState(boolean charging) {
- synchronized (mAppIdleLock) {
- if (mCharging != charging) {
- mCharging = charging;
- postParoleStateChanged();
- }
- }
- }
-
- /** Paroled here means temporary pardon from being inactive */
- void setAppIdleParoled(boolean paroled) {
- synchronized (mAppIdleLock) {
- final long now = System.currentTimeMillis();
- if (mAppIdleTempParoled != paroled) {
- mAppIdleTempParoled = paroled;
- if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
- if (paroled) {
- postParoleEndTimeout();
- } else {
- mLastAppIdleParoledTime = now;
- postNextParoleTimeout(now);
- }
- postParoleStateChanged();
- }
- }
- }
-
- boolean isParoledOrCharging() {
- synchronized (mAppIdleLock) {
- return mAppIdleTempParoled || mCharging;
- }
- }
-
- private void postNextParoleTimeout(long now) {
- if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
- mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
- // Compute when the next parole needs to happen. We check more frequently than necessary
- // since the message handler delays are based on elapsedRealTime and not wallclock time.
- // The comparison is done in wallclock time.
- long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
- if (timeLeft < 0) {
- timeLeft = 0;
- }
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
- }
-
- private void postParoleEndTimeout() {
- if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
- mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
- mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
- }
-
- private void postParoleStateChanged() {
- if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
- mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
- mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
- }
-
- void postCheckIdleStates(int userId) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
- }
-
- /**
- * We send a different message to check idle states once, otherwise we would end up
- * scheduling a series of repeating checkIdleStates each time we fired off one.
- */
- void postOneTimeCheckIdleStates() {
- if (mDeviceIdleController == null) {
- // Not booted yet; wait for it!
- mPendingOneTimeCheckIdleStates = true;
- } else {
- mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
- mPendingOneTimeCheckIdleStates = false;
- }
- }
-
- /**
- * Check all running users' or specified user's apps to see if they enter an idle state.
- * @return Returns whether checking should continue periodically.
- */
- boolean checkIdleStates(int checkUserId) {
- if (!mAppIdleEnabled) {
- return false;
- }
-
- final int[] runningUserIds;
- try {
- runningUserIds = ActivityManager.getService().getRunningUserIds();
- if (checkUserId != UserHandle.USER_ALL
- && !ArrayUtils.contains(runningUserIds, checkUserId)) {
- return false;
- }
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
-
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- for (int i = 0; i < runningUserIds.length; i++) {
- final int userId = runningUserIds[i];
- if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
- continue;
- }
- if (DEBUG) {
- Slog.d(TAG, "Checking idle state for user " + userId);
- }
- List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
- PackageManager.MATCH_DISABLED_COMPONENTS,
- userId);
- final int packageCount = packages.size();
- for (int p = 0; p < packageCount; p++) {
- final PackageInfo pi = packages.get(p);
- final String packageName = pi.packageName;
- final boolean isIdle = isAppIdleFiltered(packageName,
- UserHandle.getAppId(pi.applicationInfo.uid),
- userId, elapsedRealtime);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
- userId, isIdle ? 1 : 0, packageName));
- if (isIdle) {
- synchronized (mAppIdleLock) {
- mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
- }
- }
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "checkIdleStates took "
- + (SystemClock.elapsedRealtime() - elapsedRealtime));
- }
- return true;
- }
-
- /** Check if it's been a while since last parole and let idle apps do some work */
- void checkParoleTimeout() {
- boolean setParoled = false;
- synchronized (mAppIdleLock) {
- final long now = System.currentTimeMillis();
- if (!mAppIdleTempParoled) {
- final long timeSinceLastParole = now - mLastAppIdleParoledTime;
- if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
- if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
- setParoled = true;
- } else {
- if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
- postNextParoleTimeout(now);
- }
- }
- }
- if (setParoled) {
- setAppIdleParoled(true);
- }
- }
-
- private void notifyBatteryStats(String packageName, int userId, boolean idle) {
- try {
- final int uid = mPackageManager.getPackageUidAsUser(packageName,
- PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
- if (idle) {
- mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
- packageName, uid);
- } else {
- mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
- packageName, uid);
- }
- } catch (NameNotFoundException | RemoteException e) {
- }
- }
-
- void onDeviceIdleModeChanged() {
- final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
- if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
- boolean paroled = false;
- synchronized (mAppIdleLock) {
- final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
- if (!deviceIdle
- && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
- if (DEBUG) {
- Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
- }
- paroled = true;
- } else if (deviceIdle) {
- if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
- paroled = false;
- } else {
- return;
- }
- }
- setAppIdleParoled(paroled);
- }
-
private static void deleteRecursively(File f) {
File[] files = f.listFiles();
if (files != null) {
@@ -724,76 +355,7 @@ public class UsageStatsService extends SystemService implements
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
service.reportEvent(event);
- synchronized (mAppIdleLock) {
- // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
- // about apps that are on some kind of whitelist anyway.
- final boolean previouslyIdle = mAppIdleHistory.isIdle(
- event.mPackage, userId, elapsedRealtime);
- // Inform listeners if necessary
- if ((event.mEventType == Event.MOVE_TO_FOREGROUND
- || event.mEventType == Event.MOVE_TO_BACKGROUND
- || event.mEventType == Event.SYSTEM_INTERACTION
- || event.mEventType == Event.USER_INTERACTION)) {
- mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
- if (previouslyIdle) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
- /* idle = */ 0, event.mPackage));
- notifyBatteryStats(event.mPackage, userId, false);
- }
- }
- }
- }
- }
-
- void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
- // Get sync adapters for the authority
- String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
- authority, userId);
- for (String packageName: packages) {
- // Only force the sync adapters to active if the provider is not in the same package and
- // the sync adapter is a system package.
- try {
- PackageInfo pi = mPackageManager.getPackageInfoAsUser(
- packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
- if (pi == null || pi.applicationInfo == null) {
- continue;
- }
- if (!packageName.equals(providerPkgName)) {
- setAppIdleAsync(packageName, false, userId);
- }
- } catch (NameNotFoundException e) {
- // Shouldn't happen
- }
- }
- }
-
- /**
- * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
- * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
- * the threshold for idle.
- *
- * This method is always called from the handler thread, so not much synchronization is
- * required.
- */
- void forceIdleState(String packageName, int userId, boolean idle) {
- final int appId = getAppId(packageName);
- if (appId < 0) return;
- final long elapsedRealtime = SystemClock.elapsedRealtime();
-
- final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
- userId, elapsedRealtime);
- synchronized (mAppIdleLock) {
- mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
- }
- final boolean stillIdle = isAppIdleFiltered(packageName, appId,
- userId, elapsedRealtime);
- // Inform listeners if necessary
- if (previouslyIdle != stillIdle) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
- /* idle = */ stillIdle ? 1 : 0, packageName));
- if (!stillIdle) {
- notifyBatteryStats(packageName, userId, idle);
- }
+ mAppStandby.reportEvent(event, elapsedRealtime, userId);
}
}
@@ -813,9 +375,7 @@ public class UsageStatsService extends SystemService implements
synchronized (mLock) {
Slog.i(TAG, "Removing user " + userId + " and all data.");
mUserState.remove(userId);
- synchronized (mAppIdleLock) {
- mAppIdleHistory.onUserRemoved(userId);
- }
+ mAppStandby.onUserRemoved(userId);
cleanUpRemovedUsersLocked();
}
}
@@ -887,253 +447,6 @@ public class UsageStatsService extends SystemService implements
}
}
- private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
- synchronized (mAppIdleLock) {
- return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
- }
- }
-
- void addListener(AppIdleStateChangeListener listener) {
- synchronized (mAppIdleLock) {
- if (!mPackageAccessListeners.contains(listener)) {
- mPackageAccessListeners.add(listener);
- }
- }
- }
-
- void removeListener(AppIdleStateChangeListener listener) {
- synchronized (mAppIdleLock) {
- mPackageAccessListeners.remove(listener);
- }
- }
-
- int getAppId(String packageName) {
- try {
- ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
- PackageManager.MATCH_ANY_USER
- | PackageManager.MATCH_DISABLED_COMPONENTS);
- return ai.uid;
- } catch (NameNotFoundException re) {
- return -1;
- }
- }
-
- boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
- boolean shouldObfuscateInstantApps) {
- if (isParoledOrCharging()) {
- return false;
- }
- if (shouldObfuscateInstantApps &&
- mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
- return false;
- }
- return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
- }
-
- /**
- * Checks if an app has been idle for a while and filters out apps that are excluded.
- * It returns false if the current system state allows all apps to be considered active.
- * This happens if the device is plugged in or temporarily allowed to make exceptions.
- * Called by interface impls.
- */
- private boolean isAppIdleFiltered(String packageName, int appId, int userId,
- long elapsedRealtime) {
- if (packageName == null) return false;
- // If not enabled at all, of course nobody is ever idle.
- if (!mAppIdleEnabled) {
- return false;
- }
- if (appId < Process.FIRST_APPLICATION_UID) {
- // System uids never go idle.
- return false;
- }
- if (packageName.equals("android")) {
- // Nor does the framework (which should be redundant with the above, but for MR1 we will
- // retain this for safety).
- return false;
- }
- if (mSystemServicesReady) {
- try {
- // We allow all whitelisted apps, including those that don't want to be whitelisted
- // for idle mode, because app idle (aka app standby) is really not as big an issue
- // for controlling who participates vs. doze mode.
- if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
- return false;
- }
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
-
- if (isActiveDeviceAdmin(packageName, userId)) {
- return false;
- }
-
- if (isActiveNetworkScorer(packageName)) {
- return false;
- }
-
- if (mAppWidgetManager != null
- && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
- return false;
- }
-
- if (isDeviceProvisioningPackage(packageName)) {
- return false;
- }
- }
-
- if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
- return false;
- }
-
- // Check this last, as it is the most expensive check
- // TODO: Optimize this by fetching the carrier privileged apps ahead of time
- if (isCarrierApp(packageName)) {
- return false;
- }
-
- return true;
- }
-
- int[] getIdleUidsForUser(int userId) {
- if (!mAppIdleEnabled) {
- return new int[0];
- }
-
- final long elapsedRealtime = SystemClock.elapsedRealtime();
-
- List<ApplicationInfo> apps;
- try {
- ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
- .getInstalledApplications(/* flags= */ 0, userId);
- if (slice == null) {
- return new int[0];
- }
- apps = slice.getList();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
-
- // State of each uid. Key is the uid. Value lower 16 bits is the number of apps
- // associated with that uid, upper 16 bits is the number of those apps that is idle.
- SparseIntArray uidStates = new SparseIntArray();
-
- // Now resolve all app state. Iterating over all apps, keeping track of how many
- // we find for each uid and how many of those are idle.
- for (int i = apps.size() - 1; i >= 0; i--) {
- ApplicationInfo ai = apps.get(i);
-
- // Check whether this app is idle.
- boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
- userId, elapsedRealtime);
-
- int index = uidStates.indexOfKey(ai.uid);
- if (index < 0) {
- uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
- } else {
- int value = uidStates.valueAt(index);
- uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
- }
- int numIdle = 0;
- for (int i = uidStates.size() - 1; i >= 0; i--) {
- int value = uidStates.valueAt(i);
- if ((value&0x7fff) == (value>>16)) {
- numIdle++;
- }
- }
-
- int[] res = new int[numIdle];
- numIdle = 0;
- for (int i = uidStates.size() - 1; i >= 0; i--) {
- int value = uidStates.valueAt(i);
- if ((value&0x7fff) == (value>>16)) {
- res[numIdle] = uidStates.keyAt(i);
- numIdle++;
- }
- }
-
- return res;
- }
-
- void setAppIdleAsync(String packageName, boolean idle, int userId) {
- if (packageName == null) return;
-
- mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
- .sendToTarget();
- }
-
- private boolean isActiveDeviceAdmin(String packageName, int userId) {
- DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
- if (dpm == null) return false;
- return dpm.packageHasActiveAdmins(packageName, userId);
- }
-
- /**
- * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
- * returns {@code false}.
- */
- private boolean isDeviceProvisioningPackage(String packageName) {
- String deviceProvisioningPackage = getContext().getResources().getString(
- com.android.internal.R.string.config_deviceProvisioningPackage);
- return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
- }
-
- private boolean isCarrierApp(String packageName) {
- synchronized (mAppIdleLock) {
- if (!mHaveCarrierPrivilegedApps) {
- fetchCarrierPrivilegedAppsLA();
- }
- if (mCarrierPrivilegedApps != null) {
- return mCarrierPrivilegedApps.contains(packageName);
- }
- return false;
- }
- }
-
- void clearCarrierPrivilegedApps() {
- if (DEBUG) {
- Slog.i(TAG, "Clearing carrier privileged apps list");
- }
- synchronized (mAppIdleLock) {
- mHaveCarrierPrivilegedApps = false;
- mCarrierPrivilegedApps = null; // Need to be refetched.
- }
- }
-
- @GuardedBy("mAppIdleLock")
- private void fetchCarrierPrivilegedAppsLA() {
- TelephonyManager telephonyManager =
- getContext().getSystemService(TelephonyManager.class);
- mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
- mHaveCarrierPrivilegedApps = true;
- if (DEBUG) {
- Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
- }
- }
-
- private boolean isActiveNetworkScorer(String packageName) {
- NetworkScoreManager nsm = (NetworkScoreManager) getContext().getSystemService(
- Context.NETWORK_SCORE_SERVICE);
- return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
- }
-
- void informListeners(String packageName, int userId, boolean isIdle) {
- for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
- listener.onAppIdleStateChanged(packageName, userId, isIdle);
- }
- }
-
- void informParoleStateChanged() {
- final boolean paroled = isParoledOrCharging();
- for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
- listener.onParoleStateChanged(paroled);
- }
- }
-
private static boolean validRange(long currentTime, long beginTime, long endTime) {
return beginTime <= currentTime && beginTime < endTime;
}
@@ -1143,15 +456,10 @@ public class UsageStatsService extends SystemService implements
for (int i = 0; i < userCount; i++) {
UserUsageStatsService service = mUserState.valueAt(i);
service.persistActiveStats();
- synchronized (mAppIdleLock) {
- mAppIdleHistory.writeAppIdleTimes(mUserState.keyAt(i));
- }
- }
- // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
- // considered not-idle, which is the safest outcome in such an event.
- synchronized (mAppIdleLock) {
- mAppIdleHistory.writeAppIdleDurations();
+ mAppStandby.flushToDisk(mUserState.keyAt(i));
}
+ mAppStandby.flushDurationsToDisk();
+
mHandler.removeMessages(MSG_FLUSH_TO_DISK);
}
@@ -1166,7 +474,8 @@ public class UsageStatsService extends SystemService implements
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
- idpw.printPair("user", mUserState.keyAt(i));
+ int userId = mUserState.keyAt(i);
+ idpw.printPair("user", userId);
idpw.println();
idpw.increaseIndent();
if (argSet.contains("--checkin")) {
@@ -1176,57 +485,19 @@ public class UsageStatsService extends SystemService implements
idpw.println();
if (args.length > 0) {
if ("history".equals(args[0])) {
- synchronized (mAppIdleLock) {
- mAppIdleHistory.dumpHistory(idpw, mUserState.keyAt(i));
- }
+ mAppStandby.dumpHistory(idpw, userId);
} else if ("flush".equals(args[0])) {
- UsageStatsService.this.flushToDiskLocked();
+ flushToDiskLocked();
pw.println("Flushed stats to disk");
}
}
}
- synchronized (mAppIdleLock) {
- mAppIdleHistory.dump(idpw, mUserState.keyAt(i));
- }
+ mAppStandby.dumpUser(idpw, userId);
idpw.decreaseIndent();
}
pw.println();
- synchronized (mAppIdleLock) {
- pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
- + "): " + mCarrierPrivilegedApps);
- }
-
- pw.println();
- pw.println("Settings:");
-
- pw.print(" mAppIdleDurationMillis=");
- TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleWallclockThresholdMillis=");
- TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
- pw.println();
-
- pw.print(" mCheckIdleIntervalMillis=");
- TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleParoleIntervalMillis=");
- TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleParoleDurationMillis=");
- TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
- pw.println();
-
- pw.println();
- pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
- pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
- pw.print(" mCharging="); pw.print(mCharging);
- pw.print(" mLastAppIdleParoledTime=");
- TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
- pw.println();
+ mAppStandby.dumpState(args, pw);
}
}
@@ -1250,50 +521,6 @@ public class UsageStatsService extends SystemService implements
onUserRemoved(msg.arg1);
break;
- case MSG_INFORM_LISTENERS:
- informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
- break;
-
- case MSG_FORCE_IDLE_STATE:
- forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
- break;
-
- case MSG_CHECK_IDLE_STATES:
- if (checkIdleStates(msg.arg1)) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- MSG_CHECK_IDLE_STATES, msg.arg1, 0),
- mCheckIdleIntervalMillis);
- }
- break;
-
- case MSG_ONE_TIME_CHECK_IDLE_STATES:
- mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
- checkIdleStates(UserHandle.USER_ALL);
- break;
-
- case MSG_CHECK_PAROLE_TIMEOUT:
- checkParoleTimeout();
- break;
-
- case MSG_PAROLE_END_TIMEOUT:
- if (DEBUG) Slog.d(TAG, "Ending parole");
- setAppIdleParoled(false);
- break;
-
- case MSG_REPORT_CONTENT_PROVIDER_USAGE:
- SomeArgs args = (SomeArgs) msg.obj;
- reportContentProviderUsage((String) args.arg1, // authority name
- (String) args.arg2, // package name
- (int) args.arg3); // userId
- args.recycle();
- break;
-
- case MSG_PAROLE_STATE_CHANGED:
- if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
- + ", Charging state:" + mCharging);
- informParoleStateChanged();
- break;
-
default:
super.handleMessage(msg);
break;
@@ -1301,72 +528,6 @@ public class UsageStatsService extends SystemService implements
}
}
- /**
- * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
- */
- private class SettingsObserver extends ContentObserver {
- /**
- * This flag has been used to disable app idle on older builds with bug b/26355386.
- */
- @Deprecated
- private static final String KEY_IDLE_DURATION_OLD = "idle_duration";
-
- private static final String KEY_IDLE_DURATION = "idle_duration2";
- private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
- private static final String KEY_PAROLE_INTERVAL = "parole_interval";
- private static final String KEY_PAROLE_DURATION = "parole_duration";
-
- private final KeyValueListParser mParser = new KeyValueListParser(',');
-
- SettingsObserver(Handler handler) {
- super(handler);
- }
-
- void registerObserver() {
- getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.APP_IDLE_CONSTANTS), false, this);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- updateSettings();
- postOneTimeCheckIdleStates();
- }
-
- void updateSettings() {
- synchronized (mAppIdleLock) {
- // Look at global settings for this.
- // TODO: Maybe apply different thresholds for different users.
- try {
- mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS));
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
- // fallthrough, mParser is empty and all defaults will be returned.
- }
-
- // Default: 12 hours of screen-on time sans dream-time
- mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
- COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
-
- mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
- COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
-
- mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
- COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
-
- // Default: 24 hours between paroles
- mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
- COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
-
- mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
- COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
- mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
- mAppIdleScreenThresholdMillis);
- }
- }
- }
-
private final class BinderService extends IUsageStatsManager.Stub {
private boolean hasPermission(String callingPackage) {
@@ -1462,7 +623,8 @@ public class UsageStatsService extends SystemService implements
Binder.getCallingUid(), userId);
final long token = Binder.clearCallingIdentity();
try {
- return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId,
+ return mAppStandby.isAppIdleFilteredOrParoled(
+ packageName, userId,
SystemClock.elapsedRealtime(), obfuscateInstantApps);
} finally {
Binder.restoreCallingIdentity(token);
@@ -1483,9 +645,9 @@ public class UsageStatsService extends SystemService implements
"No permission to change app idle state");
final long token = Binder.clearCallingIdentity();
try {
- final int appId = getAppId(packageName);
+ final int appId = mAppStandby.getAppId(packageName);
if (appId < 0) return;
- UsageStatsService.this.setAppIdleAsync(packageName, idle, userId);
+ mAppStandby.setAppIdleAsync(packageName, idle, userId);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1509,7 +671,7 @@ public class UsageStatsService extends SystemService implements
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_CARRIER_SERVICES,
"onCarrierPrivilegedAppsChanged can only be called by privileged apps.");
- UsageStatsService.this.clearCarrierPrivilegedApps();
+ mAppStandby.clearCarrierPrivilegedApps();
}
@Override
@@ -1624,28 +786,23 @@ public class UsageStatsService extends SystemService implements
@Override
public void reportContentProviderUsage(String name, String packageName, int userId) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = name;
- args.arg2 = packageName;
- args.arg3 = userId;
- mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
- .sendToTarget();
+ mAppStandby.postReportContentProviderUsage(name, packageName, userId);
}
@Override
public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
- return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId,
- SystemClock.elapsedRealtime());
+ return mAppStandby.isAppIdleFiltered(packageName, uidForAppId,
+ userId, SystemClock.elapsedRealtime());
}
@Override
public int[] getIdleUidsForUser(int userId) {
- return UsageStatsService.this.getIdleUidsForUser(userId);
+ return mAppStandby.getIdleUidsForUser(userId);
}
@Override
public boolean isAppIdleParoleOn() {
- return isParoledOrCharging();
+ return mAppStandby.isParoledOrCharging();
}
@Override
@@ -1658,20 +815,20 @@ public class UsageStatsService extends SystemService implements
@Override
public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
- UsageStatsService.this.addListener(listener);
+ mAppStandby.addListener(listener);
listener.onParoleStateChanged(isAppIdleParoleOn());
}
@Override
public void removeAppIdleStateChangeListener(
AppIdleStateChangeListener listener) {
- UsageStatsService.this.removeListener(listener);
+ mAppStandby.removeListener(listener);
}
@Override
public byte[] getBackupPayload(int user, String key) {
// Check to ensure that only user 0's data is b/r for now
- synchronized (UsageStatsService.this.mLock) {
+ synchronized (mLock) {
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats =
getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
@@ -1684,7 +841,7 @@ public class UsageStatsService extends SystemService implements
@Override
public void applyRestoredPayload(int user, String key, byte[] payload) {
- synchronized (UsageStatsService.this.mLock) {
+ synchronized (mLock) {
if (user == UserHandle.USER_SYSTEM) {
final UserUsageStatsService userStats =
getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java
index 68c1d5f6..acc27bee 100644
--- a/com/android/server/usb/UsbAlsaManager.java
+++ b/com/android/server/usb/UsbAlsaManager.java
@@ -314,7 +314,11 @@ public final class UsbAlsaManager {
return null;
}
- mDevicesParser.scan();
+ if (!mDevicesParser.scan()) {
+ Slog.e(TAG, "Error parsing ALSA devices file.");
+ return null;
+ }
+
int device = mDevicesParser.getDefaultDeviceNum(card);
boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
diff --git a/com/android/server/utils/ManagedApplicationService.java b/com/android/server/utils/ManagedApplicationService.java
index 0f251fd8..c5553881 100644
--- a/com/android/server/utils/ManagedApplicationService.java
+++ b/com/android/server/utils/ManagedApplicationService.java
@@ -16,19 +16,24 @@
package com.android.server.utils;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInterface;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
+import java.text.SimpleDateFormat;
import java.util.Objects;
+import java.util.Date;
/**
* Manages the lifecycle of an application-provided service bound from system server.
@@ -38,39 +43,126 @@ import java.util.Objects;
public class ManagedApplicationService {
private final String TAG = getClass().getSimpleName();
+ /**
+ * Attempt to reconnect service forever if an onBindingDied or onServiceDisconnected event
+ * is received.
+ */
+ public static final int RETRY_FOREVER = 1;
+
+ /**
+ * Never attempt to reconnect the service - a single onBindingDied or onServiceDisconnected
+ * event will cause this to fully unbind the service and never attempt to reconnect.
+ */
+ public static final int RETRY_NEVER = 2;
+
+ /**
+ * Attempt to reconnect the service until the maximum number of retries is reached, then stop.
+ *
+ * The first retry will occur MIN_RETRY_DURATION_MS after the disconnection, and each
+ * subsequent retry will occur after 2x the duration used for the previous retry up to the
+ * MAX_RETRY_DURATION_MS duration.
+ *
+ * In this case, retries mean a full unbindService/bindService pair to handle cases when the
+ * usual service re-connection logic in ActiveServices has very high backoff times or when the
+ * serviceconnection has fully died due to a package update or similar.
+ */
+ public static final int RETRY_BEST_EFFORT = 3;
+
+ // Maximum number of retries before giving up (for RETRY_BEST_EFFORT).
+ private static final int MAX_RETRY_COUNT = 4;
+ // Max time between retry attempts.
+ private static final long MAX_RETRY_DURATION_MS = 16000;
+ // Min time between retry attempts.
+ private static final long MIN_RETRY_DURATION_MS = 2000;
+ // Time since the last retry attempt after which to clear the retry attempt counter.
+ private static final long RETRY_RESET_TIME_MS = MAX_RETRY_DURATION_MS * 4;
+
private final Context mContext;
private final int mUserId;
private final ComponentName mComponent;
private final int mClientLabel;
private final String mSettingsAction;
private final BinderChecker mChecker;
-
- private final DeathRecipient mDeathRecipient = new DeathRecipient() {
- @Override
- public void binderDied() {
- synchronized (mLock) {
- mBoundInterface = null;
- }
- }
- };
+ private final boolean mIsImportant;
+ private final int mRetryType;
+ private final Handler mHandler;
+ private final Runnable mRetryRunnable = this::doRetry;
+ private final EventCallback mEventCb;
private final Object mLock = new Object();
// State protected by mLock
- private ServiceConnection mPendingConnection;
private ServiceConnection mConnection;
private IInterface mBoundInterface;
private PendingEvent mPendingEvent;
+ private int mRetryCount;
+ private long mLastRetryTimeMs;
+ private long mNextRetryDurationMs = MIN_RETRY_DURATION_MS;
+ private boolean mRetrying;
+
+ public static interface LogFormattable {
+ String toLogString(SimpleDateFormat dateFormat);
+ }
+
+ /**
+ * Lifecycle event of this managed service.
+ */
+ public static class LogEvent implements LogFormattable {
+ public static final int EVENT_CONNECTED = 1;
+ public static final int EVENT_DISCONNECTED = 2;
+ public static final int EVENT_BINDING_DIED = 3;
+ public static final int EVENT_STOPPED_PERMANENTLY = 4;
+
+ // Time of the events in "current time ms" timebase.
+ public final long timestamp;
+ // Name of the component for this system service.
+ public final ComponentName component;
+ // ID of the event that occurred.
+ public final int event;
+
+ public LogEvent(long timestamp, ComponentName component, int event) {
+ this.timestamp = timestamp;
+ this.component = component;
+ this.event = event;
+ }
+
+ @Override
+ public String toLogString(SimpleDateFormat dateFormat) {
+ return dateFormat.format(new Date(timestamp)) + " " + eventToString(event)
+ + " Managed Service: "
+ + ((component == null) ? "None" : component.flattenToString());
+ }
+
+ public static String eventToString(int event) {
+ switch (event) {
+ case EVENT_CONNECTED:
+ return "Connected";
+ case EVENT_DISCONNECTED:
+ return "Disconnected";
+ case EVENT_BINDING_DIED:
+ return "Binding Died For";
+ case EVENT_STOPPED_PERMANENTLY:
+ return "Permanently Stopped";
+ default:
+ return "Unknown Event Occurred";
+ }
+ }
+ }
private ManagedApplicationService(final Context context, final ComponentName component,
final int userId, int clientLabel, String settingsAction,
- BinderChecker binderChecker) {
+ BinderChecker binderChecker, boolean isImportant, int retryType, Handler handler,
+ EventCallback eventCallback) {
mContext = context;
mComponent = component;
mUserId = userId;
mClientLabel = clientLabel;
mSettingsAction = settingsAction;
mChecker = binderChecker;
+ mIsImportant = isImportant;
+ mRetryType = retryType;
+ mHandler = handler;
+ mEventCb = eventCallback;
}
/**
@@ -85,7 +177,17 @@ public class ManagedApplicationService {
* Implement to call IInterface methods after service is connected.
*/
public interface PendingEvent {
- void runEvent(IInterface service) throws RemoteException;
+ void runEvent(IInterface service) throws RemoteException;
+ }
+
+ /**
+ * Implement to be notified about any problems with remote service.
+ */
+ public interface EventCallback {
+ /**
+ * Called when an sevice lifecycle event occurs.
+ */
+ void onServiceEvent(LogEvent event);
}
/**
@@ -95,19 +197,28 @@ public class ManagedApplicationService {
* @param component the {@link ComponentName} of the application service to bind.
* @param userId the user ID of user to bind the application service as.
* @param clientLabel the resource ID of a label displayed to the user indicating the
- * binding service.
+ * binding service, or 0 if none is desired.
* @param settingsAction an action that can be used to open the Settings UI to enable/disable
- * binding to these services.
- * @param binderChecker an interface used to validate the returned binder object.
+ * binding to these services, or null if none is desired.
+ * @param binderChecker an interface used to validate the returned binder object, or null if
+ * this interface is unchecked.
+ * @param isImportant bind the user service with BIND_IMPORTANT.
+ * @param retryType reconnect behavior to have when bound service is disconnected.
+ * @param handler the Handler to use for retries and delivering EventCallbacks.
+ * @param eventCallback a callback used to deliver disconnection events, or null if you
+ * don't care.
* @return a ManagedApplicationService instance.
*/
public static ManagedApplicationService build(@NonNull final Context context,
- @NonNull final ComponentName component, final int userId, @NonNull int clientLabel,
- @NonNull String settingsAction, @NonNull BinderChecker binderChecker) {
+ @NonNull final ComponentName component, final int userId, int clientLabel,
+ @Nullable String settingsAction, @Nullable BinderChecker binderChecker,
+ boolean isImportant, int retryType, @NonNull Handler handler,
+ @Nullable EventCallback eventCallback) {
return new ManagedApplicationService(context, component, userId, clientLabel,
- settingsAction, binderChecker);
+ settingsAction, binderChecker, isImportant, retryType, handler, eventCallback);
}
+
/**
* @return the user ID of the user that owns the bound service.
*/
@@ -138,13 +249,12 @@ public class ManagedApplicationService {
return true;
}
-
- /**
- * Send an event to run as soon as the binder interface is available.
- *
- * @param event a {@link PendingEvent} to send.
- */
- public void sendEvent(@NonNull PendingEvent event) {
+ /**
+ * Send an event to run as soon as the binder interface is available.
+ *
+ * @param event a {@link PendingEvent} to send.
+ */
+ public void sendEvent(@NonNull PendingEvent event) {
IInterface iface;
synchronized (mLock) {
iface = mBoundInterface;
@@ -167,15 +277,13 @@ public class ManagedApplicationService {
*/
public void disconnect() {
synchronized (mLock) {
- // Wipe out pending connections
- mPendingConnection = null;
-
// Unbind existing connection, if it exists
- if (mConnection != null) {
- mContext.unbindService(mConnection);
- mConnection = null;
+ if (mConnection == null) {
+ return;
}
+ mContext.unbindService(mConnection);
+ mConnection = null;
mBoundInterface = null;
}
}
@@ -185,48 +293,70 @@ public class ManagedApplicationService {
*/
public void connect() {
synchronized (mLock) {
- if (mConnection != null || mPendingConnection != null) {
+ if (mConnection != null) {
// We're already connected or are trying to connect
return;
}
- final PendingIntent pendingIntent = PendingIntent.getActivity(
- mContext, 0, new Intent(mSettingsAction), 0);
- final Intent intent = new Intent().setComponent(mComponent).
- putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel).
- putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+ Intent intent = new Intent().setComponent(mComponent);
+ if (mClientLabel != 0) {
+ intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel);
+ }
+ if (mSettingsAction != null) {
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT,
+ PendingIntent.getActivity(mContext, 0, new Intent(mSettingsAction), 0));
+ }
+
+ mConnection = new ServiceConnection() {
+ @Override
+ public void onBindingDied(ComponentName componentName) {
+ final long timestamp = System.currentTimeMillis();
+ Slog.w(TAG, "Service binding died: " + componentName);
+ synchronized (mLock) {
+ if (mConnection != this) {
+ return;
+ }
+ mHandler.post(() -> {
+ mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+ LogEvent.EVENT_BINDING_DIED));
+ });
+
+ mBoundInterface = null;
+ startRetriesLocked();
+ }
+ }
- final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ final long timestamp = System.currentTimeMillis();
+ Slog.i(TAG, "Service connected: " + componentName);
IInterface iface = null;
PendingEvent pendingEvent = null;
synchronized (mLock) {
- if (mPendingConnection == this) {
- // No longer pending, remove from pending connection
- mPendingConnection = null;
- mConnection = this;
- } else {
- // Service connection wasn't pending, must have been disconnected
- mContext.unbindService(this);
+ if (mConnection != this) {
+ // Must've been unbound.
return;
}
+ mHandler.post(() -> {
+ mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+ LogEvent.EVENT_CONNECTED));
+ });
- try {
- iBinder.linkToDeath(mDeathRecipient, 0);
+ stopRetriesLocked();
+
+ mBoundInterface = null;
+ if (mChecker != null) {
mBoundInterface = mChecker.asInterface(iBinder);
if (!mChecker.checkType(mBoundInterface)) {
- // Received an invalid binder, disconnect
- mContext.unbindService(this);
+ // Received an invalid binder, disconnect.
mBoundInterface = null;
+ Slog.w(TAG, "Invalid binder from " + componentName);
+ startRetriesLocked();
+ return;
}
iface = mBoundInterface;
pendingEvent = mPendingEvent;
mPendingEvent = null;
- } catch (RemoteException e) {
- // DOA
- Slog.w(TAG, "Unable to bind service: " + intent, e);
- mBoundInterface = null;
}
}
if (iface != null && pendingEvent != null) {
@@ -234,28 +364,44 @@ public class ManagedApplicationService {
pendingEvent.runEvent(iface);
} catch (RuntimeException | RemoteException ex) {
Slog.e(TAG, "Received exception from user service: ", ex);
+ startRetriesLocked();
}
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
- Slog.w(TAG, "Service disconnected: " + intent);
- mConnection = null;
- mBoundInterface = null;
+ final long timestamp = System.currentTimeMillis();
+ Slog.w(TAG, "Service disconnected: " + componentName);
+ synchronized (mLock) {
+ if (mConnection != this) {
+ return;
+ }
+
+ mHandler.post(() -> {
+ mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+ LogEvent.EVENT_DISCONNECTED));
+ });
+
+ mBoundInterface = null;
+ startRetriesLocked();
+ }
}
};
- mPendingConnection = serviceConnection;
-
+ int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+ if (mIsImportant) {
+ flags |= Context.BIND_IMPORTANT;
+ }
try {
- if (!mContext.bindServiceAsUser(intent, serviceConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ if (!mContext.bindServiceAsUser(intent, mConnection, flags,
new UserHandle(mUserId))) {
Slog.w(TAG, "Unable to bind service: " + intent);
+ startRetriesLocked();
}
} catch (SecurityException e) {
Slog.w(TAG, "Unable to bind service: " + intent, e);
+ startRetriesLocked();
}
}
}
@@ -263,4 +409,81 @@ public class ManagedApplicationService {
private boolean matches(final ComponentName component, final int userId) {
return Objects.equals(mComponent, component) && mUserId == userId;
}
+
+ private void startRetriesLocked() {
+ if (checkAndDeliverServiceDiedCbLocked()) {
+ // If we delivered the service callback, disconnect and stop retrying.
+ disconnect();
+ return;
+ }
+
+ if (mRetrying) {
+ // Retry already queued, don't queue a new one.
+ return;
+ }
+ mRetrying = true;
+ queueRetryLocked();
+ }
+
+ private void stopRetriesLocked() {
+ mRetrying = false;
+ mHandler.removeCallbacks(mRetryRunnable);
+ }
+
+ private void queueRetryLocked() {
+ long now = SystemClock.uptimeMillis();
+ if ((now - mLastRetryTimeMs) > RETRY_RESET_TIME_MS) {
+ // It's been longer than the reset time since we last had to retry. Re-initialize.
+ mNextRetryDurationMs = MIN_RETRY_DURATION_MS;
+ mRetryCount = 0;
+ }
+ mLastRetryTimeMs = now;
+ mHandler.postDelayed(mRetryRunnable, mNextRetryDurationMs);
+ mNextRetryDurationMs = Math.min(2 * mNextRetryDurationMs, MAX_RETRY_DURATION_MS);
+ mRetryCount++;
+ }
+
+ private boolean checkAndDeliverServiceDiedCbLocked() {
+
+ if (mRetryType == RETRY_NEVER || (mRetryType == RETRY_BEST_EFFORT
+ && mRetryCount >= MAX_RETRY_COUNT)) {
+ // If we never retry, or we've exhausted our retries, post the onServiceDied callback.
+ Slog.e(TAG, "Service " + mComponent + " has died too much, not retrying.");
+ if (mEventCb != null) {
+ final long timestamp = System.currentTimeMillis();
+ mHandler.post(() -> {
+ mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+ LogEvent.EVENT_STOPPED_PERMANENTLY));
+ });
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void doRetry() {
+ synchronized (mLock) {
+ if (mConnection == null) {
+ // We disconnected for good. Don't attempt to retry.
+ return;
+ }
+ if (!mRetrying) {
+ // We successfully connected. Don't attempt to retry.
+ return;
+ }
+ Slog.i(TAG, "Attempting to reconnect " + mComponent + "...");
+ // While frameworks may restart the remote Service if we stay bound, we have little
+ // control of the backoff timing for reconnecting the service. In the event of a
+ // process crash, the backoff time can be very large (1-30 min), which is not
+ // acceptable for the types of services this is used for. Instead force an unbind/bind
+ // sequence to cause a more immediate retry.
+ disconnect();
+ if (checkAndDeliverServiceDiedCbLocked()) {
+ // No more retries.
+ return;
+ }
+ queueRetryLocked();
+ connect();
+ }
+ }
}
diff --git a/com/android/server/utils/PriorityDump.java b/com/android/server/utils/PriorityDump.java
index c05cc3ff..054f1564 100644
--- a/com/android/server/utils/PriorityDump.java
+++ b/com/android/server/utils/PriorityDump.java
@@ -59,10 +59,10 @@ public class SpringfieldNuclearPowerPlant extends Binder {
Donuts in the box: 1
Nuclear reactor status: DANGER - MELTDOWN IMMINENT
- $ adb shell dumpsys snpp --dump_priority CRITICAL
+ $ adb shell dumpsys snpp --dump-priority CRITICAL
Donuts in the box: 1
- $ adb shell dumpsys snpp --dump_priority NORMAL
+ $ adb shell dumpsys snpp --dump-priority NORMAL
Nuclear reactor status: DANGER - MELTDOWN IMMINENT
* </code></pre>
@@ -84,7 +84,7 @@ public class SpringfieldNuclearPowerPlant extends Binder {
*/
public final class PriorityDump {
- public static final String PRIORITY_ARG = "--dump_priority";
+ public static final String PRIORITY_ARG = "--dump-priority";
private PriorityDump() {
throw new UnsupportedOperationException();
@@ -92,12 +92,12 @@ public final class PriorityDump {
/**
* Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is
- * {@code --dump_priority}, stripping the priority and its type.
+ * {@code --dump-priority}, stripping the priority and its type.
* <p>
- * For example, if called as {@code --dump_priority HIGH arg1 arg2 arg3}, it will call
+ * For example, if called as {@code --dump-priority HIGH arg1 arg2 arg3}, it will call
* <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) </code>
* <p>
- * If the {@code --dump_priority} is not set, it calls
+ * If the {@code --dump-priority} is not set, it calls
* {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole
* {@code args} instead.
*/
@@ -124,7 +124,7 @@ public final class PriorityDump {
}
/**
- * Gets an array without the {@code --dump_priority PRIORITY} prefix.
+ * Gets an array without the {@code --dump-priority PRIORITY} prefix.
*/
private static String[] getStrippedArgs(String[] args) {
final String[] stripped = new String[args.length - 2];
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 3788cf33..b040a632 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -20,6 +20,7 @@ import static android.app.ActivityManager.START_ASSISTANT_HIDDEN_SESSION;
import static android.app.ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION;
import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
@@ -222,8 +223,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
intent = new Intent(intent);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(StackId.ASSISTANT_STACK_ID);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT);
return mAm.startAssistantActivity(mComponent.getPackageName(), callingPid, callingUid,
intent, resolvedType, options.toBundle(), mUser);
} catch (RemoteException e) {
diff --git a/com/android/server/vr/Vr2dDisplay.java b/com/android/server/vr/Vr2dDisplay.java
index 8f50a39a..95d03d4b 100644
--- a/com/android/server/vr/Vr2dDisplay.java
+++ b/com/android/server/vr/Vr2dDisplay.java
@@ -294,6 +294,9 @@ class Vr2dDisplay {
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi,
null /* surface */, flags, null /* callback */, null /* handler */,
diff --git a/com/android/server/vr/VrManagerInternal.java b/com/android/server/vr/VrManagerInternal.java
index bdd9de01..7b1e12e2 100644
--- a/com/android/server/vr/VrManagerInternal.java
+++ b/com/android/server/vr/VrManagerInternal.java
@@ -74,6 +74,13 @@ public abstract class VrManagerInternal {
public abstract void onScreenStateChanged(boolean isScreenOn);
/**
+ * Set whether the keyguard is currently active/showing.
+ *
+ * @param isShowing is {@code true} if the keyguard is active/showing.
+ */
+ public abstract void onKeyguardStateChanged(boolean isShowing);
+
+ /**
* Return NO_ERROR if the given package is installed on the device and enabled as a
* VrListenerService for the given current user, or a negative error code indicating a failure.
*
diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java
index 1f0b2f00..e7e4efcc 100644
--- a/com/android/server/vr/VrManagerService.java
+++ b/com/android/server/vr/VrManagerService.java
@@ -66,6 +66,8 @@ import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.SystemService;
import com.android.server.utils.ManagedApplicationService.PendingEvent;
+import com.android.server.utils.ManagedApplicationService.LogEvent;
+import com.android.server.utils.ManagedApplicationService.LogFormattable;
import com.android.server.vr.EnabledComponentsObserver.EnabledComponentChangeListener;
import com.android.server.utils.ManagedApplicationService;
import com.android.server.utils.ManagedApplicationService.BinderChecker;
@@ -108,16 +110,18 @@ public class VrManagerService extends SystemService implements EnabledComponentC
static final boolean DBG = false;
private static final int PENDING_STATE_DELAY_MS = 300;
- private static final int EVENT_LOG_SIZE = 32;
+ private static final int EVENT_LOG_SIZE = 64;
private static final int INVALID_APPOPS_MODE = -1;
/** Null set of sleep sleep flags. */
private static final int FLAG_NONE = 0;
/** Flag set when the device is not sleeping. */
- private static final int FLAG_AWAKE = 1;
+ private static final int FLAG_AWAKE = 1 << 0;
/** Flag set when the screen has been turned on. */
- private static final int FLAG_SCREEN_ON = 2;
+ private static final int FLAG_SCREEN_ON = 1 << 1;
+ /** Flag set when the keyguard is not active. */
+ private static final int FLAG_KEYGUARD_UNLOCKED = 1 << 2;
/** Flag indicating that all system sleep flags have been set.*/
- private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON;
+ private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON | FLAG_KEYGUARD_UNLOCKED;
private static native void initializeNative();
private static native void setVrModeNative(boolean enabled);
@@ -134,6 +138,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC
private int mVrAppProcessId;
private EnabledComponentsObserver mComponentObserver;
private ManagedApplicationService mCurrentVrService;
+ private ManagedApplicationService mCurrentVrCompositorService;
private ComponentName mDefaultVrService;
private Context mContext;
private ComponentName mCurrentVrModeComponent;
@@ -147,19 +152,45 @@ public class VrManagerService extends SystemService implements EnabledComponentC
private int mPreviousCoarseLocationMode = INVALID_APPOPS_MODE;
private int mPreviousManageOverlayMode = INVALID_APPOPS_MODE;
private VrState mPendingState;
- private final ArrayDeque<VrState> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
+ private boolean mLogLimitHit;
+ private final ArrayDeque<LogFormattable> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager();
private INotificationManager mNotificationManager;
/** Tracks the state of the screen and keyguard UI.*/
- private int mSystemSleepFlags = FLAG_AWAKE;
+ private int mSystemSleepFlags = FLAG_AWAKE | FLAG_KEYGUARD_UNLOCKED;
/**
* Set when ACTION_USER_UNLOCKED is fired. We shouldn't try to bind to the
- * vr service before then.
+ * vr service before then. This gets set only once the first time the user unlocks the device
+ * and stays true thereafter.
*/
private boolean mUserUnlocked;
private Vr2dDisplay mVr2dDisplay;
private boolean mBootsToVr;
+ // Handles events from the managed services (e.g. VrListenerService and any bound VR compositor
+ // service).
+ private final ManagedApplicationService.EventCallback mEventCallback
+ = new ManagedApplicationService.EventCallback() {
+ @Override
+ public void onServiceEvent(LogEvent event) {
+ logEvent(event);
+
+ ComponentName component = null;
+ synchronized (mLock) {
+ component = ((mCurrentVrService == null) ? null : mCurrentVrService.getComponent());
+ }
+
+ // If not on an AIO device and we permanently stopped trying to connect to the
+ // VrListenerService (or don't have one bound), leave persistent VR mode and VR mode.
+ if (!mBootsToVr && event.event == LogEvent.EVENT_STOPPED_PERMANENTLY &&
+ (component == null || component.equals(event.component))) {
+ Slog.e(TAG, "VrListenerSevice has died permanently, leaving system VR mode.");
+ // We're not a native VR device. Leave VR + persistent mode.
+ setPersistentVrModeEnabled(false);
+ }
+ }
+ };
+
private static final int MSG_VR_STATE_CHANGE = 0;
private static final int MSG_PENDING_VR_STATE_CHANGE = 1;
private static final int MSG_PERSISTENT_VR_MODE_STATE_CHANGE = 2;
@@ -180,7 +211,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC
if (mBootsToVr) {
setPersistentVrModeEnabled(true);
}
- consumeAndApplyPendingStateLocked();
if (mBootsToVr && !mVrModeEnabled) {
setVrMode(true, mDefaultVrService, 0, -1, null);
}
@@ -202,29 +232,40 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
private void setSleepState(boolean isAsleep) {
- synchronized(mLock) {
+ setSystemState(FLAG_AWAKE, !isAsleep);
+ }
- if (!isAsleep) {
- mSystemSleepFlags |= FLAG_AWAKE;
- } else {
- mSystemSleepFlags &= ~FLAG_AWAKE;
- }
+ private void setScreenOn(boolean isScreenOn) {
+ setSystemState(FLAG_SCREEN_ON, isScreenOn);
+ }
- updateVrModeAllowedLocked();
- }
+ private void setKeyguardShowing(boolean isShowing) {
+ setSystemState(FLAG_KEYGUARD_UNLOCKED, !isShowing);
}
- private void setScreenOn(boolean isScreenOn) {
+ private void setSystemState(int flags, boolean isOn) {
synchronized(mLock) {
- if (isScreenOn) {
- mSystemSleepFlags |= FLAG_SCREEN_ON;
+ int oldState = mSystemSleepFlags;
+ if (isOn) {
+ mSystemSleepFlags |= flags;
} else {
- mSystemSleepFlags &= ~FLAG_SCREEN_ON;
+ mSystemSleepFlags &= ~flags;
+ }
+ if (oldState != mSystemSleepFlags) {
+ if (DBG) Slog.d(TAG, "System state: " + getStateAsString());
+ updateVrModeAllowedLocked();
}
- updateVrModeAllowedLocked();
}
}
+ private String getStateAsString() {
+ return new StringBuilder()
+ .append((mSystemSleepFlags & FLAG_AWAKE) != 0 ? "awake, " : "")
+ .append((mSystemSleepFlags & FLAG_SCREEN_ON) != 0 ? "screen_on, " : "")
+ .append((mSystemSleepFlags & FLAG_KEYGUARD_UNLOCKED) != 0 ? "keyguard_off" : "")
+ .toString();
+ }
+
private void setUserUnlocked() {
synchronized(mLock) {
mUserUnlocked = true;
@@ -276,7 +317,24 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
};
- private static class VrState {
+ // Event used to log when settings are changed for dumpsys logs.
+ private static class SettingEvent implements LogFormattable {
+ public final long timestamp;
+ public final String what;
+
+ SettingEvent(String what) {
+ this.timestamp = System.currentTimeMillis();
+ this.what = what;
+ }
+
+ @Override
+ public String toLogString(SimpleDateFormat dateFormat) {
+ return dateFormat.format(new Date(timestamp)) + " " + what;
+ }
+ }
+
+ // Event used to track changes of the primary on-screen VR activity.
+ private static class VrState implements LogFormattable {
final boolean enabled;
final boolean running2dInVr;
final int userId;
@@ -286,7 +344,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC
final long timestamp;
final boolean defaultPermissionsGranted;
-
VrState(boolean enabled, boolean running2dInVr, ComponentName targetPackageName, int userId,
int processId, ComponentName callingPackage) {
this.enabled = enabled;
@@ -310,6 +367,39 @@ public class VrManagerService extends SystemService implements EnabledComponentC
this.defaultPermissionsGranted = defaultPermissionsGranted;
this.timestamp = System.currentTimeMillis();
}
+
+ @Override
+ public String toLogString(SimpleDateFormat dateFormat) {
+ String tab = " ";
+ String newLine = "\n";
+ StringBuilder sb = new StringBuilder(dateFormat.format(new Date(timestamp)));
+ sb.append(tab);
+ sb.append("State changed to:");
+ sb.append(tab);
+ sb.append((enabled) ? "ENABLED" : "DISABLED");
+ sb.append(newLine);
+ if (enabled) {
+ sb.append(tab);
+ sb.append("User=");
+ sb.append(userId);
+ sb.append(newLine);
+ sb.append(tab);
+ sb.append("Current VR Activity=");
+ sb.append((callingPackage == null) ? "None" : callingPackage.flattenToString());
+ sb.append(newLine);
+ sb.append(tab);
+ sb.append("Bound VrListenerService=");
+ sb.append((targetPackageName == null) ? "None"
+ : targetPackageName.flattenToString());
+ sb.append(newLine);
+ if (defaultPermissionsGranted) {
+ sb.append(tab);
+ sb.append("Default permissions granted to the bound VrListenerService.");
+ sb.append(newLine);
+ }
+ }
+ return sb.toString();
+ }
}
private static final BinderChecker sBinderChecker = new BinderChecker() {
@@ -490,6 +580,13 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
@Override
+ public void setAndBindCompositor(String componentName) {
+ enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
+ VrManagerService.this.setAndBindCompositor(
+ (componentName == null) ? null : ComponentName.unflattenFromString(componentName));
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -497,6 +594,12 @@ public class VrManagerService extends SystemService implements EnabledComponentC
pw.println("VR mode is currently: " + ((mVrModeAllowed) ? "allowed" : "disallowed"));
pw.println("Persistent VR mode is currently: " +
((mPersistentVrModeEnabled) ? "enabled" : "disabled"));
+ pw.println("Currently bound VR listener service: "
+ + ((mCurrentVrService == null)
+ ? "None" : mCurrentVrService.getComponent().flattenToString()));
+ pw.println("Currently bound VR compositor service: "
+ + ((mCurrentVrCompositorService == null)
+ ? "None" : mCurrentVrCompositorService.getComponent().flattenToString()));
pw.println("Previous state transitions:\n");
String tab = " ";
dumpStateTransitions(pw);
@@ -582,6 +685,11 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
@Override
+ public void onKeyguardStateChanged(boolean isShowing) {
+ VrManagerService.this.setKeyguardShowing(isShowing);
+ }
+
+ @Override
public boolean isCurrentVrListener(String packageName, int userId) {
return VrManagerService.this.isCurrentVrListener(packageName, userId);
}
@@ -785,6 +893,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC
+ mCurrentVrService.getComponent() + " for user "
+ mCurrentVrService.getUserId());
mCurrentVrService.disconnect();
+ updateCompositorServiceLocked(UserHandle.USER_NULL, null);
mCurrentVrService = null;
} else {
nothingChanged = true;
@@ -798,6 +907,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC
Slog.i(TAG, "VR mode component changed to " + component
+ ", disconnecting " + mCurrentVrService.getComponent()
+ " for user " + mCurrentVrService.getUserId());
+ updateCompositorServiceLocked(UserHandle.USER_NULL, null);
createAndConnectService(component, userId);
sendUpdatedCaller = true;
} else {
@@ -985,7 +1095,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC
private void createAndConnectService(@NonNull ComponentName component, int userId) {
- mCurrentVrService = VrManagerService.create(mContext, component, userId);
+ mCurrentVrService = createVrListenerService(component, userId);
mCurrentVrService.connect();
Slog.i(TAG, "Connecting " + component + " for user " + userId);
}
@@ -1020,13 +1130,27 @@ public class VrManagerService extends SystemService implements EnabledComponentC
}
/**
- * Helper function for making ManagedApplicationService instances.
+ * Helper function for making ManagedApplicationService for VrListenerService instances.
*/
- private static ManagedApplicationService create(@NonNull Context context,
- @NonNull ComponentName component, int userId) {
- return ManagedApplicationService.build(context, component, userId,
+ private ManagedApplicationService createVrListenerService(@NonNull ComponentName component,
+ int userId) {
+ int retryType = (mBootsToVr) ? ManagedApplicationService.RETRY_FOREVER
+ : ManagedApplicationService.RETRY_NEVER;
+ return ManagedApplicationService.build(mContext, component, userId,
R.string.vr_listener_binding_label, Settings.ACTION_VR_LISTENER_SETTINGS,
- sBinderChecker);
+ sBinderChecker, /*isImportant*/true, retryType, mHandler, mEventCallback);
+ }
+
+ /**
+ * Helper function for making ManagedApplicationService for VR Compositor instances.
+ */
+ private ManagedApplicationService createVrCompositorService(@NonNull ComponentName component,
+ int userId) {
+ int retryType = (mBootsToVr) ? ManagedApplicationService.RETRY_FOREVER
+ : ManagedApplicationService.RETRY_BEST_EFFORT;
+ return ManagedApplicationService.build(mContext, component, userId, /*clientLabel*/0,
+ /*settingsAction*/null, /*binderChecker*/null, /*isImportant*/true, retryType,
+ mHandler, /*disconnectCallback*/mEventCallback);
}
/**
@@ -1057,44 +1181,35 @@ public class VrManagerService extends SystemService implements EnabledComponentC
private void logStateLocked() {
ComponentName currentBoundService = (mCurrentVrService == null) ? null :
- mCurrentVrService.getComponent();
- VrState current = new VrState(mVrModeEnabled, mRunning2dInVr, currentBoundService,
- mCurrentVrModeUser, mVrAppProcessId, mCurrentVrModeComponent, mWasDefaultGranted);
- if (mLoggingDeque.size() == EVENT_LOG_SIZE) {
- mLoggingDeque.removeFirst();
+ mCurrentVrService.getComponent();
+ logEvent(new VrState(mVrModeEnabled, mRunning2dInVr, currentBoundService,
+ mCurrentVrModeUser, mVrAppProcessId, mCurrentVrModeComponent, mWasDefaultGranted));
+ }
+
+ private void logEvent(LogFormattable event) {
+ synchronized (mLoggingDeque) {
+ if (mLoggingDeque.size() == EVENT_LOG_SIZE) {
+ mLoggingDeque.removeFirst();
+ mLogLimitHit = true;
+ }
+ mLoggingDeque.add(event);
}
- mLoggingDeque.add(current);
}
private void dumpStateTransitions(PrintWriter pw) {
SimpleDateFormat d = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
- String tab = " ";
- if (mLoggingDeque.size() == 0) {
- pw.print(tab);
- pw.println("None");
- }
- for (VrState state : mLoggingDeque) {
- pw.print(d.format(new Date(state.timestamp)));
- pw.print(tab);
- pw.print("State changed to:");
- pw.print(tab);
- pw.println((state.enabled) ? "ENABLED" : "DISABLED");
- if (state.enabled) {
- pw.print(tab);
- pw.print("User=");
- pw.println(state.userId);
- pw.print(tab);
- pw.print("Current VR Activity=");
- pw.println((state.callingPackage == null) ?
- "None" : state.callingPackage.flattenToString());
- pw.print(tab);
- pw.print("Bound VrListenerService=");
- pw.println((state.targetPackageName == null) ?
- "None" : state.targetPackageName.flattenToString());
- if (state.defaultPermissionsGranted) {
- pw.print(tab);
- pw.println("Default permissions granted to the bound VrListenerService.");
- }
+ synchronized (mLoggingDeque) {
+ if (mLoggingDeque.size() == 0) {
+ pw.print(" ");
+ pw.println("None");
+ }
+
+ if (mLogLimitHit) {
+ pw.println("..."); // Indicates log overflow
+ }
+
+ for (LogFormattable event : mLoggingDeque) {
+ pw.println(event.toLogString(d));
}
}
}
@@ -1177,10 +1292,41 @@ public class VrManagerService extends SystemService implements EnabledComponentC
return INVALID_DISPLAY;
}
+ private void setAndBindCompositor(ComponentName componentName) {
+ final int userId = UserHandle.getCallingUserId();
+ final long token = Binder.clearCallingIdentity();
+ synchronized (mLock) {
+ updateCompositorServiceLocked(userId, componentName);
+ }
+ Binder.restoreCallingIdentity(token);
+ }
+
+ private void updateCompositorServiceLocked(int userId, ComponentName componentName) {
+ if (mCurrentVrCompositorService != null
+ && mCurrentVrCompositorService.disconnectIfNotMatching(componentName, userId)) {
+ Slog.i(TAG, "Disconnecting compositor service: "
+ + mCurrentVrCompositorService.getComponent());
+ // Check if existing service matches the requested one, if not (or if the requested
+ // component is null) disconnect it.
+ mCurrentVrCompositorService = null;
+ }
+
+ if (componentName != null && mCurrentVrCompositorService == null) {
+ // We don't have an existing service matching the requested component, so attempt to
+ // connect one.
+ Slog.i(TAG, "Connecting compositor service: " + componentName);
+ mCurrentVrCompositorService = createVrCompositorService(componentName, userId);
+ mCurrentVrCompositorService.connect();
+ }
+ }
+
private void setPersistentModeAndNotifyListenersLocked(boolean enabled) {
if (mPersistentVrModeEnabled == enabled) {
return;
}
+ String eventName = "Persistent VR mode " + ((enabled) ? "enabled" : "disabled");
+ Slog.i(TAG, eventName);
+ logEvent(new SettingEvent(eventName));
mPersistentVrModeEnabled = enabled;
mHandler.sendMessage(mHandler.obtainMessage(MSG_PERSISTENT_VR_MODE_STATE_CHANGE,
diff --git a/com/android/server/webkit/SystemImpl.java b/com/android/server/webkit/SystemImpl.java
index bf769ed4..1e334b83 100644
--- a/com/android/server/webkit/SystemImpl.java
+++ b/com/android/server/webkit/SystemImpl.java
@@ -304,6 +304,6 @@ public class SystemImpl implements SystemInterface {
// flags declaring we want extra info from the package manager for webview providers
private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
- | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
- | PackageManager.MATCH_ANY_USER;
+ | PackageManager.GET_SIGNATURES | PackageManager.GET_SHARED_LIBRARY_FILES
+ | PackageManager.MATCH_DEBUG_TRIAGED_MISSING | PackageManager.MATCH_ANY_USER;
}
diff --git a/com/android/server/wifi/NetworkListStoreData.java b/com/android/server/wifi/NetworkListStoreData.java
index 5ddfd4df..f287d4b9 100644
--- a/com/android/server/wifi/NetworkListStoreData.java
+++ b/com/android/server/wifi/NetworkListStoreData.java
@@ -16,10 +16,12 @@
package com.android.server.wifi;
+import android.content.Context;
import android.net.IpConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiEnterpriseConfig;
+import android.os.Process;
import android.util.Log;
import android.util.Pair;
@@ -52,6 +54,8 @@ public class NetworkListStoreData implements WifiConfigStore.StoreData {
private static final String XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION =
"WifiEnterpriseConfiguration";
+ private final Context mContext;
+
/**
* List of saved shared networks visible to all the users to be stored in the shared store file.
*/
@@ -62,7 +66,9 @@ public class NetworkListStoreData implements WifiConfigStore.StoreData {
*/
private List<WifiConfiguration> mUserConfigurations;
- NetworkListStoreData() {}
+ NetworkListStoreData(Context context) {
+ mContext = context;
+ }
@Override
public void serializeData(XmlSerializer out, boolean shared)
@@ -282,6 +288,19 @@ public class NetworkListStoreData implements WifiConfigStore.StoreData {
"Configuration key does not match. Retrieved: " + configKeyParsed
+ ", Calculated: " + configKeyCalculated);
}
+ // Set creatorUid/creatorName for networks which don't have it set to valid value.
+ String creatorName = mContext.getPackageManager().getNameForUid(configuration.creatorUid);
+ if (creatorName == null) {
+ Log.e(TAG, "Invalid creatorUid for saved network " + configuration.configKey()
+ + ", creatorUid=" + configuration.creatorUid);
+ configuration.creatorUid = Process.SYSTEM_UID;
+ configuration.creatorName = creatorName;
+ } else if (!creatorName.equals(configuration.creatorName)) {
+ Log.w(TAG, "Invalid creatorName for saved network " + configuration.configKey()
+ + ", creatorUid=" + configuration.creatorUid
+ + ", creatorName=" + configuration.creatorName);
+ configuration.creatorName = creatorName;
+ }
configuration.setNetworkSelectionStatus(status);
configuration.setIpConfiguration(ipConfiguration);
diff --git a/com/android/server/wifi/OpenNetworkNotifier.java b/com/android/server/wifi/OpenNetworkNotifier.java
index 31ff44b7..eee4ac53 100644
--- a/com/android/server/wifi/OpenNetworkNotifier.java
+++ b/com/android/server/wifi/OpenNetworkNotifier.java
@@ -40,11 +40,13 @@ import android.os.Messenger;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
import com.android.server.wifi.util.ScanResultUtil;
import java.io.FileDescriptor;
@@ -124,6 +126,7 @@ public class OpenNetworkNotifier {
private final Context mContext;
private final Handler mHandler;
private final FrameworkFacade mFrameworkFacade;
+ private final WifiMetrics mWifiMetrics;
private final Clock mClock;
private final WifiConfigManager mConfigManager;
private final WifiStateMachine mWifiStateMachine;
@@ -138,6 +141,7 @@ public class OpenNetworkNotifier {
Looper looper,
FrameworkFacade framework,
Clock clock,
+ WifiMetrics wifiMetrics,
WifiConfigManager wifiConfigManager,
WifiConfigStore wifiConfigStore,
WifiStateMachine wifiStateMachine,
@@ -146,6 +150,7 @@ public class OpenNetworkNotifier {
mContext = context;
mHandler = new Handler(looper);
mFrameworkFacade = framework;
+ mWifiMetrics = wifiMetrics;
mClock = clock;
mConfigManager = wifiConfigManager;
mWifiStateMachine = wifiStateMachine;
@@ -206,7 +211,7 @@ public class OpenNetworkNotifier {
case WifiManager.CONNECT_NETWORK_SUCCEEDED:
break;
case WifiManager.CONNECT_NETWORK_FAILED:
- handleConnectionFailure();
+ handleConnectionAttemptFailedToSend();
break;
default:
Log.e(TAG, "Unknown message " + msg.what);
@@ -226,6 +231,13 @@ public class OpenNetworkNotifier {
if (mState != STATE_NO_NOTIFICATION) {
getNotificationManager().cancel(SystemMessage.NOTE_NETWORK_AVAILABLE);
+
+ if (mRecommendedNetwork != null) {
+ Log.d(TAG, "Notification with state="
+ + mState
+ + " was cleared for recommended network: "
+ + mRecommendedNetwork.SSID);
+ }
mState = STATE_NO_NOTIFICATION;
mRecommendedNetwork = null;
}
@@ -295,6 +307,10 @@ public class OpenNetworkNotifier {
postNotification(mNotificationBuilder.createNetworkConnectedNotification(
mRecommendedNetwork));
+
+ Log.d(TAG, "User connected to recommended network: " + mRecommendedNetwork.SSID);
+ mWifiMetrics.incrementConnectToNetworkNotification(
+ ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
mState = STATE_CONNECTED_NOTIFICATION;
mHandler.postDelayed(
() -> {
@@ -313,6 +329,10 @@ public class OpenNetworkNotifier {
return;
}
postNotification(mNotificationBuilder.createNetworkFailedNotification());
+
+ Log.d(TAG, "User failed to connect to recommended network: " + mRecommendedNetwork.SSID);
+ mWifiMetrics.incrementConnectToNetworkNotification(
+ ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
mState = STATE_CONNECT_FAILED_NOTIFICATION;
mHandler.postDelayed(
() -> {
@@ -328,8 +348,18 @@ public class OpenNetworkNotifier {
}
private void postInitialNotification(ScanResult recommendedNetwork) {
+ if (mRecommendedNetwork != null
+ && TextUtils.equals(mRecommendedNetwork.SSID, recommendedNetwork.SSID)) {
+ return;
+ }
postNotification(mNotificationBuilder.createConnectToNetworkNotification(
recommendedNetwork));
+ if (mState == STATE_NO_NOTIFICATION) {
+ mWifiMetrics.incrementConnectToNetworkNotification(
+ ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+ } else {
+ mWifiMetrics.incrementNumOpenNetworkRecommendationUpdates();
+ }
mState = STATE_SHOWING_RECOMMENDATION_NOTIFICATION;
mRecommendedNetwork = recommendedNetwork;
mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay;
@@ -340,11 +370,15 @@ public class OpenNetworkNotifier {
}
private void handleConnectToNetworkAction() {
+ mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+ ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
if (mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
return;
}
postNotification(mNotificationBuilder.createNetworkConnectingNotification(
mRecommendedNetwork));
+ mWifiMetrics.incrementConnectToNetworkNotification(
+ ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
Log.d(TAG, "User initiated connection to recommended network: " + mRecommendedNetwork.SSID);
WifiConfiguration network = ScanResultUtil.createNetworkFromScanResult(mRecommendedNetwork);
@@ -366,6 +400,8 @@ public class OpenNetworkNotifier {
}
private void handleSeeAllNetworksAction() {
+ mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+ ConnectToNetworkNotificationAndActionCount.ACTION_PICK_WIFI_NETWORK);
startWifiSettings();
}
@@ -378,14 +414,26 @@ public class OpenNetworkNotifier {
clearPendingNotification(false /* resetRepeatTime */);
}
+ private void handleConnectionAttemptFailedToSend() {
+ handleConnectionFailure();
+ mWifiMetrics.incrementNumOpenNetworkConnectMessageFailedToSend();
+ }
+
private void handlePickWifiNetworkAfterConnectFailure() {
+ mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+ ConnectToNetworkNotificationAndActionCount
+ .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
startWifiSettings();
}
private void handleUserDismissedAction() {
+ Log.d(TAG, "User dismissed notification with state=" + mState);
+ mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+ ConnectToNetworkNotificationAndActionCount.ACTION_USER_DISMISSED_NOTIFICATION);
if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
// blacklist dismissed network
mBlacklistedSsids.add(mRecommendedNetwork.SSID);
+ mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(mBlacklistedSsids.size());
mConfigManager.saveToStore(false /* forceWrite */);
Log.d(TAG, "Network is added to the open network notification blacklist: "
+ mRecommendedNetwork.SSID);
@@ -418,6 +466,7 @@ public class OpenNetworkNotifier {
@Override
public void setSsids(Set<String> ssidList) {
mBlacklistedSsids.addAll(ssidList);
+ mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(mBlacklistedSsids.size());
}
}
@@ -440,8 +489,10 @@ public class OpenNetworkNotifier {
}
private boolean getValue() {
- return mFrameworkFacade.getIntegerSetting(mContext,
+ boolean enabled = mFrameworkFacade.getIntegerSetting(mContext,
Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+ mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(enabled);
+ return enabled;
}
}
}
diff --git a/com/android/server/wifi/VelocityBasedConnectedScore.java b/com/android/server/wifi/VelocityBasedConnectedScore.java
new file mode 100644
index 00000000..9d90332e
--- /dev/null
+++ b/com/android/server/wifi/VelocityBasedConnectedScore.java
@@ -0,0 +1,133 @@
+/*
+ * 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 com.android.server.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiInfo;
+
+import com.android.internal.R;
+import com.android.server.wifi.util.KalmanFilter;
+import com.android.server.wifi.util.Matrix;
+
+/**
+ * Class used to calculate scores for connected wifi networks and report it to the associated
+ * network agent.
+ */
+public class VelocityBasedConnectedScore extends ConnectedScore {
+
+ // Device configs. The values are examples.
+ private final int mThresholdMinimumRssi5; // -82
+ private final int mThresholdMinimumRssi24; // -85
+
+ private int mFrequency = 5000;
+ private int mRssi = 0;
+ private final KalmanFilter mFilter;
+ private long mLastMillis;
+
+ public VelocityBasedConnectedScore(Context context, Clock clock) {
+ super(clock);
+ mThresholdMinimumRssi5 = context.getResources().getInteger(
+ R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+ mThresholdMinimumRssi24 = context.getResources().getInteger(
+ R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+ mFilter = new KalmanFilter();
+ mFilter.mH = new Matrix(2, new double[]{1.0, 0.0});
+ mFilter.mR = new Matrix(1, new double[]{1.0});
+ }
+
+ /**
+ * Set the Kalman filter's state transition matrix F and process noise covariance Q given
+ * a time step.
+ *
+ * @param dt delta time, in seconds
+ */
+ private void setDeltaTimeSeconds(double dt) {
+ mFilter.mF = new Matrix(2, new double[]{1.0, dt, 0.0, 1.0});
+ Matrix tG = new Matrix(1, new double[]{0.5 * dt * dt, dt});
+ double stda = 0.02; // standard deviation of modelled acceleration
+ mFilter.mQ = tG.dotTranspose(tG).dot(new Matrix(2, new double[]{
+ stda * stda, 0.0,
+ 0.0, stda * stda}));
+ }
+ /**
+ * Reset the filter state.
+ */
+ @Override
+ public void reset() {
+ mLastMillis = 0;
+ }
+
+ /**
+ * Updates scoring state using RSSI and measurement noise estimate
+ * <p>
+ * This is useful if an RSSI comes from another source (e.g. scan results) and the
+ * expected noise varies by source.
+ *
+ * @param rssi signal strength (dB).
+ * @param millis millisecond-resolution time.
+ * @param standardDeviation of the RSSI.
+ */
+ @Override
+ public void updateUsingRssi(int rssi, long millis, double standardDeviation) {
+ if (millis <= 0) return;
+ if (mLastMillis <= 0 || millis < mLastMillis) {
+ double initialVariance = 9.0 * standardDeviation * standardDeviation;
+ mFilter.mx = new Matrix(1, new double[]{rssi, 0.0});
+ mFilter.mP = new Matrix(2, new double[]{initialVariance, 0.0, 0.0, 0.0});
+ mLastMillis = millis;
+ return;
+ }
+ double dt = (millis - mLastMillis) * 0.001;
+ mFilter.mR.put(0, 0, standardDeviation * standardDeviation);
+ setDeltaTimeSeconds(dt);
+ mFilter.predict();
+ mLastMillis = millis;
+ mFilter.update(new Matrix(1, new double[]{rssi}));
+ }
+
+ /**
+ * Updates the state.
+ */
+ @Override
+ public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
+ int frequency = wifiInfo.getFrequency();
+ if (frequency != mFrequency) {
+ reset(); // Probably roamed
+ mFrequency = frequency;
+ }
+ updateUsingRssi(wifiInfo.getRssi(), millis, mDefaultRssiStandardDeviation);
+ }
+
+ /**
+ * Velocity scorer - predict the rssi a few seconds from now
+ */
+ @Override
+ public int generateScore() {
+ int badRssi = mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
+ double horizonSeconds = 15.0;
+ Matrix x = new Matrix(mFilter.mx);
+ double filteredRssi = x.get(0, 0);
+ setDeltaTimeSeconds(horizonSeconds);
+ x = mFilter.mF.dot(x);
+ double forecastRssi = x.get(0, 0);
+ if (forecastRssi > filteredRssi) {
+ forecastRssi = filteredRssi; // Be pessimistic about predicting an actual increase
+ }
+ int score = (int) (Math.round(forecastRssi) - badRssi) + WIFI_TRANSITION_SCORE;
+ return score;
+ }
+}
diff --git a/com/android/server/wifi/WifiBackupRestore.java b/com/android/server/wifi/WifiBackupRestore.java
index 60c3b488..ae5e411f 100644
--- a/com/android/server/wifi/WifiBackupRestore.java
+++ b/com/android/server/wifi/WifiBackupRestore.java
@@ -228,9 +228,8 @@ public class WifiBackupRestore {
}
return parseNetworkConfigurationsFromXml(in, rootTagDepth, version);
- } catch (XmlPullParserException e) {
- Log.e(TAG, "Error parsing the backup data: " + e);
- } catch (IOException e) {
+ } catch (XmlPullParserException | IOException | ClassCastException
+ | IllegalArgumentException e) {
Log.e(TAG, "Error parsing the backup data: " + e);
}
return null;
diff --git a/com/android/server/wifi/WifiConfigManager.java b/com/android/server/wifi/WifiConfigManager.java
index f8b33cbb..ba1695a5 100644
--- a/com/android/server/wifi/WifiConfigManager.java
+++ b/com/android/server/wifi/WifiConfigManager.java
@@ -2333,12 +2333,13 @@ public class WifiConfigManager {
public List<WifiScanner.PnoSettings.PnoNetwork> retrievePnoNetworkList() {
List<WifiScanner.PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
- // Remove any permanently disabled networks.
+ // Remove any permanently or temporarily disabled networks.
Iterator<WifiConfiguration> iter = networks.iterator();
while (iter.hasNext()) {
WifiConfiguration config = iter.next();
if (config.ephemeral || config.isPasspoint()
- || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
+ || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
+ || config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
iter.remove();
}
}
@@ -2574,10 +2575,12 @@ public class WifiConfigManager {
* @param userId The identifier of the user that stopped.
*/
public void handleUserStop(int userId) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "Handling user stop for " + userId);
+ }
if (userId == mCurrentUserId && mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
saveToStore(true);
- clearInternalData();
- mCurrentUserId = UserHandle.USER_SYSTEM;
+ clearInternalUserData(mCurrentUserId);
}
}
@@ -2589,6 +2592,7 @@ public class WifiConfigManager {
* - List of deleted ephemeral networks.
*/
private void clearInternalData() {
+ localLog("clearInternalData: Clearing all internal data");
mConfiguredNetworks.clear();
mDeletedEphemeralSSIDs.clear();
mScanDetailCaches.clear();
@@ -2607,12 +2611,16 @@ public class WifiConfigManager {
* removed from memory.
*/
private Set<Integer> clearInternalUserData(int userId) {
+ localLog("clearInternalUserData: Clearing user internal data for " + userId);
Set<Integer> removedNetworkIds = new HashSet<>();
// Remove any private networks of the old user before switching the userId.
for (WifiConfiguration config : getInternalConfiguredNetworks()) {
if (!config.shared && WifiConfigurationUtil.doesUidBelongToAnyProfile(
config.creatorUid, mUserManager.getProfiles(userId))) {
removedNetworkIds.add(config.networkId);
+ localLog("clearInternalUserData: removed config."
+ + " netId=" + config.networkId
+ + " configKey=" + config.configKey());
mConfiguredNetworks.remove(config.networkId);
}
}
diff --git a/com/android/server/wifi/WifiConnectivityManager.java b/com/android/server/wifi/WifiConnectivityManager.java
index 7e730c83..458f73ae 100644
--- a/com/android/server/wifi/WifiConnectivityManager.java
+++ b/com/android/server/wifi/WifiConnectivityManager.java
@@ -30,6 +30,8 @@ import android.net.wifi.WifiScanner.PnoSettings;
import android.net.wifi.WifiScanner.ScanSettings;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
+import android.os.WorkSource;
import android.util.LocalLog;
import android.util.Log;
@@ -214,7 +216,7 @@ public class WifiConnectivityManager {
@Override
public void onAlarm() {
- startSingleScan(mIsFullBandScan);
+ startSingleScan(mIsFullBandScan, WIFI_WORK_SOURCE);
}
}
@@ -741,7 +743,7 @@ public class WifiConnectivityManager {
localLog("connectToNetwork: Connect to " + targetAssociationId + " from "
+ currentAssociationId);
}
- mStateMachine.startConnectToNetwork(candidate.networkId, targetBssid);
+ mStateMachine.startConnectToNetwork(candidate.networkId, Process.WIFI_UID, targetBssid);
}
}
@@ -794,7 +796,7 @@ public class WifiConnectivityManager {
localLog("start a single scan from watchdogHandler");
scheduleWatchdogTimer();
- startSingleScan(true);
+ startSingleScan(true, WIFI_WORK_SOURCE);
}
}
@@ -823,7 +825,7 @@ public class WifiConnectivityManager {
}
mLastPeriodicSingleScanTimeStamp = currentTimeStamp;
- startSingleScan(isFullBandScan);
+ startSingleScan(isFullBandScan, WIFI_WORK_SOURCE);
schedulePeriodicScanTimer(mPeriodicSingleScanInterval);
// Set up the next scan interval in an exponential backoff fashion.
@@ -850,7 +852,7 @@ public class WifiConnectivityManager {
}
// Start a single scan
- private void startSingleScan(boolean isFullBandScan) {
+ private void startSingleScan(boolean isFullBandScan, WorkSource workSource) {
if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
return;
}
@@ -875,7 +877,7 @@ public class WifiConnectivityManager {
SingleScanListener singleScanListener =
new SingleScanListener(isFullBandScan);
- mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE);
+ mScanner.startScan(settings, singleScanListener, workSource);
}
// Start a periodic scan when screen is on
@@ -1132,11 +1134,11 @@ public class WifiConnectivityManager {
/**
* Handler for on-demand connectivity scan
*/
- public void forceConnectivityScan() {
- localLog("forceConnectivityScan");
+ public void forceConnectivityScan(WorkSource workSource) {
+ localLog("forceConnectivityScan in request of " + workSource);
mWaitForFullBandScanResults = true;
- startSingleScan(true);
+ startSingleScan(true, workSource);
}
/**
diff --git a/com/android/server/wifi/WifiCountryCode.java b/com/android/server/wifi/WifiCountryCode.java
index e69fb8e1..66a035f0 100644
--- a/com/android/server/wifi/WifiCountryCode.java
+++ b/com/android/server/wifi/WifiCountryCode.java
@@ -83,11 +83,9 @@ public class WifiCountryCode {
public synchronized void simCardRemoved() {
if (DBG) Log.d(TAG, "SIM Card Removed");
// SIM card is removed, we need to reset the country code to phone default.
- if (mRevertCountryCodeOnCellularLoss) {
- mTelephonyCountryCode = null;
- if (mReady) {
- updateCountryCode();
- }
+ mTelephonyCountryCode = null;
+ if (mReady) {
+ updateCountryCode();
}
}
@@ -98,12 +96,9 @@ public class WifiCountryCode {
*/
public synchronized void airplaneModeEnabled() {
if (DBG) Log.d(TAG, "Airplane Mode Enabled");
- mTelephonyCountryCode = null;
// Airplane mode is enabled, we need to reset the country code to phone default.
- if (mRevertCountryCodeOnCellularLoss) {
- mTelephonyCountryCode = null;
- // Country code will be set upon when wpa_supplicant starts next time.
- }
+ // Country code will be set upon when wpa_supplicant starts next time.
+ mTelephonyCountryCode = null;
}
/**
@@ -133,8 +128,10 @@ public class WifiCountryCode {
if (DBG) Log.d(TAG, "Receive set country code request: " + countryCode);
// Empty country code.
if (TextUtils.isEmpty(countryCode)) {
- if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
- mTelephonyCountryCode = null;
+ if (mRevertCountryCodeOnCellularLoss) {
+ if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
+ mTelephonyCountryCode = null;
+ }
} else {
mTelephonyCountryCode = countryCode.toUpperCase();
}
diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java
index fc3af83e..1cbb29ef 100644
--- a/com/android/server/wifi/WifiInjector.java
+++ b/com/android/server/wifi/WifiInjector.java
@@ -116,6 +116,7 @@ public class WifiInjector {
private final PasspointManager mPasspointManager;
private final SIMAccessor mSimAccessor;
private HandlerThread mWifiAwareHandlerThread;
+ private HandlerThread mRttHandlerThread;
private HalDeviceManager mHalDeviceManager;
private final IBatteryStats mBatteryStats;
private final WifiStateTracker mWifiStateTracker;
@@ -197,7 +198,7 @@ public class WifiInjector {
mWifiConfigManager = new WifiConfigManager(mContext, mClock,
UserManager.get(mContext), TelephonyManager.from(mContext),
mWifiKeyStore, mWifiConfigStore, mWifiPermissionsUtil,
- mWifiPermissionsWrapper, new NetworkListStoreData(),
+ mWifiPermissionsWrapper, new NetworkListStoreData(mContext),
new DeletedEphemeralSsidsStoreData());
mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
@@ -225,7 +226,7 @@ public class WifiInjector {
new WrongPasswordNotifier(mContext, mFrameworkFacade));
mCertManager = new WifiCertManager(mContext);
mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
- mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock,
+ mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
new OpenNetworkRecommender(),
new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
@@ -455,6 +456,19 @@ public class WifiInjector {
}
/**
+ * Returns a singleton instance of a HandlerThread for injection. Uses lazy initialization.
+ *
+ * TODO: share worker thread with other Wi-Fi handlers (b/27924886)
+ */
+ public HandlerThread getRttHandlerThread() {
+ if (mRttHandlerThread == null) { // lazy initialization
+ mRttHandlerThread = new HandlerThread("wifiRttService");
+ mRttHandlerThread.start();
+ }
+ return mRttHandlerThread;
+ }
+
+ /**
* Returns a single instance of HalDeviceManager for injection.
*/
public HalDeviceManager getHalDeviceManager() {
diff --git a/com/android/server/wifi/WifiMetrics.java b/com/android/server/wifi/WifiMetrics.java
index 5db5ee67..071b4f88 100644
--- a/com/android/server/wifi/WifiMetrics.java
+++ b/com/android/server/wifi/WifiMetrics.java
@@ -37,6 +37,7 @@ import com.android.server.wifi.hotspot2.PasspointManager;
import com.android.server.wifi.hotspot2.PasspointMatch;
import com.android.server.wifi.hotspot2.PasspointProvider;
import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
@@ -83,6 +84,7 @@ public class WifiMetrics {
public static final int MAX_CONNECTABLE_BSSID_NETWORK_BUCKET = 50;
public static final int MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET = 100;
public static final int MAX_TOTAL_SCAN_RESULTS_BUCKET = 250;
+ private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
private Clock mClock;
private boolean mScreenOn;
private int mWifiState;
@@ -150,6 +152,15 @@ public class WifiMetrics {
private final SparseIntArray mAvailableSavedPasspointProviderBssidsInScanHistogram =
new SparseIntArray();
+ /** Mapping of "Connect to Network" notifications to counts. */
+ private final SparseIntArray mConnectToNetworkNotificationCount = new SparseIntArray();
+ /** Mapping of "Connect to Network" notification user actions to counts. */
+ private final SparseIntArray mConnectToNetworkNotificationActionCount = new SparseIntArray();
+ private int mOpenNetworkRecommenderBlacklistSize = 0;
+ private boolean mIsWifiNetworksAvailableNotificationOn = false;
+ private int mNumOpenNetworkConnectMessageFailedToSend = 0;
+ private int mNumOpenNetworkRecommendationUpdates = 0;
+
class RouterFingerPrint {
private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
RouterFingerPrint() {
@@ -1237,6 +1248,55 @@ public class WifiMetrics {
}
}
+ /** Increments the occurence of a "Connect to Network" notification. */
+ public void incrementConnectToNetworkNotification(int notificationType) {
+ synchronized (mLock) {
+ int count = mConnectToNetworkNotificationCount.get(notificationType);
+ mConnectToNetworkNotificationCount.put(notificationType, count + 1);
+ }
+ }
+
+ /** Increments the occurence of an "Connect to Network" notification user action. */
+ public void incrementConnectToNetworkNotificationAction(int notificationType, int actionType) {
+ synchronized (mLock) {
+ int key = notificationType * CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER
+ + actionType;
+ int count = mConnectToNetworkNotificationActionCount.get(key);
+ mConnectToNetworkNotificationActionCount.put(key, count + 1);
+ }
+ }
+
+ /**
+ * Sets the number of SSIDs blacklisted from recommendation by the open network notification
+ * recommender.
+ */
+ public void setOpenNetworkRecommenderBlacklistSize(int size) {
+ synchronized (mLock) {
+ mOpenNetworkRecommenderBlacklistSize = size;
+ }
+ }
+
+ /** Sets if the available network notification feature is enabled. */
+ public void setIsWifiNetworksAvailableNotificationEnabled(boolean enabled) {
+ synchronized (mLock) {
+ mIsWifiNetworksAvailableNotificationOn = enabled;
+ }
+ }
+
+ /** Increments the occurence of connection attempts that were initiated unsuccessfully */
+ public void incrementNumOpenNetworkRecommendationUpdates() {
+ synchronized (mLock) {
+ mNumOpenNetworkRecommendationUpdates++;
+ }
+ }
+
+ /** Increments the occurence of connection attempts that were initiated unsuccessfully */
+ public void incrementNumOpenNetworkConnectMessageFailedToSend() {
+ synchronized (mLock) {
+ mNumOpenNetworkConnectMessageFailedToSend++;
+ }
+ }
+
public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
public static final String CLEAN_DUMP_ARG = "clean";
@@ -1488,6 +1548,19 @@ public class WifiMetrics {
+ mPnoScanMetrics.numPnoScanFailedOverOffload);
pw.println("mPnoScanMetrics.numPnoFoundNetworkEvents="
+ mPnoScanMetrics.numPnoFoundNetworkEvents);
+
+ pw.println("mWifiLogProto.connectToNetworkNotificationCount="
+ + mConnectToNetworkNotificationCount.toString());
+ pw.println("mWifiLogProto.connectToNetworkNotificationActionCount="
+ + mConnectToNetworkNotificationActionCount.toString());
+ pw.println("mWifiLogProto.openNetworkRecommenderBlacklistSize="
+ + mOpenNetworkRecommenderBlacklistSize);
+ pw.println("mWifiLogProto.isWifiNetworksAvailableNotificationOn="
+ + mIsWifiNetworksAvailableNotificationOn);
+ pw.println("mWifiLogProto.numOpenNetworkRecommendationUpdates="
+ + mNumOpenNetworkRecommendationUpdates);
+ pw.println("mWifiLogProto.numOpenNetworkConnectMessageFailedToSend="
+ + mNumOpenNetworkConnectMessageFailedToSend);
}
}
}
@@ -1698,6 +1771,53 @@ public class WifiMetrics {
mWifiLogProto.wifiAwareLog = mWifiAwareMetrics.consolidateProto();
mWifiLogProto.pnoScanMetrics = mPnoScanMetrics;
+
+ /**
+ * Convert the SparseIntArray of "Connect to Network" notification types and counts to
+ * proto's repeated IntKeyVal array.
+ */
+ ConnectToNetworkNotificationAndActionCount[] notificationCountArray =
+ new ConnectToNetworkNotificationAndActionCount[
+ mConnectToNetworkNotificationCount.size()];
+ for (int i = 0; i < mConnectToNetworkNotificationCount.size(); i++) {
+ ConnectToNetworkNotificationAndActionCount keyVal =
+ new ConnectToNetworkNotificationAndActionCount();
+ keyVal.notification = mConnectToNetworkNotificationCount.keyAt(i);
+ keyVal.recommender =
+ ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN;
+ keyVal.count = mConnectToNetworkNotificationCount.valueAt(i);
+ notificationCountArray[i] = keyVal;
+ }
+ mWifiLogProto.connectToNetworkNotificationCount = notificationCountArray;
+
+ /**
+ * Convert the SparseIntArray of "Connect to Network" notification types and counts to
+ * proto's repeated IntKeyVal array.
+ */
+ ConnectToNetworkNotificationAndActionCount[] notificationActionCountArray =
+ new ConnectToNetworkNotificationAndActionCount[
+ mConnectToNetworkNotificationActionCount.size()];
+ for (int i = 0; i < mConnectToNetworkNotificationActionCount.size(); i++) {
+ ConnectToNetworkNotificationAndActionCount keyVal =
+ new ConnectToNetworkNotificationAndActionCount();
+ int key = mConnectToNetworkNotificationActionCount.keyAt(i);
+ keyVal.notification = key / CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER;
+ keyVal.action = key % CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER;
+ keyVal.recommender =
+ ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN;
+ keyVal.count = mConnectToNetworkNotificationActionCount.valueAt(i);
+ notificationActionCountArray[i] = keyVal;
+ }
+ mWifiLogProto.connectToNetworkNotificationActionCount = notificationActionCountArray;
+
+ mWifiLogProto.openNetworkRecommenderBlacklistSize =
+ mOpenNetworkRecommenderBlacklistSize;
+ mWifiLogProto.isWifiNetworksAvailableNotificationOn =
+ mIsWifiNetworksAvailableNotificationOn;
+ mWifiLogProto.numOpenNetworkRecommendationUpdates =
+ mNumOpenNetworkRecommendationUpdates;
+ mWifiLogProto.numOpenNetworkConnectMessageFailedToSend =
+ mNumOpenNetworkConnectMessageFailedToSend;
}
}
@@ -1716,7 +1836,8 @@ public class WifiMetrics {
}
/**
- * Clear all WifiMetrics, except for currentConnectionEvent.
+ * Clear all WifiMetrics, except for currentConnectionEvent and Open Network Notification
+ * feature enabled state, blacklist size.
*/
private void clear() {
synchronized (mLock) {
@@ -1747,6 +1868,10 @@ public class WifiMetrics {
mAvailableSavedPasspointProviderProfilesInScanHistogram.clear();
mAvailableSavedPasspointProviderBssidsInScanHistogram.clear();
mPnoScanMetrics.clear();
+ mConnectToNetworkNotificationCount.clear();
+ mConnectToNetworkNotificationActionCount.clear();
+ mNumOpenNetworkRecommendationUpdates = 0;
+ mNumOpenNetworkConnectMessageFailedToSend = 0;
}
}
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index 5b12a364..0b1719db 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -79,6 +79,10 @@ public class WifiNative {
return mInterfaceName;
}
+ public WifiVendorHal getVendorHal() {
+ return mWifiVendorHal;
+ }
+
/**
* Enable verbose logging for all sub modules.
*/
diff --git a/com/android/server/wifi/WifiScoreReport.java b/com/android/server/wifi/WifiScoreReport.java
index 894d57cf..324cdba8 100644
--- a/com/android/server/wifi/WifiScoreReport.java
+++ b/com/android/server/wifi/WifiScoreReport.java
@@ -49,11 +49,13 @@ public class WifiScoreReport {
ConnectedScore mConnectedScore;
ConnectedScore mAggressiveConnectedScore;
+ ConnectedScore mFancyConnectedScore;
WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
mClock = clock;
mConnectedScore = new LegacyConnectedScore(context, wifiConfigManager, clock);
mAggressiveConnectedScore = new AggressiveConnectedScore(context, clock);
+ mFancyConnectedScore = new VelocityBasedConnectedScore(context, clock);
}
/**
@@ -76,6 +78,7 @@ public class WifiScoreReport {
}
mConnectedScore.reset();
mAggressiveConnectedScore.reset();
+ mFancyConnectedScore.reset();
if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
}
@@ -113,18 +116,20 @@ public class WifiScoreReport {
int aggressiveHandover, WifiMetrics wifiMetrics) {
int score;
- long millis = mConnectedScore.getMillis();
+ long millis = mClock.getWallClockMillis();
mConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
mAggressiveConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
+ mFancyConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
int s0 = mConnectedScore.generateScore();
int s1 = mAggressiveConnectedScore.generateScore();
+ int s2 = mFancyConnectedScore.generateScore();
if (aggressiveHandover == 0) {
score = s0;
} else {
- score = s1;
+ score = s2;
}
//sanitize boundaries
@@ -135,7 +140,7 @@ public class WifiScoreReport {
score = 0;
}
- logLinkMetrics(wifiInfo, s0, s1);
+ logLinkMetrics(wifiInfo, millis, s0, s1, s2);
//report score
if (score != wifiInfo.score) {
@@ -163,8 +168,7 @@ public class WifiScoreReport {
/**
* Data logging for dumpsys
*/
- private void logLinkMetrics(WifiInfo wifiInfo, int s0, int s1) {
- long now = mClock.getWallClockMillis();
+ private void logLinkMetrics(WifiInfo wifiInfo, long now, int s0, int s1, int s2) {
if (now < FIRST_REASONABLE_WALL_CLOCK) return;
double rssi = wifiInfo.getRssi();
int freq = wifiInfo.getFrequency();
@@ -176,10 +180,10 @@ public class WifiScoreReport {
try {
String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
String s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
- "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d",
+ "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d",
timestamp, mSessionNumber, rssi, freq, linkSpeed,
txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
- s0, s1);
+ s0, s1, s2);
mLinkMetricsHistory.add(s);
} catch (Exception e) {
Log.e(TAG, "format problem", e);
@@ -201,7 +205,7 @@ public class WifiScoreReport {
* @param args unused
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1");
+ pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1,s2");
for (String line : mLinkMetricsHistory) {
pw.println(line);
}
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index bb995b7d..c83f6c6b 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -1421,7 +1421,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
public void reconnect() {
enforceChangePermission();
mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
- mWifiStateMachine.reconnectCommand();
+ mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
}
/**
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index faad0e2d..1f6cada7 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -1321,7 +1321,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
/**
* Initiates connection to a network specified by the user/app. This method checks if the
- * requesting app holds the WIFI_CONFIG_OVERRIDE permission.
+ * requesting app holds the NETWORK_SETTINGS permission.
*
* @param netId Id network to initiate connection.
* @param uid UID of the app requesting the connection.
@@ -1350,7 +1350,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
logi("connectToUserSelectNetwork already connecting/connected=" + netId);
} else {
mWifiConnectivityManager.prepareForForcedConnection(netId);
- startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+ startConnectToNetwork(netId, uid, SUPPLICANT_BSSID_ANY);
}
return true;
}
@@ -1873,8 +1873,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
/**
* Initiate a reconnection to AP
*/
- public void reconnectCommand() {
- sendMessage(CMD_RECONNECT);
+ public void reconnectCommand(WorkSource workSource) {
+ sendMessage(CMD_RECONNECT, workSource);
}
/**
@@ -3980,9 +3980,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
deleteNetworkConfigAndSendReply(message, true);
break;
case WifiManager.SAVE_NETWORK:
- messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
- replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
- WifiManager.BUSY);
+ saveNetworkConfigAndSendReply(message);
break;
case WifiManager.START_WPS:
replyToMessage(message, WifiManager.WPS_FAILED,
@@ -4537,7 +4535,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
mEnableAutoJoinWhenAssociated = allowed;
if (!old_state && allowed && mScreenOn
&& getCurrentState() == mConnectedState) {
- mWifiConnectivityManager.forceConnectivityScan();
+ mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
}
break;
case CMD_SELECT_TX_POWER_SCENARIO:
@@ -4824,7 +4822,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
// Notify PasspointManager of Passpoint network connected event.
WifiConfiguration currentNetwork = getCurrentWifiConfiguration();
- if (currentNetwork.isPasspoint()) {
+ if (currentNetwork != null && currentNetwork.isPasspoint()) {
mPasspointManager.onPasspointNetworkConnected(currentNetwork.FQDN);
}
}
@@ -5160,7 +5158,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
break;
case CMD_RECONNECT:
- mWifiConnectivityManager.forceConnectivityScan();
+ WorkSource workSource = (WorkSource) message.obj;
+ mWifiConnectivityManager.forceConnectivityScan(workSource);
break;
case CMD_REASSOCIATE:
lastConnectAttemptTimestamp = mClock.getWallClockMillis();
@@ -5180,7 +5179,23 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
case CMD_START_CONNECT:
/* connect command coming from auto-join */
netId = message.arg1;
+ int uid = message.arg2;
bssid = (String) message.obj;
+
+ synchronized (mWifiReqCountLock) {
+ if (!hasConnectionRequests()) {
+ if (mNetworkAgent == null) {
+ loge("CMD_START_CONNECT but no requests and not connected,"
+ + " bailing");
+ break;
+ } else if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+ loge("CMD_START_CONNECT but no requests and connected, but app "
+ + "does not have sufficient permissions, bailing");
+ break;
+ }
+ }
+ }
+
config = mWifiConfigManager.getConfiguredNetworkWithPassword(netId);
logd("CMD_START_CONNECT sup state "
+ mSupplicantStateTracker.getSupplicantStateName()
@@ -5270,41 +5285,17 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
break;
case WifiManager.SAVE_NETWORK:
- config = (WifiConfiguration) message.obj;
- mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
- if (config == null) {
- loge("SAVE_NETWORK with null configuration"
- + mSupplicantStateTracker.getSupplicantStateName()
- + " my state " + getCurrentState().getName());
- messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
- replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
- WifiManager.ERROR);
- break;
- }
- result = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
- if (!result.isSuccess()) {
- loge("SAVE_NETWORK adding/updating config=" + config + " failed");
- messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
- replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
- WifiManager.ERROR);
- break;
- }
- if (!mWifiConfigManager.enableNetwork(
- result.getNetworkId(), false, message.sendingUid)) {
- loge("SAVE_NETWORK enabling config=" + config + " failed");
- messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
- replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
- WifiManager.ERROR);
- break;
- }
+ result = saveNetworkConfigAndSendReply(message);
netId = result.getNetworkId();
- if (mWifiInfo.getNetworkId() == netId) {
+ if (result.isSuccess() && mWifiInfo.getNetworkId() == netId) {
+ mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
if (result.hasCredentialChanged()) {
+ config = (WifiConfiguration) message.obj;
// The network credentials changed and we're connected to this network,
// start a new connection with the updated credentials.
logi("SAVE_NETWORK credential changed for config=" + config.configKey()
+ ", Reconnecting.");
- startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+ startConnectToNetwork(netId, message.sendingUid, SUPPLICANT_BSSID_ANY);
} else {
if (result.hasProxyChanged()) {
log("Reconfiguring proxy on connection");
@@ -5322,8 +5313,6 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
}
}
}
- broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
- replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
break;
case WifiManager.FORGET_NETWORK:
if (!deleteNetworkConfigAndSendReply(message, true)) {
@@ -5831,8 +5820,15 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
reportConnectionAttemptEnd(
WifiMetrics.ConnectionEvent.FAILURE_NONE,
WifiMetricsProto.ConnectionEvent.HLF_NONE);
- sendConnectedState();
- transitionTo(mConnectedState);
+ if (getCurrentWifiConfiguration() == null) {
+ // The current config may have been removed while we were connecting,
+ // trigger a disconnect to clear up state.
+ mWifiNative.disconnect();
+ transitionTo(mDisconnectingState);
+ } else {
+ sendConnectedState();
+ transitionTo(mConnectedState);
+ }
break;
case CMD_IP_CONFIGURATION_LOST:
// Get Link layer stats so that we get fresh tx packet counters.
@@ -5999,6 +5995,9 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
case CMD_STOP_RSSI_MONITORING_OFFLOAD:
stopRssiMonitoringOffload();
break;
+ case CMD_RECONNECT:
+ log(" Ignore CMD_RECONNECT request because wifi is already connected");
+ break;
case CMD_RESET_SIM_NETWORKS:
if (message.arg1 == 0 // sim was removed
&& mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
@@ -6142,7 +6141,7 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
WifiConfiguration config = getCurrentWifiConfiguration();
if (shouldEvaluateWhetherToSendExplicitlySelected(config)) {
boolean prompt =
- mWifiPermissionsUtil.checkConfigOverridePermission(config.lastConnectUid);
+ mWifiPermissionsUtil.checkNetworkSettingsPermission(config.lastConnectUid);
if (mVerboseLoggingEnabled) {
log("Network selected by UID " + config.lastConnectUid + " prompt=" + prompt);
}
@@ -6851,6 +6850,8 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
case WifiManager.CONNECT_NETWORK:
case CMD_ENABLE_NETWORK:
case CMD_RECONNECT:
+ log(" Ignore CMD_RECONNECT request because wps is running");
+ return HANDLED;
case CMD_REASSOCIATE:
deferMessage(message);
break;
@@ -7118,14 +7119,11 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
* Automatically connect to the network specified
*
* @param networkId ID of the network to connect to
+ * @param uid UID of the app triggering the connection.
* @param bssid BSSID of the network
*/
- public void startConnectToNetwork(int networkId, String bssid) {
- synchronized (mWifiReqCountLock) {
- if (hasConnectionRequests()) {
- sendMessage(CMD_START_CONNECT, networkId, 0, bssid);
- }
- }
+ public void startConnectToNetwork(int networkId, int uid, String bssid) {
+ sendMessage(CMD_START_CONNECT, networkId, uid, bssid);
}
/**
@@ -7210,6 +7208,43 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss
}
}
+ /**
+ * Private method to handle calling WifiConfigManager to add & enable network configs and reply
+ * to the message from the sender of the outcome.
+ *
+ * @return NetworkUpdateResult with networkId of the added/updated configuration. Will return
+ * {@link WifiConfiguration#INVALID_NETWORK_ID} in case of error.
+ */
+ private NetworkUpdateResult saveNetworkConfigAndSendReply(Message message) {
+ WifiConfiguration config = (WifiConfiguration) message.obj;
+ if (config == null) {
+ loge("SAVE_NETWORK with null configuration "
+ + mSupplicantStateTracker.getSupplicantStateName()
+ + " my state " + getCurrentState().getName());
+ messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+ return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+ }
+ NetworkUpdateResult result =
+ mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
+ if (!result.isSuccess()) {
+ loge("SAVE_NETWORK adding/updating config=" + config + " failed");
+ messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+ return result;
+ }
+ if (!mWifiConfigManager.enableNetwork(
+ result.getNetworkId(), false, message.sendingUid)) {
+ loge("SAVE_NETWORK enabling config=" + config + " failed");
+ messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+ replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+ return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+ }
+ broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+ replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+ return result;
+ }
+
private static String getLinkPropertiesSummary(LinkProperties lp) {
List<String> attributes = new ArrayList<>(6);
if (lp.hasIPv4Address()) {
diff --git a/com/android/server/wifi/WifiVendorHal.java b/com/android/server/wifi/WifiVendorHal.java
index 12674aa2..e0467d78 100644
--- a/com/android/server/wifi/WifiVendorHal.java
+++ b/com/android/server/wifi/WifiVendorHal.java
@@ -67,6 +67,7 @@ import android.net.wifi.WifiWakeReasonAndCounts;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.util.Log;
import android.util.MutableBoolean;
import android.util.MutableInt;
@@ -237,6 +238,11 @@ public class WifiVendorHal {
clearState();
}
+ // TODO: b/65014872 remove - have RttService (RTT2) interact directly with HalDeviceManager
+ public IWifiRttController getRttController() {
+ return mIWifiRttController;
+ }
+
private WifiNative.VendorHalDeathEventHandler mDeathEventHandler;
/**
@@ -2582,8 +2588,25 @@ public class WifiVendorHal {
// The problem here is that the two threads acquire the locks in opposite order.
// If, for example, T2.2 executes between T1.2 and 1.4, then T1 and T2
// will be deadlocked.
- eventHandler.onRingBufferData(
- ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
+ int sizeBefore = data.size();
+ boolean conversionFailure = false;
+ try {
+ eventHandler.onRingBufferData(
+ ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
+ int sizeAfter = data.size();
+ if (sizeAfter != sizeBefore) {
+ conversionFailure = true;
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ conversionFailure = true;
+ }
+ if (conversionFailure) {
+ Log.wtf("WifiVendorHal", "Conversion failure detected in "
+ + "onDebugRingBufferDataAvailable. "
+ + "The input ArrayList |data| is potentially corrupted. "
+ + "Starting size=" + sizeBefore + ", "
+ + "final size=" + data.size());
+ }
});
}
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index 056777fd..b6104a8c 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -374,8 +374,13 @@ public class WificondControl {
new InformationElementUtil.Capabilities();
capabilities.from(ies, result.capability);
String flags = capabilities.generateCapabilitiesString();
- NetworkDetail networkDetail =
- new NetworkDetail(bssid, ies, null, result.frequency);
+ NetworkDetail networkDetail;
+ try {
+ networkDetail = new NetworkDetail(bssid, ies, null, result.frequency);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Illegal argument for scan result with bssid: " + bssid, e);
+ continue;
+ }
ScanDetail scanDetail = new ScanDetail(networkDetail, wifiSsid, bssid, flags,
result.signalMbm / 100, result.frequency, result.tsf, ies, null);
diff --git a/com/android/server/wifi/aware/WifiAwareClientState.java b/com/android/server/wifi/aware/WifiAwareClientState.java
index 3570f1d7..987d49ef 100644
--- a/com/android/server/wifi/aware/WifiAwareClientState.java
+++ b/com/android/server/wifi/aware/WifiAwareClientState.java
@@ -20,7 +20,6 @@ import android.Manifest;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.wifi.RttManager;
import android.net.wifi.aware.ConfigRequest;
import android.net.wifi.aware.IWifiAwareEventCallback;
import android.os.RemoteException;
@@ -271,47 +270,6 @@ public class WifiAwareClientState {
}
/**
- * Called on RTT success - forwards call to client.
- */
- public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
- if (VDBG) {
- Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
- }
- try {
- mCallback.onRangingSuccess(rangingId, results);
- } catch (RemoteException e) {
- Log.w(TAG, "onRangingSuccess: RemoteException - ignored: " + e);
- }
- }
-
- /**
- * Called on RTT failure - forwards call to client.
- */
- public void onRangingFailure(int rangingId, int reason, String description) {
- if (VDBG) {
- Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
- + ", description=" + description);
- }
- try {
- mCallback.onRangingFailure(rangingId, reason, description);
- } catch (RemoteException e) {
- Log.w(TAG, "onRangingFailure: RemoteException - ignored: " + e);
- }
- }
-
- /**
- * Called on RTT operation aborted - forwards call to client.
- */
- public void onRangingAborted(int rangingId) {
- if (VDBG) Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId);
- try {
- mCallback.onRangingAborted(rangingId);
- } catch (RemoteException e) {
- Log.w(TAG, "onRangingAborted: RemoteException - ignored: " + e);
- }
- }
-
- /**
* Dump the internal state of the class.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/com/android/server/wifi/aware/WifiAwareRttStateManager.java b/com/android/server/wifi/aware/WifiAwareRttStateManager.java
deleted file mode 100644
index 9d0441f1..00000000
--- a/com/android/server/wifi/aware/WifiAwareRttStateManager.java
+++ /dev/null
@@ -1,206 +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 com.android.server.wifi.aware;
-
-import android.content.Context;
-import android.net.wifi.IRttManager;
-import android.net.wifi.RttManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-
-/**
- * Manages interactions between the Aware and the RTT service. Duplicates some of the functionality
- * of the RttManager.
- */
-public class WifiAwareRttStateManager {
- private static final String TAG = "WifiAwareRttStateMgr";
-
- private static final boolean DBG = false;
- private static final boolean VDBG = false; // STOPSHIP if true
-
- private final SparseArray<WifiAwareClientState> mPendingOperations = new SparseArray<>();
- private AsyncChannel mAsyncChannel;
- private Context mContext;
-
- /**
- * Initializes the connection to the RTT service.
- */
- public void start(Context context, Looper looper) {
- if (VDBG) Log.v(TAG, "start()");
-
- IBinder b = ServiceManager.getService(Context.WIFI_RTT_SERVICE);
- IRttManager service = IRttManager.Stub.asInterface(b);
- if (service == null) {
- Log.e(TAG, "start(): not able to get WIFI_RTT_SERVICE");
- return;
- }
-
- startWithRttService(context, looper, service);
- }
-
- /**
- * Initializes the connection to the RTT service.
- */
- @VisibleForTesting
- public void startWithRttService(Context context, Looper looper, IRttManager service) {
- Messenger messenger;
- try {
- messenger = service.getMessenger(null, new int[1]);
- } catch (RemoteException e) {
- Log.e(TAG, "start(): not able to getMessenger() of WIFI_RTT_SERVICE");
- return;
- }
-
- mAsyncChannel = new AsyncChannel();
- mAsyncChannel.connect(context, new AwareRttHandler(looper), messenger);
- mContext = context;
- }
-
- private WifiAwareClientState getAndRemovePendingOperationClient(int rangingId) {
- WifiAwareClientState client = mPendingOperations.get(rangingId);
- mPendingOperations.delete(rangingId);
- return client;
- }
-
- /**
- * Start a ranging operation for the client + peer MAC.
- */
- public void startRanging(int rangingId, WifiAwareClientState client,
- RttManager.RttParams[] params) {
- if (VDBG) {
- Log.v(TAG, "startRanging: rangingId=" + rangingId + ", parms="
- + Arrays.toString(params));
- }
-
- if (mAsyncChannel == null) {
- Log.d(TAG, "startRanging(): AsyncChannel to RTT service not configured - failing");
- client.onRangingFailure(rangingId, RttManager.REASON_NOT_AVAILABLE,
- "Aware service not able to configure connection to RTT service");
- return;
- }
-
- mPendingOperations.put(rangingId, client);
- RttManager.ParcelableRttParams pparams = new RttManager.ParcelableRttParams(params);
- mAsyncChannel.sendMessage(RttManager.CMD_OP_START_RANGING, 0, rangingId, pparams);
- }
-
- private class AwareRttHandler extends Handler {
- AwareRttHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (VDBG) Log.v(TAG, "handleMessage(): " + msg.what);
-
- // channel configuration messages
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION,
- new RttManager.RttClient(mContext.getPackageName()));
- } else {
- Log.e(TAG, "Failed to set up channel connection to RTT service");
- mAsyncChannel = null;
- }
- return;
- case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
- /* NOP */
- return;
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
- Log.e(TAG, "Channel connection to RTT service lost");
- mAsyncChannel = null;
- return;
- }
-
- // RTT-specific messages
- WifiAwareClientState client = getAndRemovePendingOperationClient(msg.arg2);
- if (client == null) {
- Log.e(TAG, "handleMessage(): RTT message (" + msg.what
- + ") -- cannot find registered pending operation client for ID "
- + msg.arg2);
- return;
- }
-
- switch (msg.what) {
- case RttManager.CMD_OP_SUCCEEDED: {
- int rangingId = msg.arg2;
- RttManager.ParcelableRttResults results = (RttManager.ParcelableRttResults)
- msg.obj;
- if (VDBG) {
- Log.v(TAG, "CMD_OP_SUCCEEDED: rangingId=" + rangingId + ", results="
- + results);
- }
- for (int i = 0; i < results.mResults.length; ++i) {
- /*
- * TODO: store peer ID rather than null in the return result.
- */
- results.mResults[i].bssid = null;
- }
- client.onRangingSuccess(rangingId, results);
- break;
- }
- case RttManager.CMD_OP_FAILED: {
- int rangingId = msg.arg2;
- int reason = msg.arg1;
- String description = ((Bundle) msg.obj).getString(RttManager.DESCRIPTION_KEY);
- if (VDBG) {
- Log.v(TAG, "CMD_OP_FAILED: rangingId=" + rangingId + ", reason=" + reason
- + ", description=" + description);
- }
- client.onRangingFailure(rangingId, reason, description);
- break;
- }
- case RttManager.CMD_OP_ABORTED: {
- int rangingId = msg.arg2;
- if (VDBG) {
- Log.v(TAG, "CMD_OP_ABORTED: rangingId=" + rangingId);
- }
- client.onRangingAborted(rangingId);
- break;
- }
- default:
- Log.e(TAG, "handleMessage(): ignoring message " + msg.what);
- break;
- }
- }
- }
-
- /**
- * Dump the internal state of the class.
- */
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("WifiAwareRttStateManager:");
- pw.println(" mPendingOperations: [" + mPendingOperations + "]");
- }
-}
diff --git a/com/android/server/wifi/aware/WifiAwareServiceImpl.java b/com/android/server/wifi/aware/WifiAwareServiceImpl.java
index b77ae635..421d9ac4 100644
--- a/com/android/server/wifi/aware/WifiAwareServiceImpl.java
+++ b/com/android/server/wifi/aware/WifiAwareServiceImpl.java
@@ -16,15 +16,16 @@
package com.android.server.wifi.aware;
+import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.wifi.V1_0.NanStatusType;
-import android.net.wifi.RttManager;
import android.net.wifi.aware.Characteristics;
import android.net.wifi.aware.ConfigRequest;
import android.net.wifi.aware.DiscoverySession;
import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
import android.net.wifi.aware.IWifiAwareManager;
import android.net.wifi.aware.PublishConfig;
import android.net.wifi.aware.SubscribeConfig;
@@ -42,7 +43,7 @@ import com.android.server.wifi.util.WifiPermissionsWrapper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.Arrays;
+import java.util.List;
/**
* Implementation of the IWifiAwareManager AIDL interface. Performs validity
@@ -63,7 +64,6 @@ public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
new SparseArray<>();
private int mNextClientId = 1;
- private int mNextRangingId = 1;
private final SparseIntArray mUidByClientId = new SparseIntArray();
public WifiAwareServiceImpl(Context context) {
@@ -134,7 +134,7 @@ public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
}
if (configRequest != null) {
- enforceConnectivityInternalPermission();
+ enforceNetworkStackPermission();
} else {
configRequest = new ConfigRequest.Builder().build();
}
@@ -326,7 +326,7 @@ public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
enforceChangePermission();
if (retryCount != 0) {
- enforceConnectivityInternalPermission();
+ enforceNetworkStackPermission();
}
if (message != null
@@ -352,30 +352,10 @@ public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
}
@Override
- public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
- enforceAccessPermission();
- enforceLocationPermission();
+ public void requestMacAddresses(int uid, List peerIds, IWifiAwareMacAddressProvider callback) {
+ enforceNetworkStackPermission();
- // TODO: b/35676064 restricts access to this API until decide if will open.
- enforceConnectivityInternalPermission();
-
- int uid = getMockableCallingUid();
- enforceClientValidity(uid, clientId);
- if (VDBG) {
- Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
- + ", parms=" + Arrays.toString(params.mParams));
- }
-
- if (params.mParams.length == 0) {
- throw new IllegalArgumentException("Empty ranging parameters");
- }
-
- int rangingId;
- synchronized (mLock) {
- rangingId = mNextRangingId++;
- }
- mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
- return rangingId;
+ mStateManager.requestMacAddresses(uid, peerIds, callback);
}
@Override
@@ -424,8 +404,7 @@ public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
TAG);
}
- private void enforceConnectivityInternalPermission() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
- TAG);
+ private void enforceNetworkStackPermission() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.NETWORK_STACK, TAG);
}
}
diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java
index 6ced9486..31bdff8c 100644
--- a/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -21,11 +21,11 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.wifi.V1_0.NanStatusType;
-import android.net.wifi.RttManager;
import android.net.wifi.aware.Characteristics;
import android.net.wifi.aware.ConfigRequest;
import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
import android.net.wifi.aware.PublishConfig;
import android.net.wifi.aware.SubscribeConfig;
import android.net.wifi.aware.WifiAwareManager;
@@ -48,7 +48,6 @@ import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.util.WakeupMessage;
-import com.android.server.wifi.util.NativeUtil;
import com.android.server.wifi.util.WifiPermissionsWrapper;
import libcore.util.HexEncoding;
@@ -62,6 +61,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -108,7 +108,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
private static final int COMMAND_TYPE_ENQUEUE_SEND_MESSAGE = 107;
private static final int COMMAND_TYPE_ENABLE_USAGE = 108;
private static final int COMMAND_TYPE_DISABLE_USAGE = 109;
- private static final int COMMAND_TYPE_START_RANGING = 110;
private static final int COMMAND_TYPE_GET_CAPABILITIES = 111;
private static final int COMMAND_TYPE_CREATE_ALL_DATA_PATH_INTERFACES = 112;
private static final int COMMAND_TYPE_DELETE_ALL_DATA_PATH_INTERFACES = 113;
@@ -167,7 +166,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
private static final String MESSAGE_BUNDLE_KEY_MAC_ADDRESS = "mac_address";
private static final String MESSAGE_BUNDLE_KEY_MESSAGE_DATA = "message_data";
private static final String MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID = "req_instance_id";
- private static final String MESSAGE_BUNDLE_KEY_RANGING_ID = "ranging_id";
private static final String MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME = "message_queue_time";
private static final String MESSAGE_BUNDLE_KEY_RETRY_COUNT = "retry_count";
private static final String MESSAGE_BUNDLE_KEY_SUCCESS_FLAG = "success_flag";
@@ -203,7 +201,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
private volatile Capabilities mCapabilities;
private volatile Characteristics mCharacteristics = null;
private WifiAwareStateMachine mSm;
- private WifiAwareRttStateManager mRtt;
public WifiAwareDataPathStateManager mDataPathMgr;
private PowerManager mPowerManager;
@@ -348,7 +345,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
mSm.setDbg(DBG);
mSm.start();
- mRtt = new WifiAwareRttStateManager();
mDataPathMgr = new WifiAwareDataPathStateManager(this);
mDataPathMgr.start(mContext, mSm.getHandler().getLooper(), awareMetrics,
permissionsWrapper);
@@ -417,6 +413,48 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
}
/*
+ * Cross-service API: synchronized but independent of state machine
+ */
+
+ /**
+ * Translate (and return in the callback) the peerId to its MAC address representation.
+ */
+ public void requestMacAddresses(int uid, List<Integer> peerIds,
+ IWifiAwareMacAddressProvider callback) {
+ mSm.getHandler().post(() -> {
+ if (VDBG) Log.v(TAG, "requestMacAddresses: uid=" + uid + ", peerIds=" + peerIds);
+ Map<Integer, byte[]> peerIdToMacMap = new HashMap<>();
+ for (int i = 0; i < mClients.size(); ++i) {
+ WifiAwareClientState client = mClients.valueAt(i);
+ if (client.getUid() != uid) {
+ continue;
+ }
+
+ SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
+ for (int j = 0; j < sessions.size(); ++j) {
+ WifiAwareDiscoverySessionState session = sessions.valueAt(j);
+
+ for (int peerId : peerIds) {
+ WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
+ peerId);
+ if (peerInfo != null) {
+ peerIdToMacMap.put(peerId, peerInfo.mMac);
+ }
+ }
+ }
+ }
+
+ try {
+ if (VDBG) Log.v(TAG, "requestMacAddresses: peerIdToMacMap=" + peerIdToMacMap);
+ callback.macAddress(peerIdToMacMap);
+ } catch (RemoteException e) {
+ Log.e(TAG, "requestMacAddress (sync): exception on callback -- " + e);
+
+ }
+ });
+ }
+
+ /*
* COMMANDS
*/
@@ -553,20 +591,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
}
/**
- * Place a request to range a peer on the discovery session on the state machine queue.
- */
- public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
- int rangingId) {
- Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
- msg.arg1 = COMMAND_TYPE_START_RANGING;
- msg.arg2 = clientId;
- msg.obj = params;
- msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
- msg.getData().putInt(MESSAGE_BUNDLE_KEY_RANGING_ID, rangingId);
- mSm.sendMessage(msg);
- }
-
- /**
* Enable usage of Aware. Doesn't actually turn on Aware (form clusters) - that
* only happens when a connection is created.
*/
@@ -1525,18 +1549,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
case COMMAND_TYPE_DISABLE_USAGE:
waitForResponse = disableUsageLocal(mCurrentTransactionId);
break;
- case COMMAND_TYPE_START_RANGING: {
- Bundle data = msg.getData();
-
- int clientId = msg.arg2;
- RttManager.RttParams[] params = (RttManager.RttParams[]) msg.obj;
- int sessionId = data.getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
- int rangingId = data.getInt(MESSAGE_BUNDLE_KEY_RANGING_ID);
-
- startRangingLocal(clientId, sessionId, params, rangingId);
- waitForResponse = false;
- break;
- }
case COMMAND_TYPE_GET_CAPABILITIES:
if (mCapabilities == null) {
waitForResponse = mWifiAwareNativeApi.getCapabilities(
@@ -1614,7 +1626,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
break;
case COMMAND_TYPE_DELAYED_INITIALIZATION:
mWifiAwareNativeManager.start();
- mRtt.start(mContext, mSm.getHandler().getLooper());
waitForResponse = false;
break;
default:
@@ -1827,9 +1838,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
case COMMAND_TYPE_DISABLE_USAGE:
Log.wtf(TAG, "processTimeout: DISABLE_USAGE - shouldn't be waiting!");
break;
- case COMMAND_TYPE_START_RANGING:
- Log.wtf(TAG, "processTimeout: START_RANGING - shouldn't be waiting!");
- break;
case COMMAND_TYPE_GET_CAPABILITIES:
Log.e(TAG,
"processTimeout: GET_CAPABILITIES timed-out - strange, will try again"
@@ -2308,49 +2316,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
return callDispatched;
}
- private void startRangingLocal(int clientId, int sessionId, RttManager.RttParams[] params,
- int rangingId) {
- if (VDBG) {
- Log.v(TAG, "startRangingLocal: clientId=" + clientId + ", sessionId=" + sessionId
- + ", parms=" + Arrays.toString(params) + ", rangingId=" + rangingId);
- }
-
- WifiAwareClientState client = mClients.get(clientId);
- if (client == null) {
- Log.e(TAG, "startRangingLocal: no client exists for clientId=" + clientId);
- return;
- }
-
- WifiAwareDiscoverySessionState session = client.getSession(sessionId);
- if (session == null) {
- Log.e(TAG, "startRangingLocal: no session exists for clientId=" + clientId
- + ", sessionId=" + sessionId);
- client.onRangingFailure(rangingId, RttManager.REASON_INVALID_REQUEST,
- "Invalid session ID");
- return;
- }
-
- for (RttManager.RttParams param : params) {
- String peerIdStr = param.bssid;
- try {
- WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
- Integer.parseInt(peerIdStr));
- if (peerInfo == null || peerInfo.mMac == null) {
- Log.d(TAG, "startRangingLocal: no MAC address for peer ID=" + peerIdStr);
- param.bssid = "";
- } else {
- param.bssid = NativeUtil.macAddressFromByteArray(peerInfo.mMac);
- }
- } catch (NumberFormatException e) {
- Log.e(TAG, "startRangingLocal: invalid peer ID specification (in bssid field): '"
- + peerIdStr + "'");
- param.bssid = "";
- }
- }
-
- mRtt.startRanging(rangingId, client, params);
- }
-
private boolean initiateDataPathSetupLocal(short transactionId,
WifiAwareNetworkSpecifier networkSpecifier, int peerId, int channelRequestType,
int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase,
@@ -3061,7 +3026,6 @@ public class WifiAwareStateManager implements WifiAwareShellCommand.DelegatedShe
}
pw.println(" mSettableParameters: " + mSettableParameters);
mSm.dump(fd, pw, args);
- mRtt.dump(fd, pw, args);
mDataPathMgr.dump(fd, pw, args);
mWifiAwareNativeApi.dump(fd, pw, args);
}
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
new file mode 100644
index 00000000..d30fe915
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -0,0 +1,248 @@
+/*
+ * 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 com.android.server.wifi.rtt;
+
+import android.hardware.wifi.V1_0.IWifiRttController;
+import android.hardware.wifi.V1_0.IWifiRttControllerEventCallback;
+import android.hardware.wifi.V1_0.RttBw;
+import android.hardware.wifi.V1_0.RttConfig;
+import android.hardware.wifi.V1_0.RttPeerType;
+import android.hardware.wifi.V1_0.RttPreamble;
+import android.hardware.wifi.V1_0.RttResult;
+import android.hardware.wifi.V1_0.RttStatus;
+import android.hardware.wifi.V1_0.RttType;
+import android.hardware.wifi.V1_0.WifiChannelWidthInMhz;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.wifi.ScanResult;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.wifi.HalDeviceManager;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.WifiVendorHal;
+import com.android.server.wifi.util.NativeUtil;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TBD
+ */
+public class RttNative extends IWifiRttControllerEventCallback.Stub {
+ private static final String TAG = "RttNative";
+ private static final boolean VDBG = true; // STOPSHIP if true
+
+ private final RttServiceImpl mRttService;
+ private final HalDeviceManager mHalDeviceManager;
+ private final WifiVendorHal mWifiVendorHal;
+
+ private boolean mIsInitialized = false;
+
+ public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager,
+ WifiNative wifiNative) {
+ mRttService = rttService;
+ mHalDeviceManager = halDeviceManager;
+ mWifiVendorHal = wifiNative.getVendorHal();
+ }
+
+ /**
+ * Issue a range request to the HAL.
+ *
+ * @param cmdId Command ID for the request. Will be used in the corresponding
+ * {@link #onResults(int, ArrayList)}.
+ * @param request Range request.
+ * @return Success status: true for success, false for failure.
+ */
+ public boolean rangeRequest(int cmdId, RangingRequest request) {
+ if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request);
+ // TODO: b/65014872 replace by direct access to HalDeviceManager
+ IWifiRttController rttController = mWifiVendorHal.getRttController();
+ if (rttController == null) {
+ Log.e(TAG, "rangeRequest: RttController is null");
+ return false;
+ }
+ if (!mIsInitialized) {
+ try {
+ WifiStatus status = rttController.registerEventCallback(this);
+ if (status.code != WifiStatusCode.SUCCESS) {
+ Log.e(TAG,
+ "rangeRequest: cannot register event callback -- code=" + status.code);
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "rangeRequest: exception registering callback: " + e);
+ return false;
+ }
+ mIsInitialized = true;
+ }
+
+ ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request);
+ if (rttConfig == null) {
+ Log.e(TAG, "rangeRequest: invalid request parameters");
+ return false;
+ }
+
+ try {
+ WifiStatus status = rttController.rangeRequest(cmdId, rttConfig);
+ if (status.code != WifiStatusCode.SUCCESS) {
+ Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code);
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "rangeRequest: exception issuing range request: " + e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Callback from HAL with range results.
+ *
+ * @param cmdId Command ID specified in the original request
+ * {@link #rangeRequest(int, RangingRequest)}.
+ * @param halResults A list of range results.
+ */
+ @Override
+ public void onResults(int cmdId, ArrayList<RttResult> halResults) {
+ if (VDBG) Log.v(TAG, "onResults: cmdId=" + cmdId + ", # of results=" + halResults.size());
+ List<RangingResult> results = new ArrayList<>(halResults.size());
+
+ for (RttResult halResult: halResults) {
+ results.add(new RangingResult(
+ halResult.status == RttStatus.SUCCESS ? RangingResultCallback.STATUS_SUCCESS
+ : RangingResultCallback.STATUS_FAIL, halResult.addr,
+ halResult.distanceInMm / 10, halResult.distanceSdInMm / 10, halResult.rssi,
+ halResult.timeStampInUs));
+ }
+
+ mRttService.onRangingResults(cmdId, results);
+ }
+
+ private static ArrayList<RttConfig> convertRangingRequestToRttConfigs(RangingRequest request) {
+ ArrayList<RttConfig> rttConfigs = new ArrayList<>(request.mRttPeers.size());
+
+ // Skipping any configurations which have an error (printing out a message).
+ // The caller will only get results for valid configurations.
+ for (RangingRequest.RttPeer peer: request.mRttPeers) {
+ if (peer instanceof RangingRequest.RttPeerAp) {
+ ScanResult scanResult = ((RangingRequest.RttPeerAp) peer).scanResult;
+ RttConfig config = new RttConfig();
+
+ byte[] addr = NativeUtil.macAddressToByteArray(scanResult.BSSID);
+ if (addr.length != config.addr.length) {
+ Log.e(TAG, "Invalid configuration: unexpected BSSID length -- " + scanResult);
+ continue;
+ }
+ for (int i = 0; i < config.addr.length; ++i) {
+ config.addr[i] = addr[i];
+ }
+
+ try {
+ config.type =
+ scanResult.is80211mcResponder() ? RttType.TWO_SIDED : RttType.ONE_SIDED;
+ config.peer = RttPeerType.AP;
+ config.channel.width = halChannelWidthFromScanResult(
+ scanResult.channelWidth);
+ config.channel.centerFreq = scanResult.frequency;
+ if (scanResult.centerFreq0 > 0) {
+ config.channel.centerFreq0 = scanResult.centerFreq0;
+ }
+ if (scanResult.centerFreq1 > 0) {
+ config.channel.centerFreq1 = scanResult.centerFreq1;
+ }
+ config.burstPeriod = 0;
+ config.numBurst = 0;
+ config.numFramesPerBurst = 8;
+ config.numRetriesPerRttFrame = 0;
+ config.numRetriesPerFtmr = 0;
+ config.mustRequestLci = false;
+ config.mustRequestLcr = false;
+ config.burstDuration = 15;
+ if (config.channel.centerFreq > 5000) {
+ config.preamble = RttPreamble.VHT;
+ } else {
+ config.preamble = RttPreamble.HT;
+ }
+ config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Invalid configuration: " + e.getMessage());
+ continue;
+ }
+
+ rttConfigs.add(config);
+ } else {
+ Log.e(TAG, "convertRangingRequestToRttConfigs: unknown request type -- "
+ + peer.getClass().getCanonicalName());
+ return null;
+ }
+ }
+
+
+ return rttConfigs;
+ }
+
+ static int halChannelWidthFromScanResult(int scanResultChannelWidth) {
+ switch (scanResultChannelWidth) {
+ case ScanResult.CHANNEL_WIDTH_20MHZ:
+ return WifiChannelWidthInMhz.WIDTH_20;
+ case ScanResult.CHANNEL_WIDTH_40MHZ:
+ return WifiChannelWidthInMhz.WIDTH_40;
+ case ScanResult.CHANNEL_WIDTH_80MHZ:
+ return WifiChannelWidthInMhz.WIDTH_80;
+ case ScanResult.CHANNEL_WIDTH_160MHZ:
+ return WifiChannelWidthInMhz.WIDTH_160;
+ case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+ return WifiChannelWidthInMhz.WIDTH_80P80;
+ default:
+ throw new IllegalArgumentException(
+ "halChannelWidthFromScanResult: bad " + scanResultChannelWidth);
+ }
+ }
+
+ static int halChannelBandwidthFromScanResult(int scanResultChannelWidth) {
+ switch (scanResultChannelWidth) {
+ case ScanResult.CHANNEL_WIDTH_20MHZ:
+ return RttBw.BW_20MHZ;
+ case ScanResult.CHANNEL_WIDTH_40MHZ:
+ return RttBw.BW_40MHZ;
+ case ScanResult.CHANNEL_WIDTH_80MHZ:
+ return RttBw.BW_80MHZ;
+ case ScanResult.CHANNEL_WIDTH_160MHZ:
+ return RttBw.BW_160MHZ;
+ case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+ return RttBw.BW_160MHZ;
+ default:
+ throw new IllegalArgumentException(
+ "halChannelBandwidthFromScanResult: bad " + scanResultChannelWidth);
+ }
+ }
+
+ /**
+ * Dump the internal state of the class.
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("RttNative:");
+ pw.println(" mIsInitialized: " + mIsInitialized);
+ }
+}
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
new file mode 100644
index 00000000..71e8a0ac
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.server.wifi.rtt;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.server.SystemService;
+import com.android.server.wifi.HalDeviceManager;
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+/**
+ * TBD.
+ */
+public class RttService extends SystemService {
+ private static final String TAG = "RttService";
+ private RttServiceImpl mImpl;
+
+ public RttService(Context context) {
+ super(context);
+ mImpl = new RttServiceImpl(context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.i(TAG, "Registering " + Context.WIFI_RTT2_SERVICE);
+ publishBinderService(Context.WIFI_RTT2_SERVICE, mImpl);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ Log.i(TAG, "Starting " + Context.WIFI_RTT2_SERVICE);
+
+ WifiInjector wifiInjector = WifiInjector.getInstance();
+ if (wifiInjector == null) {
+ Log.e(TAG, "onBootPhase(PHASE_SYSTEM_SERVICES_READY): NULL injector!");
+ return;
+ }
+
+ HalDeviceManager halDeviceManager = wifiInjector.getHalDeviceManager();
+ HandlerThread handlerThread = wifiInjector.getRttHandlerThread();
+ WifiNative wifiNative = wifiInjector.getWifiNative();
+ WifiPermissionsUtil wifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil();
+
+ RttNative rttNative = new RttNative(mImpl, halDeviceManager, wifiNative);
+ mImpl.start(handlerThread.getLooper(), rttNative, wifiPermissionsUtil);
+ }
+ }
+}
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
new file mode 100644
index 00000000..d248768c
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -0,0 +1,399 @@
+/*
+ * 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 com.android.server.wifi.rtt;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.rtt.IRttCallback;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.wifi.util.NativeUtil;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+/**
+ * Implementation of the IWifiRttManager AIDL interface and of the RttService state manager.
+ */
+public class RttServiceImpl extends IWifiRttManager.Stub {
+ private static final String TAG = "RttServiceImpl";
+ private static final boolean VDBG = true; // STOPSHIP if true
+
+ private final Context mContext;
+ private WifiPermissionsUtil mWifiPermissionsUtil;
+
+ private RttServiceSynchronized mRttServiceSynchronized;
+
+
+ public RttServiceImpl(Context context) {
+ mContext = context;
+ }
+
+ /*
+ * INITIALIZATION
+ */
+
+ /**
+ * Initializes the RTT service (usually with objects from an injector).
+ *
+ * @param looper The looper on which to synchronize operations.
+ * @param rttNative The Native interface to the HAL.
+ * @param wifiPermissionsUtil Utility for permission checks.
+ */
+ public void start(Looper looper, RttNative rttNative, WifiPermissionsUtil wifiPermissionsUtil) {
+ mWifiPermissionsUtil = wifiPermissionsUtil;
+ mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+ }
+
+ /*
+ * ASYNCHRONOUS DOMAIN - can be called from different threads!
+ */
+
+ /**
+ * Proxy for the final native call of the parent class. Enables mocking of
+ * the function.
+ */
+ public int getMockableCallingUid() {
+ return getCallingUid();
+ }
+
+ /**
+ * Binder interface API to start a ranging operation. Called on binder thread, operations needs
+ * to be posted to handler thread.
+ */
+ @Override
+ public void startRanging(IBinder binder, String callingPackage, RangingRequest request,
+ IRttCallback callback) throws RemoteException {
+ if (VDBG) {
+ Log.v(TAG, "startRanging: binder=" + binder + ", callingPackage=" + callingPackage
+ + ", request=" + request + ", callback=" + callback);
+ }
+ // verify arguments
+ if (binder == null) {
+ throw new IllegalArgumentException("Binder must not be null");
+ }
+ if (request == null || request.mRttPeers == null || request.mRttPeers.size() == 0) {
+ throw new IllegalArgumentException("Request must not be null or empty");
+ }
+ for (RangingRequest.RttPeer peer: request.mRttPeers) {
+ if (peer == null) {
+ throw new IllegalArgumentException(
+ "Request must not contain empty peer specifications");
+ }
+ if (!(peer instanceof RangingRequest.RttPeerAp)) {
+ throw new IllegalArgumentException(
+ "Request contains unknown peer specification types");
+ }
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback must not be null");
+ }
+ request.enforceValidity();
+
+ final int uid = getMockableCallingUid();
+
+ // permission check
+ enforceAccessPermission();
+ enforceChangePermission();
+ enforceLocationPermission(callingPackage, uid);
+
+ // register for binder death
+ IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ if (VDBG) Log.v(TAG, "binderDied: uid=" + uid);
+ binder.unlinkToDeath(this, 0);
+
+ mRttServiceSynchronized.mHandler.post(() -> {
+ mRttServiceSynchronized.cleanUpOnClientDeath(uid);
+ });
+ }
+ };
+
+ try {
+ binder.linkToDeath(dr, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error on linkToDeath - " + e);
+ }
+
+ mRttServiceSynchronized.mHandler.post(() -> {
+ mRttServiceSynchronized.queueRangingRequest(uid, binder, dr, callingPackage, request,
+ callback);
+ });
+ }
+
+ /**
+ * Called by HAL to report ranging results. Called on HAL thread - needs to post to local
+ * thread.
+ */
+ public void onRangingResults(int cmdId, List<RangingResult> results) {
+ if (VDBG) Log.v(TAG, "onRangingResults: cmdId=" + cmdId);
+ mRttServiceSynchronized.mHandler.post(() -> {
+ mRttServiceSynchronized.onRangingResults(cmdId, results);
+ });
+ }
+
+ private void enforceAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
+ }
+
+ private void enforceChangePermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
+ }
+
+ private void enforceLocationPermission(String callingPackage, int uid) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ TAG);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump RttService from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+ pw.println("Wi-Fi RTT Service");
+ mRttServiceSynchronized.dump(fd, pw, args);
+ }
+
+ /*
+ * SYNCHRONIZED DOMAIN
+ */
+
+ /**
+ * RTT service implementation - synchronized on a single thread. All commands should be posted
+ * to the exposed handler.
+ */
+ private class RttServiceSynchronized {
+ public Handler mHandler;
+
+ private RttNative mRttNative;
+ private int mNextCommandId = 1000;
+ private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
+
+ RttServiceSynchronized(Looper looper, RttNative rttNative) {
+ mRttNative = rttNative;
+
+ mHandler = new Handler(looper);
+ }
+
+ private void cleanUpOnClientDeath(int uid) {
+ if (VDBG) {
+ Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+ + ", mRttRequestQueue=" + mRttRequestQueue);
+ }
+ ListIterator<RttRequestInfo> it = mRttRequestQueue.listIterator();
+ while (it.hasNext()) {
+ RttRequestInfo rri = it.next();
+ if (rri.uid == uid) {
+ // TODO: actually abort operation - though API is not clear or clean
+ if (rri.cmdId == 0) {
+ // Until that happens we will get results for the last operation: which is
+ // why we don't dispatch a new range request off the queue and keep the
+ // currently running operation in the queue
+ it.remove();
+ }
+ }
+ }
+
+ if (VDBG) {
+ Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+ + ", after cleanup - mRttRequestQueue=" + mRttRequestQueue);
+ }
+ }
+
+ private void queueRangingRequest(int uid, IBinder binder, IBinder.DeathRecipient dr,
+ String callingPackage, RangingRequest request, IRttCallback callback) {
+ RttRequestInfo newRequest = new RttRequestInfo();
+ newRequest.uid = uid;
+ newRequest.binder = binder;
+ newRequest.dr = dr;
+ newRequest.callingPackage = callingPackage;
+ newRequest.request = request;
+ newRequest.callback = callback;
+ mRttRequestQueue.add(newRequest);
+
+ if (VDBG) {
+ Log.v(TAG, "RttServiceSynchronized.queueRangingRequest: newRequest=" + newRequest);
+ }
+
+ executeNextRangingRequestIfPossible();
+ }
+
+ private void executeNextRangingRequestIfPossible() {
+ if (mRttRequestQueue.size() == 0) {
+ if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: no requests pending");
+ return;
+ }
+
+ RttRequestInfo nextRequest = mRttRequestQueue.get(0);
+ if (nextRequest.cmdId != 0) {
+ if (VDBG) {
+ Log.v(TAG, "executeNextRangingRequestIfPossible: called but a command is "
+ + "executing. topOfQueue=" + nextRequest);
+ }
+ return;
+ }
+
+ nextRequest.cmdId = mNextCommandId++;
+ startRanging(nextRequest);
+ }
+
+ private void startRanging(RttRequestInfo nextRequest) {
+ if (VDBG) {
+ Log.v(TAG, "RttServiceSynchronized.startRanging: nextRequest=" + nextRequest);
+ }
+
+ if (!mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
+ Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
+ try {
+ nextRequest.callback.onRangingResults(RangingResultCallback.STATUS_FAIL, null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RttServiceSynchronized.startRanging: HAL request failed, callback "
+ + "failed -- " + e);
+ }
+
+ mRttRequestQueue.remove(0);
+ executeNextRangingRequestIfPossible();
+ }
+ }
+
+ private void onRangingResults(int cmdId, List<RangingResult> results) {
+ if (mRttRequestQueue.size() == 0) {
+ Log.e(TAG, "RttServiceSynchronized.onRangingResults: no current RTT request "
+ + "pending!?");
+ return;
+ }
+ RttRequestInfo topOfQueueRequest = mRttRequestQueue.get(0);
+
+ if (VDBG) {
+ Log.v(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
+ + ", topOfQueueRequest=" + topOfQueueRequest + ", results="
+ + Arrays.toString(results.toArray()));
+ }
+
+ if (topOfQueueRequest.cmdId != cmdId) {
+ Log.e(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
+ + ", does not match pending RTT request cmdId=" + topOfQueueRequest.cmdId);
+ return;
+ }
+
+ boolean permissionGranted = mWifiPermissionsUtil.checkCallersLocationPermission(
+ topOfQueueRequest.callingPackage, topOfQueueRequest.uid);
+ try {
+ if (permissionGranted) {
+ addMissingEntries(topOfQueueRequest.request, results);
+ topOfQueueRequest.callback.onRangingResults(
+ RangingResultCallback.STATUS_SUCCESS, results);
+ } else {
+ Log.w(TAG, "RttServiceSynchronized.onRangingResults: location permission "
+ + "revoked - not forwarding results");
+ topOfQueueRequest.callback.onRangingResults(RangingResultCallback.STATUS_FAIL,
+ null);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG,
+ "RttServiceSynchronized.onRangingResults: callback exception -- " + e);
+ }
+
+ // clean-up binder death listener: the callback for results is a onetime event - now
+ // done with the binder.
+ topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
+
+ mRttRequestQueue.remove(0);
+ executeNextRangingRequestIfPossible();
+ }
+
+ /*
+ * Make sure the results contain an entry for each request. Add results with FAIL status
+ * if missing.
+ */
+ private void addMissingEntries(RangingRequest request,
+ List<RangingResult> results) {
+ Set<String> resultEntries = new HashSet<>(results.size());
+ for (RangingResult result: results) {
+ resultEntries.add(new String(HexEncoding.encode(result.getMacAddress())));
+ }
+
+ for (RangingRequest.RttPeer peer: request.mRttPeers) {
+ byte[] addr;
+ if (peer instanceof RangingRequest.RttPeerAp) {
+ addr = NativeUtil.macAddressToByteArray(
+ ((RangingRequest.RttPeerAp) peer).scanResult.BSSID);
+ } else {
+ continue;
+ }
+ String canonicString = new String(HexEncoding.encode(addr));
+
+ if (!resultEntries.contains(canonicString)) {
+ if (VDBG) {
+ Log.v(TAG, "padRangingResultsWithMissingResults: missing=" + canonicString);
+ }
+ results.add(new RangingResult(RangingResultCallback.STATUS_FAIL, addr, 0, 0, 0,
+ 0));
+ }
+ }
+ }
+
+ // dump call (asynchronous most likely)
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" mNextCommandId: " + mNextCommandId);
+ pw.println(" mRttRequestQueue: " + mRttRequestQueue);
+ mRttNative.dump(fd, pw, args);
+ }
+ }
+
+ private static class RttRequestInfo {
+ public int uid;
+ public IBinder binder;
+ public IBinder.DeathRecipient dr;
+ public String callingPackage;
+ public RangingRequest request;
+ public byte[] mac;
+ public IRttCallback callback;
+
+ public int cmdId = 0; // uninitialized cmdId value
+
+ @Override
+ public String toString() {
+ return new StringBuilder("RttRequestInfo: uid=").append(uid).append(", binder=").append(
+ binder).append(", dr=").append(dr).append(", callingPackage=").append(
+ callingPackage).append(", request=").append(request.toString()).append(
+ ", callback=").append(callback).append(", cmdId=").append(cmdId).toString();
+ }
+ }
+}
diff --git a/com/android/server/wifi/scanner/ChannelHelper.java b/com/android/server/wifi/scanner/ChannelHelper.java
index d87df072..6a01f0c8 100644
--- a/com/android/server/wifi/scanner/ChannelHelper.java
+++ b/com/android/server/wifi/scanner/ChannelHelper.java
@@ -242,7 +242,7 @@ public abstract class ChannelHelper {
if (scanSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
return toString(scanSettings.channels);
} else {
- return toString(scanSettings.band);
+ return bandToString(scanSettings.band);
}
}
@@ -255,7 +255,7 @@ public abstract class ChannelHelper {
if (bucketSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
return toString(bucketSettings.channels, bucketSettings.num_channels);
} else {
- return toString(bucketSettings.band);
+ return bandToString(bucketSettings.band);
}
}
@@ -293,7 +293,10 @@ public abstract class ChannelHelper {
return sb.toString();
}
- private static String toString(int band) {
+ /**
+ * Converts a WifiScanner.WIFI_BAND_* constant to a meaningful String
+ */
+ public static String bandToString(int band) {
switch (band) {
case WifiScanner.WIFI_BAND_UNSPECIFIED:
return "unspecified";
@@ -310,7 +313,6 @@ public abstract class ChannelHelper {
case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
return "24Ghz & 5Ghz (DFS incl)";
}
-
return "invalid band";
}
}
diff --git a/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index 7d0ccbaf..890f72e6 100644
--- a/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -131,11 +131,6 @@ public class HalWifiScannerImpl extends WifiScannerImpl implements Handler.Callb
}
@Override
- public boolean shouldScheduleBackgroundScanForHwPno() {
- return mWificondScannerDelegate.shouldScheduleBackgroundScanForHwPno();
- }
-
- @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mWificondScannerDelegate.dump(fd, pw, args);
}
diff --git a/com/android/server/wifi/scanner/WifiScannerImpl.java b/com/android/server/wifi/scanner/WifiScannerImpl.java
index 5281b3ab..dacb0074 100644
--- a/com/android/server/wifi/scanner/WifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/WifiScannerImpl.java
@@ -155,11 +155,5 @@ public abstract class WifiScannerImpl {
*/
public abstract boolean isHwPnoSupported(boolean isConnectedPno);
- /**
- * This returns whether a background scan should be running for HW PNO scan or not.
- * @return true if background scan needs to be started, false otherwise.
- */
- public abstract boolean shouldScheduleBackgroundScanForHwPno();
-
protected abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
}
diff --git a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index 4b8e284c..02462f67 100644
--- a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -75,7 +75,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
private static final String TAG = WifiScanningService.TAG;
private static final boolean DBG = false;
- private static final int MIN_PERIOD_PER_CHANNEL_MS = 200; // DFS needs 120 ms
private static final int UNKNOWN_PID = -1;
private final LocalLog mLocalLog = new LocalLog(512);
@@ -240,10 +239,6 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
private static final int CMD_SCAN_RESULTS_AVAILABLE = BASE + 0;
private static final int CMD_FULL_SCAN_RESULTS = BASE + 1;
- private static final int CMD_HOTLIST_AP_FOUND = BASE + 2;
- private static final int CMD_HOTLIST_AP_LOST = BASE + 3;
- private static final int CMD_WIFI_CHANGE_DETECTED = BASE + 4;
- private static final int CMD_WIFI_CHANGE_TIMEOUT = BASE + 5;
private static final int CMD_DRIVER_LOADED = BASE + 6;
private static final int CMD_DRIVER_UNLOADED = BASE + 7;
private static final int CMD_SCAN_PAUSED = BASE + 8;
@@ -1724,10 +1719,7 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
}
logScanRequest("addHwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings);
addPnoScanRequest(ci, handler, scanSettings, pnoSettings);
- // HW PNO is supported, check if we need a background scan running for this.
- if (mScannerImpl.shouldScheduleBackgroundScanForHwPno()) {
- addBackgroundScanRequest(scanSettings);
- }
+
return true;
}
@@ -2213,7 +2205,7 @@ public class WifiScanningServiceImpl extends IWifiScanner.Stub {
static String describeTo(StringBuilder sb, ScanSettings scanSettings) {
sb.append("ScanSettings { ")
- .append(" band:").append(scanSettings.band)
+ .append(" band:").append(ChannelHelper.bandToString(scanSettings.band))
.append(" period:").append(scanSettings.periodInMs)
.append(" reportEvents:").append(scanSettings.reportEvents)
.append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan)
diff --git a/com/android/server/wifi/scanner/WificondScannerImpl.java b/com/android/server/wifi/scanner/WificondScannerImpl.java
index 12a0bdee..fb878e67 100644
--- a/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -34,7 +34,6 @@ import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -50,7 +49,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
private static final String TAG = "WificondScannerImpl";
private static final boolean DBG = false;
- public static final String BACKGROUND_PERIOD_ALARM_TAG = TAG + " Background Scan Period";
public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
// Max number of networks that can be specified to wificond per scan request
public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
@@ -69,20 +67,9 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
private final Object mSettingsLock = new Object();
// Next scan settings to apply when the previous scan completes
- private WifiNative.ScanSettings mPendingBackgroundScanSettings = null;
- private WifiNative.ScanEventHandler mPendingBackgroundScanEventHandler = null;
private WifiNative.ScanSettings mPendingSingleScanSettings = null;
private WifiNative.ScanEventHandler mPendingSingleScanEventHandler = null;
- // Active background scan settings/state
- private WifiNative.ScanSettings mBackgroundScanSettings = null;
- private WifiNative.ScanEventHandler mBackgroundScanEventHandler = null;
- private int mNextBackgroundScanPeriod = 0;
- private int mNextBackgroundScanId = 0;
- private boolean mBackgroundScanPeriodPending = false;
- private boolean mBackgroundScanPaused = false;
- private ScanBuffer mBackgroundScanBuffer = new ScanBuffer(SCAN_BUFFER_CAPACITY);
-
private ArrayList<ScanDetail> mNativeScanResults;
private WifiScanner.ScanData mLatestSingleScanResult =
new WifiScanner.ScanData(0, 0, new ScanResult[0]);
@@ -110,14 +97,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
*/
private static final long SCAN_TIMEOUT_MS = 15000;
- AlarmManager.OnAlarmListener mScanPeriodListener = new AlarmManager.OnAlarmListener() {
- public void onAlarm() {
- synchronized (mSettingsLock) {
- handleScanPeriod();
- }
- }
- };
-
AlarmManager.OnAlarmListener mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
public void onAlarm() {
synchronized (mSettingsLock) {
@@ -161,7 +140,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
mPendingSingleScanSettings = null;
mPendingSingleScanEventHandler = null;
stopHwPnoScan();
- stopBatchedScan();
mLastScanSettings = null; // finally clear any active scan
}
}
@@ -210,111 +188,23 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
@Override
public boolean startBatchedScan(WifiNative.ScanSettings settings,
WifiNative.ScanEventHandler eventHandler) {
- if (settings == null || eventHandler == null) {
- Log.w(TAG, "Invalid arguments for startBatched: settings=" + settings
- + ",eventHandler=" + eventHandler);
- return false;
- }
-
- if (settings.max_ap_per_scan < 0 || settings.max_ap_per_scan > MAX_APS_PER_SCAN) {
- return false;
- }
- if (settings.num_buckets < 0 || settings.num_buckets > MAX_SCAN_BUCKETS) {
- return false;
- }
- if (settings.report_threshold_num_scans < 0
- || settings.report_threshold_num_scans > SCAN_BUFFER_CAPACITY) {
- return false;
- }
- if (settings.report_threshold_percent < 0 || settings.report_threshold_percent > 100) {
- return false;
- }
- if (settings.base_period_ms <= 0) {
- return false;
- }
- for (int i = 0; i < settings.num_buckets; ++i) {
- WifiNative.BucketSettings bucket = settings.buckets[i];
- if (bucket.period_ms % settings.base_period_ms != 0) {
- return false;
- }
- }
-
- synchronized (mSettingsLock) {
- stopBatchedScan();
- if (DBG) {
- Log.d(TAG, "Starting scan num_buckets=" + settings.num_buckets + ", base_period="
- + settings.base_period_ms + " ms");
- }
- mPendingBackgroundScanSettings = settings;
- mPendingBackgroundScanEventHandler = eventHandler;
- handleScanPeriod(); // Try to start scan immediately
- return true;
- }
+ Log.w(TAG, "startBatchedScan() is not supported");
+ return false;
}
@Override
public void stopBatchedScan() {
- synchronized (mSettingsLock) {
- if (DBG) Log.d(TAG, "Stopping scan");
- mBackgroundScanSettings = null;
- mBackgroundScanEventHandler = null;
- mPendingBackgroundScanSettings = null;
- mPendingBackgroundScanEventHandler = null;
- mBackgroundScanPaused = false;
- mBackgroundScanPeriodPending = false;
- unscheduleScansLocked();
- }
- processPendingScans();
+ Log.w(TAG, "stopBatchedScan() is not supported");
}
@Override
public void pauseBatchedScan() {
- synchronized (mSettingsLock) {
- if (DBG) Log.d(TAG, "Pausing scan");
- // if there isn't a pending scan then make the current scan pending
- if (mPendingBackgroundScanSettings == null) {
- mPendingBackgroundScanSettings = mBackgroundScanSettings;
- mPendingBackgroundScanEventHandler = mBackgroundScanEventHandler;
- }
- mBackgroundScanSettings = null;
- mBackgroundScanEventHandler = null;
- mBackgroundScanPeriodPending = false;
- mBackgroundScanPaused = true;
-
- unscheduleScansLocked();
-
- WifiScanner.ScanData[] results = getLatestBatchedScanResults(/* flush = */ true);
- if (mPendingBackgroundScanEventHandler != null) {
- mPendingBackgroundScanEventHandler.onScanPaused(results);
- }
- }
- processPendingScans();
+ Log.w(TAG, "pauseBatchedScan() is not supported");
}
@Override
public void restartBatchedScan() {
- synchronized (mSettingsLock) {
- if (DBG) Log.d(TAG, "Restarting scan");
- if (mPendingBackgroundScanEventHandler != null) {
- mPendingBackgroundScanEventHandler.onScanRestarted();
- }
- mBackgroundScanPaused = false;
- handleScanPeriod();
- }
- }
-
- private void unscheduleScansLocked() {
- mAlarmManager.cancel(mScanPeriodListener);
- if (mLastScanSettings != null) {
- mLastScanSettings.backgroundScanActive = false;
- }
- }
-
- private void handleScanPeriod() {
- synchronized (mSettingsLock) {
- mBackgroundScanPeriodPending = true;
- processPendingScans();
- }
+ Log.w(TAG, "restartBatchedScan() is not supported");
}
private void handleScanTimeout() {
@@ -342,56 +232,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
final LastScanSettings newScanSettings =
new LastScanSettings(mClock.getElapsedSinceBootMillis());
- // Update scan settings if there is a pending scan
- if (!mBackgroundScanPaused) {
- if (mPendingBackgroundScanSettings != null) {
- mBackgroundScanSettings = mPendingBackgroundScanSettings;
- mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler;
- mNextBackgroundScanPeriod = 0;
- mPendingBackgroundScanSettings = null;
- mPendingBackgroundScanEventHandler = null;
- mBackgroundScanPeriodPending = true;
- }
- if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) {
- int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch
- for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets;
- ++bucket_id) {
- WifiNative.BucketSettings bucket =
- mBackgroundScanSettings.buckets[bucket_id];
- if (mNextBackgroundScanPeriod % (bucket.period_ms
- / mBackgroundScanSettings.base_period_ms) == 0) {
- if ((bucket.report_events
- & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
- reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
- }
- if ((bucket.report_events
- & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
- reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
- }
- // only no batch if all buckets specify it
- if ((bucket.report_events
- & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
- reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH;
- }
-
- allFreqs.addChannels(bucket);
- }
- }
- if (!allFreqs.isEmpty()) {
- newScanSettings.setBackgroundScan(mNextBackgroundScanId++,
- mBackgroundScanSettings.max_ap_per_scan, reportEvents,
- mBackgroundScanSettings.report_threshold_num_scans,
- mBackgroundScanSettings.report_threshold_percent);
- }
- mNextBackgroundScanPeriod++;
- mBackgroundScanPeriodPending = false;
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- mClock.getElapsedSinceBootMillis()
- + mBackgroundScanSettings.base_period_ms,
- BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
- }
- }
-
if (mPendingSingleScanSettings != null) {
boolean reportFullResults = false;
ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection();
@@ -422,16 +262,26 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
mPendingSingleScanEventHandler = null;
}
- if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
- && !allFreqs.isEmpty()) {
- pauseHwPnoScan();
- Set<Integer> freqs = allFreqs.getScanFreqs();
- boolean success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+ if (newScanSettings.singleScanActive) {
+ boolean success = false;
+ Set<Integer> freqs;
+ if (!allFreqs.isEmpty()) {
+ pauseHwPnoScan();
+ freqs = allFreqs.getScanFreqs();
+ success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+ if (!success) {
+ Log.e(TAG, "Failed to start scan, freqs=" + freqs);
+ }
+ } else {
+ // There is a scan request but no available channels could be scanned for.
+ // We regard it as a scan failure in this case.
+ Log.e(TAG, "Failed to start scan because there is "
+ + "no available channel to scan for");
+ }
if (success) {
// TODO handle scan timeout
if (DBG) {
Log.d(TAG, "Starting wifi scan for freqs=" + freqs
- + ", background=" + newScanSettings.backgroundScanActive
+ ", single=" + newScanSettings.singleScanActive);
}
mLastScanSettings = newScanSettings;
@@ -439,7 +289,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
} else {
- Log.e(TAG, "Failed to start scan, freqs=" + freqs);
// indicate scan failure async
mEventHandler.post(new Runnable() {
public void run() {
@@ -449,7 +298,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
}
}
});
- // TODO(b/27769665) background scans should be failed too if scans fail enough
}
} else if (isHwPnoScanRequired()) {
newScanSettings.setHwPnoScan(mPnoSettings.networkList, mPnoEventHandler);
@@ -512,7 +360,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
mLastScanSettings.singleScanEventHandler
.onScanStatus(WifiNative.WIFI_SCAN_FAILED);
}
- // TODO(b/27769665) background scans should be failed too if scans fail enough
mLastScanSettings = null;
}
}
@@ -597,18 +444,13 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
return;
}
- if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
mNativeScanResults = mWifiNative.getScanResults();
List<ScanResult> singleScanResults = new ArrayList<>();
- List<ScanResult> backgroundScanResults = new ArrayList<>();
int numFilteredScanResults = 0;
for (int i = 0; i < mNativeScanResults.size(); ++i) {
ScanResult result = mNativeScanResults.get(i).getScanResult();
long timestamp_ms = result.timestamp / 1000; // convert us -> ms
if (timestamp_ms > mLastScanSettings.startTime) {
- if (mLastScanSettings.backgroundScanActive) {
- backgroundScanResults.add(result);
- }
if (mLastScanSettings.singleScanActive
&& mLastScanSettings.singleScanFreqs.containsChannel(
result.frequency)) {
@@ -622,49 +464,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
}
- if (mLastScanSettings.backgroundScanActive) {
- if (mBackgroundScanEventHandler != null) {
- if ((mLastScanSettings.reportEvents
- & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
- for (ScanResult scanResult : backgroundScanResults) {
- // TODO(b/27506257): Fill in correct bucketsScanned value
- mBackgroundScanEventHandler.onFullScanResult(scanResult, 0);
- }
- }
- }
-
- Collections.sort(backgroundScanResults, SCAN_RESULT_SORT_COMPARATOR);
- ScanResult[] scanResultsArray = new ScanResult[Math.min(mLastScanSettings.maxAps,
- backgroundScanResults.size())];
- for (int i = 0; i < scanResultsArray.length; ++i) {
- scanResultsArray[i] = backgroundScanResults.get(i);
- }
-
- if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
- // TODO(b/27506257): Fill in correct bucketsScanned value
- mBackgroundScanBuffer.add(new WifiScanner.ScanData(mLastScanSettings.scanId, 0,
- scanResultsArray));
- }
-
- if (mBackgroundScanEventHandler != null) {
- if ((mLastScanSettings.reportEvents
- & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
- || (mLastScanSettings.reportEvents
- & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0
- || (mLastScanSettings.reportEvents
- == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL
- && (mBackgroundScanBuffer.size()
- >= (mBackgroundScanBuffer.capacity()
- * mLastScanSettings.reportPercentThreshold
- / 100)
- || mBackgroundScanBuffer.size()
- >= mLastScanSettings.reportNumScansThreshold))) {
- mBackgroundScanEventHandler
- .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
- }
- }
- }
-
if (mLastScanSettings.singleScanActive
&& mLastScanSettings.singleScanEventHandler != null) {
if (mLastScanSettings.reportSingleScanFullResults) {
@@ -675,7 +474,7 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
}
}
Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
- mLatestSingleScanResult = new WifiScanner.ScanData(mLastScanSettings.scanId, 0, 0,
+ mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
isAllChannelsScanned(mLastScanSettings.singleScanFreqs),
singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
mLastScanSettings.singleScanEventHandler
@@ -689,13 +488,7 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
@Override
public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
- synchronized (mSettingsLock) {
- WifiScanner.ScanData[] results = mBackgroundScanBuffer.get();
- if (flush) {
- mBackgroundScanBuffer.clear();
- }
- return results;
- }
+ return null;
}
private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
@@ -768,11 +561,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
}
@Override
- public boolean shouldScheduleBackgroundScanForHwPno() {
- return false;
- }
-
- @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mSettingsLock) {
pw.println("Latest native scan results:");
@@ -813,24 +601,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
this.startTime = startTime;
}
- // Background settings
- public boolean backgroundScanActive = false;
- public int scanId;
- public int maxAps;
- public int reportEvents;
- public int reportNumScansThreshold;
- public int reportPercentThreshold;
-
- public void setBackgroundScan(int scanId, int maxAps, int reportEvents,
- int reportNumScansThreshold, int reportPercentThreshold) {
- this.backgroundScanActive = true;
- this.scanId = scanId;
- this.maxAps = maxAps;
- this.reportEvents = reportEvents;
- this.reportNumScansThreshold = reportNumScansThreshold;
- this.reportPercentThreshold = reportPercentThreshold;
- }
-
// Single scan settings
public boolean singleScanActive = false;
public boolean reportSingleScanFullResults;
@@ -859,44 +629,6 @@ public class WificondScannerImpl extends WifiScannerImpl implements Handler.Call
}
}
-
- private static class ScanBuffer {
- private final ArrayDeque<WifiScanner.ScanData> mBuffer;
- private int mCapacity;
-
- ScanBuffer(int capacity) {
- mCapacity = capacity;
- mBuffer = new ArrayDeque<>(mCapacity);
- }
-
- public int size() {
- return mBuffer.size();
- }
-
- public int capacity() {
- return mCapacity;
- }
-
- public boolean isFull() {
- return size() == mCapacity;
- }
-
- public void add(WifiScanner.ScanData scanData) {
- if (isFull()) {
- mBuffer.pollFirst();
- }
- mBuffer.offerLast(scanData);
- }
-
- public void clear() {
- mBuffer.clear();
- }
-
- public WifiScanner.ScanData[] get() {
- return mBuffer.toArray(new WifiScanner.ScanData[mBuffer.size()]);
- }
- }
-
/**
* HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO
* state too often which is not handled very well by some drivers.
diff --git a/com/android/server/wifi/util/KalmanFilter.java b/com/android/server/wifi/util/KalmanFilter.java
new file mode 100644
index 00000000..b961ed80
--- /dev/null
+++ b/com/android/server/wifi/util/KalmanFilter.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.android.server.wifi.util;
+
+/**
+ * Utility providiing a basic Kalman filter
+ *
+ * For background, see https://en.wikipedia.org/wiki/Kalman_filter
+ */
+public class KalmanFilter {
+ public Matrix mF; // stateTransition
+ public Matrix mQ; // processNoiseCovariance
+ public Matrix mH; // observationModel
+ public Matrix mR; // observationNoiseCovariance
+ public Matrix mP; // aPosterioriErrorCovariance
+ public Matrix mx; // stateEstimate
+
+ /**
+ * Performs the prediction phase of the filter, using the state estimate to produce
+ * a new estimate for the current timestep.
+ */
+ public void predict() {
+ mx = mF.dot(mx);
+ mP = mF.dot(mP).dotTranspose(mF).plus(mQ);
+ }
+
+ /**
+ * Updates the state estimate to incorporate the new observation z.
+ */
+ public void update(Matrix z) {
+ Matrix y = z.minus(mH.dot(mx));
+ Matrix tS = mH.dot(mP).dotTranspose(mH).plus(mR);
+ Matrix tK = mP.dotTranspose(mH).dot(tS.inverse());
+ mx = mx.plus(tK.dot(y));
+ mP = mP.minus(tK.dot(mH).dot(mP));
+ }
+
+ @Override
+ public String toString() {
+ return "{F: " + mF
+ + " Q: " + mQ
+ + " H: " + mH
+ + " R: " + mR
+ + " P: " + mP
+ + " x: " + mx
+ + "}";
+ }
+}
diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java
index c76b9059..5365e27d 100644
--- a/com/android/server/wm/AppWindowAnimator.java
+++ b/com/android/server/wm/AppWindowAnimator.java
@@ -16,10 +16,10 @@
package com.android.server.wm;
-import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -78,11 +78,8 @@ public class AppWindowAnimator {
// requires that the duration of the two animations are the same.
SurfaceControl thumbnail;
int thumbnailTransactionSeq;
- // TODO(b/62029108): combine both members into a private one. Create a member function to set
- // the thumbnail layer to +1 to the highest layer position and replace all setter instances
- // with this function. Remove all unnecessary calls to both variables in other classes.
- int thumbnailLayer;
- int thumbnailForceAboveLayer;
+ private int mThumbnailLayer;
+
Animation thumbnailAnimation;
final Transformation thumbnailTransformation = new Transformation();
// This flag indicates that the destruction of the thumbnail surface is synchronized with
@@ -256,7 +253,7 @@ public class AppWindowAnimator {
private void updateLayers() {
mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
- thumbnailLayer = mAppToken.getHighestAnimLayer();
+ updateThumbnailLayer();
}
private void stepThumbnailAnimation(long currentTime) {
@@ -280,26 +277,35 @@ public class AppWindowAnimator {
thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
"thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
- + " layer=" + thumbnailLayer
+ + " layer=" + mThumbnailLayer
+ " matrix=[" + tmpFloats[Matrix.MSCALE_X]
+ "," + tmpFloats[Matrix.MSKEW_Y]
+ "][" + tmpFloats[Matrix.MSKEW_X]
+ "," + tmpFloats[Matrix.MSCALE_Y] + "]");
thumbnail.setAlpha(thumbnailTransformation.getAlpha());
- if (thumbnailForceAboveLayer > 0) {
- thumbnail.setLayer(thumbnailForceAboveLayer + 1);
- } else {
- // The thumbnail is layered below the window immediately above this
- // token's anim layer.
- thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
- - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
- }
+ updateThumbnailLayer();
thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
}
/**
+ * Updates the thumbnail layer z order to just above the highest animation layer if changed
+ */
+ void updateThumbnailLayer() {
+ if (thumbnail != null) {
+ final int layer = mAppToken.getHighestAnimLayer();
+ if (layer != mThumbnailLayer) {
+ if (DEBUG_LAYERS) Slog.v(TAG,
+ "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
+ thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+ - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+ mThumbnailLayer = layer;
+ }
+ }
+ }
+
+ /**
* Sometimes we need to synchronize the first frame of animation with some external event, e.g.
* Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
* and keep producing the first frame of the animation.
@@ -473,7 +479,7 @@ public class AppWindowAnimator {
}
if (thumbnail != null) {
pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
- pw.print(" layer="); pw.println(thumbnailLayer);
+ pw.print(" layer="); pw.println(mThumbnailLayer);
pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
pw.print(prefix); pw.print("thumbnailTransformation=");
pw.println(thumbnailTransformation.toShortString());
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index ea2f3059..a1eeff84 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -16,8 +16,7 @@
package com.android.server.wm;
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -53,6 +52,7 @@ import static com.android.server.wm.WindowManagerService.logWithStack;
import static com.android.server.wm.proto.AppWindowTokenProto.NAME;
import static com.android.server.wm.proto.AppWindowTokenProto.WINDOW_TOKEN;
+import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.Activity;
import android.content.res.Configuration;
@@ -1170,7 +1170,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
wAppAnimator.thumbnail.destroy();
}
wAppAnimator.thumbnail = tAppAnimator.thumbnail;
- wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;
wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
tAppAnimator.thumbnail = null;
}
@@ -1311,7 +1310,8 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
// Notify the pinned stack upon all windows drawn. If there was an animation in
// progress then this signal will resume that animation.
- final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
+ final TaskStack pinnedStack =
+ mDisplayContent.getStack(WINDOWING_MODE_PINNED);
if (pinnedStack != null) {
pinnedStack.onAllWindowsDrawn();
}
@@ -1620,8 +1620,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree
}
}
+ @CallSuper
@Override
- void writeToProto(ProtoOutputStream proto, long fieldId) {
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
writeNameToProto(proto, NAME);
super.writeToProto(proto, WINDOW_TOKEN);
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index 28ba9b3d..9e028d38 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -21,12 +21,19 @@ 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.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
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;
import static android.app.WindowConfiguration.activityTypeToString;
+import static com.android.server.wm.proto.ConfigurationContainerProto.FULL_CONFIGURATION;
+import static com.android.server.wm.proto.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
+import static com.android.server.wm.proto.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
+import android.annotation.CallSuper;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
+import android.util.proto.ProtoOutputStream;
import java.util.ArrayList;
@@ -147,6 +154,17 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
onOverrideConfigurationChanged(mTmpConfig);
}
+ /**
+ * Returns true if this container is currently in multi-window mode. I.e. sharing the screen
+ * with another activity.
+ */
+ public boolean inMultiWindowMode() {
+ /*@WindowConfiguration.WindowingMode*/ int windowingMode =
+ mFullConfiguration.windowConfiguration.getWindowingMode();
+ return windowingMode != WINDOWING_MODE_FULLSCREEN
+ && windowingMode != WINDOWING_MODE_UNDEFINED;
+ }
+
/** Returns true if this container is currently in split-screen windowing mode. */
public boolean inSplitScreenWindowingMode() {
/*@WindowConfiguration.WindowingMode*/ int windowingMode =
@@ -170,7 +188,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
* {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
* its current state.
*/
- public boolean supportSplitScreenWindowingMode() {
+ public boolean supportsSplitScreenWindowingMode() {
return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
}
@@ -220,9 +238,48 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
/*@WindowConfiguration.ActivityType*/ int thisType = getActivityType();
/*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType();
- return thisType == otherType
- || thisType == ACTIVITY_TYPE_UNDEFINED
- || otherType == ACTIVITY_TYPE_UNDEFINED;
+ if (thisType == otherType) {
+ return true;
+ }
+ if (thisType == ACTIVITY_TYPE_ASSISTANT) {
+ // Assistant activities are only compatible with themselves...
+ return false;
+ }
+ // Otherwise we are compatible if us or other is not currently defined.
+ return thisType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED;
+ }
+
+ /**
+ * Returns true if this container is compatible with the input windowing mode and activity type.
+ * The container is compatible:
+ * - If {@param activityType} and {@param windowingMode} match this container activity type and
+ * windowing mode.
+ * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
+ * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also
+ * standard or undefined and its windowing mode matches {@param windowingMode}.
+ * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
+ * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't
+ * also standard or undefined and its activity type matches {@param activityType} regardless of
+ * if {@param windowingMode} matches the containers windowing mode.
+ */
+ public boolean isCompatible(int windowingMode, int activityType) {
+ final int thisActivityType = getActivityType();
+ final int thisWindowingMode = getWindowingMode();
+ final boolean sameActivityType = thisActivityType == activityType;
+ final boolean sameWindowingMode = thisWindowingMode == windowingMode;
+
+ if (sameActivityType && sameWindowingMode) {
+ return true;
+ }
+
+ if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD)
+ || !isActivityTypeStandardOrUndefined()) {
+ // Only activity type need to match for non-standard activity types that are defined.
+ return sameActivityType;
+ }
+
+ // Otherwise we are compatible if the windowing mode is the same.
+ return sameWindowingMode;
}
public void registerConfigurationChangeListener(ConfigurationContainerListener listener) {
@@ -252,6 +309,24 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
}
}
+ /**
+ * Write to a protocol buffer output stream. Protocol buffer message definition is at
+ * {@link com.android.server.wm.proto.ConfigurationContainerProto}.
+ *
+ * @param protoOutputStream Stream to write the ConfigurationContainer object to.
+ * @param fieldId Field Id of the ConfigurationContainer as defined in the parent
+ * message.
+ * @hide
+ */
+ @CallSuper
+ public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+ final long token = protoOutputStream.start(fieldId);
+ mOverrideConfiguration.writeToProto(protoOutputStream, OVERRIDE_CONFIGURATION);
+ mFullConfiguration.writeToProto(protoOutputStream, FULL_CONFIGURATION);
+ mMergedOverrideConfiguration.writeToProto(protoOutputStream, MERGED_OVERRIDE_CONFIGURATION);
+ protoOutputStream.end(token);
+ }
+
abstract protected int getChildCount();
abstract protected E getChildAt(int index);
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 6cf608ab..0e68a8f6 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -18,9 +18,11 @@ package com.android.server.wm;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
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.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -112,10 +114,11 @@ import static com.android.server.wm.proto.DisplayProto.PINNED_STACK_CONTROLLER;
import static com.android.server.wm.proto.DisplayProto.ROTATION;
import static com.android.server.wm.proto.DisplayProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.proto.DisplayProto.STACKS;
+import static com.android.server.wm.proto.DisplayProto.WINDOW_CONTAINER;
+import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.ActivityManager.StackId;
-import android.app.WindowConfiguration;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -389,10 +392,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
!= mTmpWindowAnimator.mAnimTransactionSequence) {
appAnimator.thumbnailTransactionSeq =
mTmpWindowAnimator.mAnimTransactionSequence;
- appAnimator.thumbnailLayer = 0;
- }
- if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
- appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
}
}
};
@@ -966,10 +965,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
final int lastOrientation = mLastOrientation;
final boolean oldAltOrientation = mAltOrientation;
int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation);
- final boolean rotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
+ boolean mayRotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
rotation);
- if (rotateSeamlessly) {
+ if (mayRotateSeamlessly) {
final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
if (seamlessRotated != null) {
// We can't rotate (seamlessly or not) while waiting for the last seamless rotation
@@ -978,7 +977,20 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// window-removal.
return false;
}
+
+ // In the presence of the PINNED stack or System Alert
+ // windows we unforuntately can not seamlessly rotate.
+ if (getStackById(PINNED_STACK_ID) != null) {
+ mayRotateSeamlessly = false;
+ }
+ for (int i = 0; i < mService.mSessions.size(); i++) {
+ if (mService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {
+ mayRotateSeamlessly = false;
+ break;
+ }
+ }
}
+ final boolean rotateSeamlessly = mayRotateSeamlessly;
// TODO: Implement forced rotation changes.
// Set mAltOrientation to indicate that the application is receiving
@@ -1455,16 +1467,38 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return null;
}
+ /**
+ * Returns the topmost stack on the display that is compatible with the input windowing mode.
+ * Null is no compatible stack on the display.
+ */
+ TaskStack getStack(int windowingMode) {
+ return getStack(windowingMode, ACTIVITY_TYPE_UNDEFINED);
+ }
+
+ /**
+ * Returns the topmost stack on the display that is compatible with the input windowing mode and
+ * activity type. Null is no compatible stack on the display.
+ */
+ TaskStack getStack(int windowingMode, int activityType) {
+ for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+ final TaskStack stack = mTaskStackContainers.get(i);
+ if (stack.isCompatible(windowingMode, activityType)) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
@VisibleForTesting
int getStackCount() {
return mTaskStackContainers.size();
}
@VisibleForTesting
- int getStackPosById(int stackId) {
+ int getStackPosition(int windowingMode, int activityType) {
for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
final TaskStack stack = mTaskStackContainers.get(i);
- if (stack.mStackId == stackId) {
+ if (stack.isCompatible(windowingMode, activityType)) {
return i;
}
}
@@ -1499,7 +1533,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
// If there was no pinned stack, we still need to notify the controller of the display info
// update as a result of the config change. We do this here to consolidate the flow between
// changes when there is and is not a stack.
- if (getStackById(PINNED_STACK_ID) == null) {
+ if (getStack(WINDOWING_MODE_PINNED) == null) {
mPinnedStackControllerLocked.onDisplayInfoChanged();
}
}
@@ -1720,21 +1754,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
out.set(mContentRect);
}
- TaskStack addStackToDisplay(int stackId, boolean onTop) {
+ TaskStack addStackToDisplay(int stackId, boolean onTop, StackWindowController controller) {
if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+ mDisplayId);
TaskStack stack = getStackById(stackId);
if (stack != null) {
- // It's already attached to the display...clear mDeferRemoval and move stack to
- // appropriate z-order on display as needed.
+ // It's already attached to the display...clear mDeferRemoval, set controller, and move
+ // stack to appropriate z-order on display as needed.
stack.mDeferRemoval = false;
+ stack.setController(controller);
// We're not moving the display to front when we're adding stacks, only when
// requested to change the position of stack explicitly.
mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
false /* includingParents */);
} else {
- stack = new TaskStack(mService, stackId);
+ stack = new TaskStack(mService, stackId, controller);
mTaskStackContainers.addStackToDisplay(stack, onTop);
}
@@ -2023,8 +2058,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
final TaskStack stack = mTaskStackContainers.get(i);
final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
- if (stack.isVisible() && (imeOnBottom || isDockedOnBottom) &&
- StackId.isStackAffectedByDragResizing(stack.mStackId)) {
+ if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
+ && stack.inSplitScreenWindowingMode()) {
stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
} else {
stack.resetAdjustedForIme(false);
@@ -2119,8 +2154,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
- void writeToProto(ProtoOutputStream proto, long fieldId) {
+ @CallSuper
+ @Override
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
+ super.writeToProto(proto, WINDOW_CONTAINER);
proto.write(ID, mDisplayId);
for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
final TaskStack stack = mTaskStackContainers.get(stackNdx);
@@ -2232,7 +2270,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* @return The docked stack, but only if it is visible, and {@code null} otherwise.
*/
TaskStack getDockedStackLocked() {
- final TaskStack stack = getStackById(DOCKED_STACK_ID);
+ final TaskStack stack = getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
return (stack != null && stack.isVisible()) ? stack : null;
}
@@ -2241,7 +2279,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* visible.
*/
TaskStack getDockedStackIgnoringVisibility() {
- return getStackById(DOCKED_STACK_ID);
+ return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
}
/** Find the visible, touch-deliverable window under the given point */
@@ -3350,7 +3388,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* @see WindowManagerService#addStackToDisplay(int, int, boolean)
*/
void addStackToDisplay(TaskStack stack, boolean onTop) {
- if (stack.mStackId == HOME_STACK_ID) {
+ if (stack.isActivityTypeHome()) {
if (mHomeStack != null) {
throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
}
@@ -3415,11 +3453,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
: requestedPosition >= topChildPosition;
int targetPosition = requestedPosition;
- if (toTop && stack.mStackId != PINNED_STACK_ID
- && getStackById(PINNED_STACK_ID) != null) {
+ if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED
+ && getStack(WINDOWING_MODE_PINNED) != null) {
// The pinned stack is always the top most stack (always-on-top) when it is present.
TaskStack topStack = mChildren.get(topChildPosition);
- if (topStack.mStackId != PINNED_STACK_ID) {
+ if (topStack.getWindowingMode() != WINDOWING_MODE_PINNED) {
throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
}
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index 030b986e..6f441b98 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -17,8 +17,10 @@
package com.android.server.wm;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+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;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Surface.ROTATION_270;
@@ -330,7 +332,7 @@ public class DockedStackDividerController implements DimLayerUser {
mLastVisibility = visible;
notifyDockedDividerVisibilityChanged(visible);
if (!visible) {
- setResizeDimLayer(false, INVALID_STACK_ID, 0f);
+ setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
}
}
@@ -456,7 +458,8 @@ public class DockedStackDividerController implements DimLayerUser {
boolean isHomeStackResizable) {
long animDuration = 0;
if (animate) {
- final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+ final TaskStack stack =
+ mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final long transitionDuration = isAnimationMaximizing()
? mService.mAppTransition.getLastClipRevealTransitionDuration()
: DEFAULT_APP_TRANSITION_DURATION;
@@ -517,9 +520,18 @@ public class DockedStackDividerController implements DimLayerUser {
}
- void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+ /**
+ * Shows a dim layer with {@param alpha} if {@param visible} is true and
+ * {@param targetWindowingMode} isn't
+ * {@link android.app.WindowConfiguration#WINDOWING_MODE_UNDEFINED} and there is a stack on the
+ * display in that windowing mode.
+ */
+ void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
mService.openSurfaceTransaction();
- final TaskStack stack = mDisplayContent.getStackById(targetStackId);
+ // TODO: Maybe only allow split-screen windowing modes?
+ final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
+ ? mDisplayContent.getStack(targetWindowingMode)
+ : null;
final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
boolean visibleAndValid = visible && stack != null && dockedStack != null;
if (visibleAndValid) {
@@ -605,8 +617,8 @@ public class DockedStackDividerController implements DimLayerUser {
if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
return;
}
- final TaskStack fullscreenStack =
- mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
+ final TaskStack fullscreenStack = mDisplayContent.getStack(
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
final boolean homeBehind = fullscreenStack != null && fullscreenStack.isVisible();
setMinimizedDockedStack(homeVisible && !homeBehind, animate);
@@ -801,7 +813,8 @@ public class DockedStackDividerController implements DimLayerUser {
}
private boolean animateForMinimizedDockedStack(long now) {
- final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+ final TaskStack stack =
+ mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
if (!mAnimationStarted) {
mAnimationStarted = true;
mAnimationStartTime = now;
diff --git a/com/android/server/wm/DragResizeMode.java b/com/android/server/wm/DragResizeMode.java
index 8ab04065..c0bf1e89 100644
--- a/com/android/server/wm/DragResizeMode.java
+++ b/com/android/server/wm/DragResizeMode.java
@@ -16,11 +16,7 @@
package com.android.server.wm;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
/**
* Describes the mode in which a window is drag resizing.
@@ -39,15 +35,12 @@ class DragResizeMode {
*/
static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
- static boolean isModeAllowedForStack(int stackId, int mode) {
+ static boolean isModeAllowedForStack(TaskStack stack, int mode) {
switch (mode) {
case DRAG_RESIZE_MODE_FREEFORM:
- return stackId == FREEFORM_WORKSPACE_STACK_ID;
+ return stack.getWindowingMode() == WINDOWING_MODE_FREEFORM;
case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
- return stackId == DOCKED_STACK_ID
- || stackId == FULLSCREEN_WORKSPACE_STACK_ID
- || stackId == HOME_STACK_ID
- || stackId == RECENTS_STACK_ID;
+ return stack.inSplitScreenWindowingMode();
default:
return false;
}
diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java
index 1e7140a1..ef31598f 100644
--- a/com/android/server/wm/PinnedStackController.java
+++ b/com/android/server/wm/PinnedStackController.java
@@ -16,7 +16,8 @@
package com.android.server.wm;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -404,27 +405,29 @@ class PinnedStackController {
*/
private void notifyMovementBoundsChanged(boolean fromImeAdjustement) {
synchronized (mService.mWindowMap) {
- if (mPinnedStackListener != null) {
- try {
- final Rect insetBounds = new Rect();
- getInsetBounds(insetBounds);
- final Rect normalBounds = getDefaultBounds();
- if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
- transformBoundsToAspectRatio(normalBounds, mAspectRatio,
- false /* useCurrentMinEdgeSize */);
- }
- final Rect animatingBounds = mTmpAnimatingBoundsRect;
- final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
- if (pinnedStack != null) {
- pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
- } else {
- animatingBounds.set(normalBounds);
- }
- mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
- animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering actions changed event.", e);
+ if (mPinnedStackListener == null) {
+ return;
+ }
+ try {
+ final Rect insetBounds = new Rect();
+ getInsetBounds(insetBounds);
+ final Rect normalBounds = getDefaultBounds();
+ if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+ transformBoundsToAspectRatio(normalBounds, mAspectRatio,
+ false /* useCurrentMinEdgeSize */);
+ }
+ final Rect animatingBounds = mTmpAnimatingBoundsRect;
+ final TaskStack pinnedStack =
+ mDisplayContent.getStack(WINDOWING_MODE_PINNED);
+ if (pinnedStack != null) {
+ pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
+ } else {
+ animatingBounds.set(normalBounds);
}
+ mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
+ animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Error delivering actions changed event.", e);
}
}
}
@@ -491,7 +494,7 @@ class PinnedStackController {
pw.println(prefix + "PinnedStackController");
pw.print(prefix + " defaultBounds="); getDefaultBounds().printShortString(pw);
pw.println();
- mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+ mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
pw.print(prefix + " movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
pw.println();
pw.println(prefix + " mIsImeShowing=" + mIsImeShowing);
@@ -512,7 +515,7 @@ class PinnedStackController {
void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
getDefaultBounds().writeToProto(proto, DEFAULT_BOUNDS);
- mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+ mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
getMovementBounds(mTmpRect).writeToProto(proto, MOVEMENT_BOUNDS);
proto.end(token);
}
diff --git a/com/android/server/wm/PinnedStackWindowController.java b/com/android/server/wm/PinnedStackWindowController.java
index 590ac6ed..41f076d7 100644
--- a/com/android/server/wm/PinnedStackWindowController.java
+++ b/com/android/server/wm/PinnedStackWindowController.java
@@ -16,19 +16,16 @@
package com.android.server.wm;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
import android.app.RemoteAction;
-import android.content.res.Configuration;
import android.graphics.Rect;
-import com.android.server.UiThread;
-
import java.util.List;
/**
@@ -40,8 +37,8 @@ public class PinnedStackWindowController extends StackWindowController {
private Rect mTmpToBounds = new Rect();
public PinnedStackWindowController(int stackId, PinnedStackWindowListener listener,
- int displayId, boolean onTop, Rect outBounds) {
- super(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+ int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
+ super(stackId, listener, displayId, onTop, outBounds, service);
}
/**
@@ -101,7 +98,8 @@ public class PinnedStackWindowController extends StackWindowController {
}
schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START;
- mService.getStackBounds(FULLSCREEN_WORKSPACE_STACK_ID, mTmpToBounds);
+ mService.getStackBounds(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mTmpToBounds);
if (!mTmpToBounds.isEmpty()) {
// If there is a fullscreen bounds, use that
toBounds = new Rect(mTmpToBounds);
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index 8a749762..7832f5de 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.CallSuper;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.power.V1_0.PowerHint;
@@ -89,8 +90,9 @@ import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
-import static com.android.server.wm.proto.WindowManagerServiceProto.DISPLAYS;
-import static com.android.server.wm.proto.WindowManagerServiceProto.WINDOWS;
+import static com.android.server.wm.proto.RootWindowContainerProto.DISPLAYS;
+import static com.android.server.wm.proto.RootWindowContainerProto.WINDOWS;
+import static com.android.server.wm.proto.RootWindowContainerProto.WINDOW_CONTAINER;
/** Root {@link WindowContainer} for the device. */
class RootWindowContainer extends WindowContainer<DisplayContent> {
@@ -420,6 +422,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
return null;
}
+ TaskStack getStack(int windowingMode, int activityType) {
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final DisplayContent dc = mChildren.get(i);
+ final TaskStack stack = dc.getStack(windowingMode, activityType);
+ if (stack != null) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
void setSecureSurfaceState(int userId, boolean disabled) {
forAllWindows((w) -> {
if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
@@ -1077,7 +1090,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
}
}
- void writeToProto(ProtoOutputStream proto) {
+ @CallSuper
+ @Override
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ super.writeToProto(proto, WINDOW_CONTAINER);
if (mService.mDisplayReady) {
final int count = mChildren.size();
for (int i = 0; i < count; ++i) {
@@ -1088,6 +1105,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> {
forAllWindows((w) -> {
w.writeIdentifierToProto(proto, WINDOWS);
}, true);
+ proto.end(token);
}
@Override
diff --git a/com/android/server/wm/Session.java b/com/android/server/wm/Session.java
index 17812477..4dd147e5 100644
--- a/com/android/server/wm/Session.java
+++ b/com/android/server/wm/Session.java
@@ -719,4 +719,8 @@ public class Session extends IWindowSession.Stub
public String toString() {
return mStringName;
}
+
+ boolean hasAlertWindowSurfaces() {
+ return !mAlertWindowSurfaces.isEmpty();
+ }
}
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index a50ed71a..c0a4cb72 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -18,8 +18,6 @@ package com.android.server.wm;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import android.app.ActivityManager.StackId;
-import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
@@ -76,8 +74,7 @@ public class StackWindowController
+ " to unknown displayId=" + displayId);
}
- final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
- stack.setController(this);
+ dc.addStackToDisplay(stackId, onTop, this);
getRawBounds(outBounds);
}
}
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 55b6c912..7e8d1308 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSC
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.res.Configuration.EMPTY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static com.android.server.EventLogTags.WM_TASK_REMOVED;
@@ -34,8 +35,9 @@ import static com.android.server.wm.proto.TaskProto.BOUNDS;
import static com.android.server.wm.proto.TaskProto.FILLS_PARENT;
import static com.android.server.wm.proto.TaskProto.ID;
import static com.android.server.wm.proto.TaskProto.TEMP_INSET_BOUNDS;
+import static com.android.server.wm.proto.TaskProto.WINDOW_CONTAINER;
-import android.app.ActivityManager.StackId;
+import android.annotation.CallSuper;
import android.app.ActivityManager.TaskDescription;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -278,15 +280,9 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
// WindowConfiguration long term.
private int setBounds(Rect bounds, Configuration overrideConfig) {
if (overrideConfig == null) {
- overrideConfig = Configuration.EMPTY;
- }
- if (bounds == null && !Configuration.EMPTY.equals(overrideConfig)) {
- throw new IllegalArgumentException("null bounds but non empty configuration: "
- + overrideConfig);
- }
- if (bounds != null && Configuration.EMPTY.equals(overrideConfig)) {
- throw new IllegalArgumentException("non null bounds, but empty configuration");
+ overrideConfig = EMPTY;
}
+
boolean oldFullscreen = mFillsParent;
int rotation = Surface.ROTATION_0;
final DisplayContent displayContent = mStack.getDisplayContent();
@@ -321,7 +317,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
if (displayContent != null) {
displayContent.mDimLayerController.updateDimLayer(this);
}
- onOverrideConfigurationChanged(mFillsParent ? Configuration.EMPTY : overrideConfig);
+ onOverrideConfigurationChanged(overrideConfig);
return boundsChange;
}
@@ -404,7 +400,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
* the adjusted bounds's top.
*/
void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
- if (!isResizeable() || Configuration.EMPTY.equals(getOverrideConfiguration())) {
+ if (!isResizeable() || EMPTY.equals(getOverrideConfiguration())) {
return;
}
@@ -529,7 +525,7 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
void setDragResizing(boolean dragResizing, int dragResizeMode) {
if (mDragResizing != dragResizing) {
- if (!DragResizeMode.isModeAllowedForStack(mStack.mStackId, dragResizeMode)) {
+ if (!DragResizeMode.isModeAllowedForStack(mStack, dragResizeMode)) {
throw new IllegalArgumentException("Drag resize mode not allow for stack stackId="
+ mStack.mStackId + " dragResizeMode=" + dragResizeMode);
}
@@ -552,7 +548,9 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return;
}
if (mFillsParent) {
- setBounds(null, Configuration.EMPTY);
+ // TODO: Yeah...not sure if this works with WindowConfiguration, but shouldn't be a
+ // problem once we move mBounds into WindowConfiguration.
+ setBounds(null, getOverrideConfiguration());
return;
}
final int newRotation = displayContent.getDisplayInfo().rotation;
@@ -735,8 +733,11 @@ class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerU
return "Task=" + mTaskId;
}
- void writeToProto(ProtoOutputStream proto, long fieldId) {
+ @CallSuper
+ @Override
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
+ super.writeToProto(proto, WINDOW_CONTAINER);
proto.write(ID, mTaskId);
for (int i = mChildren.size() - 1; i >= 0; i--) {
final AppWindowToken appWindowToken = mChildren.get(i);
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
index 46324022..bff24f6e 100644
--- a/com/android/server/wm/TaskSnapshotController.java
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -254,7 +254,7 @@ class TaskSnapshotController {
@VisibleForTesting
int getSnapshotMode(Task task) {
final AppWindowToken topChild = task.getTopChild();
- if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
+ if (!task.isActivityTypeStandardOrUndefined()) {
return SNAPSHOT_MODE_NONE;
} else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
return SNAPSHOT_MODE_APP_THEME;
@@ -297,7 +297,7 @@ class TaskSnapshotController {
return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
topChild.getConfiguration().orientation, mainWindow.mStableInsets,
- false /* reduced */, 1.0f /* scale */);
+ ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
}
/**
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 4664dcbb..65278837 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -19,8 +19,11 @@ package com.android.server.wm;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+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.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -41,8 +44,9 @@ import static com.android.server.wm.proto.StackProto.BOUNDS;
import static com.android.server.wm.proto.StackProto.FILLS_PARENT;
import static com.android.server.wm.proto.StackProto.ID;
import static com.android.server.wm.proto.StackProto.TASKS;
+import static com.android.server.wm.proto.StackProto.WINDOW_CONTAINER;
-import android.app.ActivityManager.StackId;
+import android.annotation.CallSuper;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
@@ -147,9 +151,10 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
Rect mPreAnimationBounds = new Rect();
- TaskStack(WindowManagerService service, int stackId) {
+ TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
mService = service;
mStackId = stackId;
+ setController(controller);
mDockedStackMinimizeThickness = service.mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_minimize_thickness);
EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId);
@@ -733,7 +738,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
outTempTaskBounds.setEmpty();
// When the home stack is resizable, should always have the same stack and task bounds
- if (mStackId == HOME_STACK_ID) {
+ if (isActivityTypeHome()) {
final Task homeTask = findHomeTask();
if (homeTask != null && homeTask.isResizeable()) {
// Calculate the home stack bounds when in docked mode and the home stack is
@@ -918,7 +923,9 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
void resetAnimationBackgroundAnimator() {
mAnimationBackgroundAnimator = null;
- mAnimationBackgroundSurface.hide();
+ if (mAnimationBackgroundSurface != null) {
+ mAnimationBackgroundSurface.hide();
+ }
}
void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -1005,7 +1012,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
mAdjustImeAmount = 0f;
mAdjustDividerAmount = 0f;
updateAdjustedBounds();
- mService.setResizeDimLayer(false, mStackId, 1.0f);
+ mService.setResizeDimLayer(false, getWindowingMode(), 1.0f);
} else {
mImeGoingAway |= mAdjustedForIme;
}
@@ -1201,7 +1208,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
if (mAdjustedForIme && adjust && !isImeTarget) {
final float alpha = Math.max(mAdjustImeAmount, mAdjustDividerAmount)
* IME_ADJUST_DIM_AMOUNT;
- mService.setResizeDimLayer(true, mStackId, alpha);
+ mService.setResizeDimLayer(true, getWindowingMode(), alpha);
}
}
@@ -1219,8 +1226,11 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
return mMinimizeAmount != 0f;
}
- void writeToProto(ProtoOutputStream proto, long fieldId) {
+ @CallSuper
+ @Override
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
+ super.writeToProto(proto, WINDOW_CONTAINER);
proto.write(ID, mStackId);
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
mChildren.get(taskNdx).writeToProto(proto, TASKS);
@@ -1277,7 +1287,7 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
@Override
public boolean dimFullscreen() {
- return StackId.isHomeOrRecentsStack(mStackId) || fillsParent();
+ return !isActivityTypeStandard() || fillsParent();
}
@Override
@@ -1657,7 +1667,15 @@ public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLaye
@Override
int getOrientation() {
- return (StackId.canSpecifyOrientation(mStackId))
- ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+ return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+ }
+
+ private boolean canSpecifyOrientation() {
+ final int windowingMode = getWindowingMode();
+ final int activityType = getActivityType();
+ return windowingMode == WINDOWING_MODE_FULLSCREEN
+ || activityType == ACTIVITY_TYPE_HOME
+ || activityType == ACTIVITY_TYPE_RECENTS
+ || activityType == ACTIVITY_TYPE_ASSISTANT;
}
}
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 926719dd..40923c82 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -19,12 +19,14 @@ package com.android.server.wm;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.res.Configuration.EMPTY;
+import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER;
+import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION;
import android.annotation.CallSuper;
import android.content.res.Configuration;
import android.util.Pools;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.ToBooleanFunction;
import java.util.Comparator;
@@ -685,6 +687,23 @@ a * Returns whether this child is on top of the window hierarchy.
}
}
+ /**
+ * Write to a protocol buffer output stream. Protocol buffer message definition is at
+ * {@link com.android.server.wm.proto.WindowContainerProto}.
+ *
+ * @param protoOutputStream Stream to write the WindowContainer object to.
+ * @param fieldId Field Id of the WindowContainer as defined in the parent message.
+ * @hide
+ */
+ @CallSuper
+ @Override
+ public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+ final long token = protoOutputStream.start(fieldId);
+ super.writeToProto(protoOutputStream, CONFIGURATION_CONTAINER);
+ protoOutputStream.write(ORIENTATION, mOrientation);
+ protoOutputStream.end(token);
+ }
+
String getName() {
return toString();
}
diff --git a/com/android/server/wm/WindowLayersController.java b/com/android/server/wm/WindowLayersController.java
index 857b13d2..7caf2fe9 100644
--- a/com/android/server/wm/WindowLayersController.java
+++ b/com/android/server/wm/WindowLayersController.java
@@ -21,11 +21,8 @@ import android.util.Slog;
import java.util.ArrayDeque;
import java.util.function.Consumer;
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -187,12 +184,13 @@ class WindowLayersController {
}
}
- final int stackId = w.getAppToken() != null ? w.getStackId() : INVALID_STACK_ID;
- if (stackId == PINNED_STACK_ID) {
+ final int windowingMode = w.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_PINNED) {
mPinnedWindows.add(w);
- } else if (stackId == DOCKED_STACK_ID) {
+ } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
mDockedWindows.add(w);
- } else if (stackId == ASSISTANT_STACK_ID) {
+ }
+ if (w.isActivityTypeAssistant()) {
mAssistantWindows.add(w);
}
}
@@ -268,20 +266,8 @@ class WindowLayersController {
w.mLayer = layer;
w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
+ w.getSpecialWindowAnimLayerAdjustment();
- if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0) {
- if (w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
- w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
- }
- // TODO(b/62029108): the entire contents of the if statement should call the refactored
- // function to set the thumbnail layer for w.AppToken
- int highestLayer = w.mAppToken.getHighestAnimLayer();
- if (highestLayer > 0) {
- if (w.mAppToken.mAppAnimator.thumbnail != null
- && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer != highestLayer) {
- w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = highestLayer;
- w.mAppToken.mAppAnimator.thumbnail.setLayer(highestLayer + 1);
- }
- }
+ if (w.mAppToken != null) {
+ w.mAppToken.mAppAnimator.updateThumbnailLayer();
}
}
}
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index 32ee51c8..1fb21887 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -109,6 +109,7 @@ import static com.android.server.wm.proto.WindowManagerServiceProto.FOCUSED_WIND
import static com.android.server.wm.proto.WindowManagerServiceProto.INPUT_METHOD_WINDOW;
import static com.android.server.wm.proto.WindowManagerServiceProto.LAST_ORIENTATION;
import static com.android.server.wm.proto.WindowManagerServiceProto.POLICY;
+import static com.android.server.wm.proto.WindowManagerServiceProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.wm.proto.WindowManagerServiceProto.ROTATION;
import android.Manifest;
@@ -246,6 +247,7 @@ import com.android.server.Watchdog;
import com.android.server.input.InputManagerService;
import com.android.server.power.BatterySaverPolicy.ServiceType;
import com.android.server.power.ShutdownThread;
+import com.android.server.utils.PriorityDump;
import java.io.BufferedWriter;
import java.io.DataInputStream;
@@ -390,6 +392,18 @@ public class WindowManagerService extends IWindowManager.Stub
};
final WindowSurfacePlacer mWindowPlacerLocked;
+ private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+ @Override
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+ doDump(fd, pw, new String[] {"-a"});
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ doDump(fd, pw, args);
+ }
+ };
+
/**
* Current user when multi-user is enabled. Don't show windows of
* non-current user. Also see mCurrentProfileIds.
@@ -2029,10 +2043,14 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.i(TAG_WM, "Relayout " + win + ": oldVis=" + oldVisibility
+ " newVis=" + viewVisibility, stack);
}
- if (viewVisibility == View.VISIBLE &&
- (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
- || !win.mAppToken.isClientHidden())) {
+ // We should only relayout if the view is visible, it is a starting window, or the
+ // associated appToken is not hidden.
+ final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
+ (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
+ || !win.mAppToken.isClientHidden());
+
+ if (shouldRelayout) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
// We are about to create a surface, but we didn't run a layout yet. So better run
@@ -2179,8 +2197,18 @@ public class WindowManagerService extends IWindowManager.Stub
// and needs process it before handling the corresponding window frame. the variable
// {@code mergedConfiguration} is an out parameter that will be passed back to the
// client over IPC and checked there.
- win.getMergedConfiguration(mergedConfiguration);
- win.setReportedConfiguration(mergedConfiguration);
+ // Note: in the cases where the window is tied to an activity, we should not send a
+ // configuration update when the window has requested to be hidden. Doing so can lead
+ // to the client erroneously accepting a configuration that would have otherwise caused
+ // an activity restart. We instead hand back the last reported
+ // {@link MergedConfiguration}.
+ if (shouldRelayout) {
+ win.getMergedConfiguration(mergedConfiguration);
+ } else {
+ win.getLastReportedMergedConfiguration(mergedConfiguration);
+ }
+
+ win.setLastReportedMergedConfiguration(mergedConfiguration);
outFrame.set(win.mCompatFrame);
outOverscanInsets.set(win.mOverscanInsets);
@@ -2881,9 +2909,9 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void getStackBounds(int stackId, Rect bounds) {
+ public void getStackBounds(int windowingMode, int activityType, Rect bounds) {
synchronized (mWindowMap) {
- final TaskStack stack = mRoot.getStackById(stackId);
+ final TaskStack stack = mRoot.getStack(windowingMode, activityType);
if (stack != null) {
stack.getBounds(bounds);
return;
@@ -6505,7 +6533,7 @@ public class WindowManagerService extends IWindowManager.Stub
private void writeToProtoLocked(ProtoOutputStream proto) {
mPolicy.writeToProto(proto, POLICY);
- mRoot.writeToProto(proto);
+ mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER);
if (mCurrentFocus != null) {
mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
}
@@ -6793,8 +6821,11 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ }
+ private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
boolean dumpAll = false;
boolean useProto = false;
@@ -7124,10 +7155,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+ public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
synchronized (mWindowMap) {
getDefaultDisplayContentLocked().getDockedDividerController().setResizeDimLayer(
- visible, targetStackId, alpha);
+ visible, targetWindowingMode, alpha);
}
}
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 1b055664..4ff0f391 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -116,7 +116,9 @@ import static com.android.server.wm.proto.WindowStateProto.IDENTIFIER;
import static com.android.server.wm.proto.WindowStateProto.PARENT_FRAME;
import static com.android.server.wm.proto.WindowStateProto.STACK_ID;
import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS;
+import static com.android.server.wm.proto.WindowStateProto.WINDOW_CONTAINER;
+import android.annotation.CallSuper;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.Configuration;
@@ -255,7 +257,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* We'll send configuration to client only if it is different from the last applied one and
* client won't perform unnecessary updates.
*/
- private final Configuration mLastReportedConfiguration = new Configuration();
+ private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration();
/**
* Actual position of the surface shown on-screen (may be modified by animation). These are
@@ -1242,7 +1244,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// this is not necessarily what the client has processed yet. Find a
// better indicator consistent with the client.
return (mOrientationChanging || (isVisible()
- && getConfiguration().orientation != mLastReportedConfiguration.orientation))
+ && getConfiguration().orientation != getLastReportedConfiguration().orientation))
&& !mSeamlesslyRotated
&& !mOrientationChangeTimedOut;
}
@@ -1756,7 +1758,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/** Returns true if last applied config was not yet requested by client. */
boolean isConfigChanged() {
- return !mLastReportedConfiguration.equals(getConfiguration());
+ return !getLastReportedConfiguration().equals(getConfiguration());
}
void onWindowReplacementTimeout() {
@@ -2310,8 +2312,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
outConfiguration.setConfiguration(globalConfig, overrideConfig);
}
- void setReportedConfiguration(MergedConfiguration config) {
- mLastReportedConfiguration.setTo(config.getMergedConfiguration());
+ void setLastReportedMergedConfiguration(MergedConfiguration config) {
+ mLastReportedConfiguration.setTo(config);
+ }
+
+ void getLastReportedMergedConfiguration(MergedConfiguration config) {
+ config.setTo(mLastReportedConfiguration);
+ }
+
+ private Configuration getLastReportedConfiguration() {
+ return mLastReportedConfiguration.getMergedConfiguration();
}
void adjustStartingWindowFlags() {
@@ -2851,7 +2861,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
new MergedConfiguration(mService.mRoot.getConfiguration(),
getMergedOverrideConfiguration());
- setReportedConfiguration(mergedConfiguration);
+ setLastReportedMergedConfiguration(mergedConfiguration);
if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING)
Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING");
@@ -3078,7 +3088,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (task == null) {
return false;
}
- if (!StackId.isStackAffectedByDragResizing(getStackId())) {
+ if (!inSplitScreenWindowingMode()) {
return false;
}
if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3124,8 +3134,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|| (isChildWindow() && getParentWindow().isDockedResizing());
}
- void writeToProto(ProtoOutputStream proto, long fieldId) {
+ @CallSuper
+ @Override
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
+ super.writeToProto(proto, WINDOW_CONTAINER);
writeIdentifierToProto(proto, IDENTIFIER);
proto.write(DISPLAY_ID, getDisplayId());
proto.write(STACK_ID, getStackId());
@@ -3168,7 +3181,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly);
pw.print(" package="); pw.print(mAttrs.packageName);
pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp));
- pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
+ pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs.toString(prefix));
pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
pw.print(" h="); pw.print(mRequestedHeight);
pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
@@ -3249,7 +3262,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
pw.print(prefix); pw.print("mFullConfiguration="); pw.println(getConfiguration());
pw.print(prefix); pw.print("mLastReportedConfiguration=");
- pw.println(mLastReportedConfiguration);
+ pw.println(getLastReportedConfiguration());
}
pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
@@ -3309,7 +3322,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.print(prefix); pw.print("mOrientationChanging=");
pw.print(mOrientationChanging);
pw.print(" configOrientationChanging=");
- pw.print(mLastReportedConfiguration.orientation
+ pw.print(getLastReportedConfiguration().orientation
!= getConfiguration().orientation);
pw.print(" mAppFreezing="); pw.print(mAppFreezing);
pw.print(" mTurnOnScreen="); pw.print(mTurnOnScreen);
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index 88625d35..af1fa2fe 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -342,10 +342,7 @@ class WindowSurfacePlacer {
mTmpLayerAndToken.token = null;
handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
- final int topClosingLayer = mTmpLayerAndToken.layer;
-
- final AppWindowToken topOpeningApp = handleOpeningApps(transit,
- animLp, voiceInteraction, topClosingLayer);
+ final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction);
mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
@@ -387,8 +384,9 @@ class WindowSurfacePlacer {
}
private AppWindowToken handleOpeningApps(int transit, LayoutParams animLp,
- boolean voiceInteraction, int topClosingLayer) {
+ boolean voiceInteraction) {
AppWindowToken topOpeningApp = null;
+ int topOpeningLayer = Integer.MIN_VALUE;
final int appsCount = mService.mOpeningApps.size();
for (int i = 0; i < appsCount; i++) {
AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
@@ -422,7 +420,6 @@ class WindowSurfacePlacer {
}
mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
- int topOpeningLayer = 0;
if (animLp != null) {
final int layer = wtoken.getHighestAnimLayer();
if (topOpeningApp == null || layer > topOpeningLayer) {
@@ -431,7 +428,7 @@ class WindowSurfacePlacer {
}
}
if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
- createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+ createThumbnailAppAnimator(transit, wtoken);
}
}
return topOpeningApp;
@@ -473,7 +470,7 @@ class WindowSurfacePlacer {
}
}
if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
- createThumbnailAppAnimator(transit, wtoken, 0, layerAndToken.layer);
+ createThumbnailAppAnimator(transit, wtoken);
}
}
}
@@ -666,8 +663,7 @@ class WindowSurfacePlacer {
}
}
- private void createThumbnailAppAnimator(int transit, AppWindowToken appToken,
- int openingLayer, int closingLayer) {
+ private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) {
AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
if (openingAppAnimator == null || openingAppAnimator.animation == null) {
return;
@@ -724,7 +720,6 @@ class WindowSurfacePlacer {
anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
insets, thumbnailHeader, taskId, displayConfig.uiMode,
displayConfig.orientation);
- openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
openingAppAnimator.deferThumbnailDestruction =
!mService.mAppTransition.isNextThumbnailTransitionScaleUp();
} else {
@@ -734,8 +729,8 @@ class WindowSurfacePlacer {
anim.restrictDuration(MAX_ANIMATION_DURATION);
anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
+ openingAppAnimator.updateThumbnailLayer();
openingAppAnimator.thumbnail = surfaceControl;
- openingAppAnimator.thumbnailLayer = openingLayer;
openingAppAnimator.thumbnailAnimation = anim;
mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
} catch (Surface.OutOfResourcesException e) {
diff --git a/com/android/server/wm/WindowToken.java b/com/android/server/wm/WindowToken.java
index 422615b1..943448ee 100644
--- a/com/android/server/wm/WindowToken.java
+++ b/com/android/server/wm/WindowToken.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.CallSuper;
import android.util.proto.ProtoOutputStream;
import java.util.Comparator;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -28,6 +29,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.proto.WindowTokenProto.HASH_CODE;
import static com.android.server.wm.proto.WindowTokenProto.WINDOWS;
+import static com.android.server.wm.proto.WindowTokenProto.WINDOW_CONTAINER;
import android.os.Debug;
import android.os.IBinder;
@@ -263,8 +265,11 @@ class WindowToken extends WindowContainer<WindowState> {
super.onDisplayChanged(dc);
}
- void writeToProto(ProtoOutputStream proto, long fieldId) {
+ @CallSuper
+ @Override
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
+ super.writeToProto(proto, WINDOW_CONTAINER);
proto.write(HASH_CODE, System.identityHashCode(this));
for (int i = 0; i < mChildren.size(); i++) {
final WindowState w = mChildren.get(i);
diff --git a/com/android/settingslib/applications/ApplicationsState.java b/com/android/settingslib/applications/ApplicationsState.java
index 40c2b1f3..fa2499f6 100644
--- a/com/android/settingslib/applications/ApplicationsState.java
+++ b/com/android/settingslib/applications/ApplicationsState.java
@@ -52,6 +52,11 @@ import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
import java.io.File;
import java.io.IOException;
@@ -180,7 +185,11 @@ public class ApplicationsState {
}
public Session newSession(Callbacks callbacks) {
- Session s = new Session(callbacks);
+ return newSession(callbacks, null);
+ }
+
+ public Session newSession(Callbacks callbacks, Lifecycle lifecycle) {
+ Session s = new Session(callbacks, lifecycle);
synchronized (mEntriesMap) {
mSessions.add(s);
}
@@ -586,7 +595,7 @@ public class ApplicationsState {
.replaceAll("").toLowerCase();
}
- public class Session {
+ public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy {
final Callbacks mCallbacks;
boolean mResumed;
@@ -600,11 +609,19 @@ public class ApplicationsState {
ArrayList<AppEntry> mLastAppList;
boolean mRebuildForeground;
- Session(Callbacks callbacks) {
+ private final boolean mHasLifecycle;
+
+ Session(Callbacks callbacks, Lifecycle lifecycle) {
mCallbacks = callbacks;
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ mHasLifecycle = true;
+ } else {
+ mHasLifecycle = false;
+ }
}
- public void resume() {
+ public void onResume() {
if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
synchronized (mEntriesMap) {
if (!mResumed) {
@@ -616,7 +633,7 @@ public class ApplicationsState {
if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
}
- public void pause() {
+ public void onPause() {
if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
synchronized (mEntriesMap) {
if (mResumed) {
@@ -735,8 +752,11 @@ public class ApplicationsState {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
- public void release() {
- pause();
+ public void onDestroy() {
+ if (!mHasLifecycle) {
+ // TODO: Legacy, remove this later once all usages are switched to Lifecycle
+ onPause();
+ }
synchronized (mEntriesMap) {
mSessions.remove(this);
}
diff --git a/com/android/settingslib/applications/StorageStatsSource.java b/com/android/settingslib/applications/StorageStatsSource.java
index 8fc9fa6a..9fbadee3 100644
--- a/com/android/settingslib/applications/StorageStatsSource.java
+++ b/com/android/settingslib/applications/StorageStatsSource.java
@@ -131,7 +131,7 @@ public class StorageStatsSource {
}
public long getTotalBytes() {
- return mStats.getCacheBytes() + mStats.getCodeBytes() + mStats.getDataBytes();
+ return mStats.getAppBytes() + mStats.getDataBytes();
}
}
} \ No newline at end of file
diff --git a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 47cbb77a..6aae226b 100644
--- a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -28,17 +28,20 @@ import android.support.v7.preference.PreferenceScreen;
import android.support.v7.preference.TwoStatePreference;
import android.text.TextUtils;
-import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.ConfirmationDialogController;
-public abstract class AbstractEnableAdbPreferenceController extends AbstractPreferenceController
- implements ConfirmationDialogController {
+public abstract class AbstractEnableAdbPreferenceController extends
+ DeveloperOptionsPreferenceController implements ConfirmationDialogController {
private static final String KEY_ENABLE_ADB = "enable_adb";
public static final String ACTION_ENABLE_ADB_STATE_CHANGED =
"com.android.settingslib.development.AbstractEnableAdbController."
+ "ENABLE_ADB_STATE_CHANGED";
- private SwitchPreference mPreference;
+ public static final int ADB_SETTING_ON = 1;
+ public static final int ADB_SETTING_OFF = 0;
+
+
+ protected SwitchPreference mPreference;
public AbstractEnableAdbPreferenceController(Context context) {
super(context);
@@ -64,12 +67,13 @@ public abstract class AbstractEnableAdbPreferenceController extends AbstractPref
private boolean isAdbEnabled() {
final ContentResolver cr = mContext.getContentResolver();
- return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) != 0;
+ return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, ADB_SETTING_OFF)
+ != ADB_SETTING_OFF;
}
@Override
public void updateState(Preference preference) {
- ((TwoStatePreference)preference).setChecked(isAdbEnabled());
+ ((TwoStatePreference) preference).setChecked(isAdbEnabled());
}
public void enablePreference(boolean enabled) {
@@ -105,7 +109,7 @@ public abstract class AbstractEnableAdbPreferenceController extends AbstractPref
protected void writeAdbSetting(boolean enabled) {
Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.ADB_ENABLED, enabled ? 1 : 0);
+ Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF);
notifyStateChanged();
}
diff --git a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
index c1677236..7998b2ef 100644
--- a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
@@ -26,10 +26,9 @@ import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import com.android.settingslib.R;
-import com.android.settingslib.core.AbstractPreferenceController;
-public abstract class AbstractLogdSizePreferenceController extends AbstractPreferenceController
- implements Preference.OnPreferenceChangeListener {
+public abstract class AbstractLogdSizePreferenceController extends
+ DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener {
public static final String ACTION_LOGD_SIZE_UPDATED = "com.android.settingslib.development."
+ "AbstractLogdSizePreferenceController.LOGD_SIZE_UPDATED";
public static final String EXTRA_CURRENT_LOGD_VALUE = "CURRENT_LOGD_VALUE";
@@ -57,11 +56,6 @@ public abstract class AbstractLogdSizePreferenceController extends AbstractPrefe
}
@Override
- public boolean isAvailable() {
- return true;
- }
-
- @Override
public String getPreferenceKey() {
return SELECT_LOGD_SIZE_KEY;
}
diff --git a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
index 502fb174..67553adc 100644
--- a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
@@ -30,16 +30,15 @@ import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import com.android.settingslib.R;
-import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.ConfirmationDialogController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreate;
import com.android.settingslib.core.lifecycle.events.OnDestroy;
-public abstract class AbstractLogpersistPreferenceController extends AbstractPreferenceController
- implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnCreate, OnDestroy,
- ConfirmationDialogController {
+public abstract class AbstractLogpersistPreferenceController extends
+ DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
+ LifecycleObserver, OnCreate, OnDestroy, ConfirmationDialogController {
private static final String SELECT_LOGPERSIST_KEY = "select_logpersist";
private static final String SELECT_LOGPERSIST_PROPERTY = "persist.logd.logpersistd";
diff --git a/com/android/settingslib/development/DeveloperOptionsPreferenceController.java b/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
new file mode 100644
index 00000000..f68c04f9
--- /dev/null
+++ b/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.android.settingslib.development;
+
+import android.content.Context;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * This controller is used handle changes for the master switch in the developer options page.
+ *
+ * All Preference Controllers that are a part of the developer options page should inherit this
+ * class.
+ */
+public abstract class DeveloperOptionsPreferenceController extends
+ AbstractPreferenceController {
+
+ public DeveloperOptionsPreferenceController(Context context) {
+ super(context);
+ }
+
+ /**
+ * Child classes should override this method to create custom logic for hiding preferences.
+ *
+ * @return true if the preference is to be displayed.
+ */
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ /**
+ * Called when developer options is enabled
+ */
+ public void onDeveloperOptionsEnabled() {
+ if (isAvailable()) {
+ onDeveloperOptionsSwitchEnabled();
+ }
+ }
+
+ /**
+ * Called when developer options is disabled
+ */
+ public void onDeveloperOptionsDisabled() {
+ if (isAvailable()) {
+ onDeveloperOptionsSwitchDisabled();
+ }
+ }
+
+ /**
+ * Called when developer options is enabled and the preference is available
+ */
+ protected void onDeveloperOptionsSwitchEnabled() {
+ }
+
+ /**
+ * Called when developer options is disabled and the preference is available
+ */
+ protected void onDeveloperOptionsSwitchDisabled() {
+ }
+
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
new file mode 100644
index 00000000..ff7536ad
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.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 com.android.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}.
+ */
+public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController {
+ private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+ private final String mSerialNumber;
+
+ public AbstractSerialNumberPreferenceController(Context context) {
+ this(context, Build.getSerial());
+ }
+
+ @VisibleForTesting
+ AbstractSerialNumberPreferenceController(Context context, String serialNumber) {
+ super(context);
+ mSerialNumber = serialNumber;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return !TextUtils.isEmpty(mSerialNumber);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ final Preference pref = screen.findPreference(KEY_SERIAL_NUMBER);
+ if (pref != null) {
+ pref.setSummary(mSerialNumber);
+ }
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_SERIAL_NUMBER;
+ }
+}
diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java
index 9b75c00a..35ba6ae9 100644
--- a/com/android/settingslib/drawer/TileUtils.java
+++ b/com/android/settingslib/drawer/TileUtils.java
@@ -26,7 +26,6 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -320,6 +319,15 @@ public class TileUtils {
Context context, UserHandle user, Intent intent,
Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) {
+ getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
+ usePriority, checkCategory, forceTintExternalIcon, false /* shouldUpdateTiles */);
+ }
+
+ public static void getTilesForIntent(
+ Context context, UserHandle user, Intent intent,
+ Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
+ boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon,
+ boolean shouldUpdateTiles) {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
PackageManager.GET_META_DATA, user.getIdentifier());
@@ -357,9 +365,11 @@ public class TileUtils {
updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
pm, providerMap, forceTintExternalIcon);
if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
-
addedCache.put(key, tile);
+ } else if (shouldUpdateTiles) {
+ updateSummaryAndTitle(context, providerMap, tile);
}
+
if (!tile.userHandle.contains(user)) {
tile.userHandle.add(user);
}
@@ -380,7 +390,6 @@ public class TileUtils {
String summary = null;
String keyHint = null;
boolean isIconTintable = false;
- RemoteViews remoteViews = null;
// Get the activity's meta-data
try {
@@ -428,7 +437,8 @@ public class TileUtils {
}
if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
- remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+ tile.remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+ updateSummaryAndTitle(context, providerMap, tile);
}
}
} catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
@@ -462,7 +472,6 @@ public class TileUtils {
// Suggest a key for this tile
tile.key = keyHint;
tile.isIconTintable = isIconTintable;
- tile.remoteViews = remoteViews;
return true;
}
@@ -470,6 +479,26 @@ public class TileUtils {
return false;
}
+ private static void updateSummaryAndTitle(
+ Context context, Map<String, IContentProvider> providerMap, Tile tile) {
+ if (tile == null || tile.metaData == null
+ || !tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
+ return;
+ }
+
+ String uriString = tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI);
+ Bundle bundle = getBundleFromUri(context, uriString, providerMap);
+ String overrideSummary = getString(bundle, META_DATA_PREFERENCE_SUMMARY);
+ String overrideTitle = getString(bundle, META_DATA_PREFERENCE_TITLE);
+ if (overrideSummary != null) {
+ tile.remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
+ }
+
+ if (overrideTitle != null) {
+ tile.remoteViews.setTextViewText(android.R.id.title, overrideTitle);
+ }
+ }
+
/**
* Gets the icon package name and resource id from content provider.
* @param context context
@@ -535,37 +564,6 @@ public class TileUtils {
}
}
- public static void updateTileUsingSummaryUri(Context context, final Tile tile) {
- if (tile == null || tile.metaData == null ||
- !tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
- return;
- }
-
- new AsyncTask<Void, Void, Bundle>() {
- @Override
- protected Bundle doInBackground(Void... params) {
- return getBundleFromUri(context,
- tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI), new HashMap<>());
- }
-
- @Override
- protected void onPostExecute(Bundle bundle) {
- if (bundle == null) {
- return;
- }
- final String overrideSummary = getString(bundle, META_DATA_PREFERENCE_SUMMARY);
- final String overrideTitle = getString(bundle, META_DATA_PREFERENCE_TITLE);
-
- if (overrideSummary != null) {
- tile.remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
- }
- if (overrideTitle != null) {
- tile.remoteViews.setTextViewText(android.R.id.title, overrideTitle);
- }
- }
- }.execute();
- }
-
private static String getString(Bundle bundle, String key) {
return bundle == null ? null : bundle.getString(key);
}
diff --git a/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
index b7fd4048..3c5ac8df 100644
--- a/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
+++ b/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -73,7 +73,7 @@ public class BluetoothDeviceLayerDrawable extends LayerDrawable {
final Drawable deviceDrawable = context.getDrawable(resId);
final BatteryMeterDrawable batteryDrawable = new BatteryMeterDrawable(context,
- R.color.meter_background_color, batteryLevel);
+ context.getColor(R.color.meter_background_color), batteryLevel);
final int pad = context.getResources().getDimensionPixelSize(R.dimen.bt_battery_padding);
batteryDrawable.setPadding(pad, pad, pad, pad);
@@ -107,6 +107,8 @@ public class BluetoothDeviceLayerDrawable extends LayerDrawable {
@VisibleForTesting
static class BatteryMeterDrawable extends BatteryMeterDrawableBase {
private final float mAspectRatio;
+ @VisibleForTesting
+ int mFrameColor;
public BatteryMeterDrawable(Context context, int frameColor, int batteryLevel) {
super(context, frameColor);
@@ -118,6 +120,7 @@ public class BluetoothDeviceLayerDrawable extends LayerDrawable {
final int tintColor = Utils.getColorAttr(context, android.R.attr.colorControlNormal);
setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
setBatteryLevel(batteryLevel);
+ mFrameColor = frameColor;
}
@Override
diff --git a/com/android/settingslib/suggestions/SuggestionParser.java b/com/android/settingslib/suggestions/SuggestionParser.java
index 00f32b28..56b84415 100644
--- a/com/android/settingslib/suggestions/SuggestionParser.java
+++ b/com/android/settingslib/suggestions/SuggestionParser.java
@@ -195,7 +195,7 @@ public class SuggestionParser {
intent.setPackage(category.pkg);
}
TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
- mAddCache, null, suggestions, true, false, false);
+ mAddCache, null, suggestions, true, false, false, true /* shouldUpdateTiles */);
filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);
if (!category.multiple && suggestions.size() > (countBefore + 1)) {
// If there are too many, remove them all and only re-add the one with the highest
diff --git a/com/android/settingslib/wifi/WifiTracker.java b/com/android/settingslib/wifi/WifiTracker.java
index 664dcfcb..12455d85 100644
--- a/com/android/settingslib/wifi/WifiTracker.java
+++ b/com/android/settingslib/wifi/WifiTracker.java
@@ -24,7 +24,6 @@ import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
import android.net.NetworkKey;
import android.net.NetworkRequest;
import android.net.NetworkScoreManager;
@@ -36,10 +35,14 @@ import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiNetworkScoreCache.CacheListener;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.provider.Settings;
import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -47,8 +50,12 @@ import android.util.SparseArray;
import android.util.SparseIntArray;
import android.widget.Toast;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -64,7 +71,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
/**
* Tracks saved or available wifi networks and their state.
*/
-public class WifiTracker {
+public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
/**
* Default maximum age in millis of cached scored networks in
* {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
@@ -80,7 +87,7 @@ public class WifiTracker {
* and used so as to assist with in-the-field WiFi connectivity debugging */
public static boolean sVerboseLogging;
- // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
+ // TODO(b/36733768): Remove flag includeSaved
// TODO: Allow control of this?
// Combo scans can take 5-6s to complete - set to 10s.
@@ -96,9 +103,9 @@ public class WifiTracker {
private final WifiListener mListener;
private final boolean mIncludeSaved;
private final boolean mIncludeScans;
- private final boolean mIncludePasspoints;
- @VisibleForTesting final MainHandler mMainHandler;
- @VisibleForTesting final WorkHandler mWorkHandler;
+ @VisibleForTesting MainHandler mMainHandler;
+ @VisibleForTesting WorkHandler mWorkHandler;
+ private HandlerThread mWorkThread;
private WifiTrackerNetworkCallback mNetworkCallback;
@@ -142,7 +149,7 @@ public class WifiTracker {
private WifiInfo mLastInfo;
private final NetworkScoreManager mNetworkScoreManager;
- private final WifiNetworkScoreCache mScoreCache;
+ private WifiNetworkScoreCache mScoreCache;
private boolean mNetworkScoringUiEnabled;
private long mMaxSpeedLabelScoreCacheAge;
@@ -169,51 +176,43 @@ public class WifiTracker {
return filter;
}
+ /**
+ * Use the lifecycle constructor below whenever possible
+ */
+ @Deprecated
public WifiTracker(Context context, WifiListener wifiListener,
boolean includeSaved, boolean includeScans) {
- this(context, wifiListener, null, includeSaved, includeScans);
- }
-
- public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
- boolean includeSaved, boolean includeScans) {
- this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
+ this(context, wifiListener, includeSaved, includeScans,
+ context.getSystemService(WifiManager.class),
+ context.getSystemService(ConnectivityManager.class),
+ context.getSystemService(NetworkScoreManager.class),
+ newIntentFilter());
}
public WifiTracker(Context context, WifiListener wifiListener,
- boolean includeSaved, boolean includeScans, boolean includePasspoints) {
- this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
- }
-
- public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
- boolean includeSaved, boolean includeScans, boolean includePasspoints) {
- this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
+ @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
+ this(context, wifiListener, includeSaved, includeScans,
context.getSystemService(WifiManager.class),
context.getSystemService(ConnectivityManager.class),
context.getSystemService(NetworkScoreManager.class),
- Looper.myLooper(), newIntentFilter());
+ newIntentFilter());
+ lifecycle.addObserver(this);
}
@VisibleForTesting
- WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
- boolean includeSaved, boolean includeScans, boolean includePasspoints,
- WifiManager wifiManager, ConnectivityManager connectivityManager,
- NetworkScoreManager networkScoreManager, Looper currentLooper,
- IntentFilter filter) {
+ WifiTracker(Context context, WifiListener wifiListener,
+ boolean includeSaved, boolean includeScans,
+ WifiManager wifiManager, ConnectivityManager connectivityManager,
+ NetworkScoreManager networkScoreManager,
+ IntentFilter filter) {
if (!includeSaved && !includeScans) {
throw new IllegalArgumentException("Must include either saved or scans");
}
mContext = context;
- if (currentLooper == null) {
- // When we aren't on a looper thread, default to the main.
- currentLooper = Looper.getMainLooper();
- }
- mMainHandler = new MainHandler(currentLooper);
- mWorkHandler = new WorkHandler(
- workerLooper != null ? workerLooper : currentLooper);
+ mMainHandler = new MainHandler(Looper.getMainLooper());
mWifiManager = wifiManager;
mIncludeSaved = includeSaved;
mIncludeScans = includeScans;
- mIncludePasspoints = includePasspoints;
mListener = wifiListener;
mConnectivityManager = connectivityManager;
@@ -229,7 +228,22 @@ public class WifiTracker {
mNetworkScoreManager = networkScoreManager;
- mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
+ final HandlerThread workThread = new HandlerThread(TAG
+ + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ workThread.start();
+ setWorkThread(workThread);
+ }
+
+ /**
+ * Sanity warning: this wipes out mScoreCache, so use with extreme caution
+ * @param workThread substitute Handler thread, for testing purposes only
+ */
+ @VisibleForTesting
+ void setWorkThread(HandlerThread workThread) {
+ mWorkThread = workThread;
+ mWorkHandler = new WorkHandler(workThread.getLooper());
+ mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
@Override
public void networkCacheUpdated(List<ScoredNetwork> networks) {
synchronized (mLock) {
@@ -244,6 +258,11 @@ public class WifiTracker {
});
}
+ @Override
+ public void onDestroy() {
+ mWorkThread.quit();
+ }
+
/** Synchronously update the list of access points with the latest information. */
@MainThread
public void forceUpdate() {
@@ -312,8 +331,9 @@ public class WifiTracker {
* <p>Registers listeners and starts scanning for wifi networks. If this is not called
* then forceUpdate() must be called to populate getAccessPoints().
*/
+ @Override
@MainThread
- public void startTracking() {
+ public void onStart() {
synchronized (mLock) {
registerScoreCache();
@@ -361,15 +381,16 @@ public class WifiTracker {
/**
* Stop tracking wifi networks and scores.
*
- * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
+ * <p>This should always be called when done with a WifiTracker (if onStart was called) to
* ensure proper cleanup and prevent any further callbacks from occurring.
*
* <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
* {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
* is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
*/
+ @Override
@MainThread
- public void stopTracking() {
+ public void onStop() {
synchronized (mLock) {
if (mRegistered) {
mContext.unregisterReceiver(mReceiver);
@@ -769,9 +790,8 @@ public class WifiTracker {
}
public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
- boolean includeScans, boolean includePasspoints) {
- WifiTracker tracker = new WifiTracker(context,
- null, null, includeSaved, includeScans, includePasspoints);
+ boolean includeScans) {
+ WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans);
tracker.forceUpdate();
tracker.copyAndNotifyListeners(false /*notifyListeners*/);
return tracker.getAccessPoints();
diff --git a/com/android/settingslib/wifi/WifiTrackerFactory.java b/com/android/settingslib/wifi/WifiTrackerFactory.java
index 79cee046..8b5863ae 100644
--- a/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -16,8 +16,10 @@
package com.android.settingslib.wifi;
import android.content.Context;
-import android.os.Looper;
import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
/**
* Factory method used to inject WifiTracker instances.
@@ -31,12 +33,11 @@ public class WifiTrackerFactory {
}
public static WifiTracker create(
- Context context, WifiTracker.WifiListener wifiListener, Looper workerLooper,
- boolean includeSaved, boolean includeScans, boolean includePasspoints) {
+ Context context, WifiTracker.WifiListener wifiListener, @NonNull Lifecycle lifecycle,
+ boolean includeSaved, boolean includeScans) {
if(sTestingWifiTracker != null) {
return sTestingWifiTracker;
}
- return new WifiTracker(
- context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints);
+ return new WifiTracker(context, wifiListener, lifecycle, includeSaved, includeScans);
}
}
diff --git a/com/android/settingslib/wrapper/PackageManagerWrapper.java b/com/android/settingslib/wrapper/PackageManagerWrapper.java
index cd62bc36..b1f3f3ce 100644
--- a/com/android/settingslib/wrapper/PackageManagerWrapper.java
+++ b/com/android/settingslib/wrapper/PackageManagerWrapper.java
@@ -60,6 +60,13 @@ public class PackageManagerWrapper {
}
/**
+ * Calls {@code PackageManager.getInstalledPackagesAsUser}
+ */
+ public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+ return mPm.getInstalledPackagesAsUser(flags, userId);
+ }
+
+ /**
* Calls {@code PackageManager.hasSystemFeature()}.
*
* @see android.content.pm.PackageManager#hasSystemFeature
@@ -132,11 +139,11 @@ public class PackageManagerWrapper {
/**
* Gets information about a particular package from the package manager.
+ *
* @param packageName The name of the package we would like information about.
- * @param i additional options flags. see javadoc for
- * {@link PackageManager#getPackageInfo(String, int)}
+ * @param i additional options flags. see javadoc for
+ * {@link PackageManager#getPackageInfo(String, int)}
* @return The PackageInfo for the requested package
- * @throws NameNotFoundException
*/
public PackageInfo getPackageInfo(String packageName, int i) throws NameNotFoundException {
return mPm.getPackageInfo(packageName, i);
@@ -144,6 +151,7 @@ public class PackageManagerWrapper {
/**
* Retrieves the icon associated with this particular set of ApplicationInfo
+ *
* @param info The ApplicationInfo to retrieve the icon for
* @return The icon as a drawable.
*/
@@ -154,6 +162,7 @@ public class PackageManagerWrapper {
/**
* Retrieves the label associated with the particular set of ApplicationInfo
+ *
* @param app The ApplicationInfo to retrieve the label for
* @return the label as a CharSequence
*/
@@ -190,4 +199,48 @@ public class PackageManagerWrapper {
throws PackageManager.NameNotFoundException {
return mPm.getPackageUidAsUser(pkg, userId);
}
+
+ /**
+ * Calls {@code PackageManager.setApplicationEnabledSetting}
+ */
+ public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
+ mPm.setApplicationEnabledSetting(packageName, newState, flags);
+ }
+
+ /**
+ * Calls {@code PackageManager.getApplicationEnabledSetting}
+ */
+ public int getApplicationEnabledSetting(String packageName) {
+ return mPm.getApplicationEnabledSetting(packageName);
+ }
+
+ /**
+ * Calls {@code PackageManager.setComponentEnabledSetting}
+ */
+ public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
+ mPm.setComponentEnabledSetting(componentName, newState, flags);
+ }
+
+ /**
+ * Calls {@code PackageManager.getApplicationInfo}
+ */
+ public ApplicationInfo getApplicationInfo(String packageName, int flags)
+ throws NameNotFoundException {
+ return mPm.getApplicationInfo(packageName, flags);
+ }
+
+ /**
+ * Calls {@code PackageManager.getApplicationLabel}
+ */
+ public CharSequence getApplicationLabel(ApplicationInfo info) {
+ return mPm.getApplicationLabel(info);
+ }
+
+ /**
+ * Calls {@code PackageManager.queryBroadcastReceivers}
+ */
+ public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+ return mPm.queryBroadcastReceivers(intent, flags);
+ }
}
+
diff --git a/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
index 154339a7..3d11e128 100644
--- a/com/android/setupwizardlib/test/util/DrawingTestActivity.java
+++ b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
@@ -16,15 +16,14 @@
package com.android.setupwizardlib.test.util;
-import android.support.v7.app.AppCompatActivity;
+import android.app.Activity;
/**
* Activity to test view and drawable drawing behaviors. This is used to make sure that the drawing
- * behavior tested is the same as it would when inflated as part of an {@link AppCompatActivity},
- * including custom layout inflaters and theme values that the support library injects to the
- * activity.
+ * behavior tested is the same as it would when inflated as part of an activity, including any
+ * injected layout inflater factories and custom themes etc.
*
* @see DrawingTestHelper
*/
-public class DrawingTestActivity extends AppCompatActivity {
+public class DrawingTestActivity extends Activity {
}
diff --git a/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
index 1e663d6a..1fb3a373 100644
--- a/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
+++ b/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
@@ -19,6 +19,8 @@ package com.android.setupwizardlib.util;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
@@ -38,12 +40,11 @@ import java.util.List;
/**
* An accessibility delegate that allows {@link android.text.style.ClickableSpan} to be focused and
* clicked by accessibility services.
- * <p>
- * <strong>Note: </strong> From Android O on, there is native support for ClickableSpan
- * accessibility, so this class is not needed (and indeed has no effect.)
- * </p>
*
- * <p />Sample usage:
+ * <p><strong>Note:</strong> This class is a no-op on Android O or above since there is native
+ * support for ClickableSpan accessibility.
+ *
+ * <p>Sample usage:
* <pre>
* LinkAccessibilityHelper mAccessibilityHelper;
*
@@ -68,294 +69,255 @@ public class LinkAccessibilityHelper extends AccessibilityDelegateCompat {
private static final String TAG = "LinkAccessibilityHelper";
- private final TextView mView;
- private final Rect mTempRect = new Rect();
- private final ExploreByTouchHelper mExploreByTouchHelper;
+ private final AccessibilityDelegateCompat mDelegate;
public LinkAccessibilityHelper(TextView view) {
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
- // Pre-O, we essentially extend ExploreByTouchHelper to expose a virtual view hierarchy
- mExploreByTouchHelper = new ExploreByTouchHelper(view) {
- @Override
- protected int getVirtualViewAt(float x, float y) {
- return LinkAccessibilityHelper.this.getVirtualViewAt(x, y);
- }
-
- @Override
- protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
- LinkAccessibilityHelper.this.getVisibleVirtualViews(virtualViewIds);
- }
-
- @Override
- protected void onPopulateEventForVirtualView(int virtualViewId,
- AccessibilityEvent event) {
- LinkAccessibilityHelper
- .this.onPopulateEventForVirtualView(virtualViewId, event);
- }
-
- @Override
- protected void onPopulateNodeForVirtualView(int virtualViewId,
- AccessibilityNodeInfoCompat infoCompat) {
- LinkAccessibilityHelper
- .this.onPopulateNodeForVirtualView(virtualViewId, infoCompat);
-
- }
+ this(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ // Platform support was added in O. This helper will be no-op
+ ? new AccessibilityDelegateCompat()
+ // Pre-O, we extend ExploreByTouchHelper to expose a virtual view hierarchy
+ : new PreOLinkAccessibilityHelper(view));
+ }
- @Override
- protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
- Bundle arguments) {
- return LinkAccessibilityHelper.this
- .onPerformActionForVirtualView(virtualViewId, action, arguments);
- }
- };
- } else {
- mExploreByTouchHelper = null;
- }
- mView = view;
+ @VisibleForTesting
+ LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) {
+ mDelegate = delegate;
}
@Override
public void sendAccessibilityEvent(View host, int eventType) {
- if (mExploreByTouchHelper != null) {
- mExploreByTouchHelper.sendAccessibilityEvent(host, eventType);
- } else {
- super.sendAccessibilityEvent(host, eventType);
- }
+ mDelegate.sendAccessibilityEvent(host, eventType);
}
@Override
public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
- if (mExploreByTouchHelper != null) {
- mExploreByTouchHelper.sendAccessibilityEventUnchecked(host, event);
- } else {
- super.sendAccessibilityEventUnchecked(host, event);
- }
+ mDelegate.sendAccessibilityEventUnchecked(host, event);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
- return (mExploreByTouchHelper != null)
- ? mExploreByTouchHelper.dispatchPopulateAccessibilityEvent(host, event)
- : super.dispatchPopulateAccessibilityEvent(host, event);
+ return mDelegate.dispatchPopulateAccessibilityEvent(host, event);
}
@Override
public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
- if (mExploreByTouchHelper != null) {
- mExploreByTouchHelper.onPopulateAccessibilityEvent(host, event);
- } else {
- super.onPopulateAccessibilityEvent(host, event);
- }
+ mDelegate.onPopulateAccessibilityEvent(host, event);
}
@Override
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
- if (mExploreByTouchHelper != null) {
- mExploreByTouchHelper.onInitializeAccessibilityEvent(host, event);
- } else {
- super.onInitializeAccessibilityEvent(host, event);
- }
+ mDelegate.onInitializeAccessibilityEvent(host, event);
}
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- if (mExploreByTouchHelper != null) {
- mExploreByTouchHelper.onInitializeAccessibilityNodeInfo(host, info);
- } else {
- super.onInitializeAccessibilityNodeInfo(host, info);
- }
+ mDelegate.onInitializeAccessibilityNodeInfo(host, info);
}
@Override
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
AccessibilityEvent event) {
- return (mExploreByTouchHelper != null)
- ? mExploreByTouchHelper.onRequestSendAccessibilityEvent(host, child, event)
- : super.onRequestSendAccessibilityEvent(host, child, event);
+ return mDelegate.onRequestSendAccessibilityEvent(host, child, event);
}
@Override
public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
- return (mExploreByTouchHelper != null)
- ? mExploreByTouchHelper.getAccessibilityNodeProvider(host)
- : super.getAccessibilityNodeProvider(host);
+ return mDelegate.getAccessibilityNodeProvider(host);
}
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
- return (mExploreByTouchHelper != null)
- ? mExploreByTouchHelper.performAccessibilityAction(host, action, args)
- : super.performAccessibilityAction(host, action, args);
+ return mDelegate.performAccessibilityAction(host, action, args);
}
/**
- * Delegated to {@link ExploreByTouchHelper}
+ * Dispatches hover event to the virtual view hierarchy. This method should be called in
+ * {@link View#dispatchHoverEvent(MotionEvent)}.
+ *
+ * @see ExploreByTouchHelper#dispatchHoverEvent(MotionEvent)
*/
public final boolean dispatchHoverEvent(MotionEvent event) {
- return (mExploreByTouchHelper != null) ? mExploreByTouchHelper.dispatchHoverEvent(event)
- : false;
+ return mDelegate instanceof ExploreByTouchHelper
+ && ((ExploreByTouchHelper) mDelegate).dispatchHoverEvent(event);
}
- protected int getVirtualViewAt(float x, float y) {
- final CharSequence text = mView.getText();
- if (text instanceof Spanned) {
- final Spanned spannedText = (Spanned) text;
- final int offset = getOffsetForPosition(mView, x, y);
- ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class);
- if (linkSpans.length == 1) {
- ClickableSpan linkSpan = linkSpans[0];
- return spannedText.getSpanStart(linkSpan);
- }
+ @VisibleForTesting
+ static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper {
+
+ private final Rect mTempRect = new Rect();
+ private final TextView mView;
+
+ PreOLinkAccessibilityHelper(TextView view) {
+ super(view);
+ mView = view;
}
- return ExploreByTouchHelper.INVALID_ID;
- }
- protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
- final CharSequence text = mView.getText();
- if (text instanceof Spanned) {
- final Spanned spannedText = (Spanned) text;
- ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
- ClickableSpan.class);
- for (ClickableSpan span : linkSpans) {
- virtualViewIds.add(spannedText.getSpanStart(span));
+ protected int getVirtualViewAt(float x, float y) {
+ final CharSequence text = mView.getText();
+ if (text instanceof Spanned) {
+ final Spanned spannedText = (Spanned) text;
+ final int offset = getOffsetForPosition(mView, x, y);
+ ClickableSpan[] linkSpans =
+ spannedText.getSpans(offset, offset, ClickableSpan.class);
+ if (linkSpans.length == 1) {
+ ClickableSpan linkSpan = linkSpans[0];
+ return spannedText.getSpanStart(linkSpan);
+ }
}
+ return ExploreByTouchHelper.INVALID_ID;
}
- }
- protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
- final ClickableSpan span = getSpanForOffset(virtualViewId);
- if (span != null) {
- event.setContentDescription(getTextForSpan(span));
- } else {
- Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
- event.setContentDescription(mView.getText());
+ protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+ final CharSequence text = mView.getText();
+ if (text instanceof Spanned) {
+ final Spanned spannedText = (Spanned) text;
+ ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
+ ClickableSpan.class);
+ for (ClickableSpan span : linkSpans) {
+ virtualViewIds.add(spannedText.getSpanStart(span));
+ }
+ }
}
- }
- protected void onPopulateNodeForVirtualView(int virtualViewId,
- AccessibilityNodeInfoCompat info) {
- final ClickableSpan span = getSpanForOffset(virtualViewId);
- if (span != null) {
- info.setContentDescription(getTextForSpan(span));
- } else {
- Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
- info.setContentDescription(mView.getText());
- }
- info.setFocusable(true);
- info.setClickable(true);
- getBoundsForSpan(span, mTempRect);
- if (mTempRect.isEmpty()) {
- Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
- mTempRect.set(0, 0, 1, 1);
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ final ClickableSpan span = getSpanForOffset(virtualViewId);
+ if (span != null) {
+ event.setContentDescription(getTextForSpan(span));
+ } else {
+ Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+ event.setContentDescription(mView.getText());
+ }
}
- info.setBoundsInParent(mTempRect);
- info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
- }
- protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
- Bundle arguments) {
- if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
- ClickableSpan span = getSpanForOffset(virtualViewId);
+ protected void onPopulateNodeForVirtualView(
+ int virtualViewId,
+ AccessibilityNodeInfoCompat info) {
+ final ClickableSpan span = getSpanForOffset(virtualViewId);
if (span != null) {
- span.onClick(mView);
- return true;
+ info.setContentDescription(getTextForSpan(span));
} else {
Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+ info.setContentDescription(mView.getText());
}
+ info.setFocusable(true);
+ info.setClickable(true);
+ getBoundsForSpan(span, mTempRect);
+ if (mTempRect.isEmpty()) {
+ Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
+ mTempRect.set(0, 0, 1, 1);
+ }
+ info.setBoundsInParent(mTempRect);
+ info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
}
- return false;
- }
- private ClickableSpan getSpanForOffset(int offset) {
- CharSequence text = mView.getText();
- if (text instanceof Spanned) {
- Spanned spannedText = (Spanned) text;
- ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
- if (spans.length == 1) {
- return spans[0];
+ protected boolean onPerformActionForVirtualView(
+ int virtualViewId,
+ int action,
+ Bundle arguments) {
+ if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+ ClickableSpan span = getSpanForOffset(virtualViewId);
+ if (span != null) {
+ span.onClick(mView);
+ return true;
+ } else {
+ Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+ }
}
+ return false;
}
- return null;
- }
- private CharSequence getTextForSpan(ClickableSpan span) {
- CharSequence text = mView.getText();
- if (text instanceof Spanned) {
- Spanned spannedText = (Spanned) text;
- return spannedText.subSequence(spannedText.getSpanStart(span),
- spannedText.getSpanEnd(span));
+ private ClickableSpan getSpanForOffset(int offset) {
+ CharSequence text = mView.getText();
+ if (text instanceof Spanned) {
+ Spanned spannedText = (Spanned) text;
+ ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+ if (spans.length == 1) {
+ return spans[0];
+ }
+ }
+ return null;
}
- return text;
- }
- // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for the
- // section on the first line.
- private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
- CharSequence text = mView.getText();
- outRect.setEmpty();
- if (text instanceof Spanned) {
- final Layout layout = mView.getLayout();
- if (layout != null) {
+ private CharSequence getTextForSpan(ClickableSpan span) {
+ CharSequence text = mView.getText();
+ if (text instanceof Spanned) {
Spanned spannedText = (Spanned) text;
- final int spanStart = spannedText.getSpanStart(span);
- final int spanEnd = spannedText.getSpanEnd(span);
- final float xStart = layout.getPrimaryHorizontal(spanStart);
- final float xEnd = layout.getPrimaryHorizontal(spanEnd);
- final int lineStart = layout.getLineForOffset(spanStart);
- final int lineEnd = layout.getLineForOffset(spanEnd);
- layout.getLineBounds(lineStart, outRect);
- if (lineEnd == lineStart) {
- // If the span is on a single line, adjust both the left and right bounds
- // so outrect is exactly bounding the span.
- outRect.left = (int) Math.min(xStart, xEnd);
- outRect.right = (int) Math.max(xStart, xEnd);
- } else {
- // If the span wraps across multiple lines, only use the first line (as returned
- // by layout.getLineBounds above), and adjust the "start" of outrect to where
- // the span starts, leaving the "end" of outrect at the end of the line.
- // ("start" being left for LTR, and right for RTL)
- if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
- outRect.right = (int) xStart;
+ return spannedText.subSequence(
+ spannedText.getSpanStart(span),
+ spannedText.getSpanEnd(span));
+ }
+ return text;
+ }
+
+ // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for
+ // the section on the first line.
+ private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
+ CharSequence text = mView.getText();
+ outRect.setEmpty();
+ if (text instanceof Spanned) {
+ final Layout layout = mView.getLayout();
+ if (layout != null) {
+ Spanned spannedText = (Spanned) text;
+ final int spanStart = spannedText.getSpanStart(span);
+ final int spanEnd = spannedText.getSpanEnd(span);
+ final float xStart = layout.getPrimaryHorizontal(spanStart);
+ final float xEnd = layout.getPrimaryHorizontal(spanEnd);
+ final int lineStart = layout.getLineForOffset(spanStart);
+ final int lineEnd = layout.getLineForOffset(spanEnd);
+ layout.getLineBounds(lineStart, outRect);
+ if (lineEnd == lineStart) {
+ // If the span is on a single line, adjust both the left and right bounds
+ // so outrect is exactly bounding the span.
+ outRect.left = (int) Math.min(xStart, xEnd);
+ outRect.right = (int) Math.max(xStart, xEnd);
} else {
- outRect.left = (int) xStart;
+ // If the span wraps across multiple lines, only use the first line (as
+ // returned by layout.getLineBounds above), and adjust the "start" of
+ // outrect to where the span starts, leaving the "end" of outrect at the end
+ // of the line. ("start" being left for LTR, and right for RTL)
+ if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
+ outRect.right = (int) xStart;
+ } else {
+ outRect.left = (int) xStart;
+ }
}
- }
- // Offset for padding
- outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
+ // Offset for padding
+ outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
+ }
}
+ return outRect;
}
- return outRect;
- }
- // Compat implementation of TextView#getOffsetForPosition().
+ // Compat implementation of TextView#getOffsetForPosition().
- private static int getOffsetForPosition(TextView view, float x, float y) {
- if (view.getLayout() == null) return -1;
- final int line = getLineAtCoordinate(view, y);
- return getOffsetAtCoordinate(view, line, x);
- }
+ private static int getOffsetForPosition(TextView view, float x, float y) {
+ if (view.getLayout() == null) return -1;
+ final int line = getLineAtCoordinate(view, y);
+ return getOffsetAtCoordinate(view, line, x);
+ }
- private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
- x -= view.getTotalPaddingLeft();
- // Clamp the position to inside of the view.
- x = Math.max(0.0f, x);
- x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
- x += view.getScrollX();
- return x;
- }
+ private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
+ x -= view.getTotalPaddingLeft();
+ // Clamp the position to inside of the view.
+ x = Math.max(0.0f, x);
+ x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
+ x += view.getScrollX();
+ return x;
+ }
- private static int getLineAtCoordinate(TextView view, float y) {
- y -= view.getTotalPaddingTop();
- // Clamp the position to inside of the view.
- y = Math.max(0.0f, y);
- y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
- y += view.getScrollY();
- return view.getLayout().getLineForVertical((int) y);
- }
+ private static int getLineAtCoordinate(TextView view, float y) {
+ y -= view.getTotalPaddingTop();
+ // Clamp the position to inside of the view.
+ y = Math.max(0.0f, y);
+ y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
+ y += view.getScrollY();
+ return view.getLayout().getLineForVertical((int) y);
+ }
- private static int getOffsetAtCoordinate(TextView view, int line, float x) {
- x = convertToLocalHorizontalCoordinate(view, x);
- return view.getLayout().getOffsetForHorizontal(line, x);
+ private static int getOffsetAtCoordinate(TextView view, int line, float x) {
+ x = convertToLocalHorizontalCoordinate(view, x);
+ return view.getLayout().getOffsetForHorizontal(line, x);
+ }
}
}
diff --git a/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java b/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java
index 844e73e9..6228e6fc 100644
--- a/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java
+++ b/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java
@@ -14,29 +14,35 @@
* limitations under the License.
*/
-package com.android.setupwizardlib.test;
+package com.android.setupwizardlib.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import android.graphics.Rect;
-import android.os.Build;
import android.os.Bundle;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.v4.text.BidiFormatter;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
import android.support.v4.widget.ExploreByTouchHelper;
import android.text.SpannableStringBuilder;
import android.util.DisplayMetrics;
import android.util.TypedValue;
+import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.setupwizardlib.span.LinkSpan;
-import com.android.setupwizardlib.util.LinkAccessibilityHelper;
+import com.android.setupwizardlib.util.LinkAccessibilityHelper.PreOLinkAccessibilityHelper;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,13 +58,12 @@ public class LinkAccessibilityHelperTest {
private static final LinkSpan LINK_SPAN = new LinkSpan("foobar");
private TextView mTextView;
- private TestLinkAccessibilityHelper mHelper;
+ private TestPreOLinkAccessibilityHelper mHelper;
private DisplayMetrics mDisplayMetrics;
@Test
public void testGetVirtualViewAt() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView();
final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10));
assertEquals("Virtual view ID should be 1", 1, virtualViewId);
@@ -66,7 +71,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testGetVirtualViewAtHost() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView();
final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100));
assertEquals("Virtual view ID should be INVALID_ID",
@@ -75,7 +79,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testGetVisibleVirtualViews() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView();
List<Integer> virtualViewIds = new ArrayList<>();
mHelper.getVisibleVirtualViews(virtualViewIds);
@@ -86,7 +89,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testOnPopulateEventForVirtualView() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView();
AccessibilityEvent event = AccessibilityEvent.obtain();
mHelper.onPopulateEventForVirtualView(1, event);
@@ -100,7 +102,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testOnPopulateEventForVirtualViewHost() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView();
AccessibilityEvent event = AccessibilityEvent.obtain();
mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event);
@@ -113,7 +114,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testOnPopulateNodeForVirtualView() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView();
AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
mHelper.onPopulateNodeForVirtualView(1, info);
@@ -132,7 +132,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testNullLayout() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
initTextView();
// Setting the padding will cause the layout to be null-ed out.
mTextView.setPadding(1, 1, 1, 1);
@@ -150,7 +149,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testRtlLayout() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום");
ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */);
initTextView(ssb);
@@ -170,7 +168,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testMultilineLink() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
SpannableStringBuilder ssb = new SpannableStringBuilder(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
+ "Praesent accumsan efficitur eros eu porttitor.");
@@ -192,7 +189,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testRtlMultilineLink() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים "
+ "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד "
+ "דפים המחשב מיזמים ב.";
@@ -216,7 +212,6 @@ public class LinkAccessibilityHelperTest {
@Test
public void testBidiMultilineLink() {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים "
+ "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד "
+ "דפים המחשב מיזמים ב.";
@@ -243,6 +238,70 @@ public class LinkAccessibilityHelperTest {
info.recycle();
}
+ @Test
+ public void testMethodDelegation() {
+ initTextView();
+ ExploreByTouchHelper delegate = mock(TestPreOLinkAccessibilityHelper.class);
+ LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate);
+
+ AccessibilityEvent accessibilityEvent =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED);
+
+ helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ verify(delegate).sendAccessibilityEvent(
+ same(mTextView),
+ eq(AccessibilityEvent.TYPE_VIEW_CLICKED));
+
+ helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent);
+ verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent));
+
+ helper.performAccessibilityAction(
+ mTextView,
+ AccessibilityActionCompat.ACTION_CLICK.getId(),
+ Bundle.EMPTY);
+ verify(delegate).performAccessibilityAction(
+ same(mTextView),
+ eq(AccessibilityActionCompat.ACTION_CLICK.getId()),
+ eq(Bundle.EMPTY));
+
+ helper.dispatchPopulateAccessibilityEvent(
+ mTextView,
+ accessibilityEvent);
+ verify(delegate).dispatchPopulateAccessibilityEvent(
+ same(mTextView),
+ same(accessibilityEvent));
+
+ MotionEvent motionEvent = MotionEvent.obtain(0, 0, 0, 0, 0, 0);
+ helper.dispatchHoverEvent(motionEvent);
+ verify(delegate).dispatchHoverEvent(eq(motionEvent));
+
+ helper.getAccessibilityNodeProvider(mTextView);
+ verify(delegate).getAccessibilityNodeProvider(same(mTextView));
+
+ helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent);
+ verify(delegate).onInitializeAccessibilityEvent(
+ same(mTextView),
+ eq(accessibilityEvent));
+
+ AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain();
+ helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo);
+ verify(delegate).onInitializeAccessibilityNodeInfo(
+ same(mTextView),
+ same(accessibilityNodeInfo));
+
+ helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent);
+ verify(delegate).onPopulateAccessibilityEvent(
+ same(mTextView),
+ same(accessibilityEvent));
+
+ FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext());
+ helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent);
+ verify(delegate).onRequestSendAccessibilityEvent(
+ same(parent),
+ same(mTextView),
+ same(accessibilityEvent));
+ }
+
private void initTextView() {
SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world");
ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */);
@@ -254,7 +313,7 @@ public class LinkAccessibilityHelperTest {
mTextView.setSingleLine(false);
mTextView.setText(text);
mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
- mHelper = new TestLinkAccessibilityHelper(mTextView);
+ mHelper = new TestPreOLinkAccessibilityHelper(mTextView);
int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500),
View.MeasureSpec.EXACTLY);
@@ -270,9 +329,9 @@ public class LinkAccessibilityHelperTest {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics);
}
- private static class TestLinkAccessibilityHelper extends LinkAccessibilityHelper {
+ public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper {
- TestLinkAccessibilityHelper(TextView view) {
+ TestPreOLinkAccessibilityHelper(TextView view) {
super(view);
}
diff --git a/com/android/setupwizardlib/util/PartnerTest.java b/com/android/setupwizardlib/util/PartnerTest.java
index f47eef18..aeb678fa 100644
--- a/com/android/setupwizardlib/util/PartnerTest.java
+++ b/com/android/setupwizardlib/util/PartnerTest.java
@@ -31,6 +31,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Build.VERSION;
@@ -40,20 +41,25 @@ import com.android.setupwizardlib.BuildConfig;
import com.android.setupwizardlib.R;
import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner;
import com.android.setupwizardlib.util.Partner.ResourceEntry;
+import com.android.setupwizardlib.util.PartnerTest.ShadowApplicationPackageManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
-import org.robolectric.res.builder.DefaultPackageManager;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowResources;
import java.util.Arrays;
import java.util.Collections;
@RunWith(SuwLibRobolectricTestRunner.class)
-@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK })
+@Config(
+ constants = BuildConfig.class,
+ sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK },
+ shadows = ShadowApplicationPackageManager.class)
public class PartnerTest {
private static final String ACTION_PARTNER_CUSTOMIZATION =
@@ -62,7 +68,7 @@ public class PartnerTest {
private Context mContext;
private Resources mPartnerResources;
- private TestPackageManager mPackageManager;
+ private ShadowApplicationPackageManager mPackageManager;
@Before
public void setUp() throws Exception {
@@ -71,8 +77,9 @@ public class PartnerTest {
mContext = spy(application);
mPartnerResources = spy(ShadowResources.getSystem());
- mPackageManager = new TestPackageManager();
- RuntimeEnvironment.setRobolectricPackageManager(mPackageManager);
+ mPackageManager =
+ (ShadowApplicationPackageManager) Shadows.shadowOf(application.getPackageManager());
+ mPackageManager.partnerResources = mPartnerResources;
}
@Test
@@ -173,13 +180,18 @@ public class PartnerTest {
return info;
}
- private class TestPackageManager extends DefaultPackageManager {
+ @Implements(className = "android.app.ApplicationPackageManager")
+ public static class ShadowApplicationPackageManager extends
+ org.robolectric.shadows.ShadowApplicationPackageManager {
+ public Resources partnerResources;
+
+ @Implementation
@Override
public Resources getResourcesForApplication(ApplicationInfo app)
throws NameNotFoundException {
if (app != null && "test.partner.package".equals(app.packageName)) {
- return mPartnerResources;
+ return partnerResources;
} else {
return super.getResourcesForApplication(app);
}
diff --git a/com/android/setupwizardlib/view/IllustrationVideoViewTest.java b/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
index ffa228d5..ddf59ca4 100644
--- a/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
+++ b/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
@@ -48,7 +48,7 @@ import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
-import org.robolectric.internal.Shadow;
+import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowMediaPlayer;
import org.robolectric.util.ReflectionHelpers;
diff --git a/com/android/setupwizardlib/view/NavigationBarButton.java b/com/android/setupwizardlib/view/NavigationBarButton.java
index 5172c476..45d3737c 100644
--- a/com/android/setupwizardlib/view/NavigationBarButton.java
+++ b/com/android/setupwizardlib/view/NavigationBarButton.java
@@ -17,143 +17,16 @@
package com.android.setupwizardlib.view;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Build;
-import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.widget.Button;
-/**
- * Button for navigation bar, which includes tinting of its compound drawables to be used for dark
- * and light themes.
- */
public class NavigationBarButton extends Button {
public NavigationBarButton(Context context) {
super(context);
- init();
}
public NavigationBarButton(Context context, AttributeSet attrs) {
super(context, attrs);
- init();
- }
-
- private void init() {
- // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter,
- // so manually getting it and wrapping it in the compat drawable.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- Drawable[] drawables = getCompoundDrawablesRelative();
- for (int i = 0; i < drawables.length; i++) {
- if (drawables[i] != null) {
- drawables[i] = TintedDrawable.wrap(drawables[i]);
- }
- }
- setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1],
- drawables[2], drawables[3]);
- }
- }
-
- @Override
- public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
- if (left != null) left = TintedDrawable.wrap(left);
- if (top != null) top = TintedDrawable.wrap(top);
- if (right != null) right = TintedDrawable.wrap(right);
- if (bottom != null) bottom = TintedDrawable.wrap(bottom);
- super.setCompoundDrawables(left, top, right, bottom);
- tintDrawables();
- }
-
- @Override
- public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end,
- Drawable bottom) {
- if (start != null) start = TintedDrawable.wrap(start);
- if (top != null) top = TintedDrawable.wrap(top);
- if (end != null) end = TintedDrawable.wrap(end);
- if (bottom != null) bottom = TintedDrawable.wrap(bottom);
- super.setCompoundDrawablesRelative(start, top, end, bottom);
- tintDrawables();
- }
-
- @Override
- public void setTextColor(ColorStateList colors) {
- super.setTextColor(colors);
- tintDrawables();
- }
-
- private void tintDrawables() {
- final ColorStateList textColors = getTextColors();
- if (textColors != null) {
- for (Drawable drawable : getAllCompoundDrawables()) {
- if (drawable instanceof TintedDrawable) {
- ((TintedDrawable) drawable).setTintListCompat(textColors);
- }
- }
- invalidate();
- }
- }
-
- private Drawable[] getAllCompoundDrawables() {
- Drawable[] drawables = new Drawable[6];
- Drawable[] compoundDrawables = getCompoundDrawables();
- drawables[0] = compoundDrawables[0]; // left
- drawables[1] = compoundDrawables[1]; // top
- drawables[2] = compoundDrawables[2]; // right
- drawables[3] = compoundDrawables[3]; // bottom
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative();
- drawables[4] = compoundDrawablesRelative[0]; // start
- drawables[5] = compoundDrawablesRelative[2]; // end
- }
- return drawables;
- }
-
- // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0
- // or above
- private static class TintedDrawable extends LayerDrawable {
-
- public static TintedDrawable wrap(Drawable drawable) {
- if (drawable instanceof TintedDrawable) {
- return (TintedDrawable) drawable;
- }
- return new TintedDrawable(drawable.mutate());
- }
-
- private ColorStateList mTintList = null;
-
- TintedDrawable(Drawable wrapped) {
- super(new Drawable[] { wrapped });
- }
-
- @Override
- public boolean isStateful() {
- return true;
- }
-
- @Override
- public boolean setState(@NonNull int[] stateSet) {
- boolean needsInvalidate = super.setState(stateSet);
- boolean needsInvalidateForState = updateState();
- return needsInvalidate || needsInvalidateForState;
- }
-
- public void setTintListCompat(ColorStateList colors) {
- mTintList = colors;
- if (updateState()) {
- invalidateSelf();
- }
- }
-
- private boolean updateState() {
- if (mTintList != null) {
- final int color = mTintList.getColorForState(getState(), 0);
- setColorFilter(color, PorterDuff.Mode.SRC_IN);
- return true; // Needs invalidate
- }
- return false;
- }
}
}
diff --git a/com/android/setupwizardlib/view/RichTextView.java b/com/android/setupwizardlib/view/RichTextView.java
index e6bc9da0..5a78561f 100644
--- a/com/android/setupwizardlib/view/RichTextView.java
+++ b/com/android/setupwizardlib/view/RichTextView.java
@@ -17,11 +17,6 @@
package com.android.setupwizardlib.view;
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.AppCompatTextView;
import android.text.Annotation;
import android.text.SpannableString;
import android.text.Spanned;
@@ -30,18 +25,22 @@ import android.text.style.ClickableSpan;
import android.text.style.TextAppearanceSpan;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.MotionEvent;
+import android.widget.TextView;
import com.android.setupwizardlib.span.LinkSpan;
import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener;
import com.android.setupwizardlib.span.SpanHelper;
-import com.android.setupwizardlib.util.LinkAccessibilityHelper;
/**
* An extension of TextView that automatically replaces the annotation tags as specified in
* {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)}
+ *
+ * <p>Note: The accessibility interaction for ClickableSpans (and therefore LinkSpans) are built
+ * into platform in O, although the interaction paradigm is different. (See b/17726921). In this
+ * platform version, the links are exposed in the Local Context Menu of TalkBack instead of
+ * accessible directly through swiping.
*/
-public class RichTextView extends AppCompatTextView implements OnLinkClickListener {
+public class RichTextView extends TextView implements OnLinkClickListener {
/* static section */
@@ -89,22 +88,14 @@ public class RichTextView extends AppCompatTextView implements OnLinkClickListen
/* non-static section */
- private LinkAccessibilityHelper mAccessibilityHelper;
private OnLinkClickListener mOnLinkClickListener;
public RichTextView(Context context) {
super(context);
- init();
}
public RichTextView(Context context, AttributeSet attrs) {
super(context, attrs);
- init();
- }
-
- private void init() {
- mAccessibilityHelper = new LinkAccessibilityHelper(this);
- ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
}
@Override
@@ -141,32 +132,6 @@ public class RichTextView extends AppCompatTextView implements OnLinkClickListen
return false;
}
- @Override
- protected boolean dispatchHoverEvent(MotionEvent event) {
- if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
- return true;
- }
- return super.dispatchHoverEvent(event);
- }
-
- @Override
- protected void drawableStateChanged() {
- super.drawableStateChanged();
-
- if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
- // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a
- // workaround, set the state on those drawables directly.
- final int[] state = getDrawableState();
- for (Drawable drawable : getCompoundDrawablesRelative()) {
- if (drawable != null) {
- if (drawable.setState(state)) {
- invalidateDrawable(drawable);
- }
- }
- }
- }
- }
-
public void setOnLinkClickListener(OnLinkClickListener listener) {
mOnLinkClickListener = listener;
}
diff --git a/com/android/systemui/BatteryMeterView.java b/com/android/systemui/BatteryMeterView.java
index 2b31967c..2fe66a14 100644
--- a/com/android/systemui/BatteryMeterView.java
+++ b/com/android/systemui/BatteryMeterView.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
import android.animation.ArgbEvaluator;
@@ -52,6 +54,7 @@ import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
import java.text.NumberFormat;
@@ -101,6 +104,9 @@ public class BatteryMeterView extends LinearLayout implements
mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
+ addOnAttachStateChangeListener(
+ new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
+
mSlotBattery = context.getString(
com.android.internal.R.string.status_bar_battery);
mBatteryIconView = new ImageView(context);
diff --git a/com/android/systemui/assist/AssistManager.java b/com/android/systemui/assist/AssistManager.java
index c5eebccb..8a8bafaf 100644
--- a/com/android/systemui/assist/AssistManager.java
+++ b/com/android/systemui/assist/AssistManager.java
@@ -61,6 +61,7 @@ public class AssistManager implements ConfigurationChangedReceiver {
private AssistOrbContainer mView;
private final DeviceProvisionedController mDeviceProvisionedController;
protected final AssistUtils mAssistUtils;
+ private final boolean mShouldEnableOrb;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -96,6 +97,7 @@ public class AssistManager implements ConfigurationChangedReceiver {
| ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
| ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
onConfigurationChanged(context.getResources().getConfiguration());
+ mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
}
protected void registerVoiceInteractionSessionListener() {
@@ -179,7 +181,9 @@ public class AssistManager implements ConfigurationChangedReceiver {
private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
maybeSwapSearchIcon(assistComponent, isService);
- mView.show(true /* show */, true /* animate */);
+ if (mShouldEnableOrb) {
+ mView.show(true /* show */, true /* animate */);
+ }
}
private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
diff --git a/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index 5c99961e..debda210 100644
--- a/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -16,8 +16,13 @@
package com.android.systemui.doze;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.util.KeyValueListParser;
@@ -34,6 +39,10 @@ import java.util.Arrays;
public class AlwaysOnDisplayPolicy {
public static final String TAG = "AlwaysOnDisplayPolicy";
+ private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
+ private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
+ private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+
static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
@@ -46,7 +55,7 @@ public class AlwaysOnDisplayPolicy {
* @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
* @see #KEY_SCREEN_BRIGHTNESS_ARRAY
*/
- public final int[] screenBrightnessArray;
+ public int[] screenBrightnessArray;
/**
* Integer array to map ambient brightness type to dimming scrim.
@@ -54,7 +63,7 @@ public class AlwaysOnDisplayPolicy {
* @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
* @see #KEY_DIMMING_SCRIM_ARRAY
*/
- public final int[] dimmingScrimArray;
+ public int[] dimmingScrimArray;
/**
* Delay time(ms) from covering the prox to turning off the screen.
@@ -62,7 +71,7 @@ public class AlwaysOnDisplayPolicy {
* @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
* @see #KEY_PROX_SCREEN_OFF_DELAY_MS
*/
- public final long proxScreenOffDelayMs;
+ public long proxScreenOffDelayMs;
/**
* The threshold time(ms) to trigger the cooldown timer, which will
@@ -71,7 +80,7 @@ public class AlwaysOnDisplayPolicy {
* @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
* @see #KEY_PROX_COOLDOWN_TRIGGER_MS
*/
- public final long proxCooldownTriggerMs;
+ public long proxCooldownTriggerMs;
/**
* The period(ms) to turning off the prox sensor if
@@ -80,43 +89,78 @@ public class AlwaysOnDisplayPolicy {
* @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
* @see #KEY_PROX_COOLDOWN_PERIOD_MS
*/
- public final long proxCooldownPeriodMs;
+ public long proxCooldownPeriodMs;
private final KeyValueListParser mParser;
+ private final Context mContext;
+ private SettingsObserver mSettingsObserver;
public AlwaysOnDisplayPolicy(Context context) {
- final Resources resources = context.getResources();
+ mContext = context;
mParser = new KeyValueListParser(',');
-
- final String value = Settings.Global.getString(context.getContentResolver(),
- Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
-
- try {
- mParser.setString(value);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Bad AOD constants");
- }
-
- proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
- 10 * DateUtils.SECOND_IN_MILLIS);
- proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
- 2 * DateUtils.SECOND_IN_MILLIS);
- proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
- 5 * DateUtils.SECOND_IN_MILLIS);
- screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
- resources.getIntArray(R.array.config_doze_brightness_sensor_to_brightness));
- dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
- resources.getIntArray(R.array.config_doze_brightness_sensor_to_scrim_opacity));
+ mSettingsObserver = new SettingsObserver(context.getMainThreadHandler());
+ mSettingsObserver.observe();
}
private int[] parseIntArray(final String key, final int[] defaultArray) {
final String value = mParser.getString(key, null);
if (value != null) {
- return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
- Integer::parseInt).toArray();
+ try {
+ return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+ Integer::parseInt).toArray();
+ } catch (NumberFormatException e) {
+ return defaultArray;
+ }
} else {
return defaultArray;
}
}
+ private final class SettingsObserver extends ContentObserver {
+ private final Uri ALWAYS_ON_DISPLAY_CONSTANTS_URI
+ = Settings.Global.getUriFor(Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(ALWAYS_ON_DISPLAY_CONSTANTS_URI,
+ false, this, UserHandle.USER_ALL);
+ update(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update(uri);
+ }
+
+ public void update(Uri uri) {
+ if (uri == null || ALWAYS_ON_DISPLAY_CONSTANTS_URI.equals(uri)) {
+ final Resources resources = mContext.getResources();
+ final String value = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+ try {
+ mParser.setString(value);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Bad AOD constants");
+ }
+
+ proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
+ DEFAULT_PROX_SCREEN_OFF_DELAY_MS);
+ proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
+ DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
+ proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
+ DEFAULT_PROX_COOLDOWN_PERIOD_MS);
+ screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
+ resources.getIntArray(
+ R.array.config_doze_brightness_sensor_to_brightness));
+ dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
+ resources.getIntArray(
+ R.array.config_doze_brightness_sensor_to_scrim_opacity));
+ }
+ }
+ }
}
diff --git a/com/android/systemui/doze/DozePauser.java b/com/android/systemui/doze/DozePauser.java
index 76a19021..58f14483 100644
--- a/com/android/systemui/doze/DozePauser.java
+++ b/com/android/systemui/doze/DozePauser.java
@@ -28,20 +28,21 @@ public class DozePauser implements DozeMachine.Part {
public static final String TAG = DozePauser.class.getSimpleName();
private final AlarmTimeout mPauseTimeout;
private final DozeMachine mMachine;
- private final long mTimeoutMs;
+ private final AlwaysOnDisplayPolicy mPolicy;
public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager,
AlwaysOnDisplayPolicy policy) {
mMachine = machine;
mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler);
- mTimeoutMs = policy.proxScreenOffDelayMs;
+ mPolicy = policy;
}
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
case DOZE_AOD_PAUSING:
- mPauseTimeout.schedule(mTimeoutMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+ mPauseTimeout.schedule(mPolicy.proxScreenOffDelayMs,
+ AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
break;
default:
mPauseTimeout.cancel();
diff --git a/com/android/systemui/doze/DozeScreenBrightness.java b/com/android/systemui/doze/DozeScreenBrightness.java
index 03407e2b..4bb4e79c 100644
--- a/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/com/android/systemui/doze/DozeScreenBrightness.java
@@ -22,6 +22,7 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
+import android.os.Trace;
import com.android.internal.annotations.VisibleForTesting;
@@ -94,9 +95,14 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen
@Override
public void onSensorChanged(SensorEvent event) {
- if (mRegistered) {
- mLastSensorValue = (int) event.values[0];
- updateBrightnessAndReady();
+ Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]);
+ try {
+ if (mRegistered) {
+ mLastSensorValue = (int) event.values[0];
+ updateBrightnessAndReady();
+ }
+ } finally {
+ Trace.endSection();
}
}
diff --git a/com/android/systemui/globalactions/GlobalActionsDialog.java b/com/android/systemui/globalactions/GlobalActionsDialog.java
index 4cbbbd6c..189badfc 100644
--- a/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1280,7 +1280,23 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
mGradientDrawable.setScreenSize(displaySize.x, displaySize.y);
GradientColors colors = mColorExtractor.getColors(mKeyguardShowing ?
WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM);
- mGradientDrawable.setColors(colors, false);
+ updateColors(colors, false /* animate */);
+ }
+
+ /**
+ * Updates background and system bars according to current GradientColors.
+ * @param colors Colors and hints to use.
+ * @param animate Interpolates gradient if true, just sets otherwise.
+ */
+ private void updateColors(GradientColors colors, boolean animate) {
+ mGradientDrawable.setColors(colors, animate);
+ View decorView = getWindow().getDecorView();
+ if (colors.supportsDarkText()) {
+ decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
+ View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ } else {
+ decorView.setSystemUiVisibility(0);
+ }
}
@Override
@@ -1350,11 +1366,13 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogIn
public void onColorsChanged(ColorExtractor extractor, int which) {
if (mKeyguardShowing) {
if ((WallpaperManager.FLAG_LOCK & which) != 0) {
- mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK));
+ updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK),
+ true /* animate */);
}
} else {
if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
- mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+ updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM),
+ true /* animate */);
}
}
}
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3eb68f52..28adca97 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.telephony.IccCardConstants.State.ABSENT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
@@ -239,6 +240,9 @@ public class KeyguardViewMediator extends SystemUI {
// answer whether the input should be restricted)
private boolean mShowing;
+ // display id of the secondary display on which we have put a keyguard window
+ private int mSecondaryDisplayShowing = INVALID_DISPLAY;
+
/** Cached value of #isInputRestricted */
private boolean mInputRestricted;
@@ -646,6 +650,13 @@ public class KeyguardViewMediator extends SystemUI {
}
return KeyguardSecurityView.PROMPT_REASON_NONE;
}
+
+ @Override
+ public void onSecondaryDisplayShowingChanged(int displayId) {
+ synchronized (KeyguardViewMediator.this) {
+ setShowingLocked(mShowing, displayId, false);
+ }
+ }
};
public void userActivity() {
@@ -670,7 +681,7 @@ public class KeyguardViewMediator extends SystemUI {
filter.addAction(Intent.ACTION_SHUTDOWN);
mContext.registerReceiver(mBroadcastReceiver, filter);
- mKeyguardDisplayManager = new KeyguardDisplayManager(mContext);
+ mKeyguardDisplayManager = new KeyguardDisplayManager(mContext, mViewMediatorCallback);
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
@@ -685,7 +696,8 @@ public class KeyguardViewMediator extends SystemUI {
com.android.keyguard.R.bool.config_enableKeyguardService)) {
setShowingLocked(!shouldWaitForProvisioning()
&& !mLockPatternUtils.isLockScreenDisabled(
- KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
+ KeyguardUpdateMonitor.getCurrentUser()),
+ mSecondaryDisplayShowing, true /* forceCallbacks */);
}
mStatusBarKeyguardViewManager =
@@ -1694,10 +1706,10 @@ public class KeyguardViewMediator extends SystemUI {
playSound(mTrustedSoundId);
}
- private void updateActivityLockScreenState(boolean showing) {
+ private void updateActivityLockScreenState(boolean showing, int secondaryDisplayShowing) {
mUiOffloadThread.submit(() -> {
try {
- ActivityManager.getService().setLockScreenShown(showing);
+ ActivityManager.getService().setLockScreenShown(showing, secondaryDisplayShowing);
} catch (RemoteException e) {
}
});
@@ -2060,30 +2072,39 @@ public class KeyguardViewMediator extends SystemUI {
}
private void setShowingLocked(boolean showing) {
- setShowingLocked(showing, false /* forceCallbacks */);
+ setShowingLocked(showing, mSecondaryDisplayShowing, false /* forceCallbacks */);
}
- private void setShowingLocked(boolean showing, boolean forceCallbacks) {
- if (showing != mShowing || forceCallbacks) {
+ private void setShowingLocked(
+ boolean showing, int secondaryDisplayShowing, boolean forceCallbacks) {
+ final boolean notifyDefaultDisplayCallbacks = showing != mShowing || forceCallbacks;
+ if (notifyDefaultDisplayCallbacks || secondaryDisplayShowing != mSecondaryDisplayShowing) {
mShowing = showing;
- int size = mKeyguardStateCallbacks.size();
- for (int i = size - 1; i >= 0; i--) {
- IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
- try {
- callback.onShowingStateChanged(showing);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onShowingStateChanged", e);
- if (e instanceof DeadObjectException) {
- mKeyguardStateCallbacks.remove(callback);
- }
+ mSecondaryDisplayShowing = secondaryDisplayShowing;
+ if (notifyDefaultDisplayCallbacks) {
+ notifyDefaultDisplayCallbacks(showing);
+ }
+ updateActivityLockScreenState(showing, secondaryDisplayShowing);
+ }
+ }
+
+ private void notifyDefaultDisplayCallbacks(boolean showing) {
+ int size = mKeyguardStateCallbacks.size();
+ for (int i = size - 1; i >= 0; i--) {
+ IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
+ try {
+ callback.onShowingStateChanged(showing);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onShowingStateChanged", e);
+ if (e instanceof DeadObjectException) {
+ mKeyguardStateCallbacks.remove(callback);
}
}
- updateInputRestrictedLocked();
- mUiOffloadThread.submit(() -> {
- mTrustManager.reportKeyguardShowingChanged();
- });
- updateActivityLockScreenState(showing);
}
+ updateInputRestrictedLocked();
+ mUiOffloadThread.submit(() -> {
+ mTrustManager.reportKeyguardShowingChanged();
+ });
}
private void notifyTrustedChangedLocked(boolean trusted) {
diff --git a/com/android/systemui/media/NotificationPlayer.java b/com/android/systemui/media/NotificationPlayer.java
index 50720e9f..b5c0d538 100644
--- a/com/android/systemui/media/NotificationPlayer.java
+++ b/com/android/systemui/media/NotificationPlayer.java
@@ -29,6 +29,8 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.LinkedList;
/**
@@ -57,8 +59,12 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
}
}
- private LinkedList<Command> mCmdQueue = new LinkedList();
+ private final LinkedList<Command> mCmdQueue = new LinkedList<Command>();
+ private final Object mCompletionHandlingLock = new Object();
+ @GuardedBy("mCompletionHandlingLock")
+ private CreationAndCompletionThread mCompletionThread;
+ @GuardedBy("mCompletionHandlingLock")
private Looper mLooper;
/*
@@ -76,7 +82,10 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
public void run() {
Looper.prepare();
+ // ok to modify mLooper as here we are
+ // synchronized on mCompletionHandlingLock due to the Object.wait() in startSound(cmd)
mLooper = Looper.myLooper();
+ if (DEBUG) Log.d(mTag, "in run: new looper " + mLooper);
synchronized(this) {
AudioManager audioManager =
(AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
@@ -97,7 +106,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
&& (mCmd.uri.getEncodedPath().length() > 0)) {
if (!audioManager.isMusicActiveRemotely()) {
- synchronized(mQueueAudioFocusLock) {
+ synchronized (mQueueAudioFocusLock) {
if (mAudioManagerWithAudioFocus == null) {
if (DEBUG) Log.d(mTag, "requesting AudioFocus");
int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
@@ -129,7 +138,9 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
Log.e(mTag, "Exception while sleeping to sync notification playback"
+ " with ducking", e);
}
+ if (DEBUG) { Log.d(mTag, "player.start"); }
if (mPlayer != null) {
+ if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
mPlayer.release();
}
mPlayer = player;
@@ -148,7 +159,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
// is playing, let it continue until we're done, so there
// is less of a glitch.
try {
- if (DEBUG) Log.d(mTag, "Starting playback");
+ if (DEBUG) { Log.d(mTag, "startSound()"); }
//-----------------------------------
// This is were we deviate from the AsyncPlayer implementation and create the
// MediaPlayer in a new thread with which we're synchronized
@@ -158,10 +169,11 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
// matters
if((mLooper != null)
&& (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
+ if (DEBUG) { Log.d(mTag, "in startSound quitting looper " + mLooper); }
mLooper.quit();
}
mCompletionThread = new CreationAndCompletionThread(cmd);
- synchronized(mCompletionThread) {
+ synchronized (mCompletionThread) {
mCompletionThread.start();
mCompletionThread.wait();
}
@@ -209,13 +221,18 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
mPlayer = null;
synchronized(mQueueAudioFocusLock) {
if (mAudioManagerWithAudioFocus != null) {
+ if (DEBUG) { Log.d(mTag, "in STOP: abandonning AudioFocus"); }
mAudioManagerWithAudioFocus.abandonAudioFocus(null);
mAudioManagerWithAudioFocus = null;
}
}
- if((mLooper != null)
- && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
- mLooper.quit();
+ synchronized (mCompletionHandlingLock) {
+ if ((mLooper != null) &&
+ (mLooper.getThread().getState() != Thread.State.TERMINATED))
+ {
+ if (DEBUG) { Log.d(mTag, "in STOP: quitting looper "+ mLooper); }
+ mLooper.quit();
+ }
}
} else {
Log.w(mTag, "STOP command without a player");
@@ -250,9 +267,11 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
}
// if there are no more sounds to play, end the Looper to listen for media completion
synchronized (mCmdQueue) {
- if (mCmdQueue.size() == 0) {
- synchronized(mCompletionHandlingLock) {
- if(mLooper != null) {
+ synchronized(mCompletionHandlingLock) {
+ if (DEBUG) { Log.d(mTag, "onCompletion queue size=" + mCmdQueue.size()); }
+ if ((mCmdQueue.size() == 0)) {
+ if (mLooper != null) {
+ if (DEBUG) { Log.d(mTag, "in onCompletion quitting looper " + mLooper); }
mLooper.quit();
}
mCompletionThread = null;
@@ -269,13 +288,20 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
}
private String mTag;
+
+ @GuardedBy("mCmdQueue")
private CmdThread mThread;
- private CreationAndCompletionThread mCompletionThread;
- private final Object mCompletionHandlingLock = new Object();
+
private MediaPlayer mPlayer;
+
+
+ @GuardedBy("mCmdQueue")
private PowerManager.WakeLock mWakeLock;
+
private final Object mQueueAudioFocusLock = new Object();
- private AudioManager mAudioManagerWithAudioFocus; // synchronized on mQueueAudioFocusLock
+ @GuardedBy("mQueueAudioFocusLock")
+ private AudioManager mAudioManagerWithAudioFocus;
+
private int mNotificationRampTimeMs = 0;
// The current state according to the caller. Reality lags behind
@@ -311,6 +337,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
*/
@Deprecated
public void play(Context context, Uri uri, boolean looping, int stream) {
+ if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play");
Command cmd = new Command();
cmd.requestTime = SystemClock.uptimeMillis();
@@ -339,6 +366,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
* (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
*/
public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
+ if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
Command cmd = new Command();
cmd.requestTime = SystemClock.uptimeMillis();
cmd.code = PLAY;
@@ -357,6 +385,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
* at this point. Calling this multiple times has no ill effects.
*/
public void stop() {
+ if (DEBUG) { Log.d(mTag, "stop"); }
synchronized (mCmdQueue) {
// This check allows stop to be called multiple times without starting
// a thread that ends up doing nothing.
@@ -370,6 +399,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
}
}
+ @GuardedBy("mCmdQueue")
private void enqueueLocked(Command cmd) {
mCmdQueue.add(cmd);
if (mThread == null) {
@@ -393,22 +423,26 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener
* @hide
*/
public void setUsesWakeLock(Context context) {
- if (mWakeLock != null || mThread != null) {
- // if either of these has happened, we've already played something.
- // and our releases will be out of sync.
- throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
- + " mThread=" + mThread);
+ synchronized (mCmdQueue) {
+ if (mWakeLock != null || mThread != null) {
+ // if either of these has happened, we've already played something.
+ // and our releases will be out of sync.
+ throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
+ + " mThread=" + mThread);
+ }
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
}
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
}
+ @GuardedBy("mCmdQueue")
private void acquireWakeLock() {
if (mWakeLock != null) {
mWakeLock.acquire();
}
}
+ @GuardedBy("mCmdQueue")
private void releaseWakeLock() {
if (mWakeLock != null) {
mWakeLock.release();
diff --git a/com/android/systemui/pip/phone/PipManager.java b/com/android/systemui/pip/phone/PipManager.java
index b3f992db..f8996aae 100644
--- a/com/android/systemui/pip/phone/PipManager.java
+++ b/com/android/systemui/pip/phone/PipManager.java
@@ -16,7 +16,8 @@
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
@@ -30,6 +31,7 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
import android.view.IPinnedStackController;
import android.view.IPinnedStackListener;
import android.view.IWindowManager;
@@ -70,11 +72,11 @@ public class PipManager implements BasePipManager {
*/
TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
- public void onActivityPinned(String packageName, int taskId) {
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mMenuController.onActivityPinned();
- mNotificationController.onActivityPinned(packageName,
+ mNotificationController.onActivityPinned(packageName, userId,
true /* deferUntilAnimationEnds */);
SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
@@ -82,13 +84,15 @@ public class PipManager implements BasePipManager {
@Override
public void onActivityUnpinned() {
- ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext,
- mActivityManager);
- mMenuController.onActivityUnpinned(topPipActivity);
- mTouchHandler.onActivityUnpinned(topPipActivity);
- mNotificationController.onActivityUnpinned(topPipActivity);
-
- SystemServicesProxy.getInstance(mContext).setPipVisibility(topPipActivity != null);
+ final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPinnedActivity(
+ mContext, mActivityManager);
+ final ComponentName topActivity = topPipActivityInfo.first;
+ final int userId = topActivity != null ? topPipActivityInfo.second : 0;
+ mMenuController.onActivityUnpinned();
+ mTouchHandler.onActivityUnpinned(topActivity);
+ mNotificationController.onActivityUnpinned(topActivity, userId);
+
+ SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
}
@Override
@@ -196,7 +200,8 @@ public class PipManager implements BasePipManager {
public final void onBusEvent(ExpandPipEvent event) {
if (event.clearThumbnailWindows) {
try {
- StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo stackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (stackInfo != null && stackInfo.taskIds != null) {
SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
for (int taskId : stackInfo.taskIds) {
diff --git a/com/android/systemui/pip/phone/PipMediaController.java b/com/android/systemui/pip/phone/PipMediaController.java
index b3a0794f..174a7ef1 100644
--- a/com/android/systemui/pip/phone/PipMediaController.java
+++ b/com/android/systemui/pip/phone/PipMediaController.java
@@ -230,7 +230,7 @@ public class PipMediaController {
private void resolveActiveMediaController(List<MediaController> controllers) {
if (controllers != null) {
final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
- mActivityManager);
+ mActivityManager).first;
if (topActivity != null) {
for (int i = 0; i < controllers.size(); i++) {
final MediaController controller = controllers.get(i);
diff --git a/com/android/systemui/pip/phone/PipMenuActivityController.java b/com/android/systemui/pip/phone/PipMenuActivityController.java
index 34666fb3..9fb201b8 100644
--- a/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -16,7 +16,8 @@
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
@@ -223,7 +224,7 @@ public class PipMenuActivityController {
}
}
- public void onActivityUnpinned(ComponentName topPipActivity) {
+ public void onActivityUnpinned() {
hideMenu();
setStartActivityRequested(false);
}
@@ -383,7 +384,8 @@ public class PipMenuActivityController {
private void startMenuActivity(int menuState, Rect stackBounds, Rect movementBounds,
boolean allowMenuTimeout, boolean willResizeMenu) {
try {
- StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
pinnedStackInfo.taskIds.length > 0) {
Intent intent = new Intent(mContext, PipMenuActivity.class);
@@ -421,7 +423,8 @@ public class PipMenuActivityController {
// Fetch the pinned stack bounds
Rect stackBounds = null;
try {
- StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (pinnedStackInfo != null) {
stackBounds = pinnedStackInfo.bounds;
}
diff --git a/com/android/systemui/pip/phone/PipMotionHelper.java b/com/android/systemui/pip/phone/PipMotionHelper.java
index cebb22f0..21a836c0 100644
--- a/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -16,8 +16,10 @@
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
@@ -121,7 +123,8 @@ public class PipMotionHelper implements Handler.Callback {
void synchronizePinnedStackBounds() {
cancelAnimations();
try {
- StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo stackInfo =
+ mActivityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (stackInfo != null) {
mBounds.set(stackInfo.bounds);
}
@@ -158,13 +161,7 @@ public class PipMotionHelper implements Handler.Callback {
mMenuController.hideMenuWithoutResize();
mHandler.post(() -> {
try {
- if (skipAnimation) {
- mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true /* onTop */);
- } else {
- mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
- true /* allowResizeInDockedMode */, true /* preserveWindows */,
- true /* animate */, EXPAND_STACK_TO_FULLSCREEN_DURATION);
- }
+ mActivityManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION);
} catch (RemoteException e) {
Log.e(TAG, "Error expanding PiP activity", e);
}
@@ -182,7 +179,7 @@ public class PipMotionHelper implements Handler.Callback {
mMenuController.hideMenuWithoutResize();
mHandler.post(() -> {
try {
- mActivityManager.removeStack(PINNED_STACK_ID);
+ mActivityManager.removeStacksInWindowingModes(new int[]{ WINDOWING_MODE_PINNED });
} catch (RemoteException e) {
Log.e(TAG, "Failed to remove PiP", e);
}
@@ -529,14 +526,15 @@ public class PipMotionHelper implements Handler.Callback {
Rect toBounds = (Rect) args.arg1;
int duration = args.argi1;
try {
- StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ StackInfo stackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (stackInfo == null) {
// In the case where we've already re-expanded or dismissed the PiP, then
// just skip the resize
return true;
}
- mActivityManager.resizeStack(PINNED_STACK_ID, toBounds,
+ mActivityManager.resizeStack(stackInfo.stackId, toBounds,
false /* allowResizeInDockedMode */, true /* preserveWindows */,
true /* animate */, duration);
mBounds.set(toBounds);
diff --git a/com/android/systemui/pip/phone/PipNotificationController.java b/com/android/systemui/pip/phone/PipNotificationController.java
index 696fdbc8..6d083e9d 100644
--- a/com/android/systemui/pip/phone/PipNotificationController.java
+++ b/com/android/systemui/pip/phone/PipNotificationController.java
@@ -35,10 +35,15 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.drawable.Icon;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
import android.util.Log;
+import android.util.Pair;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -57,22 +62,29 @@ public class PipNotificationController {
private IActivityManager mActivityManager;
private AppOpsManager mAppOpsManager;
private NotificationManager mNotificationManager;
+ private IconDrawableFactory mIconDrawableFactory;
private PipMotionHelper mMotionHelper;
// Used when building a deferred notification
private String mDeferredNotificationPackageName;
+ private int mDeferredNotificationUserId;
private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
@Override
public void onOpChanged(String op, String packageName) {
try {
// Dismiss the PiP once the user disables the app ops setting for that package
- final ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
- packageName, 0);
- if (mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, packageName)
- != MODE_ALLOWED) {
- mMotionHelper.dismissPip();
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+ if (topPipActivityInfo.first != null) {
+ final ApplicationInfo appInfo = mContext.getPackageManager()
+ .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
+ if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
+ mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
+ packageName) != MODE_ALLOWED) {
+ mMotionHelper.dismissPip();
+ }
}
} catch (NameNotFoundException e) {
// Unregister the listener if the package can't be found
@@ -88,16 +100,18 @@ public class PipNotificationController {
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mNotificationManager = NotificationManager.from(context);
mMotionHelper = motionHelper;
+ mIconDrawableFactory = IconDrawableFactory.newInstance(context);
}
- public void onActivityPinned(String packageName, boolean deferUntilAnimationEnds) {
+ public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
// Clear any existing notification
mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
if (deferUntilAnimationEnds) {
mDeferredNotificationPackageName = packageName;
+ mDeferredNotificationUserId = userId;
} else {
- showNotificationForApp(mDeferredNotificationPackageName);
+ showNotificationForApp(packageName, userId);
}
// Register for changes to the app ops setting for this package while it is in PiP
@@ -106,22 +120,25 @@ public class PipNotificationController {
public void onPinnedStackAnimationEnded() {
if (mDeferredNotificationPackageName != null) {
- showNotificationForApp(mDeferredNotificationPackageName);
+ showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
mDeferredNotificationPackageName = null;
+ mDeferredNotificationUserId = 0;
}
}
- public void onActivityUnpinned(ComponentName topPipActivity) {
+ public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
// Unregister for changes to the previously PiP'ed package
unregisterAppOpsListener();
// Reset the deferred notification package
mDeferredNotificationPackageName = null;
+ mDeferredNotificationUserId = 0;
if (topPipActivity != null) {
// onActivityUnpinned() is only called after the transition is complete, so we don't
// need to defer until the animation ends to update the notification
- onActivityPinned(topPipActivity.getPackageName(), false /* deferUntilAnimationEnds */);
+ onActivityPinned(topPipActivity.getPackageName(), userId,
+ false /* deferUntilAnimationEnds */);
} else {
mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
}
@@ -130,20 +147,27 @@ public class PipNotificationController {
/**
* Builds and shows the notification for the given app.
*/
- private void showNotificationForApp(String packageName) {
+ private void showNotificationForApp(String packageName, int userId) {
// Build a new notification
- final Notification.Builder builder =
- new Notification.Builder(mContext, NotificationChannels.GENERAL)
- .setLocalOnly(true)
- .setOngoing(true)
- .setSmallIcon(R.drawable.pip_notification_icon)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- if (updateNotificationForApp(builder, packageName)) {
- SystemUI.overrideNotificationAppName(mContext, builder);
-
- // Show the new notification
- mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+ try {
+ final UserHandle user = UserHandle.of(userId);
+ final Context userContext = mContext.createPackageContextAsUser(
+ mContext.getPackageName(), 0, user);
+ final Notification.Builder builder =
+ new Notification.Builder(userContext, NotificationChannels.GENERAL)
+ .setLocalOnly(true)
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.pip_notification_icon)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ if (updateNotificationForApp(builder, packageName, user)) {
+ SystemUI.overrideNotificationAppName(mContext, builder);
+
+ // Show the new notification
+ mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not show notification for application", e);
}
}
@@ -151,33 +175,33 @@ public class PipNotificationController {
* Updates the notification builder with app-specific information, returning whether it was
* successful.
*/
- private boolean updateNotificationForApp(Notification.Builder builder, String packageName) {
+ private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
+ UserHandle user) throws NameNotFoundException {
final PackageManager pm = mContext.getPackageManager();
final ApplicationInfo appInfo;
try {
- appInfo = pm.getApplicationInfo(packageName, 0);
+ appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
} catch (NameNotFoundException e) {
Log.e(TAG, "Could not update notification for application", e);
return false;
}
if (appInfo != null) {
- final String appName = pm.getApplicationLabel(appInfo).toString();
+ final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
+ .toString();
final String message = mContext.getString(R.string.pip_notification_message, appName);
final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
Uri.fromParts("package", packageName, null));
+ settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
- final Icon appIcon = appInfo.icon != 0
- ? Icon.createWithResource(packageName, appInfo.icon)
- : Icon.createWithResource(Resources.getSystem(),
- com.android.internal.R.drawable.sym_def_app_icon);
+ final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
.setContentText(message)
- .setContentIntent(PendingIntent.getActivity(mContext, packageName.hashCode(),
- settingsIntent, FLAG_CANCEL_CURRENT))
+ .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
+ settingsIntent, FLAG_CANCEL_CURRENT, null, user))
.setStyle(new Notification.BigTextStyle().bigText(message))
- .setLargeIcon(appIcon);
+ .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
return true;
}
return false;
@@ -191,4 +215,17 @@ public class PipNotificationController {
private void unregisterAppOpsListener() {
mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
}
+
+ /**
+ * Bakes a drawable into a bitmap.
+ */
+ private Bitmap createBitmap(Drawable d) {
+ Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
+ Config.ARGB_8888);
+ Canvas c = new Canvas(bitmap);
+ d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+ d.draw(c);
+ c.setBitmap(null);
+ return bitmap;
+ }
}
diff --git a/com/android/systemui/pip/phone/PipUtils.java b/com/android/systemui/pip/phone/PipUtils.java
index a8cdd1bd..2f53de96 100644
--- a/com/android/systemui/pip/phone/PipUtils.java
+++ b/com/android/systemui/pip/phone/PipUtils.java
@@ -16,7 +16,8 @@
package com.android.systemui.pip.phone;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
@@ -24,33 +25,35 @@ import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pair;
public class PipUtils {
private static final String TAG = "PipUtils";
/**
- * @return the ComponentName of the top non-SystemUI activity in the pinned stack, or null if
- * none exists.
+ * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
+ * The component name may be null if no such activity exists.
*/
- public static ComponentName getTopPinnedActivity(Context context,
+ public static Pair<ComponentName, Integer> getTopPinnedActivity(Context context,
IActivityManager activityManager) {
try {
final String sysUiPackageName = context.getPackageName();
- final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+ final StackInfo pinnedStackInfo =
+ activityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
pinnedStackInfo.taskIds.length > 0) {
for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) {
ComponentName cn = ComponentName.unflattenFromString(
pinnedStackInfo.taskNames[i]);
if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
- return cn;
+ return new Pair<>(cn, pinnedStackInfo.taskUserIds[i]);
}
}
}
} catch (RemoteException e) {
Log.w(TAG, "Unable to get pinned stack.");
}
- return null;
+ return new Pair<>(null, 0);
}
}
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index e8c12952..e0445c16 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -53,7 +53,9 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
/**
@@ -121,6 +123,7 @@ public class PipManager implements BasePipManager {
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
private boolean mInitialized;
private int mPipTaskId = TASK_ID_NO_PIP;
+ private int mPinnedStackId = INVALID_STACK_ID;
private ComponentName mPipComponentName;
private MediaController mPipMediaController;
private String[] mLastPackagesResourceGranted;
@@ -336,9 +339,11 @@ public class PipManager implements BasePipManager {
mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
if (removePipStack) {
try {
- mActivityManager.removeStack(PINNED_STACK_ID);
+ mActivityManager.removeStack(mPinnedStackId);
} catch (RemoteException e) {
Log.e(TAG, "removeStack failed", e);
+ } finally {
+ mPinnedStackId = INVALID_STACK_ID;
}
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
@@ -424,7 +429,7 @@ public class PipManager implements BasePipManager {
}
try {
int animationDurationMs = -1;
- mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+ mActivityManager.resizeStack(mPinnedStackId, mCurrentPipBounds,
true, true, true, animationDurationMs);
} catch (RemoteException e) {
Log.e(TAG, "resizeStack failed", e);
@@ -502,7 +507,8 @@ public class PipManager implements BasePipManager {
private StackInfo getPinnedStackInfo() {
StackInfo stackInfo = null;
try {
- stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ stackInfo = mActivityManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
} catch (RemoteException e) {
Log.e(TAG, "getStackInfo failed", e);
}
@@ -654,7 +660,7 @@ public class PipManager implements BasePipManager {
}
@Override
- public void onActivityPinned(String packageName, int taskId) {
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
if (!checkCurrentUserId(mContext, DEBUG)) {
return;
@@ -665,6 +671,7 @@ public class PipManager implements BasePipManager {
return;
}
if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+ mPinnedStackId = stackInfo.stackId;
mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
mPipComponentName = ComponentName.unflattenFromString(
stackInfo.taskNames[stackInfo.taskNames.length - 1]);
diff --git a/com/android/systemui/qs/AlphaControlledSignalTileView.java b/com/android/systemui/qs/AlphaControlledSignalTileView.java
new file mode 100644
index 00000000..2c7ec70a
--- /dev/null
+++ b/com/android/systemui/qs/AlphaControlledSignalTileView.java
@@ -0,0 +1,86 @@
+/*
+ * 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 com.android.systemui.qs;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import com.android.systemui.qs.tileimpl.SlashImageView;
+
+
+/**
+ * Creates AlphaControlledSlashImageView instead of SlashImageView
+ */
+public class AlphaControlledSignalTileView extends SignalTileView {
+ public AlphaControlledSignalTileView(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected SlashImageView createSlashImageView(Context context) {
+ return new AlphaControlledSlashImageView(context);
+ }
+
+ /**
+ * Creates AlphaControlledSlashDrawable instead of regular SlashDrawables
+ */
+ public static class AlphaControlledSlashImageView extends SlashImageView {
+ public AlphaControlledSlashImageView(Context context) {
+ super(context);
+ }
+
+ public void setFinalImageTintList(ColorStateList tint) {
+ super.setImageTintList(tint);
+ final SlashDrawable slash = getSlash();
+ if (slash != null) {
+ ((AlphaControlledSlashDrawable)slash).setFinalTintList(tint);
+ }
+ }
+
+ @Override
+ protected void ensureSlashDrawable() {
+ if (getSlash() == null) {
+ final SlashDrawable slash = new AlphaControlledSlashDrawable(getDrawable());
+ setSlash(slash);
+ slash.setAnimationEnabled(getAnimationEnabled());
+ setImageViewDrawable(slash);
+ }
+ }
+ }
+
+ /**
+ * SlashDrawable that disobeys orders to change its drawable's tint except when you tell
+ * it not to disobey. The slash still will animate its alpha.
+ */
+ public static class AlphaControlledSlashDrawable extends SlashDrawable {
+ AlphaControlledSlashDrawable(Drawable d) {
+ super(d);
+ }
+
+ @Override
+ protected void setDrawableTintList(ColorStateList tint) {
+ }
+
+ /**
+ * Set a target tint list instead of
+ */
+ public void setFinalTintList(ColorStateList tint) {
+ super.setDrawableTintList(tint);
+ }
+ }
+}
+
diff --git a/com/android/systemui/qs/SignalTileView.java b/com/android/systemui/qs/SignalTileView.java
index b300e4a3..9ee40ccf 100644
--- a/com/android/systemui/qs/SignalTileView.java
+++ b/com/android/systemui/qs/SignalTileView.java
@@ -63,13 +63,17 @@ public class SignalTileView extends QSIconViewImpl {
@Override
protected View createIcon() {
mIconFrame = new FrameLayout(mContext);
- mSignal = new SlashImageView(mContext);
+ mSignal = createSlashImageView(mContext);
mIconFrame.addView(mSignal);
mOverlay = new ImageView(mContext);
mIconFrame.addView(mOverlay, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
return mIconFrame;
}
+ protected SlashImageView createSlashImageView(Context context) {
+ return new SlashImageView(context);
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/com/android/systemui/qs/SlashDrawable.java b/com/android/systemui/qs/SlashDrawable.java
index c3561489..a9b2376e 100644
--- a/com/android/systemui/qs/SlashDrawable.java
+++ b/com/android/systemui/qs/SlashDrawable.java
@@ -197,11 +197,15 @@ public class SlashDrawable extends Drawable {
public void setTintList(@Nullable ColorStateList tint) {
mTintList = tint;
super.setTintList(tint);
- mDrawable.setTintList(tint);
+ setDrawableTintList(tint);
mPaint.setColor(tint.getDefaultColor());
invalidateSelf();
}
+ protected void setDrawableTintList(@Nullable ColorStateList tint) {
+ mDrawable.setTintList(tint);
+ }
+
@Override
public void setTintMode(@NonNull Mode tintMode) {
mTintMode = tintMode;
diff --git a/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 8074cb9b..e8c8b907 100644
--- a/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -33,6 +33,7 @@ import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.qs.AlphaControlledSignalTileView.AlphaControlledSlashImageView;
import java.util.Objects;
public class QSIconViewImpl extends QSIconView {
@@ -138,7 +139,12 @@ public class QSIconViewImpl extends QSIconView {
animateGrayScale(mTint, color, iv);
mTint = color;
} else {
- setTint(iv, color);
+ if (iv instanceof AlphaControlledSlashImageView) {
+ ((AlphaControlledSlashImageView)iv)
+ .setFinalImageTintList(ColorStateList.valueOf(color));
+ } else {
+ setTint(iv, color);
+ }
mTint = color;
}
}
@@ -149,6 +155,10 @@ public class QSIconViewImpl extends QSIconView {
}
public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
+ if (iv instanceof AlphaControlledSlashImageView) {
+ ((AlphaControlledSlashImageView)iv)
+ .setFinalImageTintList(ColorStateList.valueOf(toColor));
+ }
if (ValueAnimator.areAnimatorsEnabled()) {
final float fromAlpha = Color.alpha(fromColor);
final float toAlpha = Color.alpha(toColor);
diff --git a/com/android/systemui/qs/tileimpl/SlashImageView.java b/com/android/systemui/qs/tileimpl/SlashImageView.java
index 97e9c3df..63d6f82c 100644
--- a/com/android/systemui/qs/tileimpl/SlashImageView.java
+++ b/com/android/systemui/qs/tileimpl/SlashImageView.java
@@ -34,7 +34,15 @@ public class SlashImageView extends ImageView {
super(context);
}
- private void ensureSlashDrawable() {
+ protected SlashDrawable getSlash() {
+ return mSlash;
+ }
+
+ protected void setSlash(SlashDrawable slash) {
+ mSlash = slash;
+ }
+
+ protected void ensureSlashDrawable() {
if (mSlash == null) {
mSlash = new SlashDrawable(getDrawable());
mSlash.setAnimationEnabled(mAnimationEnabled);
@@ -56,10 +64,18 @@ public class SlashImageView extends ImageView {
}
}
+ protected void setImageViewDrawable(SlashDrawable slash) {
+ super.setImageDrawable(slash);
+ }
+
public void setAnimationEnabled(boolean enabled) {
mAnimationEnabled = enabled;
}
+ public boolean getAnimationEnabled() {
+ return mAnimationEnabled;
+ }
+
private void setSlashState(@NonNull SlashState slashState) {
ensureSlashDrawable();
mSlash.setRotation(slashState.rotation);
diff --git a/com/android/systemui/qs/tiles/BluetoothTile.java b/com/android/systemui/qs/tiles/BluetoothTile.java
index 8d62f2aa..81b8622c 100644
--- a/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -23,6 +23,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
@@ -34,10 +35,8 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.R.drawable;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -135,11 +134,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
if (lastDevice != null) {
int batteryLevel = lastDevice.getBatteryLevel();
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- BluetoothDeviceLayerDrawable drawable = createLayerDrawable(mContext,
- R.drawable.ic_qs_bluetooth_connected, batteryLevel,
- mContext.getResources().getFraction(
- R.fraction.bt_battery_scale_fraction, 1, 1));
- state.icon = new DrawableIcon(drawable);
+ state.icon = new BluetoothBatteryDrawable(batteryLevel);
}
}
state.contentDescription = mContext.getString(
@@ -215,6 +210,22 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
return new BluetoothDetailAdapter();
}
+ private class BluetoothBatteryDrawable extends Icon {
+ private int mLevel;
+
+ BluetoothBatteryDrawable(int level) {
+ mLevel = level;
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ return createLayerDrawable(context,
+ R.drawable.ic_qs_bluetooth_connected, mLevel,
+ context.getResources().getFraction(
+ R.fraction.bt_battery_scale_fraction, 1, 1));
+ }
+ }
+
protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
// We probably won't ever have space in the UI for more than 20 devices, so don't
// get info for them.
diff --git a/com/android/systemui/qs/tiles/CellularTile.java b/com/android/systemui/qs/tiles/CellularTile.java
index 2e389ba1..0ce3e6aa 100644
--- a/com/android/systemui/qs/tiles/CellularTile.java
+++ b/com/android/systemui/qs/tiles/CellularTile.java
@@ -266,7 +266,7 @@ public class CellularTile extends QSTileImpl<SignalState> {
}
@Override
- public void setNoSims(boolean show) {
+ public void setNoSims(boolean show, boolean simDetected) {
mInfo.noSim = show;
if (mInfo.noSim) {
// Make sure signal gets cleared out when no sims.
diff --git a/com/android/systemui/qs/tiles/WifiTile.java b/com/android/systemui/qs/tiles/WifiTile.java
index 33b15121..23702736 100644
--- a/com/android/systemui/qs/tiles/WifiTile.java
+++ b/com/android/systemui/qs/tiles/WifiTile.java
@@ -37,10 +37,10 @@ import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
+import com.android.systemui.qs.AlphaControlledSignalTileView;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SignalTileView;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
@@ -104,7 +104,7 @@ public class WifiTile extends QSTileImpl<SignalState> {
@Override
public QSIconView createTileView(Context context) {
- return new SignalTileView(context);
+ return new AlphaControlledSignalTileView(context);
}
@Override
diff --git a/com/android/systemui/recents/Recents.java b/com/android/systemui/recents/Recents.java
index 406bcac0..283ac0c4 100644
--- a/com/android/systemui/recents/Recents.java
+++ b/com/android/systemui/recents/Recents.java
@@ -16,6 +16,9 @@
package com.android.systemui.recents;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
import android.app.ActivityManager;
@@ -437,9 +440,12 @@ public class Recents extends SystemUI
int currentUser = sSystemServicesProxy.getCurrentUser();
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ final int activityType = runningTask != null
+ ? runningTask.configuration.windowConfiguration.getActivityType()
+ : ACTIVITY_TYPE_UNDEFINED;
boolean screenPinningActive = ssp.isScreenPinningActive();
- boolean isRunningTaskInHomeOrRecentsStack = runningTask != null &&
- ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId);
+ boolean isRunningTaskInHomeOrRecentsStack =
+ activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
if (runningTask.supportsSplitScreenMultiWindow) {
diff --git a/com/android/systemui/recents/RecentsActivity.java b/com/android/systemui/recents/RecentsActivity.java
index f5455568..86b77900 100644
--- a/com/android/systemui/recents/RecentsActivity.java
+++ b/com/android/systemui/recents/RecentsActivity.java
@@ -358,6 +358,9 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
mScrimViews = new SystemBarScrimViews(this);
getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+ if (Recents.getConfiguration().isLowRamDevice) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index aecf95fc..3e2a5f3f 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -16,9 +16,9 @@
package com.android.systemui.recents;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.isHomeOrRecentsStack;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.View.MeasureSpec;
import android.app.ActivityManager;
@@ -173,7 +173,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
}
@Override
- public void onActivityPinned(String packageName, int taskId) {
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
// Check this is for the right user
if (!checkCurrentUserId(mContext, false /* debug */)) {
return;
@@ -533,7 +533,9 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
if (runningTask == null) return;
// Find the task in the recents list
- boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
+ boolean isRunningTaskInHomeStack =
+ runningTask.configuration.windowConfiguration.getActivityType()
+ == ACTIVITY_TYPE_HOME;
ArrayList<Task> tasks = focusedStack.getStackTasks();
Task toTask = null;
ActivityOptions launchOpts = null;
@@ -565,8 +567,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Launch the task
ssp.startActivityFromRecents(
- mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
- null /* resultListener */);
+ mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
}
/**
@@ -584,9 +585,10 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Return early if there is no running task (can't determine affiliated tasks in this case)
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+ final int activityType = runningTask.configuration.windowConfiguration.getActivityType();
if (runningTask == null) return;
// Return early if the running task is in the home/recents stack (optimization)
- if (isHomeOrRecentsStack(runningTask.stackId)) return;
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return;
// Find the task in the recents list
ArrayList<Task> tasks = focusedStack.getStackTasks();
@@ -639,8 +641,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
// Launch the task
ssp.startActivityFromRecents(
- mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
- null /* resultListener */);
+ mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
}
public void showNextAffiliatedTask() {
@@ -872,7 +873,9 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener
getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
Rect windowOverrideRect) {
final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
- if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
+ if (runningTask != null
+ && runningTask.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM) {
ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
diff --git a/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java b/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
index e02fb147..e4a4f592 100644
--- a/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
+++ b/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -22,5 +22,15 @@ import com.android.systemui.recents.events.EventBus;
* This is sent when the stack action button should be hidden.
*/
public class HideStackActionButtonEvent extends EventBus.Event {
- // Simple event
+
+ // Whether or not to translate the stack action button when hiding it
+ public final boolean translate;
+
+ public HideStackActionButtonEvent() {
+ this(true);
+ }
+
+ public HideStackActionButtonEvent(boolean translate) {
+ this.translate = translate;
+ }
}
diff --git a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
index 3db106e7..862a1eee 100644
--- a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
@@ -16,6 +16,9 @@
package com.android.systemui.recents.events.activity;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.graphics.Rect;
import com.android.systemui.recents.events.EventBus;
@@ -30,15 +33,23 @@ public class LaunchTaskEvent extends EventBus.Event {
public final TaskView taskView;
public final Task task;
public final Rect targetTaskBounds;
- public final int targetTaskStack;
+ public final int targetWindowingMode;
+ public final int targetActivityType;
public final boolean screenPinningRequested;
- public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds, int targetTaskStack,
+ public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
boolean screenPinningRequested) {
+ this(taskView, task, targetTaskBounds, screenPinningRequested,
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED);
+ }
+
+ public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
+ boolean screenPinningRequested, int windowingMode, int activityType) {
this.taskView = taskView;
this.task = task;
this.targetTaskBounds = targetTaskBounds;
- this.targetTaskStack = targetTaskStack;
+ this.targetWindowingMode = windowingMode;
+ this.targetActivityType = activityType;
this.screenPinningRequested = screenPinningRequested;
}
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index 71777822..bddf9a59 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,13 +16,15 @@
package com.android.systemui.recents.misc;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+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.ACTIVITY_TYPE_UNDEFINED;
+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;
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
import android.annotation.NonNull;
@@ -34,6 +36,7 @@ import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.KeyguardManager;
+import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -172,7 +175,7 @@ public class SystemServicesProxy {
public void onTaskStackChangedBackground() { }
public void onTaskStackChanged() { }
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
- public void onActivityPinned(String packageName, int taskId) { }
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
public void onActivityUnpinned() { }
public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
public void onPinnedStackAnimationStarted() { }
@@ -227,9 +230,11 @@ public class SystemServicesProxy {
}
@Override
- public void onActivityPinned(String packageName, int taskId) throws RemoteException {
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
+ throws RemoteException {
mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
- mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, taskId, 0, packageName).sendToTarget();
+ mHandler.obtainMessage(H.ON_ACTIVITY_PINNED,
+ new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
}
@Override
@@ -481,16 +486,22 @@ public class SystemServicesProxy {
public ActivityManager.RunningTaskInfo getRunningTask() {
// Note: The set of running tasks from the system is ordered by recency
List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
- if (tasks != null && !tasks.isEmpty()) {
- // Find the first task in a valid stack, we ignore everything from the Recents and PiP
- // stacks
- for (int i = 0; i < tasks.size(); i++) {
- ActivityManager.RunningTaskInfo task = tasks.get(i);
- int stackId = task.stackId;
- if (stackId != RECENTS_STACK_ID && stackId != PINNED_STACK_ID) {
- return task;
- }
+ if (tasks == null || tasks.isEmpty()) {
+ return null;
+ }
+
+ // Find the first task in a valid stack, we ignore everything from the Recents and PiP
+ // stacks
+ for (int i = 0; i < tasks.size(); i++) {
+ final ActivityManager.RunningTaskInfo task = tasks.get(i);
+ final WindowConfiguration winConfig = task.configuration.windowConfiguration;
+ if (winConfig.getActivityType() == ACTIVITY_TYPE_RECENTS) {
+ continue;
+ }
+ if (winConfig.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ continue;
}
+ return task;
}
return null;
}
@@ -517,12 +528,17 @@ public class SystemServicesProxy {
ActivityManager.StackInfo fullscreenStackInfo = null;
ActivityManager.StackInfo recentsStackInfo = null;
for (int i = 0; i < stackInfos.size(); i++) {
- StackInfo stackInfo = stackInfos.get(i);
- if (stackInfo.stackId == HOME_STACK_ID) {
+ final StackInfo stackInfo = stackInfos.get(i);
+ final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
+ final int activityType = winConfig.getActivityType();
+ final int windowingMode = winConfig.getWindowingMode();
+ if (activityType == ACTIVITY_TYPE_HOME) {
homeStackInfo = stackInfo;
- } else if (stackInfo.stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ } else if (activityType == ACTIVITY_TYPE_STANDARD
+ && (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
fullscreenStackInfo = stackInfo;
- } else if (stackInfo.stackId == RECENTS_STACK_ID) {
+ } else if (activityType == ACTIVITY_TYPE_RECENTS) {
recentsStackInfo = stackInfo;
}
}
@@ -576,7 +592,7 @@ public class SystemServicesProxy {
try {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setDockCreateMode(createMode);
- options.setLaunchStackId(DOCKED_STACK_ID);
+ options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
mIam.startActivityFromRecents(taskId, options.toBundle());
return true;
} catch (Exception e) {
@@ -601,34 +617,6 @@ public class SystemServicesProxy {
}
/**
- * Returns whether the given stack id is the home stack id.
- */
- public static boolean isHomeStack(int stackId) {
- return stackId == HOME_STACK_ID;
- }
-
- /**
- * Returns whether the given stack id is the pinned stack id.
- */
- public static boolean isPinnedStack(int stackId){
- return stackId == PINNED_STACK_ID;
- }
-
- /**
- * Returns whether the given stack id is the docked stack id.
- */
- public static boolean isDockedStack(int stackId) {
- return stackId == DOCKED_STACK_ID;
- }
-
- /**
- * Returns whether the given stack id is the freeform workspace stack id.
- */
- public static boolean isFreeformStack(int stackId) {
- return stackId == FREEFORM_WORKSPACE_STACK_ID;
- }
-
- /**
* @return whether there are any docked tasks for the current user.
*/
public boolean hasDockedTask() {
@@ -636,7 +624,8 @@ public class SystemServicesProxy {
ActivityManager.StackInfo stackInfo = null;
try {
- stackInfo = mIam.getStackInfo(DOCKED_STACK_ID);
+ stackInfo =
+ mIam.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -737,14 +726,12 @@ public class SystemServicesProxy {
}
}
- /**
- * Moves a task into another stack.
- */
- public void moveTaskToStack(int taskId, int stackId) {
+ /** Set the task's windowing mode. */
+ public void setTaskWindowingMode(int taskId, int windowingMode) {
if (mIam == null) return;
try {
- mIam.positionTaskInStack(taskId, stackId, 0);
+ mIam.setTaskWindowingMode(taskId, windowingMode, false /* onTop */);
} catch (RemoteException | IllegalArgumentException e) {
e.printStackTrace();
}
@@ -1101,9 +1088,10 @@ public class SystemServicesProxy {
try {
// Use the recents stack bounds, fallback to fullscreen stack if it is null
- ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID);
+ ActivityManager.StackInfo stackInfo =
+ mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
if (stackInfo == null) {
- stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
+ stackInfo = mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
}
if (stackInfo != null) {
windowRect.set(stackInfo.bounds);
@@ -1120,25 +1108,34 @@ public class SystemServicesProxy {
opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
}
+ public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
+ ActivityOptions options,
+ @Nullable final StartActivityFromRecentsResultListener resultListener) {
+ startActivityFromRecents(context, taskKey, taskName, options,
+ WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, resultListener);
+ }
+
/** Starts an activity from recents. */
public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
- ActivityOptions options, int stackId,
+ ActivityOptions options, int windowingMode, int activityType,
@Nullable final StartActivityFromRecentsResultListener resultListener) {
if (mIam == null) {
return;
}
- if (taskKey.stackId == DOCKED_STACK_ID) {
+ if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
// We show non-visible docked tasks in Recents, but we always want to launch
// them in the fullscreen stack.
if (options == null) {
options = ActivityOptions.makeBasic();
}
- options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID);
- } else if (stackId != INVALID_STACK_ID) {
+ options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ } else if (windowingMode != WINDOWING_MODE_UNDEFINED
+ || activityType != ACTIVITY_TYPE_UNDEFINED) {
if (options == null) {
options = ActivityOptions.makeBasic();
}
- options.setLaunchStackId(stackId);
+ options.setLaunchWindowingMode(windowingMode);
+ options.setLaunchActivityType(activityType);
}
final ActivityOptions finalOptions = options;
@@ -1307,6 +1304,20 @@ public class SystemServicesProxy {
void onStartActivityResult(boolean succeeded);
}
+ private class PinnedActivityInfo {
+ final String mPackageName;
+ final int mUserId;
+ final int mTaskId;
+ final int mStackId;
+
+ PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) {
+ mPackageName = packageName;
+ mUserId = userId;
+ mTaskId = taskId;
+ mStackId = stackId;
+ }
+ }
+
private final class H extends Handler {
private static final int ON_TASK_STACK_CHANGED = 1;
private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
@@ -1342,8 +1353,10 @@ public class SystemServicesProxy {
break;
}
case ON_ACTIVITY_PINNED: {
+ final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityPinned((String) msg.obj, msg.arg1);
+ mTaskStackListeners.get(i).onActivityPinned(
+ info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
}
break;
}
diff --git a/com/android/systemui/recents/model/HighResThumbnailLoader.java b/com/android/systemui/recents/model/HighResThumbnailLoader.java
index 48fa6c3c..6414ea1e 100644
--- a/com/android/systemui/recents/model/HighResThumbnailLoader.java
+++ b/com/android/systemui/recents/model/HighResThumbnailLoader.java
@@ -187,7 +187,7 @@ public class HighResThumbnailLoader implements TaskCallbacks {
}
@Override
- public void onTaskStackIdChanged() {
+ public void onTaskWindowingModeChanged() {
}
private final Runnable mLoader = new Runnable() {
diff --git a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 8d31730d..d5e03135 100644
--- a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -16,6 +16,8 @@
package com.android.systemui.recents.model;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -155,12 +157,13 @@ public class RecentsTaskLoadPlan {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
// Compose the task key
- Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
+ final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
+ Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, windowingMode, t.baseIntent,
t.userId, t.firstActiveTime, t.lastActiveTime);
// This task is only shown in the stack if it satisfies the historical time or min
// number of tasks constraints. Freeform tasks are also always shown.
- boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
+ boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
boolean isStackTask;
if (Recents.getConfiguration().isGridEnabled) {
// When grid layout is enabled, we only show the first
diff --git a/com/android/systemui/recents/model/Task.java b/com/android/systemui/recents/model/Task.java
index 9e6bf854..abdb5cb8 100644
--- a/com/android/systemui/recents/model/Task.java
+++ b/com/android/systemui/recents/model/Task.java
@@ -16,6 +16,8 @@
package com.android.systemui.recents.model;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Intent;
@@ -46,8 +48,8 @@ public class Task {
public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
/* Notifies when a task has been unbound */
public void onTaskDataUnloaded();
- /* Notifies when a task's stack id has changed. */
- public void onTaskStackIdChanged();
+ /* Notifies when a task's windowing mode has changed. */
+ public void onTaskWindowingModeChanged();
}
/* The Task Key represents the unique primary key for the task */
@@ -55,7 +57,7 @@ public class Task {
@ViewDebug.ExportedProperty(category="recents")
public final int id;
@ViewDebug.ExportedProperty(category="recents")
- public int stackId;
+ public int windowingMode;
@ViewDebug.ExportedProperty(category="recents")
public final Intent baseIntent;
@ViewDebug.ExportedProperty(category="recents")
@@ -67,10 +69,10 @@ public class Task {
private int mHashCode;
- public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
+ public TaskKey(int id, int windowingMode, Intent intent, int userId, long firstActiveTime,
long lastActiveTime) {
this.id = id;
- this.stackId = stackId;
+ this.windowingMode = windowingMode;
this.baseIntent = intent;
this.userId = userId;
this.firstActiveTime = firstActiveTime;
@@ -78,8 +80,8 @@ public class Task {
updateHashCode();
}
- public void setStackId(int stackId) {
- this.stackId = stackId;
+ public void setWindowingMode(int windowingMode) {
+ this.windowingMode = windowingMode;
updateHashCode();
}
@@ -93,7 +95,9 @@ public class Task {
return false;
}
TaskKey otherKey = (TaskKey) o;
- return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId;
+ return id == otherKey.id
+ && windowingMode == otherKey.windowingMode
+ && userId == otherKey.userId;
}
@Override
@@ -103,12 +107,12 @@ public class Task {
@Override
public String toString() {
- return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" +
- lastActiveTime;
+ return "id=" + id + " windowingMode=" + windowingMode + " user=" + userId
+ + " lastActiveTime=" + lastActiveTime;
}
private void updateHashCode() {
- mHashCode = Objects.hash(id, stackId, userId);
+ mHashCode = Objects.hash(id, windowingMode, userId);
}
}
@@ -277,14 +281,12 @@ public class Task {
this.group = group;
}
- /**
- * Updates the stack id of this task.
- */
- public void setStackId(int stackId) {
- key.setStackId(stackId);
+ /** Updates the task's windowing mode. */
+ public void setWindowingMode(int windowingMode) {
+ key.setWindowingMode(windowingMode);
int callbackCount = mCallbacks.size();
for (int i = 0; i < callbackCount; i++) {
- mCallbacks.get(i).onTaskStackIdChanged();
+ mCallbacks.get(i).onTaskWindowingModeChanged();
}
}
@@ -293,7 +295,7 @@ public class Task {
*/
public boolean isFreeformTask() {
SystemServicesProxy ssp = Recents.getSystemServices();
- return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
+ return ssp.hasFreeformWorkspaceSupport() && key.windowingMode == WINDOWING_MODE_FREEFORM;
}
/** Notifies the callback listeners that this task has been loaded */
diff --git a/com/android/systemui/recents/model/TaskKeyCache.java b/com/android/systemui/recents/model/TaskKeyCache.java
index be99f930..247a6542 100644
--- a/com/android/systemui/recents/model/TaskKeyCache.java
+++ b/com/android/systemui/recents/model/TaskKeyCache.java
@@ -45,7 +45,7 @@ public abstract class TaskKeyCache<V> {
final V getAndInvalidateIfModified(Task.TaskKey key) {
Task.TaskKey lastKey = mKeys.get(key.id);
if (lastKey != null) {
- if ((lastKey.stackId != key.stackId) ||
+ if ((lastKey.windowingMode != key.windowingMode) ||
(lastKey.lastActiveTime != key.lastActiveTime)) {
// The task has updated (been made active since the last time it was put into the
// LRU cache) or the stack id for the task has changed, invalidate that cache item
diff --git a/com/android/systemui/recents/model/TaskStack.java b/com/android/systemui/recents/model/TaskStack.java
index 6e3be09b..fdae917c 100644
--- a/com/android/systemui/recents/model/TaskStack.java
+++ b/com/android/systemui/recents/model/TaskStack.java
@@ -18,8 +18,8 @@ package com.android.systemui.recents.model;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
@@ -115,7 +115,7 @@ class FilteredTaskList {
/**
* Moves the given task.
*/
- public void moveTaskToStack(Task task, int insertIndex, int newStackId) {
+ public void setTaskWindowingMode(Task task, int insertIndex, int windowingMode) {
int taskIndex = indexOf(task);
if (taskIndex != insertIndex) {
mTasks.remove(taskIndex);
@@ -127,7 +127,7 @@ class FilteredTaskList {
// Update the stack id now, after we've moved the task, and before we update the
// filtered tasks
- task.setStackId(newStackId);
+ task.setWindowingMode(windowingMode);
updateFilteredTasks();
}
@@ -590,17 +590,15 @@ public class TaskStack {
mCb = cb;
}
- /**
- * Moves the given task to either the front of the freeform workspace or the stack.
- */
- public void moveTaskToStack(Task task, int newStackId) {
+ /** Sets the windowing mode for a given task. */
+ public void setTaskWindowingMode(Task task, int windowingMode) {
// Find the index to insert into
ArrayList<Task> taskList = mStackTaskList.getTasks();
int taskCount = taskList.size();
- if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
+ if (!task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FREEFORM)) {
// Insert freeform tasks at the front
- mStackTaskList.moveTaskToStack(task, taskCount, newStackId);
- } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
+ mStackTaskList.setTaskWindowingMode(task, taskCount, windowingMode);
+ } else if (task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FULLSCREEN)) {
// Insert after the first stacked task
int insertIndex = 0;
for (int i = taskCount - 1; i >= 0; i--) {
@@ -609,7 +607,7 @@ public class TaskStack {
break;
}
}
- mStackTaskList.moveTaskToStack(task, insertIndex, newStackId);
+ mStackTaskList.setTaskWindowingMode(task, insertIndex, windowingMode);
}
}
diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java
index b2675d7a..ee05d81c 100644
--- a/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -16,14 +16,17 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+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.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;
import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
@@ -107,7 +110,7 @@ public class RecentsTransitionHelper {
*/
public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
final TaskStackView stackView, final TaskView taskView,
- final boolean screenPinningRequested, final int destinationStack) {
+ final boolean screenPinningRequested, final int windowingMode, final int activityType) {
final ActivityOptions.OnAnimationStartedListener animStartedListener;
final AppTransitionAnimationSpecsFuture transitionFuture;
@@ -116,8 +119,8 @@ public class RecentsTransitionHelper {
// Fetch window rect here already in order not to be blocked on lock contention in WM
// when the future calls it.
final Rect windowRect = Recents.getSystemServices().getWindowRect();
- transitionFuture = getAppTransitionFuture(
- () -> composeAnimationSpecs(task, stackView, destinationStack, windowRect));
+ transitionFuture = getAppTransitionFuture(() -> composeAnimationSpecs(
+ task, stackView, windowingMode, activityType, windowRect));
animStartedListener = new OnAnimationStartedListener() {
private boolean mHandled;
@@ -180,7 +183,8 @@ public class RecentsTransitionHelper {
if (taskView == null) {
// If there is no task view, then we do not need to worry about animating out occluding
// task views, and we can launch immediately
- startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
+ startTaskActivity(stack, task, taskView, opts, transitionFuture,
+ windowingMode, activityType);
} else {
LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
screenPinningRequested);
@@ -189,13 +193,14 @@ public class RecentsTransitionHelper {
@Override
public void run() {
startTaskActivity(stack, task, taskView, opts, transitionFuture,
- destinationStack);
+ windowingMode, activityType);
}
});
EventBus.getDefault().send(launchStartedEvent);
} else {
EventBus.getDefault().send(launchStartedEvent);
- startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
+ startTaskActivity(stack, task, taskView, opts, transitionFuture,
+ windowingMode, activityType);
}
}
Recents.getSystemServices().sendCloseSystemWindows(
@@ -224,13 +229,13 @@ public class RecentsTransitionHelper {
*
* @param taskView this is the {@link TaskView} that we are launching from. This can be null if
* we are toggling recents and the launch-to task is now offscreen.
- * @param destinationStack id of the stack to put the task into.
*/
private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
- int destinationStack) {
+ int windowingMode, int activityType) {
SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.startActivityFromRecents(mContext, task.key, task.title, opts, destinationStack,
+ ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
+ activityType,
succeeded -> {
if (succeeded) {
// Keep track of the index of the task launch
@@ -310,11 +315,9 @@ public class RecentsTransitionHelper {
* Composes the animation specs for all the tasks in the target stack.
*/
private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
- final TaskStackView stackView, final int destinationStack, Rect windowRect) {
- // Ensure we have a valid target stack id
- final int targetStackId = destinationStack != INVALID_STACK_ID ?
- destinationStack : task.key.stackId;
- if (!StackId.useAnimationSpecForAppTransition(targetStackId)) {
+ final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
+ if (activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_HOME
+ || windowingMode == WINDOWING_MODE_PINNED) {
return null;
}
@@ -329,9 +332,12 @@ public class RecentsTransitionHelper {
List<AppTransitionAnimationSpec> specs = new ArrayList<>();
// TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
- // check for INVALID_STACK_ID
- if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID || targetStackId == DOCKED_STACK_ID
- || targetStackId == ASSISTANT_STACK_ID || targetStackId == INVALID_STACK_ID) {
+ // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ || activityType == ACTIVITY_TYPE_ASSISTANT
+ || windowingMode == WINDOWING_MODE_UNDEFINED) {
if (taskView == null) {
specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
} else {
@@ -353,7 +359,7 @@ public class RecentsTransitionHelper {
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
Task t = tasks.get(i);
- if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) {
+ if (t.isFreeformTask() || windowingMode == WINDOWING_MODE_FREEFORM) {
TaskView tv = stackView.getChildViewForTask(t);
if (tv == null) {
// TODO: Create a different animation task rect for this case (though it should
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index c44cd728..c7edb9ae 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -174,8 +174,6 @@ public class RecentsView extends FrameLayout {
? R.layout.recents_low_ram_stack_action_button
: R.layout.recents_stack_action_button,
this, false);
- mStackActionButton.setOnClickListener(
- v -> EventBus.getDefault().send(new DismissAllTaskViewsEvent()));
mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
@@ -205,10 +203,6 @@ public class RecentsView extends FrameLayout {
mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
mStackButtonShadowColor);
}
- if (Recents.getConfiguration().isLowRamDevice) {
- int bgColor = Utils.getColorAttr(mContext, R.attr.clearAllBackgroundColor);
- mStackActionButton.setBackgroundColor(bgColor);
- }
}
// Let's also require dark status and nav bars if the text is dark
@@ -338,8 +332,7 @@ public class RecentsView extends FrameLayout {
Task task = mTaskStackView.getFocusedTask();
if (task != null) {
TaskView taskView = mTaskStackView.getChildViewForTask(task);
- EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
- INVALID_STACK_ID, false));
+ EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
if (logEvent != 0) {
MetricsLogger.action(getContext(), logEvent,
@@ -363,32 +356,13 @@ public class RecentsView extends FrameLayout {
Task task = getStack().getLaunchTarget();
if (task != null) {
TaskView taskView = mTaskStackView.getChildViewForTask(task);
- EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
- INVALID_STACK_ID, false));
+ EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
return true;
}
}
return false;
}
- /** Launches a given task. */
- public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
- if (mTaskStackView != null) {
- // Iterate the stack views and try and find the given task.
- List<TaskView> taskViews = mTaskStackView.getTaskViews();
- int taskViewCount = taskViews.size();
- for (int j = 0; j < taskViewCount; j++) {
- TaskView tv = taskViews.get(j);
- if (tv.getTask() == task) {
- EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
- destinationStack, false));
- return true;
- }
- }
- }
- return false;
- }
-
/**
* Hides the task stack and shows the empty view.
*/
@@ -570,9 +544,10 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(LaunchTaskEvent event) {
mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
- event.taskView, event.screenPinningRequested, event.targetTaskStack);
+ event.taskView, event.screenPinningRequested, event.targetWindowingMode,
+ event.targetActivityType);
if (Recents.getConfiguration().isLowRamDevice) {
- hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, false /* translate */);
+ EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
}
}
@@ -580,7 +555,7 @@ public class RecentsView extends FrameLayout {
int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Hide the stack action button
- hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
+ EventBus.getDefault().send(new HideStackActionButtonEvent());
}
animateBackgroundScrim(0f, taskViewExitToHomeDuration);
@@ -741,13 +716,10 @@ public class RecentsView extends FrameLayout {
animateBackgroundScrim(getOpaqueScrimAlpha(),
TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
}
- if (Recents.getConfiguration().isLowRamDevice && mEmptyView.getVisibility() != View.VISIBLE) {
- showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, false /* translate */);
- }
}
public final void onBusEvent(AllTaskViewsDismissedEvent event) {
- hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
+ EventBus.getDefault().send(new HideStackActionButtonEvent());
}
public final void onBusEvent(DismissAllTaskViewsEvent event) {
@@ -795,7 +767,8 @@ public class RecentsView extends FrameLayout {
mStackActionButton.setVisibility(View.VISIBLE);
mStackActionButton.setAlpha(0f);
if (translate) {
- mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
+ mStackActionButton.setTranslationY(mStackActionButton.getMeasuredHeight() *
+ (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
} else {
mStackActionButton.setTranslationY(0f);
}
@@ -841,8 +814,8 @@ public class RecentsView extends FrameLayout {
if (mStackActionButton.getVisibility() == View.VISIBLE) {
if (translate) {
- mStackActionButton.animate()
- .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
+ mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
+ * (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
}
mStackActionButton.animate()
.alpha(0f)
@@ -954,7 +927,7 @@ public class RecentsView extends FrameLayout {
/**
* @return the bounds of the stack action button.
*/
- private Rect getStackActionButtonBoundsFromStackLayout() {
+ Rect getStackActionButtonBoundsFromStackLayout() {
Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
int left, top;
if (Recents.getConfiguration().isLowRamDevice) {
@@ -976,6 +949,16 @@ public class RecentsView extends FrameLayout {
return actionButtonRect;
}
+ View getStackActionButton() {
+ return mStackActionButton;
+ }
+
+ @Override
+ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ super.requestDisallowInterceptTouchEvent(disallowIntercept);
+ mTouchHandler.cancelStackActionButtonClick();
+ }
+
public void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
String id = Integer.toHexString(System.identityHashCode(this));
diff --git a/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 46619c21..b6b24bcd 100644
--- a/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -23,6 +23,7 @@ import android.graphics.Rect;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.PointerIcon;
+import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
@@ -31,6 +32,8 @@ import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
@@ -99,8 +102,7 @@ public class RecentsViewTouchHandler {
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
- handleTouchEvent(ev);
- return mDragRequested;
+ return handleTouchEvent(ev) || mDragRequested;
}
/** Handles touch events once we have intercepted them */
@@ -183,22 +185,47 @@ public class RecentsViewTouchHandler {
}
}
+ void cancelStackActionButtonClick() {
+ mRv.getStackActionButton().setPressed(false);
+ }
+
+ private boolean isWithinStackActionButton(float x, float y) {
+ Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
+ return mRv.getStackActionButton().getVisibility() == View.VISIBLE &&
+ mRv.getStackActionButton().pointInView(x - rect.left, y - rect.top, 0 /* slop */);
+ }
+
+ private void changeStackActionButtonDrawableHotspot(float x, float y) {
+ Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
+ mRv.getStackActionButton().drawableHotspotChanged(x - rect.left, y - rect.top);
+ }
+
/**
* Handles dragging touch events
*/
- private void handleTouchEvent(MotionEvent ev) {
+ private boolean handleTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
+ boolean consumed = false;
+ float evX = ev.getX();
+ float evY = ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
- mDownPos.set((int) ev.getX(), (int) ev.getY());
+ mDownPos.set((int) evX, (int) evY);
mDeviceId = ev.getDeviceId();
+
+ if (isWithinStackActionButton(evX, evY)) {
+ changeStackActionButtonDrawableHotspot(evX, evY);
+ mRv.getStackActionButton().setPressed(true);
+ }
break;
case MotionEvent.ACTION_MOVE: {
- float evX = ev.getX();
- float evY = ev.getY();
float x = evX - mTaskViewOffset.x;
float y = evY - mTaskViewOffset.y;
+ if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
+ changeStackActionButtonDrawableHotspot(evX, evY);
+ }
+
if (mDragRequested) {
if (!mIsDragging) {
mIsDragging = Math.hypot(evX - mDownPos.x, evY - mDownPos.y) > mDragSlop;
@@ -232,9 +259,7 @@ public class RecentsViewTouchHandler {
EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
currentDropTarget));
}
-
}
-
mTaskView.setTranslationX(x);
mTaskView.setTranslationY(y);
}
@@ -242,6 +267,11 @@ public class RecentsViewTouchHandler {
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
+ EventBus.getDefault().send(new DismissAllTaskViewsEvent());
+ consumed = true;
+ }
+ cancelStackActionButtonClick();
if (mDragRequested) {
boolean cancelled = action == MotionEvent.ACTION_CANCEL;
if (cancelled) {
@@ -254,5 +284,6 @@ public class RecentsViewTouchHandler {
mDeviceId = -1;
}
}
+ return consumed;
}
}
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 8899e307..3160ee0e 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -16,9 +16,8 @@
package com.android.systemui.recents.views;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1487,7 +1486,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Task frontTask = tasks.get(tasks.size() - 1);
if (frontTask != null && frontTask.isFreeformTask()) {
EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
- frontTask, null, INVALID_STACK_ID, false));
+ frontTask, null, false));
return true;
}
}
@@ -1768,8 +1767,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// In grid layout, the stack action button always remains visible.
- if (mEnterAnimationComplete && !useGridLayout() &&
- !Recents.getConfiguration().isLowRamDevice) {
+ if (mEnterAnimationComplete && !useGridLayout()) {
+ if (Recents.getConfiguration().isLowRamDevice) {
+ // Show stack button when user drags down to show older tasks on low ram devices
+ if (mStack.getTaskCount() > 0 && !mStackActionButtonVisible
+ && mTouchHandler.mIsScrolling && curScroll - prevScroll < 0) {
+ // Going up
+ EventBus.getDefault().send(
+ new ShowStackActionButtonEvent(true /* translate */));
+ }
+ return;
+ }
if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
mStack.getTaskCount() > 0) {
@@ -1956,6 +1964,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Remove the task from the stack
mStack.removeTask(event.task, event.animation, false /* fromDockGesture */);
EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
+ if (mStack.getTaskCount() > 0 && Recents.getConfiguration().isLowRamDevice) {
+ EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
+ }
MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
event.task.key.getComponent().toString());
@@ -2082,18 +2093,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
boolean isFreeformTask = event.task.isFreeformTask();
- boolean hasChangedStacks =
+ boolean hasChangedWindowingMode =
(!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
(isFreeformTask && event.dropTarget == mStackDropTarget);
- if (hasChangedStacks) {
+ if (hasChangedWindowingMode) {
// Move the task to the right position in the stack (ie. the front of the stack if
// freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
// before we update their stack ids, otherwise, the keys will have changed.
if (event.dropTarget == mFreeformWorkspaceDropTarget) {
- mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
+ mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FREEFORM);
} else if (event.dropTarget == mStackDropTarget) {
- mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
+ mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FULLSCREEN);
}
updateLayoutAlgorithm(true /* boundScroll */);
@@ -2102,7 +2113,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void run() {
SystemServicesProxy ssp = Recents.getSystemServices();
- ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
+ ssp.setTaskWindowingMode(event.task.key.id, event.task.key.windowingMode);
}
});
}
@@ -2369,12 +2380,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void run() {
EventBus.getDefault().send(new LaunchTaskEvent(
getChildViewForTask(task), task, null,
- INVALID_STACK_ID, false /* screenPinningRequested */));
+ false /* screenPinningRequested */));
}
});
} else {
- EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task),
- task, null, INVALID_STACK_ID, false /* screenPinningRequested */));
+ EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), task, null,
+ false /* screenPinningRequested */));
}
}
diff --git a/com/android/systemui/recents/views/TaskView.java b/com/android/systemui/recents/views/TaskView.java
index ceeebd96..9d639647 100644
--- a/com/android/systemui/recents/views/TaskView.java
+++ b/com/android/systemui/recents/views/TaskView.java
@@ -647,7 +647,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
}
@Override
- public void onTaskStackIdChanged() {
+ public void onTaskWindowingModeChanged() {
// Force rebind the header, the thumbnail does not change due to stack changes
mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
mHeaderView.onTaskDataLoaded();
@@ -674,8 +674,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
mActionButtonView.setTranslationZ(0f);
screenPinningRequested = true;
}
- EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
- screenPinningRequested));
+ EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, screenPinningRequested));
MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
mTask.key.getComponent().toString());
diff --git a/com/android/systemui/recents/views/TaskViewHeader.java b/com/android/systemui/recents/views/TaskViewHeader.java
index ae922fcc..198ecae2 100644
--- a/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,6 +16,11 @@
package com.android.systemui.recents.views;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
@@ -57,10 +62,6 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
/* The task bar view */
public class TaskViewHeader extends FrameLayout
implements View.OnClickListener, View.OnLongClickListener {
@@ -172,7 +173,7 @@ public class TaskViewHeader extends FrameLayout
int mTaskBarViewLightTextColor;
int mTaskBarViewDarkTextColor;
int mDisabledTaskBarBackgroundColor;
- int mMoveTaskTargetStackId = INVALID_STACK_ID;
+ int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED;
// Header background
private HighlightColorDrawable mBackground;
@@ -485,12 +486,12 @@ public class TaskViewHeader extends FrameLayout
// current task
if (mMoveTaskButton != null) {
if (t.isFreeformTask()) {
- mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
+ mTaskWindowingMode = WINDOWING_MODE_FULLSCREEN;
mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
? mLightFullscreenIcon
: mDarkFullscreenIcon);
} else {
- mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
+ mTaskWindowingMode = WINDOWING_MODE_FREEFORM;
mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
? mLightFreeformIcon
: mDarkFreeformIcon);
@@ -621,8 +622,8 @@ public class TaskViewHeader extends FrameLayout
Constants.Metrics.DismissSourceHeaderButton);
} else if (v == mMoveTaskButton) {
TaskView tv = Utilities.findParent(this, TaskView.class);
- EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
- mMoveTaskTargetStackId, false));
+ EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, false,
+ mTaskWindowingMode, ACTIVITY_TYPE_UNDEFINED));
} else if (v == mAppInfoView) {
EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
} else if (v == mAppIconView) {
diff --git a/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java b/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
index bcf4f17d..2d7cfb1a 100644
--- a/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
+++ b/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
@@ -26,8 +26,8 @@ import com.android.systemui.recents.views.TaskViewThumbnail;
public class GridTaskViewThumbnail extends TaskViewThumbnail {
- private Path mThumbnailOutline;
- private Path mRestBackgroundOutline;
+ private final Path mThumbnailOutline = new Path();
+ private final Path mRestBackgroundOutline = new Path();
// True if either this view's size or thumbnail scale has changed and mThumbnailOutline should
// be updated.
private boolean mUpdateThumbnailOutline = true;
@@ -77,47 +77,38 @@ public class GridTaskViewThumbnail extends TaskViewThumbnail {
(int) (mThumbnailRect.width() * mThumbnailScale));
final int thumbnailHeight = Math.min(viewHeight,
(int) (mThumbnailRect.height() * mThumbnailScale));
- // Draw the thumbnail, we only round the bottom corners:
- //
- // outerLeft outerRight
- // <-----------------------> mRestBackgroundOutline
- // _________________________ (thumbnailWidth < viewWidth)
- // |_______________________| outerTop A ____ B
- // | | ↑ | |
- // | | | | |
- // | | | | |
- // | | | | | C
- // \_______________________/ ↓ |__/
- // mCornerRadius outerBottom E D
- //
- // mRestBackgroundOutline (thumbnailHeight < viewHeight)
- // A _________________________ B
- // | | C
- // F \_______________________/
- // E D
- final int outerLeft = 0;
- final int outerTop = 0;
- final int outerRight = outerLeft + thumbnailWidth;
- final int outerBottom = outerTop + thumbnailHeight;
- mThumbnailOutline = new Path();
- mThumbnailOutline.moveTo(outerLeft, outerTop);
- mThumbnailOutline.lineTo(outerRight, outerTop);
- mThumbnailOutline.lineTo(outerRight, outerBottom - mCornerRadius);
- mThumbnailOutline.arcTo(outerRight - 2 * mCornerRadius, outerBottom - 2 * mCornerRadius,
- outerRight, outerBottom, 0, 90, false);
- mThumbnailOutline.lineTo(outerLeft + mCornerRadius, outerBottom);
- mThumbnailOutline.arcTo(outerLeft, outerBottom - 2 * mCornerRadius,
- outerLeft + 2 * mCornerRadius, outerBottom, 90, 90, false);
- mThumbnailOutline.lineTo(outerLeft, outerTop);
- mThumbnailOutline.close();
if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+ // Draw the thumbnail, we only round the bottom corners:
+ //
+ // outerLeft outerRight
+ // <-----------------------> mRestBackgroundOutline
+ // _________________________ (thumbnailWidth < viewWidth)
+ // |_______________________| outerTop A ____ B
+ // | | ↑ | |
+ // | | | | |
+ // | | | | |
+ // | | | | | C
+ // \_______________________/ ↓ |__/
+ // mCornerRadius outerBottom E D
+ //
+ // mRestBackgroundOutline (thumbnailHeight < viewHeight)
+ // A _________________________ B
+ // | | C
+ // F \_______________________/
+ // E D
+ final int outerLeft = 0;
+ final int outerTop = 0;
+ final int outerRight = outerLeft + thumbnailWidth;
+ final int outerBottom = outerTop + thumbnailHeight;
+ createThumbnailPath(outerLeft, outerTop, outerRight, outerBottom, mThumbnailOutline);
+
if (thumbnailWidth < viewWidth) {
final int l = Math.max(0, outerRight - mCornerRadius);
final int r = outerRight;
final int t = outerTop;
final int b = outerBottom;
- mRestBackgroundOutline = new Path();
+ mRestBackgroundOutline.reset();
mRestBackgroundOutline.moveTo(l, t); // A
mRestBackgroundOutline.lineTo(r, t); // B
mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -133,7 +124,7 @@ public class GridTaskViewThumbnail extends TaskViewThumbnail {
final int r = outerRight;
final int t = Math.max(0, thumbnailHeight - mCornerRadius);
final int b = outerBottom;
- mRestBackgroundOutline = new Path();
+ mRestBackgroundOutline.reset();
mRestBackgroundOutline.moveTo(l, t); // A
mRestBackgroundOutline.lineTo(r, t); // B
mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -145,9 +136,26 @@ public class GridTaskViewThumbnail extends TaskViewThumbnail {
mRestBackgroundOutline.lineTo(l, t); // A
mRestBackgroundOutline.close();
}
+ } else {
+ createThumbnailPath(0, 0, viewWidth, viewHeight, mThumbnailOutline);
}
}
+ private void createThumbnailPath(int outerLeft, int outerTop, int outerRight, int outerBottom,
+ Path outPath) {
+ outPath.reset();
+ outPath.moveTo(outerLeft, outerTop);
+ outPath.lineTo(outerRight, outerTop);
+ outPath.lineTo(outerRight, outerBottom - mCornerRadius);
+ outPath.arcTo(outerRight - 2 * mCornerRadius, outerBottom - 2 * mCornerRadius, outerRight,
+ outerBottom, 0, 90, false);
+ outPath.lineTo(outerLeft + mCornerRadius, outerBottom);
+ outPath.arcTo(outerLeft, outerBottom - 2 * mCornerRadius, outerLeft + 2 * mCornerRadius,
+ outerBottom, 90, 90, false);
+ outPath.lineTo(outerLeft, outerTop);
+ outPath.close();
+ }
+
@Override
protected void onDraw(Canvas canvas) {
final int titleHeight = getResources().getDimensionPixelSize(
diff --git a/com/android/systemui/stackdivider/DividerView.java b/com/android/systemui/stackdivider/DividerView.java
index 6bfef20c..7bcef574 100644
--- a/com/android/systemui/stackdivider/DividerView.java
+++ b/com/android/systemui/stackdivider/DividerView.java
@@ -16,6 +16,9 @@
package com.android.systemui.stackdivider;
+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;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
@@ -23,7 +26,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -36,8 +38,6 @@ import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.Display;
import android.view.DisplayInfo;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VelocityTracker;
@@ -62,8 +62,8 @@ import com.android.internal.policy.DockedDividerUtils;
import com.android.internal.view.SurfaceFlingerVsyncChoreographer;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
import com.android.systemui.recents.events.activity.UndockingTaskEvent;
@@ -93,7 +93,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
- private static final boolean SWAPPING_ENABLED = false;
/**
* How much the background gets scaled when we are in the minimized dock state.
@@ -153,7 +152,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private boolean mEntranceAnimationRunning;
private boolean mExitAnimationRunning;
private int mExitStartPosition;
- private GestureDetector mGestureDetector;
private boolean mDockedStackMinimized;
private boolean mHomeStackResizable;
private boolean mAdjustedForIme;
@@ -295,21 +293,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
- mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- if (SWAPPING_ENABLED) {
- updateDockSide();
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (mDockSide != WindowManager.DOCKED_INVALID
- && !ssp.isRecentsActivityVisible()) {
- mWindowManagerProxy.swapTasks();
- return true;
- }
- }
- return false;
- }
- });
}
@Override
@@ -478,7 +461,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
@Override
public boolean onTouch(View v, MotionEvent event) {
convertToScreenCoordinates(event);
- mGestureDetector.onTouchEvent(event);
final int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
@@ -679,7 +661,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
} else {
mWindowManagerProxy.maximizeDockedStack();
}
- mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
+ mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
}
private void liftBackground() {
@@ -1015,8 +997,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
float dimFraction = getDimFraction(position, closestDismissTarget);
mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
- getStackIdForDismissTarget(closestDismissTarget),
- dimFraction);
+ getWindowingModeForDismissTarget(closestDismissTarget), dimFraction);
}
private void applyExitAnimationParallax(Rect taskRect, int position) {
@@ -1150,13 +1131,13 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
}
- private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
+ private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) {
if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
|| (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
&& dockSideBottomRight(mDockSide))) {
- return StackId.DOCKED_STACK_ID;
+ return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
} else {
- return StackId.RECENTS_STACK_ID;
+ return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
}
}
@@ -1210,6 +1191,10 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
}
+ public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+ saveSnapTargetBeforeMinimized(mSnapAlgorithm.getMiddleTarget());
+ }
+
public final void onBusEvent(DockedTopTaskEvent event) {
if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
mState.growAfterRecentsDrawn = false;
diff --git a/com/android/systemui/stackdivider/WindowManagerProxy.java b/com/android/systemui/stackdivider/WindowManagerProxy.java
index c2451264..85a60624 100644
--- a/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -16,7 +16,6 @@
package com.android.systemui.stackdivider;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.view.WindowManager.DOCKED_INVALID;
import android.app.ActivityManager;
@@ -56,7 +55,7 @@ public class WindowManagerProxy {
private final Rect mTouchableRegion = new Rect();
private boolean mDimLayerVisible;
- private int mDimLayerTargetStack;
+ private int mDimLayerTargetWindowingMode;
private float mDimLayerAlpha;
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@@ -88,8 +87,7 @@ public class WindowManagerProxy {
@Override
public void run() {
try {
- ActivityManager.getService().moveTasksToFullscreenStack(
- DOCKED_STACK_ID, false /* onTop */);
+ ActivityManager.getService().dismissSplitScreenMode(false /* onTop */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to remove stack: " + e);
}
@@ -100,8 +98,7 @@ public class WindowManagerProxy {
@Override
public void run() {
try {
- ActivityManager.getService().resizeStack(
- DOCKED_STACK_ID, null, true, true, false, -1);
+ ActivityManager.getService().dismissSplitScreenMode(true /* onTop */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to resize stack: " + e);
}
@@ -113,18 +110,7 @@ public class WindowManagerProxy {
public void run() {
try {
WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible,
- mDimLayerTargetStack, mDimLayerAlpha);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to resize stack: " + e);
- }
- }
- };
-
- private final Runnable mSwapRunnable = new Runnable() {
- @Override
- public void run() {
- try {
- ActivityManager.getService().swapDockedAndFullscreenStack();
+ mDimLayerTargetWindowingMode, mDimLayerAlpha);
} catch (RemoteException e) {
Log.w(TAG, "Failed to resize stack: " + e);
}
@@ -211,17 +197,13 @@ public class WindowManagerProxy {
return DOCKED_INVALID;
}
- public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+ public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
mDimLayerVisible = visible;
- mDimLayerTargetStack = targetStackId;
+ mDimLayerTargetWindowingMode = targetWindowingMode;
mDimLayerAlpha = alpha;
mExecutor.execute(mDimLayerRunnable);
}
- public void swapTasks() {
- mExecutor.execute(mSwapRunnable);
- }
-
public void setTouchRegion(Rect region) {
synchronized (mDockedRect) {
mTouchableRegion.set(region);
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 7fe7f399..966e7899 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -64,10 +64,10 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -436,9 +436,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else {
minHeight = mNotificationMinHeight;
}
- NotificationViewWrapper collapsedWrapper = layout.getVisibleWrapper(
- NotificationContentView.VISIBLE_TYPE_CONTRACTED);
- minHeight += collapsedWrapper.getMinHeightIncrease(mUseIncreasedCollapsedHeight);
boolean headsUpCustom = layout.getHeadsUpChild() != null &&
layout.getHeadsUpChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
@@ -450,11 +447,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else {
headsUpheight = mMaxHeadsUpHeight;
}
- NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
- NotificationContentView.VISIBLE_TYPE_HEADSUP);
- if (headsUpWrapper != null) {
- headsUpheight += headsUpWrapper.getMinHeightIncrease(mUseIncreasedCollapsedHeight);
- }
layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
mNotificationAmbientHeight);
}
@@ -2024,14 +2016,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
@Override
- public int getMinHeight() {
- if (mGuts != null && mGuts.isExposed()) {
+ public int getMinHeight(boolean ignoreTemporaryStates) {
+ if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) {
return mGuts.getIntrinsicHeight();
- } else if (isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
+ } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp
+ && mHeadsUpManager.isTrackingHeadsUp()) {
return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
} else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
return mChildrenContainer.getMinHeight();
- } else if (isHeadsUpAllowed() && mIsHeadsUp) {
+ } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) {
return mHeadsUpHeight;
}
NotificationContentView showingLayout = getShowingLayout();
diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java
index efe5e0c2..aac9af8a 100644
--- a/com/android/systemui/statusbar/ExpandableView.java
+++ b/com/android/systemui/statusbar/ExpandableView.java
@@ -151,9 +151,21 @@ public abstract class ExpandableView extends FrameLayout {
}
/**
- * @return The minimum content height of this notification.
+ * @return The minimum content height of this notification. This also respects the temporary
+ * states of the view.
*/
public int getMinHeight() {
+ return getMinHeight(false /* ignoreTemporaryStates */);
+ }
+
+ /**
+ * Get the minimum height of this view.
+ *
+ * @param ignoreTemporaryStates should temporary states be ignored like the guts or heads-up.
+ *
+ * @return The minimum height that this view needs.
+ */
+ public int getMinHeight(boolean ignoreTemporaryStates) {
return getHeight();
}
diff --git a/com/android/systemui/statusbar/KeyboardShortcuts.java b/com/android/systemui/statusbar/KeyboardShortcuts.java
index d370a633..2d16d220 100644
--- a/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -433,24 +433,27 @@ public final class KeyboardShortcuts {
// Assist.
final AssistUtils assistUtils = new AssistUtils(mContext);
final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
- PackageInfo assistPackageInfo = null;
- try {
- assistPackageInfo = mPackageManager.getPackageInfo(
- assistComponent.getPackageName(), 0, userId);
- } catch (RemoteException e) {
- Log.e(TAG, "PackageManagerService is dead");
- }
+ // Not all devices have an assist component.
+ if (assistComponent != null) {
+ PackageInfo assistPackageInfo = null;
+ try {
+ assistPackageInfo = mPackageManager.getPackageInfo(
+ assistComponent.getPackageName(), 0, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManagerService is dead");
+ }
- if (assistPackageInfo != null) {
- final Icon assistIcon = Icon.createWithResource(
- assistPackageInfo.applicationInfo.packageName,
- assistPackageInfo.applicationInfo.icon);
+ if (assistPackageInfo != null) {
+ final Icon assistIcon = Icon.createWithResource(
+ assistPackageInfo.applicationInfo.packageName,
+ assistPackageInfo.applicationInfo.icon);
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
- assistIcon,
- KeyEvent.KEYCODE_UNKNOWN,
- KeyEvent.META_META_ON));
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
+ assistIcon,
+ KeyEvent.KEYCODE_UNKNOWN,
+ KeyEvent.META_META_ON));
+ }
}
// Browser.
diff --git a/com/android/systemui/statusbar/NotificationData.java b/com/android/systemui/statusbar/NotificationData.java
index ddc7dd06..d0417b59 100644
--- a/com/android/systemui/statusbar/NotificationData.java
+++ b/com/android/systemui/statusbar/NotificationData.java
@@ -292,8 +292,8 @@ public class NotificationData {
if (mRankingMap != null) {
// RankingMap as received from NoMan
- mRankingMap.getRanking(a.key, mRankingA);
- mRankingMap.getRanking(b.key, mRankingB);
+ getRanking(a.key, mRankingA);
+ getRanking(b.key, mRankingB);
aImportance = mRankingA.getImportance();
bImportance = mRankingB.getImportance();
aRank = mRankingA.getRank();
@@ -381,7 +381,7 @@ public class NotificationData {
public boolean isAmbient(String key) {
if (mRankingMap != null) {
- mRankingMap.getRanking(key, mTmpRanking);
+ getRanking(key, mTmpRanking);
return mTmpRanking.isAmbient();
}
return false;
@@ -389,7 +389,7 @@ public class NotificationData {
public int getVisibilityOverride(String key) {
if (mRankingMap != null) {
- mRankingMap.getRanking(key, mTmpRanking);
+ getRanking(key, mTmpRanking);
return mTmpRanking.getVisibilityOverride();
}
return Ranking.VISIBILITY_NO_OVERRIDE;
@@ -397,7 +397,7 @@ public class NotificationData {
public boolean shouldSuppressScreenOff(String key) {
if (mRankingMap != null) {
- mRankingMap.getRanking(key, mTmpRanking);
+ getRanking(key, mTmpRanking);
return (mTmpRanking.getSuppressedVisualEffects()
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0;
}
@@ -406,7 +406,7 @@ public class NotificationData {
public boolean shouldSuppressScreenOn(String key) {
if (mRankingMap != null) {
- mRankingMap.getRanking(key, mTmpRanking);
+ getRanking(key, mTmpRanking);
return (mTmpRanking.getSuppressedVisualEffects()
& NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON) != 0;
}
@@ -415,7 +415,7 @@ public class NotificationData {
public int getImportance(String key) {
if (mRankingMap != null) {
- mRankingMap.getRanking(key, mTmpRanking);
+ getRanking(key, mTmpRanking);
return mTmpRanking.getImportance();
}
return NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -423,7 +423,7 @@ public class NotificationData {
public String getOverrideGroupKey(String key) {
if (mRankingMap != null) {
- mRankingMap.getRanking(key, mTmpRanking);
+ getRanking(key, mTmpRanking);
return mTmpRanking.getOverrideGroupKey();
}
return null;
@@ -431,7 +431,7 @@ public class NotificationData {
public List<SnoozeCriterion> getSnoozeCriteria(String key) {
if (mRankingMap != null) {
- mRankingMap.getRanking(key, mTmpRanking);
+ getRanking(key, mTmpRanking);
return mTmpRanking.getSnoozeCriteria();
}
return null;
@@ -439,7 +439,7 @@ public class NotificationData {
public NotificationChannel getChannel(String key) {
if (mRankingMap != null) {
- mRankingMap.getRanking(key, mTmpRanking);
+ getRanking(key, mTmpRanking);
return mTmpRanking.getChannel();
}
return null;
@@ -452,6 +452,9 @@ public class NotificationData {
final int N = mEntries.size();
for (int i = 0; i < N; i++) {
Entry entry = mEntries.valueAt(i);
+ if (!getRanking(entry.key, mTmpRanking)) {
+ continue;
+ }
final StatusBarNotification oldSbn = entry.notification.cloneLight();
final String overrideGroupKey = getOverrideGroupKey(entry.key);
if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
@@ -466,6 +469,19 @@ public class NotificationData {
filterAndSort();
}
+ /**
+ * Get the ranking from the current ranking map.
+ *
+ * @param key the key to look up
+ * @param outRanking the ranking to populate
+ *
+ * @return {@code true} if the ranking was properly obtained.
+ */
+ @VisibleForTesting
+ protected boolean getRanking(String key, Ranking outRanking) {
+ return mRankingMap.getRanking(key, outRanking);
+ }
+
// TODO: This should not be public. Instead the Environment should notify this class when
// anything changed, and this class should call back the UI so it updates itself.
public void filterAndSort() {
@@ -573,7 +589,7 @@ public class NotificationData {
}
private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
- mRankingMap.getRanking(e.key, mTmpRanking);
+ getRanking(e.key, mTmpRanking);
pw.print(indent);
pw.println(" [" + i + "] key=" + e.key + " icon=" + e.icon);
StatusBarNotification n = e.notification;
diff --git a/com/android/systemui/statusbar/NotificationSnooze.java b/com/android/systemui/statusbar/NotificationSnooze.java
index c45ca540..492ab44d 100644
--- a/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,8 +16,13 @@ package com.android.systemui.statusbar;
*/
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.TimeUnit;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
@@ -28,12 +33,15 @@ import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Typeface;
+import android.metrics.LogMaker;
import android.os.Bundle;
+import android.provider.Settings;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -51,11 +59,23 @@ import com.android.systemui.R;
public class NotificationSnooze extends LinearLayout
implements NotificationGuts.GutsContent, View.OnClickListener {
+ private static final String TAG = "NotificationSnooze";
/**
* If this changes more number increases, more assistant action resId's should be defined for
* accessibility purposes, see {@link #setSnoozeOptions(List)}
*/
private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
+ private static final String KEY_DEFAULT_SNOOZE = "default";
+ private static final String KEY_OPTIONS = "options_array";
+ private static final LogMaker OPTIONS_OPEN_LOG =
+ new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+ .setType(MetricsEvent.TYPE_OPEN);
+ private static final LogMaker OPTIONS_CLOSE_LOG =
+ new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+ .setType(MetricsEvent.TYPE_CLOSE);
+ private static final LogMaker UNDO_LOG =
+ new LogMaker(MetricsEvent.NOTIFICATION_UNDO_SNOOZE)
+ .setType(MetricsEvent.TYPE_ACTION);
private NotificationGuts mGutsContainer;
private NotificationSwipeActionHelper mSnoozeListener;
private StatusBarNotification mSbn;
@@ -72,9 +92,31 @@ public class NotificationSnooze extends LinearLayout
private boolean mSnoozing;
private boolean mExpanded;
private AnimatorSet mExpandAnimation;
+ private KeyValueListParser mParser;
+
+ private final static int[] sAccessibilityActions = {
+ R.id.action_snooze_shorter,
+ R.id.action_snooze_short,
+ R.id.action_snooze_long,
+ R.id.action_snooze_longer,
+ };
+
+ private MetricsLogger mMetricsLogger = new MetricsLogger();
public NotificationSnooze(Context context, AttributeSet attrs) {
super(context, attrs);
+ mParser = new KeyValueListParser(',');
+ }
+
+ @VisibleForTesting
+ SnoozeOption getDefaultOption()
+ {
+ return mDefaultOption;
+ }
+
+ @VisibleForTesting
+ void setKeyValueListParser(KeyValueListParser parser) {
+ mParser = parser;
}
@Override
@@ -96,7 +138,13 @@ public class NotificationSnooze extends LinearLayout
mSnoozeOptions = getDefaultSnoozeOptions();
createOptionViews();
- setSelected(mDefaultOption);
+ setSelected(mDefaultOption, false);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ logOptionSelection(MetricsEvent.NOTIFICATION_SNOOZE_CLICKED, mDefaultOption);
}
@Override
@@ -136,7 +184,7 @@ public class NotificationSnooze extends LinearLayout
SnoozeOption so = mSnoozeOptions.get(i);
if (so.getAccessibilityAction() != null
&& so.getAccessibilityAction().getId() == action) {
- setSelected(so);
+ setSelected(so, true);
return true;
}
}
@@ -172,17 +220,49 @@ public class NotificationSnooze extends LinearLayout
mSbn = sbn;
}
- private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+ @VisibleForTesting
+ ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+ final Resources resources = getContext().getResources();
ArrayList<SnoozeOption> options = new ArrayList<>();
+ try {
+ final String config = Settings.Global.getString(getContext().getContentResolver(),
+ Settings.Global.NOTIFICATION_SNOOZE_OPTIONS);
+ mParser.setString(config);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Bad snooze constants");
+ }
+
+ final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
+ resources.getInteger(R.integer.config_notification_snooze_time_default));
+ final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+ resources.getIntArray(R.array.config_notification_snooze_times));
- options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min));
- options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min));
- mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour);
- options.add(mDefaultOption);
- options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours));
+ for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
+ int snoozeTime = snoozeTimes[i];
+ SnoozeOption option = createOption(snoozeTime, sAccessibilityActions[i]);
+ if (i == 0 || snoozeTime == defaultSnooze) {
+ mDefaultOption = option;
+ }
+ options.add(option);
+ }
return options;
}
+ @VisibleForTesting
+ int[] parseIntArray(final String key, final int[] defaultArray) {
+ final String value = mParser.getString(key, null);
+ if (value != null) {
+ try {
+ return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+ Integer::parseInt).toArray();
+ } catch (NumberFormatException e) {
+ return defaultArray;
+ }
+ } else {
+ return defaultArray;
+ }
+ }
+
private SnoozeOption createOption(int minutes, int accessibilityActionId) {
Resources res = getResources();
boolean showInHours = minutes >= 60;
@@ -268,12 +348,24 @@ public class NotificationSnooze extends LinearLayout
mExpandAnimation.start();
}
- private void setSelected(SnoozeOption option) {
+ private void setSelected(SnoozeOption option, boolean userAction) {
mSelectedOption = option;
mSelectedOptionText.setText(option.getConfirmation());
showSnoozeOptions(false);
hideSelectedOption();
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ if (userAction) {
+ logOptionSelection(MetricsEvent.NOTIFICATION_SELECT_SNOOZE, option);
+ }
+ }
+
+ private void logOptionSelection(int category, SnoozeOption option) {
+ int index = mSnoozeOptions.indexOf(option);
+ long duration = TimeUnit.MINUTES.toMillis(option.getMinutesToSnoozeFor());
+ mMetricsLogger.write(new LogMaker(category)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_INDEX, index)
+ .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS, duration));
}
@Override
@@ -284,13 +376,15 @@ public class NotificationSnooze extends LinearLayout
final int id = v.getId();
final SnoozeOption tag = (SnoozeOption) v.getTag();
if (tag != null) {
- setSelected(tag);
+ setSelected(tag, true);
} else if (id == R.id.notification_snooze) {
// Toggle snooze options
showSnoozeOptions(!mExpanded);
+ mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
} else {
// Undo snooze was selected
undoSnooze(v);
+ mMetricsLogger.write(UNDO_LOG);
}
}
@@ -321,7 +415,7 @@ public class NotificationSnooze extends LinearLayout
@Override
public View getContentView() {
// Reset the view before use
- setSelected(mDefaultOption);
+ setSelected(mDefaultOption, false);
return this;
}
@@ -343,7 +437,7 @@ public class NotificationSnooze extends LinearLayout
return true;
} else {
// The view should actually be closed
- setSelected(mSnoozeOptions.get(0));
+ setSelected(mSnoozeOptions.get(0), false);
return false; // Return false here so that guts handles closing the view
}
}
diff --git a/com/android/systemui/statusbar/SignalClusterView.java b/com/android/systemui/statusbar/SignalClusterView.java
index 759d2cf2..a7fb61a1 100644
--- a/com/android/systemui/statusbar/SignalClusterView.java
+++ b/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,14 +16,15 @@
package com.android.systemui.statusbar;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
import android.annotation.DrawableRes;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.telephony.SubscriptionInfo;
import android.util.ArraySet;
@@ -50,14 +51,15 @@ import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
// Intimately tied to the design of res/layout/signal_cluster_view.xml
public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
- SecurityController.SecurityControllerCallback, Tunable,
- DarkReceiver {
+ SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
static final String TAG = "SignalClusterView";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -73,6 +75,7 @@ public class SignalClusterView extends LinearLayout implements NetworkController
private boolean mNoSimsVisible = false;
private boolean mVpnVisible = false;
+ private boolean mSimDetected;
private int mVpnIconId = 0;
private int mLastVpnIconId = -1;
private boolean mEthernetVisible = false;
@@ -148,6 +151,8 @@ public class SignalClusterView extends LinearLayout implements NetworkController
mIconScaleFactor = typedValue.getFloat();
mNetworkController = Dependency.get(NetworkController.class);
mSecurityController = Dependency.get(SecurityController.class);
+ addOnAttachStateChangeListener(
+ new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
updateActivityEnabled();
}
@@ -327,8 +332,9 @@ public class SignalClusterView extends LinearLayout implements NetworkController
}
@Override
- public void setNoSims(boolean show) {
+ public void setNoSims(boolean show, boolean simDetected) {
mNoSimsVisible = show && !mBlockMobile;
+ mSimDetected = simDetected;
apply();
}
@@ -548,6 +554,23 @@ public class SignalClusterView extends LinearLayout implements NetworkController
if (mNoSimsVisible) {
mIconLogger.onIconShown(SLOT_MOBILE);
mNoSimsCombo.setVisibility(View.VISIBLE);
+ if (!Objects.equals(mSimDetected, mNoSimsCombo.getTag())) {
+ mNoSimsCombo.setTag(mSimDetected);
+ if (mSimDetected) {
+ SignalDrawable d = new SignalDrawable(mNoSims.getContext());
+ d.setDarkIntensity(0);
+ mNoSims.setImageDrawable(d);
+ mNoSims.setImageLevel(SignalDrawable.getEmptyState(4));
+
+ SignalDrawable dark = new SignalDrawable(mNoSims.getContext());
+ dark.setDarkIntensity(1);
+ mNoSimsDark.setImageDrawable(dark);
+ mNoSimsDark.setImageLevel(SignalDrawable.getEmptyState(4));
+ } else {
+ mNoSims.setImageResource(R.drawable.stat_sys_no_sims);
+ mNoSimsDark.setImageResource(R.drawable.stat_sys_no_sims);
+ }
+ }
} else {
mIconLogger.onIconHidden(SLOT_MOBILE);
mNoSimsCombo.setVisibility(View.GONE);
diff --git a/com/android/systemui/statusbar/StatusBarIconView.java b/com/android/systemui/statusbar/StatusBarIconView.java
index 2cff79df..6cfd42fa 100644
--- a/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/com/android/systemui/statusbar/StatusBarIconView.java
@@ -43,6 +43,7 @@ import android.util.FloatProperty;
import android.util.Log;
import android.util.Property;
import android.util.TypedValue;
+import android.view.View;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
@@ -142,6 +143,7 @@ public class StatusBarIconView extends AnimatedImageView {
private float[] mMatrix;
private ColorMatrixColorFilter mMatrixColorFilter;
private boolean mIsInShelf;
+ private Runnable mLayoutRunnable;
public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
this(context, slot, sbn, false);
@@ -796,6 +798,24 @@ public class StatusBarIconView extends AnimatedImageView {
}
}
+ /**
+ * This method returns the drawing rect for the view which is different from the regular
+ * drawing rect, since we layout all children at position 0 and usually the translation is
+ * neglected. The standard implementation doesn't account for translation.
+ *
+ * @param outRect The (scrolled) drawing bounds of the view.
+ */
+ @Override
+ public void getDrawingRect(Rect outRect) {
+ super.getDrawingRect(outRect);
+ float translationX = getTranslationX();
+ float translationY = getTranslationY();
+ outRect.left += translationX;
+ outRect.right += translationX;
+ outRect.top += translationY;
+ outRect.bottom += translationY;
+ }
+
public void setIsInShelf(boolean isInShelf) {
mIsInShelf = isInShelf;
}
@@ -804,6 +824,19 @@ public class StatusBarIconView extends AnimatedImageView {
return mIsInShelf;
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (mLayoutRunnable != null) {
+ mLayoutRunnable.run();
+ mLayoutRunnable = null;
+ }
+ }
+
+ public void executeOnLayout(Runnable runnable) {
+ mLayoutRunnable = runnable;
+ }
+
public interface OnVisibilityChangedListener {
void onVisibilityChanged(int newVisibility);
}
diff --git a/com/android/systemui/statusbar/car/CarNavigationBarController.java b/com/android/systemui/statusbar/car/CarNavigationBarController.java
index 7e08d560..f5c77f26 100644
--- a/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -15,7 +15,13 @@
*/
package com.android.systemui.statusbar.car;
-import android.app.ActivityManager.StackId;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -101,7 +107,7 @@ class CarNavigationBarController {
}
}
- public void taskChanged(String packageName, int stackId) {
+ public void taskChanged(String packageName, ActivityManager.RunningTaskInfo taskInfo) {
// If the package name belongs to a filter, then highlight appropriate button in
// the navigation bar.
if (mFacetPackageMap.containsKey(packageName)) {
@@ -115,9 +121,11 @@ class CarNavigationBarController {
}
// Set up the persistent docked task if needed.
- if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask()
- && stackId != StackId.HOME_STACK_ID) {
- mStatusBar.startActivityOnStack(mPersistentTaskIntent, StackId.DOCKED_STACK_ID);
+ boolean isHomeTask =
+ taskInfo.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME;
+ if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() && !isHomeTask) {
+ mStatusBar.startActivityOnStack(mPersistentTaskIntent,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
}
}
@@ -375,13 +383,15 @@ class CarNavigationBarController {
// rather than the "preferred/last run" app.
intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex);
- int stackId = StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+ int windowingMode = WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+ int activityType = ACTIVITY_TYPE_UNDEFINED;
if (intent.getCategories().contains(Intent.CATEGORY_HOME)) {
- stackId = StackId.HOME_STACK_ID;
+ windowingMode = WINDOWING_MODE_UNDEFINED;
+ activityType = ACTIVITY_TYPE_HOME;
}
setCurrentFacet(index);
- mStatusBar.startActivityOnStack(intent, stackId);
+ mStatusBar.startActivityOnStack(intent, windowingMode, activityType);
}
/**
@@ -391,6 +401,7 @@ class CarNavigationBarController {
*/
private void onFacetLongClicked(Intent intent, int index) {
setCurrentFacet(index);
- mStatusBar.startActivityOnStack(intent, StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+ mStatusBar.startActivityOnStack(intent,
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED);
}
}
diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java
index 680f693a..59d3e0a3 100644
--- a/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -314,7 +314,7 @@ public class CarStatusBar extends StatusBar implements
ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
- runningTaskInfo.stackId);
+ runningTaskInfo);
}
}
}
@@ -378,9 +378,10 @@ public class CarStatusBar extends StatusBar implements
return result;
}
- public int startActivityOnStack(Intent intent, int stackId) {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(stackId);
+ public int startActivityOnStack(Intent intent, int windowingMode, int activityType) {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(windowingMode);
+ options.setLaunchActivityType(activityType);
return startActivityWithOptions(intent, options.toBundle());
}
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 9bfa7a97..bb979ebd 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -25,7 +25,6 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
-import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -48,7 +47,6 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
private int mContentHeight;
private int mMinHeightHint;
- private boolean mColorized;
protected NotificationTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
@@ -164,9 +162,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
public void onContentUpdated(ExpandableNotificationRow row) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
- StatusBarNotification sbn = row.getStatusBarNotification();
- resolveTemplateViews(sbn);
- mColorized = sbn.getNotification().isColorized();
+ resolveTemplateViews(row.getStatusBarNotification());
super.onContentUpdated(row);
}
@@ -269,17 +265,6 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
updateActionOffset();
}
- @Override
- public int getMinHeightIncrease(boolean useIncreasedCollapsedHeight) {
- if (mColorized) {
- int dimen = useIncreasedCollapsedHeight
- ? R.dimen.notification_height_increase_colorized_increased
- : R.dimen.notification_height_increase_colorized;
- return mRow.getResources().getDimensionPixelSize(dimen);
- }
- return super.getMinHeightIncrease(useIncreasedCollapsedHeight);
- }
-
private void updateActionOffset() {
if (mActionsContainer != null) {
// We should never push the actions higher than they are in the headsup view.
diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 085bce97..5200d696 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,14 +190,4 @@ public abstract class NotificationViewWrapper implements TransformableView {
public boolean disallowSingleClick(float x, float y) {
return false;
}
-
- /**
- * Get the amount that the minheight is allowed to be increased based on this layout.
- *
- * @param increasedHeight is the view allowed to show even bigger, i.e for messaging layouts
- * @return
- */
- public int getMinHeightIncrease(boolean increasedHeight) {
- return 0;
- }
}
diff --git a/com/android/systemui/statusbar/phone/BarTransitions.java b/com/android/systemui/statusbar/phone/BarTransitions.java
index 1f44abea..4bca7971 100644
--- a/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -50,7 +50,7 @@ public class BarTransitions {
public static final int MODE_LIGHTS_OUT_TRANSPARENT = 6;
public static final int LIGHTS_IN_DURATION = 250;
- public static final int LIGHTS_OUT_DURATION = 750;
+ public static final int LIGHTS_OUT_DURATION = 1500;
public static final int BACKGROUND_DURATION = 200;
private final String mTag;
diff --git a/com/android/systemui/statusbar/phone/DozeScrimController.java b/com/android/systemui/statusbar/phone/DozeScrimController.java
index 021b4518..8afb8490 100644
--- a/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -384,7 +384,7 @@ public class DozeScrimController {
if (mDozeParameters.getAlwaysOn()) {
// Setting power states can happen after we push out the frame. Make sure we
// stay fully opaque until the power state request reaches the lower levels.
- setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 30);
+ setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
}
}
};
diff --git a/com/android/systemui/statusbar/phone/LightBarController.java b/com/android/systemui/statusbar/phone/LightBarController.java
index 533771a3..d226fede 100644
--- a/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/com/android/systemui/statusbar/phone/LightBarController.java
@@ -264,8 +264,10 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
pw.println(" StatusBarTransitionsController:");
mStatusBarIconController.getTransitionsController().dump(fd, pw, args);
pw.println();
- pw.println(" NavigationBarTransitionsController:");
- mNavigationBarController.dump(fd, pw, args);
- pw.println();
+ if (mNavigationBarController != null) {
+ pw.println(" NavigationBarTransitionsController:");
+ mNavigationBarController.dump(fd, pw, args);
+ pw.println();
+ }
}
}
diff --git a/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index cfe0a4a6..6d3bc1df 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -437,7 +437,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
}
private boolean onNavigationTouch(View v, MotionEvent event) {
- mStatusBar.checkUserAutohide(v, event);
+ mStatusBar.checkUserAutohide(event);
return false;
}
diff --git a/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 41a69b4b..40fe50fb 100644
--- a/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -4,10 +4,8 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.drawable.Icon;
import android.support.annotation.NonNull;
import android.support.v4.util.ArrayMap;
-import android.support.v4.util.ArraySet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
@@ -269,18 +267,26 @@ public class NotificationIconAreaController implements DarkReceiver {
*/
private void applyNotificationIconsTint() {
for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
- StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
- boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
- int color = StatusBarIconView.NO_COLOR;
- boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
- if (colorize) {
- color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
+ final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
+ if (iv.getWidth() != 0) {
+ updateTintForIcon(iv);
+ } else {
+ iv.executeOnLayout(() -> updateTintForIcon(iv));
}
- v.setStaticDrawableColor(color);
- v.setDecorColor(mIconTint);
}
}
+ private void updateTintForIcon(StatusBarIconView v) {
+ boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
+ int color = StatusBarIconView.NO_COLOR;
+ boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
+ if (colorize) {
+ color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
+ }
+ v.setStaticDrawableColor(color);
+ v.setDecorColor(mIconTint);
+ }
+
public void setDark(boolean dark) {
mNotificationIcons.setDark(dark, false, 0);
mShelfIcons.setDark(dark, false, 0);
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 078e8189..7b11ace8 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -509,7 +509,8 @@ public class NotificationPanelView extends PanelView implements
if (row.isRemoved()) {
continue;
}
- availableSpace -= child.getMinHeight() + notificationPadding;
+ availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
+ + notificationPadding;
if (availableSpace >= 0 && count < maximum) {
count++;
} else if (availableSpace > -shelfSize) {
@@ -666,7 +667,7 @@ public class NotificationPanelView extends PanelView implements
return false;
}
initDownStates(event);
- if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mIsExpansionFromHeadsUp = true;
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 4ae13936..9c837ed8 100644
--- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -16,8 +16,12 @@
package com.android.systemui.statusbar.phone;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
@@ -523,12 +527,18 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
mCurrentNotifs.clear();
mUiOffloadThread.submit(() -> {
try {
- int focusedId = ActivityManager.getService().getFocusedStackId();
- if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
- checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+ final StackInfo focusedStack = ActivityManager.getService().getFocusedStackInfo();
+ if (focusedStack != null) {
+ final int windowingMode =
+ focusedStack.configuration.windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+ checkStack(focusedStack, notifs, noMan, pm);
+ }
}
if (mDockedStackExists) {
- checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
+ checkStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED,
+ notifs, noMan, pm);
}
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -539,10 +549,19 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks,
});
}
- private void checkStack(int stackId, ArraySet<Pair<String, Integer>> notifs,
+ private void checkStack(int windowingMode, int activityType,
+ ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm) {
+ try {
+ final StackInfo info =
+ ActivityManager.getService().getStackInfo(windowingMode, activityType);
+ checkStack(info, notifs, noMan, pm);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ private void checkStack(StackInfo info, ArraySet<Pair<String, Integer>> notifs,
NotificationManager noMan, IPackageManager pm) {
try {
- StackInfo info = ActivityManager.getService().getStackInfo(stackId);
if (info == null || info.topActivity == null) return;
String pkg = info.topActivity.getPackageName();
if (!hasNotif(notifs, pkg, info.userId)) {
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index efc8d8b9..54be857d 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -20,6 +20,7 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -37,7 +38,6 @@ import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.INotificationManager;
import android.app.KeyguardManager;
@@ -69,9 +69,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.PorterDuff;
@@ -105,10 +102,10 @@ import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -127,13 +124,11 @@ import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewParent;
-import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
import android.widget.DateTimeView;
import android.widget.ImageView;
import android.widget.RemoteViews;
@@ -259,7 +254,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
@@ -279,11 +273,9 @@ public class StatusBar extends SystemUI implements DemoMode,
= SystemProperties.getBoolean("debug.child_notifs", true);
public static final boolean FORCE_REMOTE_INPUT_HISTORY =
SystemProperties.getBoolean("debug.force_remoteinput_history", false);
- private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+ private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
- protected static final int MSG_SHOW_RECENT_APPS = 1019;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
- protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
@@ -339,7 +331,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private static final int STATUS_OR_NAV_TRANSIENT =
View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
- private static final long AUTOHIDE_TIMEOUT_MS = 3000;
+ private static final long AUTOHIDE_TIMEOUT_MS = 2250;
/** The minimum delay in ms between reports of notification visibility. */
private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
@@ -365,10 +357,6 @@ public class StatusBar extends SystemUI implements DemoMode,
/** If true, the lockscreen will show a distinct wallpaper */
private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
- /* If true, the device supports freeform window management.
- * This affects the status bar UI. */
- private static final boolean FREEFORM_WINDOW_MANAGEMENT;
-
/**
* How long to wait before auto-dismissing a notification that was kept for remote input, and
* has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
@@ -387,19 +375,14 @@ public class StatusBar extends SystemUI implements DemoMode,
static {
boolean onlyCoreApps;
- boolean freeformWindowManagement;
try {
IPackageManager packageManager =
IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
onlyCoreApps = packageManager.isOnlyCoreApps();
- freeformWindowManagement = packageManager.hasSystemFeature(
- PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, 0);
} catch (RemoteException e) {
onlyCoreApps = false;
- freeformWindowManagement = false;
}
ONLY_CORE_APPS = onlyCoreApps;
- FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
}
/**
@@ -410,17 +393,17 @@ public class StatusBar extends SystemUI implements DemoMode,
protected boolean mShowLockscreenNotifications;
protected boolean mAllowLockscreenRemoteInput;
- PhoneStatusBarPolicy mIconPolicy;
+ private PhoneStatusBarPolicy mIconPolicy;
- VolumeComponent mVolumeComponent;
- BrightnessMirrorController mBrightnessMirrorController;
+ private VolumeComponent mVolumeComponent;
+ private BrightnessMirrorController mBrightnessMirrorController;
protected FingerprintUnlockController mFingerprintUnlockController;
- LightBarController mLightBarController;
+ private LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
- int mNaturalBarHeight = -1;
+ private int mNaturalBarHeight = -1;
- Point mCurrentDisplaySize = new Point();
+ private final Point mCurrentDisplaySize = new Point();
protected StatusBarWindowView mStatusBarWindow;
protected PhoneStatusBarView mStatusBarView;
@@ -431,15 +414,13 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mWakeUpComingFromTouch;
private PointF mWakeUpTouchLocation;
- int mPixelFormat;
- Object mQueueLock = new Object();
+ private final Object mQueueLock = new Object();
protected StatusBarIconController mIconController;
// expanded notifications
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
- View mExpandedContents;
- TextView mNotificationPanelDebugText;
+ private TextView mNotificationPanelDebugText;
/**
* {@code true} if notifications not part of a group should by default be rendered in their
@@ -452,12 +433,10 @@ public class StatusBar extends SystemUI implements DemoMode,
private QSPanel mQSPanel;
// top bar
- protected KeyguardStatusBarView mKeyguardStatusBar;
- boolean mLeaveOpenOnKeyguardHide;
+ private KeyguardStatusBarView mKeyguardStatusBar;
+ private boolean mLeaveOpenOnKeyguardHide;
KeyguardIndicationController mKeyguardIndicationController;
- // Keyguard is going away soon.
- private boolean mKeyguardGoingAway;
// Keyguard is actually fading away now.
protected boolean mKeyguardFadingAway;
protected long mKeyguardFadingAwayDelay;
@@ -469,25 +448,19 @@ public class StatusBar extends SystemUI implements DemoMode,
private View mReportRejectedTouch;
- int mMaxAllowedKeyguardNotifications;
-
- boolean mExpandedVisible;
-
- // the tracker view
- int mTrackingPosition; // the position of the top of the tracking view.
+ private int mMaxAllowedKeyguardNotifications;
- // Tracking finger for opening/closing.
- boolean mTracking;
+ private boolean mExpandedVisible;
- int[] mAbsPos = new int[2];
- ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ private final int[] mAbsPos = new int[2];
+ private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
// for disabling the status bar
- int mDisabled1 = 0;
- int mDisabled2 = 0;
+ private int mDisabled1 = 0;
+ private int mDisabled2 = 0;
// tracking calls to View.setSystemUiVisibility()
- int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
+ private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
private final Rect mLastFullscreenStackBounds = new Rect();
private final Rect mLastDockedStackBounds = new Rect();
private final Rect mTmpRect = new Rect();
@@ -495,7 +468,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// last value sent to window manager
private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE;
- DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
// XXX: gesture research
private final GestureRecorder mGestureRec = DEBUG_GESTURES
@@ -507,14 +480,17 @@ public class StatusBar extends SystemUI implements DemoMode,
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
// ensure quick settings is disabled until the current user makes it through the setup wizard
- private boolean mUserSetup = false;
- private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
+ @VisibleForTesting
+ protected boolean mUserSetup = false;
+ private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
@Override
public void onUserSetupChanged() {
final boolean userSetup = mDeviceProvisionedController.isUserSetup(
mDeviceProvisionedController.getCurrentUser());
- if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " +
- "userSetup=%s mUserSetup=%s", userSetup, mUserSetup));
+ if (MULTIUSER_DEBUG) {
+ Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s",
+ userSetup, mUserSetup));
+ }
if (userSetup != mUserSetup) {
mUserSetup = userSetup;
@@ -528,7 +504,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- protected H mHandler = createHandler();
+ protected final H mHandler = createHandler();
final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -537,8 +513,6 @@ public class StatusBar extends SystemUI implements DemoMode,
&& Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
Settings.Global.HEADS_UP_OFF);
- mHeadsUpTicker = mUseHeadsUp && 0 != Settings.Global.getInt(
- mContext.getContentResolver(), SETTING_HEADS_UP_TICKER, 0);
Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
if (wasUsing != mUseHeadsUp) {
if (!mUseHeadsUp) {
@@ -566,26 +540,21 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- private boolean mWaitingForKeyguardExit;
protected boolean mDozing;
private boolean mDozingRequested;
protected boolean mScrimSrcModeEnabled;
- public static final Interpolator ALPHA_IN = Interpolators.ALPHA_IN;
- public static final Interpolator ALPHA_OUT = Interpolators.ALPHA_OUT;
-
protected BackDropView mBackdrop;
protected ImageView mBackdropFront, mBackdropBack;
- protected PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
- protected PorterDuffXfermode mSrcOverXferMode =
+ protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
+ protected final PorterDuffXfermode mSrcOverXferMode =
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
private MediaSessionManager mMediaSessionManager;
private MediaController mMediaController;
private String mMediaNotificationKey;
private MediaMetadata mMediaMetadata;
- private MediaController.Callback mMediaListener
- = new MediaController.Callback() {
+ private final MediaController.Callback mMediaListener = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
super.onPlaybackStateChanged(state);
@@ -607,17 +576,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- private final OnChildLocationsChangedListener mOnChildLocationsChangedListener =
- new OnChildLocationsChangedListener() {
- @Override
- public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) {
- userActivity();
- }
- };
-
- private int mDisabledUnmodified1;
- private int mDisabledUnmodified2;
-
/** Keys of notifications currently visible to the user. */
private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
new ArraySet<>();
@@ -644,15 +602,6 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mWereIconsJustHidden;
private boolean mBouncerWasShowingWhenHidden;
- public boolean isStartedGoingToSleep() {
- return mStartedGoingToSleep;
- }
-
- /**
- * If set, the device has started going to sleep but isn't fully non-interactive yet.
- */
- protected boolean mStartedGoingToSleep;
-
private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
new OnChildLocationsChangedListener() {
@Override
@@ -686,7 +635,6 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void run() {
mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
- final String mediaKey = getCurrentMediaNotificationKey();
// 1. Loop over mNotificationData entries:
// A. Keep list of visible notifications.
@@ -743,10 +691,10 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean mKeyguardRequested;
private boolean mIsKeyguard;
private LogMaker mStatusBarStateLog;
- private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+ private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
protected NotificationIconAreaController mNotificationIconAreaController;
private boolean mReinflateNotificationsOnUserSwitched;
- private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
+ private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
private boolean mClearAllEnabled;
@Nullable private View mAmbientIndicationContainer;
private String mKeyToRemoveOnGutsClosed;
@@ -769,20 +717,21 @@ public class StatusBar extends SystemUI implements DemoMode,
goToLockedShade(null);
}
};
- private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
- = new HashMap<>();
+ private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+ mTmpChildOrderMap = new HashMap<>();
private RankingMap mLatestRankingMap;
private boolean mNoAnimationOnNextBarModeChange;
private FalsingManager mFalsingManager;
- private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onDreamingStateChanged(boolean dreaming) {
- if (dreaming) {
- maybeEscalateHeadsUp();
- }
- }
- };
+ private final KeyguardUpdateMonitorCallback mUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onDreamingStateChanged(boolean dreaming) {
+ if (dreaming) {
+ maybeEscalateHeadsUp();
+ }
+ }
+ };
private NavigationBarFragment mNavigationBar;
private View mNavigationBarView;
@@ -861,10 +810,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mRecents = getComponent(Recents.class);
- final Configuration currentConfig = res.getConfiguration();
- mLocale = currentConfig.locale;
- mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
-
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mLockPatternUtils = new LockPatternUtils(mContext);
@@ -994,9 +939,6 @@ public class StatusBar extends SystemUI implements DemoMode,
Dependency.get(ConfigurationController.class).addCallback(this);
}
- protected void createIconController() {
- }
-
// ================================================================================
// Constructing the view
// ================================================================================
@@ -1012,16 +954,14 @@ public class StatusBar extends SystemUI implements DemoMode,
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
- mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
- R.id.notification_panel);
- mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
- R.id.notification_stack_scroller);
+ mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+ mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
R.id.notification_container_parent));
- mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
+ mKeyguardStatusBar = mStatusBarWindow.findViewById(R.id.keyguard_header);
mNotificationIconAreaController = SystemUIFactory.getInstance()
.createNotificationIconAreaController(context, this);
@@ -1057,10 +997,10 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationData.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
+ putComponent(HeadsUpManager.class, mHeadsUpManager);
if (MULTIUSER_DEBUG) {
- mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
- R.id.header_debug_info);
+ mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
mNotificationPanelDebugText.setVisibility(View.VISIBLE);
}
@@ -1074,9 +1014,6 @@ public class StatusBar extends SystemUI implements DemoMode,
// no window manager? good luck with that
}
- // figure out which pixel-format to use for the status bar.
- mPixelFormat = PixelFormat.OPAQUE;
-
mStackScroller.setLongPressListener(getNotificationLongClicker());
mStackScroller.setStatusBar(this);
mStackScroller.setGroupManager(mGroupManager);
@@ -1086,11 +1023,10 @@ public class StatusBar extends SystemUI implements DemoMode,
inflateEmptyShadeView();
inflateDismissView();
- mExpandedContents = mStackScroller;
- mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop);
- mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
- mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);
+ mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop);
+ mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front);
+ mBackdropBack = mBackdrop.findViewById(R.id.backdrop_back);
if (ENABLE_LOCKSCREEN_WALLPAPER) {
mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
@@ -1098,8 +1034,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mKeyguardIndicationController =
SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
- (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
- mNotificationPanel.getLockIcon());
+ mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
+ mNotificationPanel.getLockIcon());
mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
@@ -1130,8 +1066,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mNavigationBar.setLightBarController(mLightBarController);
}
- ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
- ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
+ ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
+ ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
mScrimController = SystemUIFactory.getInstance().createScrimController(mLightBarController,
scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
@@ -1141,13 +1077,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
});
if (mScrimSrcModeEnabled) {
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
- mScrimController.setDrawBehindAsSrc(asSrc);
- mStackScroller.setDrawBackgroundAsSrc(asSrc);
- }
+ Runnable runnable = () -> {
+ boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
+ mScrimController.setDrawBehindAsSrc(asSrc);
+ mStackScroller.setDrawBackgroundAsSrc(asSrc);
};
mBackdrop.setOnVisibilityChangedRunnable(runnable);
runnable.run();
@@ -1169,11 +1102,11 @@ public class StatusBar extends SystemUI implements DemoMode,
if (container != null) {
FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
- Dependency.get(ExtensionController.class).newExtension(QS.class)
+ Dependency.get(ExtensionController.class)
+ .newExtension(QS.class)
.withPlugin(QS.class)
- .withFeature(
- PackageManager.FEATURE_AUTOMOTIVE, () -> new CarQSFragment())
- .withDefault(() -> new QSFragment())
+ .withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new)
+ .withDefault(QSFragment::new)
.build());
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
@@ -1275,7 +1208,7 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
protected View.OnTouchListener getStatusBarWindowTouchListener() {
return (v, event) -> {
- checkUserAutohide(v, event);
+ checkUserAutohide(event);
checkRemoteInputOutside(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mExpandedVisible) {
@@ -1379,8 +1312,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public static SignalClusterView reinflateSignalCluster(View view) {
Context context = view.getContext();
- SignalClusterView signalCluster =
- (SignalClusterView) view.findViewById(R.id.signal_cluster);
+ SignalClusterView signalCluster = view.findViewById(R.id.signal_cluster);
if (signalCluster != null) {
ViewParent parent = signalCluster.getParent();
if (parent instanceof ViewGroup) {
@@ -1420,20 +1352,17 @@ public class StatusBar extends SystemUI implements DemoMode,
mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_dismiss_all, mStackScroller, false);
- mDismissView.setOnButtonClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
- clearAllNotifications();
- }
+ mDismissView.setOnButtonClickListener(v -> {
+ mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
+ clearAllNotifications();
});
mStackScroller.setDismissView(mDismissView);
}
protected void createUserSwitcher() {
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
- (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
- mKeyguardStatusBar, mNotificationPanel);
+ mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mKeyguardStatusBar,
+ mNotificationPanel);
}
protected void inflateStatusBarWindow(Context context) {
@@ -1446,7 +1375,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// animate-swipe all dismissable notifications, then animate the shade closed
int numChildren = mStackScroller.getChildCount();
- final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
+ final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
for (int i = 0; i < numChildren; i++) {
final View child = mStackScroller.getChildAt(i);
@@ -1486,20 +1415,18 @@ public class StatusBar extends SystemUI implements DemoMode,
return;
}
- addPostCollapseAction(new Runnable() {
- @Override
- public void run() {
- mStackScroller.setDismissAllInProgress(false);
- for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
- if (mStackScroller.canChildBeDismissed(rowToRemove)) {
- removeNotification(rowToRemove.getEntry().key, null);
- } else {
- rowToRemove.resetTranslation();
- }
+ addPostCollapseAction(() -> {
+ mStackScroller.setDismissAllInProgress(false);
+ for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+ if (mStackScroller.canChildBeDismissed(rowToRemove)) {
+ removeNotification(rowToRemove.getEntry().key, null);
+ } else {
+ rowToRemove.resetTranslation();
}
- try {
- mBarService.onClearAllNotifications(mCurrentUserId);
- } catch (Exception ex) { }
+ }
+ try {
+ mBarService.onClearAllNotifications(mCurrentUserId);
+ } catch (Exception ex) {
}
});
@@ -1508,13 +1435,15 @@ public class StatusBar extends SystemUI implements DemoMode,
}
private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) {
- Runnable animationFinishAction = new Runnable() {
- @Override
- public void run() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- }
+ Runnable animationFinishAction = () -> {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
};
+ if (hideAnimatedList.isEmpty()) {
+ animationFinishAction.run();
+ return;
+ }
+
// let's disable our normal animations
mStackScroller.setDismissAllInProgress(true);
@@ -1631,10 +1560,6 @@ public class StatusBar extends SystemUI implements DemoMode,
SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
}
- public UserHandle getCurrentUserHandle() {
- return new UserHandle(mCurrentUserId);
- }
-
public void addNotification(StatusBarNotification notification, RankingMap ranking)
throws InflationException {
String key = notification.getKey();
@@ -1745,7 +1670,7 @@ public class StatusBar extends SystemUI implements DemoMode,
boolean deferRemoval = false;
abortExistingInflation(key);
if (mHeadsUpManager.isHeadsUp(key)) {
- // A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the
+ // A cancel() in response to a remote input shouldn't be delayed, as it makes the
// sending look longer than it takes.
// Also we should not defer the removal if reordering isn't allowed since otherwise
// some notifications can't disappear before the panel is closed.
@@ -1771,9 +1696,7 @@ public class StatusBar extends SystemUI implements DemoMode,
newHistory = new CharSequence[1];
} else {
newHistory = new CharSequence[oldHistory.length + 1];
- for (int i = 0; i < oldHistory.length; i++) {
- newHistory[i + 1] = oldHistory[i];
- }
+ System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
}
newHistory[0] = String.valueOf(entry.remoteInputText);
b.setRemoteInputHistory(newHistory);
@@ -1832,7 +1755,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mStackScroller.cleanUpViewState(entry.row);
}
// Let's remove the children if this was a summary
- handleGroupSummaryRemoved(key, ranking);
+ handleGroupSummaryRemoved(key);
StatusBarNotification old = removeNotificationViews(key, ranking);
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
@@ -1856,12 +1779,10 @@ public class StatusBar extends SystemUI implements DemoMode,
*
* This also ensures that the animation looks nice and only consists of a single disappear
* animation instead of multiple.
+ * @param key the key of the notification was removed
*
- * @param key the key of the notification was removed
- * @param ranking the current ranking
*/
- private void handleGroupSummaryRemoved(String key,
- RankingMap ranking) {
+ private void handleGroupSummaryRemoved(String key) {
Entry entry = mNotificationData.get(key);
if (entry != null && entry.row != null
&& entry.row.isSummaryWithChildren()) {
@@ -1872,15 +1793,13 @@ public class StatusBar extends SystemUI implements DemoMode,
}
List<ExpandableNotificationRow> notificationChildren =
entry.row.getNotificationChildren();
- ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow row = notificationChildren.get(i);
if ((row.getStatusBarNotification().getNotification().flags
& Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- // the child is a forground service notification which we can't remove!
+ // the child is a foreground service notification which we can't remove!
continue;
}
- toRemove.add(row);
row.setKeepInParent(true);
// we need to set this state earlier as otherwise we might generate some weird
// animations
@@ -1900,7 +1819,9 @@ public class StatusBar extends SystemUI implements DemoMode,
final int id = n.getId();
final int userId = n.getUserId();
try {
- mBarService.onNotificationClear(pkg, tag, id, userId);
+ // TODO: record actual dismissal surface
+ mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
+ NotificationStats.DISMISSAL_OTHER);
if (FORCE_REMOTE_INPUT_HISTORY
&& mKeysKeptForRemoteInput.contains(n.getKey())) {
mKeysKeptForRemoteInput.remove(n.getKey());
@@ -1923,19 +1844,14 @@ public class StatusBar extends SystemUI implements DemoMode,
// Do not modify the notifications during collapse.
if (isCollapsing()) {
- addPostCollapseAction(new Runnable() {
- @Override
- public void run() {
- updateNotificationShade();
- }
- });
+ addPostCollapseAction(this::updateNotificationShade);
return;
}
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
final int N = activeNotifications.size();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
Entry ent = activeNotifications.get(i);
if (ent.row.isDismissed() || ent.row.isRemoved()) {
// we don't want to update removed notifications because they could
@@ -1979,7 +1895,8 @@ public class StatusBar extends SystemUI implements DemoMode,
for (ExpandableNotificationRow remove : toRemove) {
if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
- // we are only transfering this notification to its parent, don't generate an animation
+ // we are only transferring this notification to its parent, don't generate an
+ // animation
mStackScroller.setChildTransferInProgress(true);
}
if (remove.isSummaryWithChildren()) {
@@ -1991,7 +1908,7 @@ public class StatusBar extends SystemUI implements DemoMode,
removeNotificationChildren();
- for (int i=0; i<toShow.size(); i++) {
+ for (int i = 0; i < toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) {
mVisualStabilityManager.notifyViewAddition(v);
@@ -2065,6 +1982,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned()
&& (mUserSetup || mUserSwitcherController == null
|| !mUserSwitcherController.isSimpleUserSwitcher())
+ && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0)
&& ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
&& !mDozing
&& !ONLY_CORE_APPS);
@@ -2101,7 +2019,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- // Finally after removing and adding has been beformed we can apply the order.
+ // Finally after removing and adding has been performed we can apply the order.
orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
}
if (orderChanged) {
@@ -2274,10 +2192,11 @@ public class StatusBar extends SystemUI implements DemoMode,
MediaController controller = null;
for (int i = 0; i < N; i++) {
final Entry entry = activeNotifications.get(i);
+
if (isMediaNotification(entry)) {
final MediaSession.Token token =
- entry.notification.getNotification().extras
- .getParcelable(Notification.EXTRA_MEDIA_SESSION);
+ entry.notification.getNotification().extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION);
if (token != null) {
MediaController aController = new MediaController(mContext, token);
if (PlaybackState.STATE_PLAYING ==
@@ -2315,7 +2234,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (entry.notification.getPackageName().equals(pkg)) {
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: found controller matching "
- + entry.notification.getKey());
+ + entry.notification.getKey());
}
controller = aController;
mediaNotification = entry;
@@ -2366,12 +2285,8 @@ public class StatusBar extends SystemUI implements DemoMode,
}
private boolean isPlaybackActive(int state) {
- if (state != PlaybackState.STATE_STOPPED
- && state != PlaybackState.STATE_ERROR
- && state != PlaybackState.STATE_NONE) {
- return true;
- }
- return false;
+ return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+ && state != PlaybackState.STATE_NONE;
}
private void clearCurrentMediaNotification() {
@@ -2396,7 +2311,7 @@ public class StatusBar extends SystemUI implements DemoMode,
/**
* Hide the album artwork that is fading out and release its bitmap.
*/
- protected Runnable mHideBackdropFront = new Runnable() {
+ protected final Runnable mHideBackdropFront = new Runnable() {
@Override
public void run() {
if (DEBUG_MEDIA) {
@@ -2548,14 +2463,11 @@ public class StatusBar extends SystemUI implements DemoMode,
.setInterpolator(Interpolators.ACCELERATE_DECELERATE)
.setDuration(300)
.setStartDelay(0)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mBackdrop.setVisibility(View.GONE);
- mBackdropFront.animate().cancel();
- mBackdropBack.setImageDrawable(null);
- mHandler.post(mHideBackdropFront);
- }
+ .withEndAction(() -> {
+ mBackdrop.setVisibility(View.GONE);
+ mBackdropFront.animate().cancel();
+ mBackdropBack.setImageDrawable(null);
+ mHandler.post(mHideBackdropFront);
});
if (mKeyguardFadingAway) {
mBackdrop.animate()
@@ -2586,8 +2498,6 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void disable(int state1, int state2, boolean animate) {
animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
- mDisabledUnmodified1 = state1;
- mDisabledUnmodified2 = state2;
final int old1 = mDisabled1;
final int diff1 = state1 ^ old1;
mDisabled1 = state1;
@@ -2623,8 +2533,13 @@ public class StatusBar extends SystemUI implements DemoMode,
flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_CLOCK)) ? '!' : ' ');
flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH)) ? 'S' : 's');
flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SEARCH)) ? '!' : ' ');
+ flagdbg.append("> disable2<");
flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? 'Q' : 'q');
flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? '!' : ' ');
+ flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? 'I' : 'i');
+ flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? '!' : ' ');
+ flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? 'N' : 'n');
+ flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? '!' : ' ');
flagdbg.append('>');
Log.d(TAG, flagdbg.toString());
@@ -2651,6 +2566,13 @@ public class StatusBar extends SystemUI implements DemoMode,
if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
updateQsExpansionEnabled();
}
+
+ if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ updateQsExpansionEnabled();
+ if ((state1 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ animateCollapsePanels();
+ }
+ }
}
/**
@@ -2738,11 +2660,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// make sure that the window stays small for one frame until the touchableRegion is set.
mNotificationPanel.requestLayout();
mStatusBarWindowManager.setForceWindowCollapsed(true);
- mNotificationPanel.post(new Runnable() {
- @Override
- public void run() {
- mStatusBarWindowManager.setForceWindowCollapsed(false);
- }
+ mNotificationPanel.post(() -> {
+ mStatusBarWindowManager.setForceWindowCollapsed(false);
});
}
} else {
@@ -2754,15 +2673,12 @@ public class StatusBar extends SystemUI implements DemoMode,
// we need to keep the panel open artificially, let's wait until the animation
// is finished.
mHeadsUpManager.setHeadsUpGoingAway(true);
- mStackScroller.runAfterAnimationFinished(new Runnable() {
- @Override
- public void run() {
- if (!mHeadsUpManager.hasPinnedHeadsUp()) {
- mStatusBarWindowManager.setHeadsUpShowing(false);
- mHeadsUpManager.setHeadsUpGoingAway(false);
- }
- removeRemoteInputEntriesKeptUntilCollapsed();
+ mStackScroller.runAfterAnimationFinished(() -> {
+ if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+ mStatusBarWindowManager.setHeadsUpShowing(false);
+ mHeadsUpManager.setHeadsUpGoingAway(false);
}
+ removeRemoteInputEntriesKeptUntilCollapsed();
});
}
}
@@ -3013,7 +2929,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
boolean panelsEnabled() {
- return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && !ONLY_CORE_APPS;
+ return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0
+ && (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
+ && !ONLY_CORE_APPS;
}
void makeExpandedVisible(boolean force) {
@@ -3029,7 +2947,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarWindowManager.setPanelVisible(true);
visibilityChanged(true);
- mWaitingForKeyguardExit = false;
recomputeDisableFlags(!force /* animate */);
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
}
@@ -3038,23 +2955,15 @@ public class StatusBar extends SystemUI implements DemoMode,
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
}
- private final Runnable mAnimateCollapsePanels = new Runnable() {
- @Override
- public void run() {
- animateCollapsePanels();
- }
- };
+ private final Runnable mAnimateCollapsePanels = this::animateCollapsePanels;
public void postAnimateCollapsePanels() {
mHandler.post(mAnimateCollapsePanels);
}
public void postAnimateForceCollapsePanels() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
- }
+ mHandler.post(() -> {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
});
}
@@ -3104,6 +3013,9 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
+ // TODO(b/62444020): remove when this bug is fixed
+ Log.v(TAG, "mStatusBarWindow: " + mStatusBarWindow + " canPanelBeCollapsed(): "
+ + mNotificationPanel.canPanelBeCollapsed());
if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) {
// release focus immediately to kick off focus change transition
mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -3209,7 +3121,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (SPEW) {
Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
- + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking);
+ + mDisabled1 + " mDisabled2=" + mDisabled2);
} else if (CHATTY) {
if (event.getAction() != MotionEvent.ACTION_MOVE) {
Log.d(TAG, String.format(
@@ -3294,10 +3206,8 @@ public class StatusBar extends SystemUI implements DemoMode,
sbModeChanged = sbMode != -1;
if (sbModeChanged && sbMode != mStatusBarMode) {
- if (sbMode != mStatusBarMode) {
- mStatusBarMode = sbMode;
- checkBarModes();
- }
+ mStatusBarMode = sbMode;
+ checkBarModes();
touchAutoHide();
}
@@ -3321,7 +3231,6 @@ public class StatusBar extends SystemUI implements DemoMode,
} else {
cancelAutohide();
}
- touchAutoDim();
}
protected int computeStatusBarMode(int oldVal, int newVal) {
@@ -3385,12 +3294,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- private final Runnable mCheckBarModes = new Runnable() {
- @Override
- public void run() {
- checkBarModes();
- }
- };
+ private final Runnable mCheckBarModes = this::checkBarModes;
public void setInteracting(int barWindow, boolean interacting) {
final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting;
@@ -3404,10 +3308,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
// manually dismiss the volume panel when interacting with the nav bar
if (changing && interacting && barWindow == StatusBarManager.WINDOW_NAVIGATION_BAR) {
+ touchAutoDim();
dismissVolumeDialog();
}
checkBarModes();
- touchAutoDim();
}
private void dismissVolumeDialog() {
@@ -3449,7 +3353,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- void checkUserAutohide(View v, MotionEvent event) {
+ void checkUserAutohide(MotionEvent event) {
if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0 // a transient bar is revealed
&& event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
&& event.getX() == 0 && event.getY() == 0 // a touch outside both bars
@@ -3516,9 +3420,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mQueueLock) {
pw.println("Current Status Bar state:");
- pw.println(" mExpandedVisible=" + mExpandedVisible
- + ", mTrackingPosition=" + mTrackingPosition);
- pw.println(" mTracking=" + mTracking);
+ pw.println(" mExpandedVisible=" + mExpandedVisible);
pw.println(" mDisplayMetrics=" + mDisplayMetrics);
pw.println(" mStackScroller: " + viewInfo(mStackScroller));
pw.println(" mStackScroller: " + viewInfo(mStackScroller)
@@ -3606,16 +3508,12 @@ public class StatusBar extends SystemUI implements DemoMode,
if (false) {
pw.println("see the logcat for a dump of the views we have created.");
// must happen on ui thread
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mStatusBarView.getLocationOnScreen(mAbsPos);
- Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
- + ") " + mStatusBarView.getWidth() + "x"
- + getStatusBarHeight());
- mStatusBarView.debug();
- }
- });
+ mHandler.post(() -> {
+ mStatusBarView.getLocationOnScreen(mAbsPos);
+ Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] +
+ ") " + mStatusBarView.getWidth() + "x" + getStatusBarHeight());
+ mStatusBarView.debug();
+ });
}
}
@@ -3695,49 +3593,43 @@ public class StatusBar extends SystemUI implements DemoMode,
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
mContext, intent, mCurrentUserId);
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- mAssistManager.hideAssist();
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- int result = ActivityManager.START_CANCELED;
- ActivityOptions options = new ActivityOptions(getActivityOptions());
- options.setDisallowEnterPictureInPictureWhileLaunching(
- disallowEnterPictureInPictureWhileLaunching);
- if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
- // Normally an activity will set it's requested rotation
- // animation on its window. However when launching an activity
- // causes the orientation to change this is too late. In these cases
- // the default animation is used. This doesn't look good for
- // the camera (as it rotates the camera contents out of sync
- // with physical reality). So, we ask the WindowManager to
- // force the crossfade animation if an orientation change
- // happens to occur during the launch.
- options.setRotationAnimationHint(
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
- }
- try {
- result = ActivityManager.getService().startActivityAsUser(
- null, mContext.getBasePackageName(),
- intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
- options.toBundle(), UserHandle.CURRENT.getIdentifier());
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to start activity", e);
- }
- if (callback != null) {
- callback.onActivityStarted(result);
- }
+ Runnable runnable = () -> {
+ mAssistManager.hideAssist();
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ int result = ActivityManager.START_CANCELED;
+ ActivityOptions options = new ActivityOptions(getActivityOptions());
+ options.setDisallowEnterPictureInPictureWhileLaunching(
+ disallowEnterPictureInPictureWhileLaunching);
+ if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
+ // Normally an activity will set it's requested rotation
+ // animation on its window. However when launching an activity
+ // causes the orientation to change this is too late. In these cases
+ // the default animation is used. This doesn't look good for
+ // the camera (as it rotates the camera contents out of sync
+ // with physical reality). So, we ask the WindowManager to
+ // force the crossfade animation if an orientation change
+ // happens to occur during the launch.
+ options.setRotationAnimationHint(
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
+ }
+ try {
+ result = ActivityManager.getService().startActivityAsUser(
+ null, mContext.getBasePackageName(),
+ intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
+ options.toBundle(), UserHandle.CURRENT.getIdentifier());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to start activity", e);
+ }
+ if (callback != null) {
+ callback.onActivityStarted(result);
}
};
- Runnable cancelRunnable = new Runnable() {
- @Override
- public void run() {
- if (callback != null) {
- callback.onActivityStarted(ActivityManager.START_CANCELED);
- }
+ Runnable cancelRunnable = () -> {
+ if (callback != null) {
+ callback.onActivityStarted(ActivityManager.START_CANCELED);
}
};
executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade,
@@ -3782,7 +3674,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}, cancelAction, afterKeyguardGone);
}
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3811,7 +3703,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- private BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3972,7 +3864,6 @@ public class StatusBar extends SystemUI implements DemoMode,
* The LEDs are turned off when the notification panel is shown, even just a little bit.
* See also StatusBar.setPanelExpanded for another place where we attempt to do this.
*/
- // Old BaseStatusBar.handleVisibileToUserChanged
private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
try {
if (visibleToUser) {
@@ -3997,8 +3888,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// Report all notifications as invisible and turn down the
// reporter.
if (!mCurrentlyVisibleNotifications.isEmpty()) {
- logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(),
- mCurrentlyVisibleNotifications);
+ logNotificationVisibilityChanges(
+ Collections.emptyList(), mCurrentlyVisibleNotifications);
recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
}
mHandler.removeCallbacks(mVisibilityReporter);
@@ -4105,7 +3996,7 @@ public class StatusBar extends SystemUI implements DemoMode,
vib.vibrate(250, VIBRATION_ATTRIBUTES);
}
- Runnable mStartTracing = new Runnable() {
+ final Runnable mStartTracing = new Runnable() {
@Override
public void run() {
vibrate();
@@ -4116,13 +4007,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- Runnable mStopTracing = new Runnable() {
- @Override
- public void run() {
- android.os.Debug.stopMethodTracing();
- Log.d(TAG, "stopTracing");
- vibrate();
- }
+ final Runnable mStopTracing = () -> {
+ android.os.Debug.stopMethodTracing();
+ Log.d(TAG, "stopTracing");
+ vibrate();
};
@Override
@@ -4149,40 +4037,6 @@ public class StatusBar extends SystemUI implements DemoMode,
startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
}
- private static class FastColorDrawable extends Drawable {
- private final int mColor;
-
- public FastColorDrawable(int color) {
- mColor = 0xff000000 | color;
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawColor(mColor, PorterDuff.Mode.SRC);
- }
-
- @Override
- public void setAlpha(int alpha) {
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.OPAQUE;
- }
-
- @Override
- public void setBounds(int left, int top, int right, int bottom) {
- }
-
- @Override
- public void setBounds(Rect bounds) {
- }
- }
-
public void destroy() {
// Begin old BaseStatusBar.destroy().
mContext.unregisterReceiver(mBaseBroadcastReceiver);
@@ -4400,31 +4254,23 @@ public class StatusBar extends SystemUI implements DemoMode,
Runnable endRunnable) {
mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
mLaunchTransitionEndRunnable = endRunnable;
- Runnable hideRunnable = new Runnable() {
- @Override
- public void run() {
- mLaunchTransitionFadingAway = true;
- if (beforeFading != null) {
- beforeFading.run();
- }
- mScrimController.forceHideScrims(true /* hide */, false /* animated */);
- updateMediaMetaData(false, true);
- mNotificationPanel.setAlpha(1);
- mStackScroller.setParentNotFullyVisible(true);
- mNotificationPanel.animate()
- .alpha(0)
- .setStartDelay(FADE_KEYGUARD_START_DELAY)
- .setDuration(FADE_KEYGUARD_DURATION)
- .withLayer()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- onLaunchTransitionFadingEnded();
- }
- });
- mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
- LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
- }
+ Runnable hideRunnable = () -> {
+ mLaunchTransitionFadingAway = true;
+ if (beforeFading != null) {
+ beforeFading.run();
+ }
+ mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+ updateMediaMetaData(false, true);
+ mNotificationPanel.setAlpha(1);
+ mStackScroller.setParentNotFullyVisible(true);
+ mNotificationPanel.animate()
+ .alpha(0)
+ .setStartDelay(FADE_KEYGUARD_START_DELAY)
+ .setDuration(FADE_KEYGUARD_DURATION)
+ .withLayer()
+ .withEndAction(this::onLaunchTransitionFadingEnded);
+ mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
+ LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
};
if (mNotificationPanel.isLaunchTransitionRunning()) {
mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
@@ -4553,7 +4399,6 @@ public class StatusBar extends SystemUI implements DemoMode,
// Treat Keyguard exit animation as an app transition to achieve nice transition for status
// bar.
- mKeyguardGoingAway = true;
mKeyguardMonitor.notifyKeyguardGoingAway(true);
mCommandQueue.appTransitionPending(true);
}
@@ -4562,14 +4407,13 @@ public class StatusBar extends SystemUI implements DemoMode,
* Notifies the status bar the Keyguard is fading away with the specified timings.
*
* @param startTime the start time of the animations in uptime millis
- * @param delay the precalculated animation delay in miliseconds
+ * @param delay the precalculated animation delay in milliseconds
* @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) {
mKeyguardFadingAway = true;
mKeyguardFadingAwayDelay = delay;
mKeyguardFadingAwayDuration = fadeoutDuration;
- mWaitingForKeyguardExit = false;
mCommandQueue.appTransitionStarting(startTime + fadeoutDuration
- LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
@@ -4589,14 +4433,9 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
public void finishKeyguardFadingAway() {
mKeyguardFadingAway = false;
- mKeyguardGoingAway = false;
mKeyguardMonitor.notifyKeyguardDoneFading();
}
- public void stopWaitingForKeyguardExit() {
- mWaitingForKeyguardExit = false;
- }
-
private void updatePublicMode() {
final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing();
final boolean devicePublic = showingKeyguard
@@ -4810,7 +4649,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
protected void showBouncer() {
- mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing();
mStatusBarKeyguardViewManager.dismiss();
}
@@ -4827,7 +4665,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onActivated(ActivatableNotificationView view) {
- onActivated((View)view);
+ onActivated((View) view);
mStackScroller.setActivatedChild(view);
}
@@ -5022,6 +4860,10 @@ public class StatusBar extends SystemUI implements DemoMode,
* @param expandView The view to expand after going to the shade.
*/
public void goToLockedShade(View expandView) {
+ if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ return;
+ }
+
int userId = mCurrentUserId;
ExpandableNotificationRow row = null;
if (expandView instanceof ExpandableNotificationRow) {
@@ -5129,51 +4971,41 @@ public class StatusBar extends SystemUI implements DemoMode,
updateNotifications();
if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) {
// Expand notification panel and the notification row, then click on remote input view
- final Runnable clickPendingViewRunnable = new Runnable() {
- @Override
- public void run() {
- final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
- if (pendingWorkRemoteInputView == null) {
+ final Runnable clickPendingViewRunnable = () -> {
+ final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
+ if (pendingWorkRemoteInputView == null) {
+ return;
+ }
+
+ // Climb up the hierarchy until we get to the container for this row.
+ ViewParent p = pendingWorkRemoteInputView.getParent();
+ while (!(p instanceof ExpandableNotificationRow)) {
+ if (p == null) {
return;
}
+ p = p.getParent();
+ }
- // Climb up the hierarchy until we get to the container for this row.
- ViewParent p = pendingWorkRemoteInputView.getParent();
- while (!(p instanceof ExpandableNotificationRow)) {
- if (p == null) {
- return;
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
+ ViewParent viewParent = row.getParent();
+ if (viewParent instanceof NotificationStackScrollLayout) {
+ final NotificationStackScrollLayout scrollLayout =
+ (NotificationStackScrollLayout) viewParent;
+ row.makeActionsVisibile();
+ row.post(() -> {
+ final Runnable finishScrollingCallback = () -> {
+ mPendingWorkRemoteInputView.callOnClick();
+ mPendingWorkRemoteInputView = null;
+ scrollLayout.setFinishScrollingCallback(null);
+ };
+ if (scrollLayout.scrollTo(row)) {
+ // It scrolls! So call it when it's finished.
+ scrollLayout.setFinishScrollingCallback(finishScrollingCallback);
+ } else {
+ // It does not scroll, so call it now!
+ finishScrollingCallback.run();
}
- p = p.getParent();
- }
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
- ViewParent viewParent = row.getParent();
- if (viewParent instanceof NotificationStackScrollLayout) {
- final NotificationStackScrollLayout scrollLayout =
- (NotificationStackScrollLayout) viewParent;
- row.makeActionsVisibile();
- row.post(new Runnable() {
- @Override
- public void run() {
- final Runnable finishScrollingCallback = new Runnable() {
- @Override
- public void run() {
- mPendingWorkRemoteInputView.callOnClick();
- mPendingWorkRemoteInputView = null;
- scrollLayout.setFinishScrollingCallback(null);
- }
- };
- if (scrollLayout.scrollTo(row)) {
- // It scrolls! So call it when it's finished.
- scrollLayout.setFinishScrollingCallback(
- finishScrollingCallback);
- } else {
- // It does not scroll, so call it now!
- finishScrollingCallback.run();
- }
- }
- });
- }
+ });
}
};
mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
@@ -5236,7 +5068,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+ final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
mNotificationPanel.onAffordanceLaunchEnded();
@@ -5259,12 +5091,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// This gets executed before we will show Keyguard, so post it in order that the state
// is correct.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- onCameraLaunchGestureDetected(mLastCameraLaunchSource);
- }
- });
+ mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource));
}
updateIsKeyguard();
}
@@ -5290,7 +5117,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+ final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOn() {
mFalsingManager.onScreenTurningOn();
@@ -5487,7 +5314,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
private final class DozeServiceHost implements DozeHost {
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private boolean mAnimateWakeup;
private boolean mIgnoreTouchWhilePulsing;
@@ -5700,7 +5527,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected NotificationData mNotificationData;
protected NotificationStackScrollLayout mStackScroller;
- protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
+ protected final NotificationGroupManager mGroupManager = new NotificationGroupManager();
protected RemoteInputController mRemoteInputController;
@@ -5710,34 +5537,30 @@ public class StatusBar extends SystemUI implements DemoMode,
private AboveShelfObserver mAboveShelfObserver;
// handling reordering
- protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
+ protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
protected int mCurrentUserId = 0;
- final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+ final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
- protected int mLayoutDirection = -1; // invalid
protected AccessibilityManager mAccessibilityManager;
protected boolean mDeviceInteractive;
protected boolean mVisible;
- protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
- protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
+ protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
+ protected final ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
/**
* Notifications with keys in this set are not actually around anymore. We kept them around
* when they were canceled in response to a remote input interaction. This allows us to show
* what you replied and allows you to continue typing into it.
*/
- protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
+ protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
// mScreenOnFromKeyguard && mVisible.
private boolean mVisibleToUser;
- private Locale mLocale;
-
protected boolean mUseHeadsUp = false;
- protected boolean mHeadsUpTicker = false;
protected boolean mDisableNotificationAlerts = false;
protected DevicePolicyManager mDevicePolicyManager;
@@ -5772,13 +5595,11 @@ public class StatusBar extends SystemUI implements DemoMode,
private NotificationGuts mNotificationGutsExposed;
private MenuItem mGutsMenuItem;
- private KeyboardShortcuts mKeyboardShortcuts;
-
protected NotificationShelf mNotificationShelf;
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
- private NotificationClicker mNotificationClicker = new NotificationClicker();
+ private final NotificationClicker mNotificationClicker = new NotificationClicker();
protected AssistManager mAssistManager;
@@ -5838,15 +5659,14 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+ private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
@Override
public boolean onClickHandler(
final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
wakeUpIfDozing(SystemClock.uptimeMillis(), view);
-
- if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
+ if (handleRemoteInput(view, pendingIntent)) {
return true;
}
@@ -5864,33 +5684,29 @@ public class StatusBar extends SystemUI implements DemoMode,
}
final boolean isActivity = pendingIntent.isActivity();
if (isActivity) {
- final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
mContext, pendingIntent.getIntent(), mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- try {
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
-
- boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
+ dismissKeyguardThenExecute(() -> {
+ try {
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
- // close the shade if it was open
- if (handled && !mNotificationPanel.isFullyCollapsed()) {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
- visibilityChanged(false);
- mAssistManager.hideAssist();
+ boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
- // Wait for activity start.
- return true;
- } else {
- return false;
- }
+ // close the shade if it was open
+ if (handled && !mNotificationPanel.isFullyCollapsed()) {
+ animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+ visibilityChanged(false);
+ mAssistManager.hideAssist();
+ // Wait for activity start.
+ return true;
+ } else {
+ return false;
}
+
}, afterKeyguardGone);
return true;
} else {
@@ -5932,10 +5748,15 @@ public class StatusBar extends SystemUI implements DemoMode,
private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
Intent fillInIntent) {
return super.onClickHandler(view, pendingIntent, fillInIntent,
- StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+ WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
}
- private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+ private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
+ if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+ // Skip remote input as doing so will expand the notification shade.
+ return true;
+ }
+
Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
RemoteInput[] inputs = null;
if (tag instanceof RemoteInput[]) {
@@ -6050,7 +5871,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
updateCurrentProfilesCache();
- if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+ Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
updateLockscreenNotificationSetting();
@@ -6073,8 +5894,7 @@ public class StatusBar extends SystemUI implements DemoMode,
Toast toast = Toast.makeText(mContext,
R.string.managed_profile_foreground_toast,
Toast.LENGTH_SHORT);
- TextView text = (TextView) toast.getView().findViewById(
- android.R.id.message);
+ TextView text = toast.getView().findViewById(android.R.id.message);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(
R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
int paddingPx = mContext.getResources().getDimensionPixelSize(
@@ -6150,15 +5970,12 @@ public class StatusBar extends SystemUI implements DemoMode,
return;
}
final RankingMap currentRanking = getCurrentRanking();
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (StatusBarNotification sbn : notifications) {
- try {
- addNotification(sbn, currentRanking);
- } catch (InflationException e) {
- handleInflationException(sbn, e);
- }
+ mHandler.post(() -> {
+ for (StatusBarNotification sbn : notifications) {
+ try {
+ addNotification(sbn, currentRanking);
+ } catch (InflationException e) {
+ handleInflationException(sbn, e);
}
}
});
@@ -6169,40 +5986,37 @@ public class StatusBar extends SystemUI implements DemoMode,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- processForRemoteInput(sbn.getNotification());
- String key = sbn.getKey();
- mKeysKeptForRemoteInput.remove(key);
- boolean isUpdate = mNotificationData.get(key) != null;
- // In case we don't allow child notifications, we ignore children of
- // notifications that have a summary, since we're not going to show them
- // anyway. This is true also when the summary is canceled,
- // because children are automatically canceled by NoMan in that case.
- if (!ENABLE_CHILD_NOTIFICATIONS
+ mHandler.post(() -> {
+ processForRemoteInput(sbn.getNotification());
+ String key = sbn.getKey();
+ mKeysKeptForRemoteInput.remove(key);
+ boolean isUpdate = mNotificationData.get(key) != null;
+ // In case we don't allow child notifications, we ignore children of
+ // notifications that have a summary, since we're not going to show them
+ // anyway. This is true also when the summary is canceled,
+ // because children are automatically canceled by NoMan in that case.
+ if (!ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
- if (DEBUG) {
- Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
- }
+ if (DEBUG) {
+ Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+ }
- // Remove existing notification to avoid stale data.
- if (isUpdate) {
- removeNotification(key, rankingMap);
- } else {
- mNotificationData.updateRanking(rankingMap);
- }
- return;
+ // Remove existing notification to avoid stale data.
+ if (isUpdate) {
+ removeNotification(key, rankingMap);
+ } else {
+ mNotificationData.updateRanking(rankingMap);
}
- try {
- if (isUpdate) {
- updateNotification(sbn, rankingMap);
- } else {
- addNotification(sbn, rankingMap);
- }
- } catch (InflationException e) {
- handleInflationException(sbn, e);
+ return;
+ }
+ try {
+ if (isUpdate) {
+ updateNotification(sbn, rankingMap);
+ } else {
+ addNotification(sbn, rankingMap);
}
+ } catch (InflationException e) {
+ handleInflationException(sbn, e);
}
});
}
@@ -6250,7 +6064,7 @@ public class StatusBar extends SystemUI implements DemoMode,
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
return;
}
- Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+ Log.d(TAG, "disabling lockscreen notifications and alerting the user");
// disable lockscreen notifications until user acts on the banner.
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
@@ -6291,11 +6105,10 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override // NotificationData.Environment
public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
- final int thisUserId = mCurrentUserId;
final int notificationUserId = n.getUserId();
if (DEBUG && MULTIUSER_DEBUG) {
- Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
- n, thisUserId, notificationUserId));
+ Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n,
+ mCurrentUserId, notificationUserId));
}
return isCurrentProfile(notificationUserId);
}
@@ -6343,21 +6156,15 @@ public class StatusBar extends SystemUI implements DemoMode,
}
private void startNotificationGutsIntent(final Intent intent, final int appUid) {
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- TaskStackBuilder.create(mContext)
- .addNextIntentWithParentStack(intent)
- .startActivities(getActivityOptions(),
- new UserHandle(UserHandle.getUserId(appUid)));
- }
- });
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
- return true;
- }
+ dismissKeyguardThenExecute(() -> {
+ AsyncTask.execute(() -> {
+ TaskStackBuilder.create(mContext)
+ .addNextIntentWithParentStack(intent)
+ .startActivities(getActivityOptions(),
+ new UserHandle(UserHandle.getUserId(appUid)));
+ });
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+ return true;
}, false /* afterKeyguardGone */);
}
@@ -6428,7 +6235,7 @@ public class StatusBar extends SystemUI implements DemoMode,
startNotificationGutsIntent(intent, sbn.getUid());
};
final View.OnClickListener onDoneClick = (View v) -> {
- saveAndCloseNotificationMenu(info, row, guts, v);
+ saveAndCloseNotificationMenu(row, guts, v);
};
final NotificationInfo.CheckSaveListener checkSaveListener =
(Runnable saveImportance) -> {
@@ -6445,7 +6252,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
- ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>();
+ ArraySet<NotificationChannel> channels = new ArraySet<>();
channels.add(row.getEntry().channel);
if (row.isSummaryWithChildren()) {
// If this is a summary, then add in the children notification channels for the
@@ -6473,7 +6280,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
- private void saveAndCloseNotificationMenu(NotificationInfo info,
+ private void saveAndCloseNotificationMenu(
ExpandableNotificationRow row, NotificationGuts guts, View done) {
guts.resetFalsingCheck();
int[] rowLocation = new int[2];
@@ -6642,13 +6449,6 @@ public class StatusBar extends SystemUI implements DemoMode,
updateHideIconsForBouncer(true /* animate */);
}
- protected void sendCloseSystemWindows(String reason) {
- try {
- ActivityManager.getService().closeSystemDialogs(reason);
- } catch (RemoteException e) {
- }
- }
-
protected void toggleKeyboardShortcuts(int deviceId) {
KeyboardShortcuts.toggle(mContext, deviceId);
}
@@ -6750,18 +6550,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return isLockscreenPublicMode(userId);
}
- public void onNotificationClear(StatusBarNotification notification) {
- try {
- mBarService.onNotificationClear(
- notification.getPackageName(),
- notification.getTag(),
- notification.getId(),
- notification.getUserId());
- } catch (android.os.RemoteException ex) {
- // oh well
- }
- }
-
/**
* Called when the notification panel layouts
*/
@@ -6919,49 +6707,42 @@ public class StatusBar extends SystemUI implements DemoMode,
public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
if (!isDeviceProvisioned()) return;
- final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
final boolean afterKeyguardGone = intent.isActivity()
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- new Thread() {
- @Override
- public void run() {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- try {
- intent.send(null, 0, null, null, null, null, getActivityOptions());
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending intent failed: " + e);
+ dismissKeyguardThenExecute(() -> {
+ new Thread(() -> {
+ try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ try {
+ intent.send(null, 0, null, null, null, null, getActivityOptions());
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: " + e);
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
- }
- }
- }.start();
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
+ }
+ }).start();
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- visibilityChanged(false);
+ if (!mNotificationPanel.isFullyCollapsed()) {
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+ true /* delayed */);
+ visibilityChanged(false);
- return true;
- } else {
- return false;
- }
+ return true;
+ } else {
+ return false;
}
}, afterKeyguardGone);
}
@@ -6999,130 +6780,110 @@ public class StatusBar extends SystemUI implements DemoMode,
// Mark notification for one frame.
row.setJustClicked(true);
- DejankUtils.postAfterTraversal(new Runnable() {
- @Override
- public void run() {
- row.setJustClicked(false);
- }
- });
+ DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
final boolean afterKeyguardGone = intent.isActivity()
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
mCurrentUserId);
- dismissKeyguardThenExecute(new OnDismissAction() {
- @Override
- public boolean onDismiss() {
- if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
- // Release the HUN notification to the shade.
+ dismissKeyguardThenExecute(() -> {
+ if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+ // Release the HUN notification to the shade.
- if (isPanelFullyCollapsed()) {
- HeadsUpManager.setIsClickedNotification(row, true);
- }
- //
- // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
- // become canceled shortly by NoMan, but we can't assume that.
- mHeadsUpManager.releaseImmediately(notificationKey);
+ if (isPanelFullyCollapsed()) {
+ HeadsUpManager.setIsClickedNotification(row, true);
}
- StatusBarNotification parentToCancel = null;
- if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
- StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
- .getStatusBarNotification();
- if (shouldAutoCancel(summarySbn)) {
- parentToCancel = summarySbn;
- }
+ //
+ // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+ // become canceled shortly by NoMan, but we can't assume that.
+ mHeadsUpManager.releaseImmediately(notificationKey);
+ }
+ StatusBarNotification parentToCancel = null;
+ if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+ StatusBarNotification summarySbn =
+ mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
+ if (shouldAutoCancel(summarySbn)) {
+ parentToCancel = summarySbn;
}
- final StatusBarNotification parentToCancelFinal = parentToCancel;
- final Runnable runnable = new Runnable() {
- @Override
- public void run() {
- try {
- // The intent we are sending is for the application, which
- // won't have permission to immediately start an activity after
- // the user switches to home. We know it is safe to do at this
- // point, so make sure new activity switches are now allowed.
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
- if (intent != null) {
- // If we are launching a work activity and require to launch
- // separate work challenge, we defer the activity action and cancel
- // notification until work challenge is unlocked.
- if (intent.isActivity()) {
- final int userId = intent.getCreatorUserHandle()
- .getIdentifier();
- if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
- && mKeyguardManager.isDeviceLocked(userId)) {
- // TODO(b/28935539): should allow certain activities to
- // bypass work challenge
- if (startWorkChallengeIfNecessary(userId,
- intent.getIntentSender(), notificationKey)) {
- // Show work challenge, do not run PendingIntent and
- // remove notification
- return;
- }
- }
- }
- try {
- intent.send(null, 0, null, null, null, null,
- getActivityOptions());
- } catch (PendingIntent.CanceledException e) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending contentIntent failed: " + e);
-
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity()) {
- mAssistManager.hideAssist();
+ }
+ final StatusBarNotification parentToCancelFinal = parentToCancel;
+ final Runnable runnable = () -> {
+ try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ if (intent != null) {
+ // If we are launching a work activity and require to launch
+ // separate work challenge, we defer the activity action and cancel
+ // notification until work challenge is unlocked.
+ if (intent.isActivity()) {
+ final int userId = intent.getCreatorUserHandle().getIdentifier();
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+ && mKeyguardManager.isDeviceLocked(userId)) {
+ // TODO(b/28935539): should allow certain activities to
+ // bypass work challenge
+ if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
+ notificationKey)) {
+ // Show work challenge, do not run PendingIntent and
+ // remove notification
+ return;
}
}
+ }
+ try {
+ intent.send(null, 0, null, null, null, null, getActivityOptions());
+ } catch (PendingIntent.CanceledException e) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending contentIntent failed: " + e);
- try {
- mBarService.onNotificationClick(notificationKey);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
- if (parentToCancelFinal != null) {
- // We have to post it to the UI thread for synchronization
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- Runnable removeRunnable = new Runnable() {
- @Override
- public void run() {
- performRemoveNotification(parentToCancelFinal);
- }
- };
- if (isCollapsing()) {
- // To avoid lags we're only performing the remove
- // after the shade was collapsed
- addPostCollapseAction(removeRunnable);
- } else {
- removeRunnable.run();
- }
- }
- });
- }
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity()) {
+ mAssistManager.hideAssist();
}
- };
+ }
- if (mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded()) {
- mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
- } else {
- new Thread(runnable).start();
+ try {
+ mBarService.onNotificationClick(notificationKey);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ if (parentToCancelFinal != null) {
+ // We have to post it to the UI thread for synchronization
+ mHandler.post(() -> {
+ Runnable removeRunnable =
+ () -> performRemoveNotification(parentToCancelFinal);
+ if (isCollapsing()) {
+ // To avoid lags we're only performing the remove
+ // after the shade was collapsed
+ addPostCollapseAction(removeRunnable);
+ } else {
+ removeRunnable.run();
+ }
+ });
}
+ };
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- visibilityChanged(false);
+ if (mStatusBarKeyguardViewManager.isShowing()
+ && mStatusBarKeyguardViewManager.isOccluded()) {
+ mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+ } else {
+ new Thread(runnable).start();
+ }
- return true;
- } else {
- return false;
- }
+ if (!mNotificationPanel.isFullyCollapsed()) {
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+ true /* delayed */);
+ visibilityChanged(false);
+
+ return true;
+ } else {
+ return false;
}
}, afterKeyguardGone);
}
@@ -7149,10 +6910,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
protected Bundle getActivityOptions() {
- // Anything launched from the notification shade should always go into the
- // fullscreen stack.
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+ // Anything launched from the notification shade should always go into the secondary
+ // split-screen windowing mode.
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
return options.toBundle();
}
diff --git a/com/android/systemui/statusbar/phone/StatusBarIconController.java b/com/android/systemui/statusbar/phone/StatusBarIconController.java
index c2407652..bcda60eb 100644
--- a/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -14,10 +14,10 @@
package com.android.systemui.statusbar.phone;
-import android.annotation.ColorInt;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -29,11 +29,11 @@ import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.util.Utils.DisableStateTracker;
public interface StatusBarIconController {
@@ -149,6 +149,14 @@ public interface StatusBarIconController {
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
+
+ DisableStateTracker tracker =
+ new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS);
+ mGroup.addOnAttachStateChangeListener(tracker);
+ if (mGroup.isAttachedToWindow()) {
+ // In case we miss the first onAttachedToWindow event
+ tracker.onViewAttachedToWindow(mGroup);
+ }
}
protected void onIconAdded(int index, String slot, boolean blocked,
diff --git a/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 68f8e065..1c3ee758 100644
--- a/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -33,7 +33,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.IconLogger;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -50,21 +49,17 @@ import java.util.ArrayList;
public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
- private final DarkIconDispatcher mDarkIconDispatcher;
-
- private Context mContext;
- private DemoStatusIcons mDemoStatusIcons;
-
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
-
private final ArraySet<String> mIconBlacklist = new ArraySet<>();
private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
+ private Context mContext;
+ private DemoStatusIcons mDemoStatusIcons;
+
public StatusBarIconControllerImpl(Context context) {
super(context.getResources().getStringArray(
com.android.internal.R.array.config_statusBarIcons));
Dependency.get(ConfigurationController.class).addCallback(this);
- mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
mContext = context;
loadDimens();
diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bbce751d..09828dcd 100644
--- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -225,7 +225,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mShowing) {
if (mOccluded && !mDozing) {
mStatusBar.hideKeyguard();
- mStatusBar.stopWaitingForKeyguardExit();
if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
hideBouncer(false /* destroyView */);
}
diff --git a/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index c0a68373..0d21c4ef 100644
--- a/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -20,7 +20,6 @@ import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager.ActionListener;
-import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -59,13 +58,19 @@ public class AccessPointControllerImpl
private int mCurrentUser;
- public AccessPointControllerImpl(Context context, Looper bgLooper) {
+ public AccessPointControllerImpl(Context context) {
mContext = context;
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mWifiTracker = new WifiTracker(context, this, bgLooper, false, true);
+ mWifiTracker = new WifiTracker(context, this, false, true);
mCurrentUser = ActivityManager.getCurrentUser();
}
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ mWifiTracker.onDestroy();
+ }
+
public boolean canConfigWifi() {
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
new UserHandle(mCurrentUser));
@@ -81,7 +86,7 @@ public class AccessPointControllerImpl
if (DEBUG) Log.d(TAG, "addCallback " + callback);
mCallbacks.add(callback);
if (mCallbacks.size() == 1) {
- mWifiTracker.startTracking();
+ mWifiTracker.onStart();
}
}
@@ -91,7 +96,7 @@ public class AccessPointControllerImpl
if (DEBUG) Log.d(TAG, "removeCallback " + callback);
mCallbacks.remove(callback);
if (mCallbacks.isEmpty()) {
- mWifiTracker.stopTracking();
+ mWifiTracker.onStop();
}
}
diff --git a/com/android/systemui/statusbar/policy/CallbackHandler.java b/com/android/systemui/statusbar/policy/CallbackHandler.java
index a456786d..5159e8d0 100644
--- a/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -71,7 +71,7 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa
break;
case MSG_NO_SIM_VISIBLE_CHANGED:
for (SignalCallback signalCluster : mSignalCallbacks) {
- signalCluster.setNoSims(msg.arg1 != 0);
+ signalCluster.setNoSims(msg.arg1 != 0, msg.arg2 != 0);
}
break;
case MSG_ETHERNET_CHANGED:
@@ -144,8 +144,8 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa
}
@Override
- public void setNoSims(boolean show) {
- obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, 0).sendToTarget();
+ public void setNoSims(boolean show, boolean simDetected) {
+ obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
}
@Override
diff --git a/com/android/systemui/statusbar/policy/NetworkController.java b/com/android/systemui/statusbar/policy/NetworkController.java
index 2771011c..9eee906b 100644
--- a/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/com/android/systemui/statusbar/policy/NetworkController.java
@@ -52,7 +52,7 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
String description, boolean isWide, int subId, boolean roaming) {}
default void setSubs(List<SubscriptionInfo> subs) {}
- default void setNoSims(boolean show) {}
+ default void setNoSims(boolean show, boolean simDetected) {}
default void setEthernetIndicators(IconState icon) {}
diff --git a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c217bda9..d24e51c7 100644
--- a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -58,10 +58,8 @@ import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
@@ -116,7 +114,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
// States that don't belong to a subcontroller.
private boolean mAirplaneMode = false;
- private boolean mHasNoSims;
+ private boolean mHasNoSubs;
private Locale mLocale = null;
// This list holds our ordering.
private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();
@@ -140,6 +138,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
@VisibleForTesting
ServiceState mLastServiceState;
private boolean mUserSetup;
+ private boolean mSimDetected;
/**
* Construct this controller object and register for updates.
@@ -151,7 +150,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
(WifiManager) context.getSystemService(Context.WIFI_SERVICE),
SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
new CallbackHandler(),
- new AccessPointControllerImpl(context, bgLooper),
+ new AccessPointControllerImpl(context),
new DataUsageController(context),
new SubscriptionDefaults(),
deviceProvisionedController);
@@ -363,7 +362,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
cb.setSubs(mCurrentSubscriptions);
cb.setIsAirplaneMode(new IconState(mAirplaneMode,
TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
- cb.setNoSims(mHasNoSims);
+ cb.setNoSims(mHasNoSubs, mSimDetected);
mWifiSignalController.notifyListeners(cb);
mEthernetSignalController.notifyListeners(cb);
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
@@ -498,11 +497,25 @@ public class NetworkControllerImpl extends BroadcastReceiver
@VisibleForTesting
protected void updateNoSims() {
- boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
- if (hasNoSims != mHasNoSims) {
- mHasNoSims = hasNoSims;
- mCallbackHandler.setNoSims(mHasNoSims);
+ boolean hasNoSubs = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
+ boolean simDetected = hasAnySim();
+ if (hasNoSubs != mHasNoSubs || simDetected != mSimDetected) {
+ mHasNoSubs = hasNoSubs;
+ mSimDetected = simDetected;
+ mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
+ }
+ }
+
+ private boolean hasAnySim() {
+ int simCount = mPhone.getSimCount();
+ for (int i = 0; i < simCount; i++) {
+ int state = mPhone.getSimState(i);
+ if (state != TelephonyManager.SIM_STATE_ABSENT
+ && state != TelephonyManager.SIM_STATE_UNKNOWN) {
+ return true;
+ }
}
+ return false;
}
@VisibleForTesting
@@ -631,7 +644,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
private void notifyListeners() {
mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
- mCallbackHandler.setNoSims(mHasNoSims);
+ mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
}
/**
@@ -804,6 +817,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
} else {
mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
}
+ String ssid = args.getString("ssid");
+ if (ssid != null) {
+ mDemoWifiState.ssid = ssid;
+ }
mDemoWifiState.enabled = show;
mWifiSignalController.notifyListeners();
}
@@ -822,8 +839,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
}
String nosim = args.getString("nosim");
if (nosim != null) {
- mHasNoSims = nosim.equals("show");
- mCallbackHandler.setNoSims(mHasNoSims);
+ mHasNoSubs = nosim.equals("show");
+ mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
}
String mobile = args.getString("mobile");
if (mobile != null) {
diff --git a/com/android/systemui/util/Utils.java b/com/android/systemui/util/Utils.java
index f4aebae7..eca61277 100644
--- a/com/android/systemui/util/Utils.java
+++ b/com/android/systemui/util/Utils.java
@@ -14,6 +14,11 @@
package com.android.systemui.util;
+import android.view.View;
+
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.statusbar.CommandQueue;
+
import java.util.List;
import java.util.function.Consumer;
@@ -28,4 +33,52 @@ public class Utils {
c.accept(list.get(i));
}
}
+
+ /**
+ * Sets the visibility of an UI element according to the DISABLE_* flags in
+ * {@link android.app.StatusBarManager}.
+ */
+ public static class DisableStateTracker implements CommandQueue.Callbacks,
+ View.OnAttachStateChangeListener {
+ private final int mMask1;
+ private final int mMask2;
+ private View mView;
+ private boolean mDisabled;
+
+ public DisableStateTracker(int disableMask, int disable2Mask) {
+ mMask1 = disableMask;
+ mMask2 = disable2Mask;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mView = v;
+ SysUiServiceProvider.getComponent(v.getContext(), CommandQueue.class)
+ .addCallbacks(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ SysUiServiceProvider.getComponent(mView.getContext(), CommandQueue.class)
+ .removeCallbacks(this);
+ mView = null;
+ }
+
+ /**
+ * Sets visibility of this {@link View} given the states passed from
+ * {@link com.android.systemui.statusbar.CommandQueue.Callbacks#disable(int, int)}.
+ */
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ final boolean disabled = ((state1 & mMask1) != 0) || ((state2 & mMask2) != 0);
+ if (disabled == mDisabled) return;
+ mDisabled = disabled;
+ mView.setVisibility(disabled ? View.GONE : View.VISIBLE);
+ }
+
+ /** @return {@code true} if and only if this {@link View} is currently disabled */
+ public boolean isDisabled() {
+ return mDisabled;
+ }
+ }
}
diff --git a/com/android/systemui/volume/ZenModePanel.java b/com/android/systemui/volume/ZenModePanel.java
index a3aca6e1..7bb987ca 100644
--- a/com/android/systemui/volume/ZenModePanel.java
+++ b/com/android/systemui/volume/ZenModePanel.java
@@ -524,18 +524,17 @@ public class ZenModePanel extends FrameLayout {
bindGenericCountdown();
bindNextAlarm(getTimeUntilNextAlarmCondition());
} else if (isForever(c)) {
+
getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
bindGenericCountdown();
bindNextAlarm(getTimeUntilNextAlarmCondition());
} else {
if (isAlarm(c)) {
bindGenericCountdown();
-
bindNextAlarm(c);
getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true);
} else if (isCountdown(c)) {
bindNextAlarm(getTimeUntilNextAlarmCondition());
-
bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
COUNTDOWN_CONDITION_INDEX);
getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
@@ -568,8 +567,8 @@ public class ZenModePanel extends FrameLayout {
tag = (ConditionTag) alarmContent.getTag();
boolean showAlarm = tag != null && tag.condition != null;
mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(
- showAlarm ? View.VISIBLE : View.GONE);
- alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.GONE);
+ showAlarm ? View.VISIBLE : View.INVISIBLE);
+ alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.INVISIBLE);
}
private Condition forever() {
diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 7c9aeded..3d5476d0 100644
--- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,24 +16,55 @@
package com.android.uiautomator.testrunner;
-import android.app.Instrumentation;
+import android.content.Context;
import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
-import android.test.InstrumentationTestCase;
+import android.view.inputmethod.InputMethodInfo;
-import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
+import com.android.internal.view.IInputMethodManager;
import com.android.uiautomator.core.UiDevice;
+import junit.framework.TestCase;
+
+import java.util.List;
+
/**
- * UI Automator test case that is executed on the device.
+ * UI automation test should extend this class. This class provides access
+ * to the following:
+ * {@link UiDevice} instance
+ * {@link Bundle} for command line parameters.
+ * @since API Level 16
* @deprecated New tests should be written using UI Automator 2.0 which is available as part of the
* Android Testing Support Library.
*/
@Deprecated
-public class UiAutomatorTestCase extends InstrumentationTestCase {
+public class UiAutomatorTestCase extends TestCase {
+ private static final String DISABLE_IME = "disable_ime";
+ private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
+ private UiDevice mUiDevice;
private Bundle mParams;
private IAutomationSupport mAutomationSupport;
+ private boolean mShouldDisableIme = false;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mShouldDisableIme = "true".equals(mParams.getString(DISABLE_IME));
+ if (mShouldDisableIme) {
+ setDummyIme();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mShouldDisableIme) {
+ restoreActiveIme();
+ }
+ super.tearDown();
+ }
/**
* Get current instance of {@link UiDevice}. Works similar to calling the static
@@ -41,7 +72,7 @@ public class UiAutomatorTestCase extends InstrumentationTestCase {
* @since API Level 16
*/
public UiDevice getUiDevice() {
- return UiDevice.getInstance();
+ return mUiDevice;
}
/**
@@ -54,43 +85,34 @@ public class UiAutomatorTestCase extends InstrumentationTestCase {
return mParams;
}
- void setAutomationSupport(IAutomationSupport automationSupport) {
- mAutomationSupport = automationSupport;
- }
-
/**
* Provides support for running tests to report interim status
*
* @return IAutomationSupport
* @since API Level 16
- * @deprecated Use {@link Instrumentation#sendStatus(int, Bundle)} instead
*/
public IAutomationSupport getAutomationSupport() {
- if (mAutomationSupport == null) {
- mAutomationSupport = new InstrumentationAutomationSupport(getInstrumentation());
- }
return mAutomationSupport;
}
/**
- * Initializes this test case.
- *
- * @param params Instrumentation arguments.
+ * package private
+ * @param uiDevice
*/
- void initialize(Bundle params) {
- mParams = params;
+ void setUiDevice(UiDevice uiDevice) {
+ mUiDevice = uiDevice;
+ }
- // check if this is a monkey test mode
- String monkeyVal = mParams.getString("monkey");
- if (monkeyVal != null) {
- // only if the monkey key is specified, we alter the state of monkey
- // else we should leave things as they are.
- getInstrumentation().getUiAutomation().setRunAsMonkey(Boolean.valueOf(monkeyVal));
- }
+ /**
+ * package private
+ * @param params
+ */
+ void setParams(Bundle params) {
+ mParams = params;
+ }
- UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge(
- getInstrumentation().getContext(),
- getInstrumentation().getUiAutomation()));
+ void setAutomationSupport(IAutomationSupport automationSupport) {
+ mAutomationSupport = automationSupport;
}
/**
@@ -101,4 +123,28 @@ public class UiAutomatorTestCase extends InstrumentationTestCase {
public void sleep(long ms) {
SystemClock.sleep(ms);
}
+
+ private void setDummyIme() throws RemoteException {
+ IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager
+ .getService(Context.INPUT_METHOD_SERVICE));
+ List<InputMethodInfo> infos = im.getInputMethodList();
+ String id = null;
+ for (InputMethodInfo info : infos) {
+ if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) {
+ id = info.getId();
+ }
+ }
+ if (id == null) {
+ throw new RuntimeException(String.format(
+ "Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE));
+ }
+ im.setInputMethod(null, id);
+ }
+
+ private void restoreActiveIme() throws RemoteException {
+ // TODO: figure out a way to restore active IME
+ // Currently retrieving active IME requires querying secure settings provider, which is hard
+ // to do without a Context; so the caveat here is that to make the post test device usable,
+ // the active IME needs to be manually switched.
+ }
}
diff --git a/com/android/webview/nullwebview/NullWebViewFactoryProvider.java b/com/android/webview/nullwebview/NullWebViewFactoryProvider.java
deleted file mode 100644
index ed12446d..00000000
--- a/com/android/webview/nullwebview/NullWebViewFactoryProvider.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.webview.nullwebview;
-
-import android.content.Context;
-import android.webkit.CookieManager;
-import android.webkit.GeolocationPermissions;
-import android.webkit.ServiceWorkerController;
-import android.webkit.TokenBindingService;
-import android.webkit.WebIconDatabase;
-import android.webkit.WebStorage;
-import android.webkit.WebView;
-import android.webkit.WebViewDatabase;
-import android.webkit.WebViewDelegate;
-import android.webkit.WebViewFactoryProvider;
-import android.webkit.WebViewProvider;
-
-public class NullWebViewFactoryProvider implements WebViewFactoryProvider {
-
- public static WebViewFactoryProvider create(WebViewDelegate delegate) {
- return new NullWebViewFactoryProvider(delegate);
- }
-
- public NullWebViewFactoryProvider(WebViewDelegate delegate) {
- }
-
- @Override
- public WebViewFactoryProvider.Statics getStatics() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public GeolocationPermissions getGeolocationPermissions() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public CookieManager getCookieManager() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public TokenBindingService getTokenBindingService() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public ServiceWorkerController getServiceWorkerController() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public WebIconDatabase getWebIconDatabase() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public WebStorage getWebStorage() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public WebViewDatabase getWebViewDatabase(Context context) {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/foo/bar/ComplexDao.java b/foo/bar/ComplexDao.java
index ca901632..89859e50 100644
--- a/foo/bar/ComplexDao.java
+++ b/foo/bar/ComplexDao.java
@@ -31,6 +31,11 @@ abstract class ComplexDao {
mDb = db;
}
+ @Transaction
+ public boolean transactionMethod(int i, String s, long l) {
+ return true;
+ }
+
@Query("SELECT name || lastName as fullName, uid as id FROM user where uid = :id")
abstract public List<FullName> fullNames(int id);
diff --git a/foo/bar/MultiPKeyEntity.java b/foo/bar/MultiPKeyEntity.java
index dccb9dd9..59570adb 100644
--- a/foo/bar/MultiPKeyEntity.java
+++ b/foo/bar/MultiPKeyEntity.java
@@ -15,9 +15,12 @@
*/
package foo.bar;
+import android.support.annotation.NonNull;
import android.arch.persistence.room.*;
@Entity(primaryKeys = {"name", "lastName"})
public class MultiPKeyEntity {
+ @NonNull
String name;
+ @NonNull
String lastName;
}
diff --git a/java/io/File.java b/java/io/File.java
index 0d2fbb95..98956caf 100644
--- a/java/io/File.java
+++ b/java/io/File.java
@@ -37,6 +37,7 @@ import java.nio.file.Path;
import java.nio.file.FileSystems;
import sun.security.action.GetPropertyAction;
+// Android-added: Info about UTF-8 usage in filenames.
/**
* An abstract representation of file and directory pathnames.
*
@@ -321,9 +322,11 @@ public class File
if (child == null) {
throw new NullPointerException();
}
+ // BEGIN Android-changed: b/25859957, app-compat; don't substitute empty parent.
if (parent != null && !parent.isEmpty()) {
this.path = fs.resolve(fs.normalize(parent),
fs.normalize(child));
+ // END Android-changed: b/25859957, app-compat; don't substitute empty parent.
} else {
this.path = fs.normalize(child);
}
@@ -515,6 +518,7 @@ public class File
/* -- Path operations -- */
+ // Android-changed: Android-specific path information
/**
* Tests whether this abstract pathname is absolute. The definition of
* absolute pathname is system dependent. On Android, absolute paths start with
@@ -527,6 +531,7 @@ public class File
return fs.isAbsolute(this);
}
+ // Android-changed: Android-specific path information
/**
* Returns the absolute path of this file. An absolute path is a path that starts at a root
* of the file system. On Android, there is only one root: {@code /}.
@@ -671,6 +676,8 @@ public class File
if (isInvalid()) {
throw new MalformedURLException("Invalid file path");
}
+ // Android-changed: Fix for new File("").toURL().
+ // return new URL("file", "", slashify(getAbsolutePath(), isDirectory()));
return new URL("file", "", slashify(getAbsolutePath(),
getAbsoluteFile().isDirectory()));
}
@@ -804,6 +811,7 @@ public class File
return false;
}
+ // Android-changed: b/25878034 work around SELinux stat64 denial.
return fs.checkAccess(this, FileSystem.ACCESS_OK);
}
@@ -1029,6 +1037,7 @@ public class File
return fs.delete(this);
}
+ // Android-added: Additional information about Android behaviour.
/**
* Requests that the file or directory denoted by this abstract
* pathname be deleted when the virtual machine terminates.
@@ -1355,6 +1364,7 @@ public class File
canonFile.mkdir());
}
+ // Android-changed: Replaced generic platform info with Android specific one.
/**
* Renames the file denoted by this abstract pathname.
*
@@ -1441,7 +1451,7 @@ public class File
}
// Android-changed. Removed javadoc comment about special privileges
- // that doesn't make sense on android
+ // that doesn't make sense on Android.
/**
* Marks the file or directory named by this abstract pathname so that
* only read operations are allowed. After invoking this method the file
@@ -1471,7 +1481,7 @@ public class File
}
// Android-changed. Removed javadoc comment about special privileges
- // that doesn't make sense on android
+ // that doesn't make sense on Android.
/**
* Sets the owner's or everybody's write permission for this abstract
* pathname.
@@ -1514,7 +1524,7 @@ public class File
}
// Android-changed. Removed javadoc comment about special privileges
- // that doesn't make sense on android
+ // that doesn't make sense on Android.
/**
* A convenience method to set the owner's write permission for this abstract
* pathname.
@@ -1545,7 +1555,7 @@ public class File
}
// Android-changed. Removed javadoc comment about special privileges
- // that doesn't make sense on android
+ // that doesn't make sense on Android.
/**
* Sets the owner's or everybody's read permission for this abstract
* pathname.
@@ -1591,7 +1601,7 @@ public class File
}
// Android-changed. Removed javadoc comment about special privileges
- // that doesn't make sense on android
+ // that doesn't make sense on Android.
/**
* A convenience method to set the owner's read permission for this abstract
* pathname.
@@ -1625,7 +1635,7 @@ public class File
}
// Android-changed. Removed javadoc comment about special privileges
- // that doesn't make sense on android
+ // that doesn't make sense on Android.
/**
* Sets the owner's or everybody's execute permission for this abstract
* pathname.
@@ -1671,7 +1681,7 @@ public class File
}
// Android-changed. Removed javadoc comment about special privileges
- // that doesn't make sense on android
+ // that doesn't make sense on Android.
/**
* A convenience method to set the owner's execute permission for this
* abstract pathname.
@@ -1705,7 +1715,7 @@ public class File
}
// Android-changed. Removed javadoc comment about special privileges
- // that doesn't make sense on android
+ // that doesn't make sense on Android.
/**
* Tests whether the application can execute the file denoted by this
* abstract pathname.
@@ -1734,7 +1744,7 @@ public class File
/* -- Filesystem interface -- */
-
+ // Android-changed: Replaced generic platform info with Android specific one.
/**
* Returns the file system roots. On Android and other Unix systems, there is
* a single root, {@code /}.
@@ -1811,6 +1821,7 @@ public class File
return fs.getSpace(this, FileSystem.SPACE_FREE);
}
+ // Android-added: Replaced generic platform info with Android specific one.
/**
* Returns the number of bytes available to this virtual machine on the
* partition <a href="#partName">named</a> by this abstract pathname. When
@@ -1858,18 +1869,22 @@ public class File
}
/* -- Temporary files -- */
+
private static class TempDirectory {
private TempDirectory() { }
// Android-changed: Don't cache java.io.tmpdir value
- // temporary directory location
- // private static final File tmpdir = new File(AccessController
- // .doPrivileged(new GetPropertyAction("java.io.tmpdir")));
- // static File location() {
- // return tmpdir;
- // }
+ // temporary directory location.
+ /*
+ private static final File tmpdir = new File(AccessController
+ .doPrivileged(new GetPropertyAction("java.io.tmpdir")));
+ static File location() {
+ return tmpdir;
+ }
+ */
// file name generation
+ // private static final SecureRandom random = new SecureRandom();
static File generateFile(String prefix, String suffix, File dir)
throws IOException
{
@@ -1885,7 +1900,7 @@ public class File
// Android-changed: Reject invalid file prefixes
// Use only the file name from the supplied prefix
- //prefix = (new File(prefix)).getName();
+ // prefix = (new File(prefix)).getName();
String name = prefix + Long.toString(n) + suffix;
File f = new File(dir, name);
@@ -1977,7 +1992,7 @@ public class File
if (suffix == null)
suffix = ".tmp";
-
+ // Android-changed: Handle java.io.tmpdir changes.
File tmpdir = (directory != null) ? directory
: new File(System.getProperty("java.io.tmpdir", "."));
//SecurityManager sm = System.getSecurityManager();
@@ -1985,17 +2000,19 @@ public class File
do {
f = TempDirectory.generateFile(prefix, suffix, tmpdir);
- // Android change: sm is always null on android
- // if (sm != null) {
- // try {
- // sm.checkWrite(f.getPath());
- // } catch (SecurityException se) {
- // // don't reveal temporary directory location
- // if (directory == null)
- // throw new SecurityException("Unable to create temporary file");
- // throw se;
- // }
- // }
+ // Android-changed: sm is always null on Android.
+ /*
+ if (sm != null) {
+ try {
+ sm.checkWrite(f.getPath());
+ } catch (SecurityException se) {
+ // don't reveal temporary directory location
+ if (directory == null)
+ throw new SecurityException("Unable to create temporary file");
+ throw se;
+ }
+ }
+ */
} while ((fs.getBooleanAttributes(f) & FileSystem.BA_EXISTS) != 0);
if (!fs.createFileExclusively(f.getPath()))
diff --git a/java/io/RandomAccessFile.java b/java/io/RandomAccessFile.java
index f4830c14..df607cb6 100644
--- a/java/io/RandomAccessFile.java
+++ b/java/io/RandomAccessFile.java
@@ -68,7 +68,6 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
// BEGIN Android-added: CloseGuard and some helper fields for Android changes in this file.
private final CloseGuard guard = CloseGuard.get();
private final byte[] scratch = new byte[8];
- private boolean syncMetadata = false;
private int mode;
// END Android-added: CloseGuard and some helper fields for Android changes in this file.
@@ -231,13 +230,9 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
rw = true;
if (mode.length() > 2) {
if (mode.equals("rws")) {
- // Android-changed: Don't use O_SYNC for "rws". Is this correct?
- // imode |= O_SYNC;
- syncMetadata = true;
- } else if (mode.equals("rwd")) {
- // Android-changed: Use O_SYNC rather than O_DSYNC for "rwd". Is this correct?
- // imode |= O_DSYNC;
imode |= O_SYNC;
+ } else if (mode.equals("rwd")) {
+ imode |= O_DSYNC;
} else {
imode = -1;
}
@@ -272,13 +267,6 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
// BEGIN Android-changed: Use IoBridge.open() instead of open.
fd = IoBridge.open(name, imode);
- if (syncMetadata) {
- try {
- fd.sync();
- } catch (IOException e) {
- // Ignored
- }
- }
guard.open("close");
// END Android-changed: Use IoBridge.open() instead of open.
}
@@ -515,10 +503,6 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
// Android-changed: Implement on top of low-level API, not directly natively.
ioTracker.trackIo(len, IoTracker.Mode.WRITE);
IoBridge.write(fd, b, off, len);
- // if we are in "rws" mode, attempt to sync file+metadata
- if (syncMetadata) {
- fd.sync();
- }
}
/**
@@ -643,10 +627,6 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
if (filePointer > newLength) {
seek(newLength);
}
- // if we are in "rws" mode, attempt to sync file+metadata
- if (syncMetadata) {
- fd.sync();
- }
// END Android-changed: Implement on top of low-level API, not directly natively.
}
diff --git a/java/lang/String.java b/java/lang/String.java
index eb247122..4cefed81 100644
--- a/java/lang/String.java
+++ b/java/lang/String.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,27 +23,23 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
+
package java.lang;
import dalvik.annotation.optimization.FastNative;
import java.io.ObjectStreamField;
import java.io.UnsupportedEncodingException;
-import java.lang.ArrayIndexOutOfBoundsException;
import java.nio.charset.Charset;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Comparator;
import java.util.Formatter;
import java.util.Locale;
import java.util.Objects;
import java.util.StringJoiner;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import libcore.util.CharsetUtils;
-import libcore.util.EmptyArray;
/**
* The {@code String} class represents character strings. All
@@ -117,11 +113,26 @@ import libcore.util.EmptyArray;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
- // The associated character storage is managed by the runtime. We only
- // keep track of the length here.
+ // BEGIN Android-changed: The character data is managed by the runtime.
+ // We only keep track of the length here and compression here. This has several consequences
+ // throughout this class:
+ // - References to value[i] are replaced by charAt(i).
+ // - References to value.length are replaced by calls to length().
+ // - Sometimes the result of length() is assigned to a local variable to avoid repeated calls.
+ // - We skip several attempts at optimization where the values field was assigned to a local
+ // variable to avoid the getfield opcode.
+ // These changes are not all marked individually.
//
// private final char value[];
+ //
+ // If STRING_COMPRESSION_ENABLED, count stores the length shifted one bit to the left with the
+ // lowest bit used to indicate whether or not the bytes are compressed (see GetFlaggedCount in
+ // the native code).
private final int count;
+ // END Android-changed: The character data is managed by the runtime.
+
+ // Android-changed: We make use of new StringIndexOutOfBoundsException constructor signatures.
+ // These improve some error messages. These changes are not all marked individually.
/** Cache the hash code for the string */
private int hash; // Default to 0
@@ -145,6 +156,7 @@ public final class String
* unnecessary since Strings are immutable.
*/
public String() {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -159,6 +171,7 @@ public final class String
* A {@code String}
*/
public String(String original) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -172,6 +185,7 @@ public final class String
* The initial value of the string
*/
public String(char value[]) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -197,6 +211,7 @@ public final class String
* characters outside the bounds of the {@code value} array
*/
public String(char value[], int offset, int count) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -229,6 +244,7 @@ public final class String
* @since 1.5
*/
public String(int[] codePoints, int offset, int count) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -273,6 +289,7 @@ public final class String
*/
@Deprecated
public String(byte ascii[], int hibyte, int offset, int count) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -308,6 +325,7 @@ public final class String
*/
@Deprecated
public String(byte ascii[], int hibyte) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -346,6 +364,7 @@ public final class String
*/
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -380,6 +399,7 @@ public final class String
* @since 1.6
*/
public String(byte bytes[], int offset, int length, Charset charset) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -408,6 +428,7 @@ public final class String
*/
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -432,6 +453,7 @@ public final class String
* @since 1.6
*/
public String(byte bytes[], Charset charset) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -462,6 +484,7 @@ public final class String
* @since JDK1.1
*/
public String(byte bytes[], int offset, int length) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -482,6 +505,7 @@ public final class String
* @since JDK1.1
*/
public String(byte bytes[]) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -495,6 +519,7 @@ public final class String
* A {@code StringBuffer}
*/
public String(StringBuffer buffer) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
@@ -514,10 +539,12 @@ public final class String
* @since 1.5
*/
public String(StringBuilder builder) {
+ // Android-changed: Constructor unsupported as all calls are intercepted by the runtime.
throw new UnsupportedOperationException("Use StringFactory instead.");
}
+ // BEGIN Android-changed: Deprecated & unsupported as all calls are intercepted by the runtime.
/**
* Package private constructor
*
@@ -527,6 +554,7 @@ public final class String
String(int offset, int count, char[] value) {
throw new UnsupportedOperationException("Use StringFactory instead.");
}
+ // END Android-changed: Deprecated & unsupported as all calls are intercepted by the runtime.
/**
* Returns the length of this string.
@@ -537,6 +565,8 @@ public final class String
* object.
*/
public int length() {
+ // BEGIN Android-changed: Get length from count field rather than value array (see above).
+ // return value.length;
final boolean STRING_COMPRESSION_ENABLED = true;
if (STRING_COMPRESSION_ENABLED) {
// For the compression purposes (save the characters as 8-bit if all characters
@@ -545,6 +575,7 @@ public final class String
} else {
return count;
}
+ // END Android-changed: Get length from count field rather than value array (see above).
}
/**
@@ -556,7 +587,9 @@ public final class String
* @since 1.6
*/
public boolean isEmpty() {
+ // Android-changed: Get length from count field rather than value array (see above).
// Empty string has {@code count == 0} with or without string compression enabled.
+ // return value.length == 0;
return count == 0;
}
@@ -578,8 +611,10 @@ public final class String
* argument is negative or not less than the length of this
* string.
*/
+ // BEGIN Android-changed: Replace with implementation in runtime to access chars (see above).
@FastNative
public native char charAt(int index);
+ // END Android-changed: Replace with implementation in runtime to access chars (see above).
/**
* Returns the character (Unicode code point) at the specified
@@ -593,7 +628,7 @@ public final class String
* {@code char} value at the following index is in the
* low-surrogate range, then the supplementary code point
* corresponding to this surrogate pair is returned. Otherwise,
- * the {@code char} value at the given index is returned
+ * the {@code char} value at the given index is returned.
*
* @param index the index to the {@code char} values
* @return the code point value of the character at the
@@ -607,6 +642,7 @@ public final class String
if ((index < 0) || (index >= length())) {
throw new StringIndexOutOfBoundsException(index);
}
+ // Android-changed: Skip codePointAtImpl optimization that needs access to java chars.
return Character.codePointAt(this, index);
}
@@ -637,6 +673,7 @@ public final class String
if ((i < 0) || (i >= length())) {
throw new StringIndexOutOfBoundsException(index);
}
+ // Android-changed: Skip codePointBeforeImpl optimization that needs access to java chars.
return Character.codePointBefore(this, index);
}
@@ -665,6 +702,7 @@ public final class String
if (beginIndex < 0 || endIndex > length() || beginIndex > endIndex) {
throw new IndexOutOfBoundsException();
}
+ // Android-changed: Skip codePointCountImpl optimization that needs access to java chars.
return Character.codePointCount(this, beginIndex, endIndex);
}
@@ -692,6 +730,7 @@ public final class String
if (index < 0 || index > length()) {
throw new IndexOutOfBoundsException();
}
+ // Android-changed: Skip offsetByCodePointsImpl optimization that needs access to java chars
return Character.offsetByCodePoints(this, index, codePointOffset);
}
@@ -700,6 +739,7 @@ public final class String
* This method doesn't perform any range checking.
*/
void getChars(char dst[], int dstBegin) {
+ // Android-changed: Replace arraycopy with native call since chars are managed by runtime.
getCharsNoCheck(0, length(), dst, dstBegin);
}
@@ -734,6 +774,7 @@ public final class String
* {@code dst.length}</ul>
*/
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
+ // BEGIN Android-changed: Implement in terms of length() and native getCharsNoCheck method.
if (dst == null) {
throw new NullPointerException("dst == null");
}
@@ -766,8 +807,10 @@ public final class String
}
getCharsNoCheck(srcBegin, srcEnd, dst, dstBegin);
+ // END Android-changed: Implement in terms of length() and native getCharsNoCheck method.
}
+ // BEGIN Android-added: Native method to access char storage managed by runtime.
/**
* getChars without bounds checks, for use by other classes
* within the java.lang package only. The caller is responsible for
@@ -775,7 +818,7 @@ public final class String
*/
@FastNative
native void getCharsNoCheck(int start, int end, char[] buffer, int index);
-
+ // END Android-added: Native method to access char storage managed by runtime.
/**
* Copies characters from this string into the destination byte array. Each
@@ -829,7 +872,7 @@ public final class String
throw new StringIndexOutOfBoundsException(this, srcEnd);
}
if (srcBegin > srcEnd) {
- throw new StringIndexOutOfBoundsException(this, (srcEnd - srcBegin));
+ throw new StringIndexOutOfBoundsException(this, srcEnd - srcBegin);
}
int j = dstBegin;
@@ -864,6 +907,8 @@ public final class String
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
+ // Android-changed: Skip StringCoding optimization that needs access to java chars.
+ // return StringCoding.encode(charsetName, value, 0, value.length);
return getBytes(Charset.forNameUEE(charsetName));
}
@@ -886,6 +931,9 @@ public final class String
* @since 1.6
*/
public byte[] getBytes(Charset charset) {
+ // BEGIN Android-changed: Skip StringCoding optimization that needs access to java chars.
+ // if (charset == null) throw new NullPointerException();
+ // return StringCoding.encode(charset, value, 0, value.length);
if (charset == null) {
throw new NullPointerException("charset == null");
}
@@ -906,6 +954,7 @@ public final class String
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
return bytes;
+ // END Android-changed: Skip StringCoding optimization that needs access to java chars.
}
/**
@@ -922,6 +971,8 @@ public final class String
* @since JDK1.1
*/
public byte[] getBytes() {
+ // Android-changed: Skip StringCoding optimization that needs access to java chars.
+ // return StringCoding.encode(value, 0, value.length);
return getBytes(Charset.defaultCharset());
}
@@ -945,7 +996,7 @@ public final class String
return true;
}
if (anObject instanceof String) {
- String anotherString = (String) anObject;
+ String anotherString = (String)anObject;
int n = length();
if (n == anotherString.length()) {
int i = 0;
@@ -1114,8 +1165,10 @@ public final class String
* value greater than {@code 0} if this string is
* lexicographically greater than the string argument.
*/
+ // BEGIN Android-changed: Replace with implementation in runtime to access chars (see above).
@FastNative
public native int compareTo(String anotherString);
+ // END Android-changed: Replace with implementation in runtime to access chars (see above).
/**
* A Comparator that orders {@code String} objects as by
@@ -1506,8 +1559,11 @@ public final class String
}
}
+ // BEGIN Android-added: Native method to access char storage managed by runtime.
+ // TODO(b/67411061): This seems to be unused, see whether we can remove it.
@FastNative
private native int fastIndexOf(int c, int start);
+ // END Android-added: Native method to access char storage managed by runtime.
/**
* Handles (rare) calls of indexOf with a supplementary character.
@@ -1655,6 +1711,7 @@ public final class String
* or {@code -1} if there is no such occurrence.
*/
public int indexOf(String str, int fromIndex) {
+ // Android-changed: Change parameters to static indexOf to match new signature below.
return indexOf(this, str, fromIndex);
}
@@ -1667,6 +1724,8 @@ public final class String
* @param target the characters being searched for.
* @param fromIndex the index to begin searching from.
*/
+ // BEGIN Android-changed: Change signature to take String object rather than char arrays.
+ // The implementation using a java char array is replaced with one using length() & charAt().
static int indexOf(String source,
String target,
int fromIndex) {
@@ -1706,6 +1765,7 @@ public final class String
}
return -1;
}
+ // END Android-changed: Change signature to take String object rather than char arrays.
/**
* Code shared by String and StringBuffer to do searches. The
@@ -1794,6 +1854,7 @@ public final class String
* or {@code -1} if there is no such occurrence.
*/
public int lastIndexOf(String str, int fromIndex) {
+ // Android-changed: Change parameters to static lastIndexOf to match new signature below.
return lastIndexOf(this, str, fromIndex);
}
@@ -1806,6 +1867,8 @@ public final class String
* @param target the characters being searched for.
* @param fromIndex the index to begin searching from.
*/
+ // BEGIN Android-changed: Change signature to take String object rather than char arrays.
+ // The implementation using a java char array is replaced with one using length() & charAt().
static int lastIndexOf(String source,
String target,
int fromIndex) {
@@ -1853,6 +1916,7 @@ public final class String
return start + 1;
}
}
+ // END Android-changed: Change signature to take String object rather than char arrays.
/**
* Code shared by String and StringBuffer to do searches. The
@@ -1938,6 +2002,7 @@ public final class String
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(this, beginIndex);
}
+ // Android-changed: Use native fastSubstring instead of String constructor.
return (beginIndex == 0) ? this : fastSubstring(beginIndex, subLen);
}
@@ -1975,12 +2040,15 @@ public final class String
throw new StringIndexOutOfBoundsException(subLen);
}
+ // Android-changed: Use native fastSubstring instead of String constructor.
return ((beginIndex == 0) && (endIndex == length())) ? this
: fastSubstring(beginIndex, subLen);
}
+ // BEGIN Android-added: Native method to access char storage managed by runtime.
@FastNative
private native String fastSubstring(int start, int length);
+ // END Android-added: Native method to access char storage managed by runtime.
/**
* Returns a character sequence that is a subsequence of this sequence.
@@ -2035,8 +2103,10 @@ public final class String
* @return a string that represents the concatenation of this object's
* characters followed by the string argument's characters.
*/
+ // BEGIN Android-changed: Replace with implementation in runtime to access chars (see above).
@FastNative
public native String concat(String str);
+ // END Android-changed: Replace with implementation in runtime to access chars (see above).
/**
* Returns a string resulting from replacing all occurrences of
@@ -2068,6 +2138,7 @@ public final class String
* occurrence of {@code oldChar} with {@code newChar}.
*/
public String replace(char oldChar, char newChar) {
+ // BEGIN Android-changed: Replace with implementation using native doReplace method.
if (oldChar != newChar) {
final int len = length();
for (int i = 0; i < len; ++i) {
@@ -2076,12 +2147,15 @@ public final class String
}
}
}
+ // END Android-changed: Replace with implementation using native doReplace method.
return this;
}
+ // BEGIN Android-added: Native method to access char storage managed by runtime.
// Implementation of replace(char oldChar, char newChar) called when we found a match.
@FastNative
private native String doReplace(char oldChar, char newChar);
+ // END Android-added: Native method to access char storage managed by runtime.
/**
* Tells whether or not this string matches the given <a
@@ -2226,11 +2300,10 @@ public final class String
* @param target The sequence of char values to be replaced
* @param replacement The replacement sequence of char values
* @return The resulting string
- * @throws NullPointerException if <code>target</code> or
- * <code>replacement</code> is <code>null</code>.
* @since 1.5
*/
public String replace(CharSequence target, CharSequence replacement) {
+ // BEGIN Android-changed: Replace regex-based implementation with a bespoke one.
if (target == null) {
throw new NullPointerException("target == null");
}
@@ -2286,6 +2359,7 @@ public final class String
} else {
return this;
}
+ // END Android-changed: Replace regex-based implementation with a bespoke one.
}
/**
@@ -2375,12 +2449,13 @@ public final class String
* @spec JSR-51
*/
public String[] split(String regex, int limit) {
+ // BEGIN Android-changed: Replace custom fast-path with use of new Pattern.fastSplit method.
// Try fast splitting without allocating Pattern object
String[] fast = Pattern.fastSplit(regex, this, limit);
if (fast != null) {
return fast;
}
-
+ // END Android-changed: Replace custom fast-path with use of new Pattern.fastSplit method.
return Pattern.compile(regex).split(this, limit);
}
@@ -2563,6 +2638,7 @@ public final class String
* @since 1.1
*/
public String toLowerCase(Locale locale) {
+ // Android-changed: Replace custom code with call to new CaseMapper class.
return CaseMapper.toLowerCase(locale, this);
}
@@ -2638,6 +2714,7 @@ public final class String
* @since 1.1
*/
public String toUpperCase(Locale locale) {
+ // Android-changed: Replace custom code with call to new CaseMapper class.
return CaseMapper.toUpperCase(locale, this, length());
}
@@ -2724,8 +2801,10 @@ public final class String
* of this string and whose contents are initialized to contain
* the character sequence represented by this string.
*/
+ // BEGIN Android-changed: Replace with implementation in runtime to access chars (see above).
@FastNative
public native char[] toCharArray();
+ // END Android-changed: Replace with implementation in runtime to access chars (see above).
/**
@@ -2758,9 +2837,6 @@ public final class String
* href="../util/Formatter.html#detail">Details</a> section of the
* formatter class specification.
*
- * @throws NullPointerException
- * If the <tt>format</tt> is <tt>null</tt>
- *
* @return A formatted string
*
* @see java.util.Formatter
@@ -2802,9 +2878,6 @@ public final class String
* href="../util/Formatter.html#detail">Details</a> section of the
* formatter class specification
*
- * @throws NullPointerException
- * If the <tt>format</tt> is <tt>null</tt>
- *
* @return A formatted string
*
* @see java.util.Formatter
@@ -2838,6 +2911,8 @@ public final class String
* character array.
*/
public static String valueOf(char data[]) {
+ // Android-changed: Replace constructor call with call to new StringFactory class.
+ // return new String(data);
return StringFactory.newStringFromChars(data);
}
@@ -2862,6 +2937,8 @@ public final class String
* {@code data.length}.
*/
public static String valueOf(char data[], int offset, int count) {
+ // Android-changed: Replace constructor call with call to new StringFactory class.
+ // return new String(data, offset, count);
return StringFactory.newStringFromChars(data, offset, count);
}
@@ -2879,7 +2956,9 @@ public final class String
* {@code data.length}.
*/
public static String copyValueOf(char data[], int offset, int count) {
+ // Android-changed: Replace constructor call with call to new StringFactory class.
// All public String constructors now copy the data.
+ // return new String(data, offset, count);
return StringFactory.newStringFromChars(data, offset, count);
}
@@ -2891,6 +2970,8 @@ public final class String
* character array.
*/
public static String copyValueOf(char data[]) {
+ // Android-changed: Replace constructor call with call to new StringFactory class.
+ // return new String(data);
return StringFactory.newStringFromChars(data);
}
@@ -2915,6 +2996,9 @@ public final class String
* as its single character the argument {@code c}.
*/
public static String valueOf(char c) {
+ // Android-changed: Replace constructor call with call to new StringFactory class.
+ // char data[] = {c};
+ // return new String(data, true);
return StringFactory.newStringFromChars(0, 1, new char[] { c });
}
@@ -2997,6 +3081,8 @@ public final class String
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
+ // BEGIN Android-changed: Annotate native method as @FastNative.
@FastNative
+ // END Android-changed: Annotate native method as @FastNative.
public native String intern();
}
diff --git a/java/lang/StringCoding.java b/java/lang/StringCoding.java
deleted file mode 100644
index 54abaed4..00000000
--- a/java/lang/StringCoding.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package java.lang;
-
-import java.io.UnsupportedEncodingException;
-import java.lang.ref.SoftReference;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.CoderResult;
-import java.nio.charset.CodingErrorAction;
-import java.nio.charset.IllegalCharsetNameException;
-import java.nio.charset.UnsupportedCharsetException;
-import java.util.Arrays;
-import sun.misc.MessageUtils;
-import sun.nio.cs.HistoricallyNamedCharset;
-import sun.nio.cs.ArrayDecoder;
-import sun.nio.cs.ArrayEncoder;
-
-/**
- * Utility class for string encoding and decoding.
- */
-
-class StringCoding {
-
- private StringCoding() { }
-
- /** The cached coders for each thread */
- private final static ThreadLocal<SoftReference<StringDecoder>> decoder =
- new ThreadLocal<>();
- private final static ThreadLocal<SoftReference<StringEncoder>> encoder =
- new ThreadLocal<>();
-
- private static boolean warnUnsupportedCharset = true;
-
- private static <T> T deref(ThreadLocal<SoftReference<T>> tl) {
- SoftReference<T> sr = tl.get();
- if (sr == null)
- return null;
- return sr.get();
- }
-
- private static <T> void set(ThreadLocal<SoftReference<T>> tl, T ob) {
- tl.set(new SoftReference<T>(ob));
- }
-
- // Trim the given byte array to the given length
- //
- private static byte[] safeTrim(byte[] ba, int len, Charset cs, boolean isTrusted) {
-
- // Android-changed: System.getSecurityManager() == null is always true on Android.
- // Libcore tests expect a defensive copy in pretty much all cases.
- // if (len == ba.length && (isTrusted || System.getSecurityManager() == null))
- if (len == ba.length && (isTrusted))
- return ba;
- else
- return Arrays.copyOf(ba, len);
- }
-
- // Trim the given char array to the given length
- //
- private static char[] safeTrim(char[] ca, int len,
- Charset cs, boolean isTrusted) {
- // Android-changed: System.getSecurityManager() == null is always true on Android.
- // Libcore tests expect a defensive copy in pretty much all cases.
- // if (len == ca.length && (isTrusted || System.getSecurityManager() == null))
- if (len == ca.length && (isTrusted))
- return ca;
- else
- return Arrays.copyOf(ca, len);
- }
-
- private static int scale(int len, float expansionFactor) {
- // We need to perform double, not float, arithmetic; otherwise
- // we lose low order bits when len is larger than 2**24.
- return (int)(len * (double)expansionFactor);
- }
-
- private static Charset lookupCharset(String csn) {
- if (Charset.isSupported(csn)) {
- try {
- return Charset.forName(csn);
- } catch (UnsupportedCharsetException x) {
- throw new Error(x);
- }
- }
- return null;
- }
-
- private static void warnUnsupportedCharset(String csn) {
- if (warnUnsupportedCharset) {
- // Use sun.misc.MessageUtils rather than the Logging API or
- // System.err since this method may be called during VM
- // initialization before either is available.
- MessageUtils.err("WARNING: Default charset " + csn +
- " not supported, using ISO-8859-1 instead");
- warnUnsupportedCharset = false;
- }
- }
-
-
- // -- Decoding --
- private static class StringDecoder {
- private final String requestedCharsetName;
- private final Charset cs;
- private final CharsetDecoder cd;
- private final boolean isTrusted;
-
- private StringDecoder(Charset cs, String rcn) {
- this.requestedCharsetName = rcn;
- this.cs = cs;
- this.cd = cs.newDecoder()
- .onMalformedInput(CodingErrorAction.REPLACE)
- .onUnmappableCharacter(CodingErrorAction.REPLACE);
- this.isTrusted = (cs.getClass().getClassLoader() == null);
- }
-
- String charsetName() {
- if (cs instanceof HistoricallyNamedCharset)
- return ((HistoricallyNamedCharset)cs).historicalName();
- return cs.name();
- }
-
- final String requestedCharsetName() {
- return requestedCharsetName;
- }
-
- char[] decode(byte[] ba, int off, int len) {
- int en = scale(len, cd.maxCharsPerByte());
- char[] ca = new char[en];
- if (len == 0)
- return ca;
- if (cd instanceof ArrayDecoder) {
- int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca);
- return safeTrim(ca, clen, cs, isTrusted);
- } else {
- cd.reset();
- ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
- CharBuffer cb = CharBuffer.wrap(ca);
- try {
- CoderResult cr = cd.decode(bb, cb, true);
- if (!cr.isUnderflow())
- cr.throwException();
- cr = cd.flush(cb);
- if (!cr.isUnderflow())
- cr.throwException();
- } catch (CharacterCodingException x) {
- // Substitution is always enabled,
- // so this shouldn't happen
- throw new Error(x);
- }
- return safeTrim(ca, cb.position(), cs, isTrusted);
- }
- }
- }
-
- static char[] decode(String charsetName, byte[] ba, int off, int len)
- throws UnsupportedEncodingException
- {
- StringDecoder sd = deref(decoder);
- String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
- if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
- || csn.equals(sd.charsetName()))) {
- sd = null;
- try {
- Charset cs = lookupCharset(csn);
- if (cs != null)
- sd = new StringDecoder(cs, csn);
- } catch (IllegalCharsetNameException x) {}
- if (sd == null)
- throw new UnsupportedEncodingException(csn);
- set(decoder, sd);
- }
- return sd.decode(ba, off, len);
- }
-
- static char[] decode(Charset cs, byte[] ba, int off, int len) {
- // (1)We never cache the "external" cs, the only benefit of creating
- // an additional StringDe/Encoder object to wrap it is to share the
- // de/encode() method. These SD/E objects are short-lifed, the young-gen
- // gc should be able to take care of them well. But the best approash
- // is still not to generate them if not really necessary.
- // (2)The defensive copy of the input byte/char[] has a big performance
- // impact, as well as the outgoing result byte/char[]. Need to do the
- // optimization check of (sm==null && classLoader0==null) for both.
- // (3)getClass().getClassLoader0() is expensive
- // (4)There might be a timing gap in isTrusted setting. getClassLoader0()
- // is only chcked (and then isTrusted gets set) when (SM==null). It is
- // possible that the SM==null for now but then SM is NOT null later
- // when safeTrim() is invoked...the "safe" way to do is to redundant
- // check (... && (isTrusted || SM == null || getClassLoader0())) in trim
- // but it then can be argued that the SM is null when the opertaion
- // is started...
- CharsetDecoder cd = cs.newDecoder();
- int en = scale(len, cd.maxCharsPerByte());
- char[] ca = new char[en];
- if (len == 0)
- return ca;
- boolean isTrusted = false;
- if (System.getSecurityManager() != null) {
- if (!(isTrusted = (cs.getClass().getClassLoader() == null))) {
- ba = Arrays.copyOfRange(ba, off, off + len);
- off = 0;
- }
- }
- cd.onMalformedInput(CodingErrorAction.REPLACE)
- .onUnmappableCharacter(CodingErrorAction.REPLACE)
- .reset();
- if (cd instanceof ArrayDecoder) {
- int clen = ((ArrayDecoder)cd).decode(ba, off, len, ca);
- return safeTrim(ca, clen, cs, isTrusted);
- } else {
- ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
- CharBuffer cb = CharBuffer.wrap(ca);
- try {
- CoderResult cr = cd.decode(bb, cb, true);
- if (!cr.isUnderflow())
- cr.throwException();
- cr = cd.flush(cb);
- if (!cr.isUnderflow())
- cr.throwException();
- } catch (CharacterCodingException x) {
- // Substitution is always enabled,
- // so this shouldn't happen
- throw new Error(x);
- }
- return safeTrim(ca, cb.position(), cs, isTrusted);
- }
- }
-
- static char[] decode(byte[] ba, int off, int len) {
- String csn = Charset.defaultCharset().name();
- try {
- // use charset name decode() variant which provides caching.
- return decode(csn, ba, off, len);
- } catch (UnsupportedEncodingException x) {
- warnUnsupportedCharset(csn);
- }
- try {
- return decode("ISO-8859-1", ba, off, len);
- } catch (UnsupportedEncodingException x) {
- // If this code is hit during VM initialization, MessageUtils is
- // the only way we will be able to get any kind of error message.
- MessageUtils.err("ISO-8859-1 charset not available: "
- + x.toString());
- // If we can not find ISO-8859-1 (a required encoding) then things
- // are seriously wrong with the installation.
- System.exit(1);
- return null;
- }
- }
-
- // -- Encoding --
- private static class StringEncoder {
- private Charset cs;
- private CharsetEncoder ce;
- private final String requestedCharsetName;
- private final boolean isTrusted;
-
- private StringEncoder(Charset cs, String rcn) {
- this.requestedCharsetName = rcn;
- this.cs = cs;
- this.ce = cs.newEncoder()
- .onMalformedInput(CodingErrorAction.REPLACE)
- .onUnmappableCharacter(CodingErrorAction.REPLACE);
- this.isTrusted = (cs.getClass().getClassLoader() == null);
- }
-
- String charsetName() {
- if (cs instanceof HistoricallyNamedCharset)
- return ((HistoricallyNamedCharset)cs).historicalName();
- return cs.name();
- }
-
- final String requestedCharsetName() {
- return requestedCharsetName;
- }
-
- byte[] encode(char[] ca, int off, int len) {
- int en = scale(len, ce.maxBytesPerChar());
- byte[] ba = new byte[en];
- if (len == 0)
- return ba;
- if (ce instanceof ArrayEncoder) {
- int blen = ((ArrayEncoder)ce).encode(ca, off, len, ba);
- return safeTrim(ba, blen, cs, isTrusted);
- } else {
- ce.reset();
- ByteBuffer bb = ByteBuffer.wrap(ba);
- CharBuffer cb = CharBuffer.wrap(ca, off, len);
- try {
- // Android-changed: Pass read-only buffer, so the encoder can't alter it
- CoderResult cr = ce.encode(cb.asReadOnlyBuffer(), bb, true);
- if (!cr.isUnderflow())
- cr.throwException();
- cr = ce.flush(bb);
- if (!cr.isUnderflow())
- cr.throwException();
- } catch (CharacterCodingException x) {
- // Substitution is always enabled,
- // so this shouldn't happen
- throw new Error(x);
- }
- return safeTrim(ba, bb.position(), cs, isTrusted);
- }
- }
- }
-
- static byte[] encode(String charsetName, char[] ca, int off, int len)
- throws UnsupportedEncodingException
- {
- StringEncoder se = deref(encoder);
- String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
- if ((se == null) || !(csn.equals(se.requestedCharsetName())
- || csn.equals(se.charsetName()))) {
- se = null;
- try {
- Charset cs = lookupCharset(csn);
- if (cs != null)
- se = new StringEncoder(cs, csn);
- } catch (IllegalCharsetNameException x) {}
- if (se == null)
- throw new UnsupportedEncodingException (csn);
- set(encoder, se);
- }
- return se.encode(ca, off, len);
- }
-
- static byte[] encode(Charset cs, char[] ca, int off, int len) {
- CharsetEncoder ce = cs.newEncoder();
- int en = scale(len, ce.maxBytesPerChar());
- byte[] ba = new byte[en];
- if (len == 0)
- return ba;
- boolean isTrusted = false;
- if (System.getSecurityManager() != null) {
- if (!(isTrusted = (cs.getClass().getClassLoader() == null))) {
- ca = Arrays.copyOfRange(ca, off, off + len);
- off = 0;
- }
- }
- ce.onMalformedInput(CodingErrorAction.REPLACE)
- .onUnmappableCharacter(CodingErrorAction.REPLACE)
- .reset();
- if (ce instanceof ArrayEncoder) {
- int blen = ((ArrayEncoder)ce).encode(ca, off, len, ba);
- return safeTrim(ba, blen, cs, isTrusted);
- } else {
- ByteBuffer bb = ByteBuffer.wrap(ba);
- CharBuffer cb = CharBuffer.wrap(ca, off, len);
- try {
- // Android-changed: Pass read-only buffer, so the encoder can't alter it
- CoderResult cr = ce.encode(cb.asReadOnlyBuffer(), bb, true);
- if (!cr.isUnderflow())
- cr.throwException();
- cr = ce.flush(bb);
- if (!cr.isUnderflow())
- cr.throwException();
- } catch (CharacterCodingException x) {
- throw new Error(x);
- }
- return safeTrim(ba, bb.position(), cs, isTrusted);
- }
- }
-
- static byte[] encode(char[] ca, int off, int len) {
- String csn = Charset.defaultCharset().name();
- try {
- // use charset name encode() variant which provides caching.
- return encode(csn, ca, off, len);
- } catch (UnsupportedEncodingException x) {
- warnUnsupportedCharset(csn);
- }
- try {
- return encode("ISO-8859-1", ca, off, len);
- } catch (UnsupportedEncodingException x) {
- // If this code is hit during VM initialization, MessageUtils is
- // the only way we will be able to get any kind of error message.
- MessageUtils.err("ISO-8859-1 charset not available: "
- + x.toString());
- // If we can not find ISO-8859-1 (a required encoding) then things
- // are seriously wrong with the installation.
- System.exit(1);
- return null;
- }
- }
-}
diff --git a/java/lang/StringIndexOutOfBoundsException.java b/java/lang/StringIndexOutOfBoundsException.java
index 4fdd0613..a40bd29a 100644
--- a/java/lang/StringIndexOutOfBoundsException.java
+++ b/java/lang/StringIndexOutOfBoundsException.java
@@ -70,11 +70,12 @@ class StringIndexOutOfBoundsException extends IndexOutOfBoundsException {
super("String index out of range: " + index);
}
+ // BEGIN Android-added: Additional constructors for internal use.
/**
* Used internally for consistent high-quality error reporting.
* @hide
*/
- public StringIndexOutOfBoundsException(String s, int index) {
+ StringIndexOutOfBoundsException(String s, int index) {
this(s.length(), index);
}
@@ -82,7 +83,7 @@ class StringIndexOutOfBoundsException extends IndexOutOfBoundsException {
* Used internally for consistent high-quality error reporting.
* @hide
*/
- public StringIndexOutOfBoundsException(int sourceLength, int index) {
+ StringIndexOutOfBoundsException(int sourceLength, int index) {
super("length=" + sourceLength + "; index=" + index);
}
@@ -90,7 +91,7 @@ class StringIndexOutOfBoundsException extends IndexOutOfBoundsException {
* Used internally for consistent high-quality error reporting.
* @hide
*/
- public StringIndexOutOfBoundsException(String s, int offset, int count) {
+ StringIndexOutOfBoundsException(String s, int offset, int count) {
this(s.length(), offset, count);
}
@@ -98,9 +99,10 @@ class StringIndexOutOfBoundsException extends IndexOutOfBoundsException {
* Used internally for consistent high-quality error reporting.
* @hide
*/
- public StringIndexOutOfBoundsException(int sourceLength, int offset,
+ StringIndexOutOfBoundsException(int sourceLength, int offset,
int count) {
super("length=" + sourceLength + "; regionStart=" + offset
+ "; regionLength=" + count);
}
+ // END Android-added: Additional constructors for internal use.
}
diff --git a/java/lang/invoke/CallSite.java b/java/lang/invoke/CallSite.java
index 1ff1eb84..85b4bb9f 100644
--- a/java/lang/invoke/CallSite.java
+++ b/java/lang/invoke/CallSite.java
@@ -25,15 +25,363 @@
package java.lang.invoke;
+// Android-changed: Not using Empty
+//import sun.invoke.empty.Empty;
+import static java.lang.invoke.MethodHandleStatics.*;
+import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
+
+/**
+ * A {@code CallSite} is a holder for a variable {@link MethodHandle},
+ * which is called its {@code target}.
+ * An {@code invokedynamic} instruction linked to a {@code CallSite} delegates
+ * all calls to the site's current target.
+ * A {@code CallSite} may be associated with several {@code invokedynamic}
+ * instructions, or it may be "free floating", associated with none.
+ * In any case, it may be invoked through an associated method handle
+ * called its {@linkplain #dynamicInvoker dynamic invoker}.
+ * <p>
+ * {@code CallSite} is an abstract class which does not allow
+ * direct subclassing by users. It has three immediate,
+ * concrete subclasses that may be either instantiated or subclassed.
+ * <ul>
+ * <li>If a mutable target is not required, an {@code invokedynamic} instruction
+ * may be permanently bound by means of a {@linkplain ConstantCallSite constant call site}.
+ * <li>If a mutable target is required which has volatile variable semantics,
+ * because updates to the target must be immediately and reliably witnessed by other threads,
+ * a {@linkplain VolatileCallSite volatile call site} may be used.
+ * <li>Otherwise, if a mutable target is required,
+ * a {@linkplain MutableCallSite mutable call site} may be used.
+ * </ul>
+ * <p>
+ * A non-constant call site may be <em>relinked</em> by changing its target.
+ * The new target must have the same {@linkplain MethodHandle#type() type}
+ * as the previous target.
+ * Thus, though a call site can be relinked to a series of
+ * successive targets, it cannot change its type.
+ * <p>
+ * Here is a sample use of call sites and bootstrap methods which links every
+ * dynamic call site to print its arguments:
+<blockquote><pre>{@code
+static void test() throws Throwable {
+ // THE FOLLOWING LINE IS PSEUDOCODE FOR A JVM INSTRUCTION
+ InvokeDynamic[#bootstrapDynamic].baz("baz arg", 2, 3.14);
+}
+private static void printArgs(Object... args) {
+ System.out.println(java.util.Arrays.deepToString(args));
+}
+private static final MethodHandle printArgs;
+static {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ Class thisClass = lookup.lookupClass(); // (who am I?)
+ printArgs = lookup.findStatic(thisClass,
+ "printArgs", MethodType.methodType(void.class, Object[].class));
+}
+private static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
+ // ignore caller and name, but match the type:
+ return new ConstantCallSite(printArgs.asType(type));
+}
+}</pre></blockquote>
+ * @author John Rose, JSR 292 EG
+ */
abstract
public class CallSite {
+ // Android-changed: not used.
+ // static { MethodHandleImpl.initStatics(); }
+
+ // The actual payload of this call site:
+ /*package-private*/
+ MethodHandle target; // Note: This field is known to the JVM. Do not change.
+
+ /**
+ * Make a blank call site object with the given method type.
+ * An initial target method is supplied which will throw
+ * an {@link IllegalStateException} if called.
+ * <p>
+ * Before this {@code CallSite} object is returned from a bootstrap method,
+ * it is usually provided with a more useful target method,
+ * via a call to {@link CallSite#setTarget(MethodHandle) setTarget}.
+ * @throws NullPointerException if the proposed type is null
+ */
+ /*package-private*/
+ CallSite(MethodType type) {
+ // Android-changed: No cache for these so create uninitializedCallSite target here using
+ // method handle transformations to create a method handle that has the expected method
+ // type but throws an IllegalStateException.
+ // target = makeUninitializedCallSite(type);
+ this.target = MethodHandles.throwException(type.returnType(), IllegalStateException.class);
+ this.target = MethodHandles.insertArguments(
+ this.target, 0, new IllegalStateException("uninitialized call site"));
+ if (type.parameterCount() > 0) {
+ this.target = MethodHandles.dropArguments(this.target, 0, type.ptypes());
+ }
+
+ // Android-changed: Using initializer method for GET_TARGET
+ // rather than complex static initializer.
+ initializeGetTarget();
+ }
+
+ /**
+ * Make a call site object equipped with an initial target method handle.
+ * @param target the method handle which will be the initial target of the call site
+ * @throws NullPointerException if the proposed target is null
+ */
+ /*package-private*/
+ CallSite(MethodHandle target) {
+ target.type(); // null check
+ this.target = target;
+
+ // Android-changed: Using initializer method for GET_TARGET
+ // rather than complex static initializer.
+ initializeGetTarget();
+ }
- public MethodType type() { return null; }
+ /**
+ * Make a call site object equipped with an initial target method handle.
+ * @param targetType the desired type of the call site
+ * @param createTargetHook a hook which will bind the call site to the target method handle
+ * @throws WrongMethodTypeException if the hook cannot be invoked on the required arguments,
+ * or if the target returned by the hook is not of the given {@code targetType}
+ * @throws NullPointerException if the hook returns a null value
+ * @throws ClassCastException if the hook returns something other than a {@code MethodHandle}
+ * @throws Throwable anything else thrown by the hook function
+ */
+ /*package-private*/
+ CallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable {
+ this(targetType);
+ ConstantCallSite selfCCS = (ConstantCallSite) this;
+ MethodHandle boundTarget = (MethodHandle) createTargetHook.invokeWithArguments(selfCCS);
+ checkTargetChange(this.target, boundTarget);
+ this.target = boundTarget;
+ // Android-changed: Using initializer method for GET_TARGET
+ // rather than complex static initializer.
+ initializeGetTarget();
+ }
+
+ /**
+ * Returns the type of this call site's target.
+ * Although targets may change, any call site's type is permanent, and can never change to an unequal type.
+ * The {@code setTarget} method enforces this invariant by refusing any new target that does
+ * not have the previous target's type.
+ * @return the type of the current target, which is also the type of any future target
+ */
+ public MethodType type() {
+ // warning: do not call getTarget here, because CCS.getTarget can throw IllegalStateException
+ return target.type();
+ }
+
+ /**
+ * Returns the target method of the call site, according to the
+ * behavior defined by this call site's specific class.
+ * The immediate subclasses of {@code CallSite} document the
+ * class-specific behaviors of this method.
+ *
+ * @return the current linkage state of the call site, its target method handle
+ * @see ConstantCallSite
+ * @see VolatileCallSite
+ * @see #setTarget
+ * @see ConstantCallSite#getTarget
+ * @see MutableCallSite#getTarget
+ * @see VolatileCallSite#getTarget
+ */
public abstract MethodHandle getTarget();
+ /**
+ * Updates the target method of this call site, according to the
+ * behavior defined by this call site's specific class.
+ * The immediate subclasses of {@code CallSite} document the
+ * class-specific behaviors of this method.
+ * <p>
+ * The type of the new target must be {@linkplain MethodType#equals equal to}
+ * the type of the old target.
+ *
+ * @param newTarget the new target
+ * @throws NullPointerException if the proposed new target is null
+ * @throws WrongMethodTypeException if the proposed new target
+ * has a method type that differs from the previous target
+ * @see CallSite#getTarget
+ * @see ConstantCallSite#setTarget
+ * @see MutableCallSite#setTarget
+ * @see VolatileCallSite#setTarget
+ */
public abstract void setTarget(MethodHandle newTarget);
+ void checkTargetChange(MethodHandle oldTarget, MethodHandle newTarget) {
+ MethodType oldType = oldTarget.type();
+ MethodType newType = newTarget.type(); // null check!
+ if (!newType.equals(oldType))
+ throw wrongTargetType(newTarget, oldType);
+ }
+
+ private static WrongMethodTypeException wrongTargetType(MethodHandle target, MethodType type) {
+ return new WrongMethodTypeException(String.valueOf(target)+" should be of type "+type);
+ }
+
+ /**
+ * Produces a method handle equivalent to an invokedynamic instruction
+ * which has been linked to this call site.
+ * <p>
+ * This method is equivalent to the following code:
+ * <blockquote><pre>{@code
+ * MethodHandle getTarget, invoker, result;
+ * getTarget = MethodHandles.publicLookup().bind(this, "getTarget", MethodType.methodType(MethodHandle.class));
+ * invoker = MethodHandles.exactInvoker(this.type());
+ * result = MethodHandles.foldArguments(invoker, getTarget)
+ * }</pre></blockquote>
+ *
+ * @return a method handle which always invokes this call site's current target
+ */
public abstract MethodHandle dynamicInvoker();
+ /*non-public*/ MethodHandle makeDynamicInvoker() {
+ // Android-changed: Use bindTo() rather than bindArgumentL() (not implemented).
+ MethodHandle getTarget = GET_TARGET.bindTo(this);
+ MethodHandle invoker = MethodHandles.exactInvoker(this.type());
+ return MethodHandles.foldArguments(invoker, getTarget);
+ }
+
+ // Android-changed: no longer final. GET_TARGET assigned in initializeGetTarget().
+ private static MethodHandle GET_TARGET = null;
+
+ private void initializeGetTarget() {
+ // Android-changed: moved from static initializer for
+ // GET_TARGET to avoid issues with running early. Called from
+ // constructors. CallSite creation is not performance critical.
+ synchronized (CallSite.class) {
+ if (GET_TARGET == null) {
+ try {
+ GET_TARGET = IMPL_LOOKUP.
+ findVirtual(CallSite.class, "getTarget",
+ MethodType.methodType(MethodHandle.class));
+ } catch (ReflectiveOperationException e) {
+ throw new InternalError(e);
+ }
+ }
+ }
+ }
+
+ // Android-changed: not used.
+ // /** This guy is rolled into the default target if a MethodType is supplied to the constructor. */
+ // /*package-private*/
+ // static Empty uninitializedCallSite() {
+ // throw new IllegalStateException("uninitialized call site");
+ // }
+
+ // unsafe stuff:
+ private static final long TARGET_OFFSET;
+ static {
+ try {
+ TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target"));
+ } catch (Exception ex) { throw new Error(ex); }
+ }
+
+ /*package-private*/
+ void setTargetNormal(MethodHandle newTarget) {
+ // Android-changed: Set value directly.
+ // MethodHandleNatives.setCallSiteTargetNormal(this, newTarget);
+ target = newTarget;
+ }
+ /*package-private*/
+ MethodHandle getTargetVolatile() {
+ return (MethodHandle) UNSAFE.getObjectVolatile(this, TARGET_OFFSET);
+ }
+ /*package-private*/
+ void setTargetVolatile(MethodHandle newTarget) {
+ // Android-changed: Set value directly.
+ // MethodHandleNatives.setCallSiteTargetVolatile(this, newTarget);
+ UNSAFE.putObjectVolatile(this, TARGET_OFFSET, newTarget);
+ }
+
+ // Android-changed: not used.
+ // this implements the upcall from the JVM, MethodHandleNatives.makeDynamicCallSite:
+ // static CallSite makeSite(MethodHandle bootstrapMethod,
+ // // Callee information:
+ // String name, MethodType type,
+ // // Extra arguments for BSM, if any:
+ // Object info,
+ // // Caller information:
+ // Class<?> callerClass) {
+ // MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
+ // CallSite site;
+ // try {
+ // Object binding;
+ // info = maybeReBox(info);
+ // if (info == null) {
+ // binding = bootstrapMethod.invoke(caller, name, type);
+ // } else if (!info.getClass().isArray()) {
+ // binding = bootstrapMethod.invoke(caller, name, type, info);
+ // } else {
+ // Object[] argv = (Object[]) info;
+ // maybeReBoxElements(argv);
+ // switch (argv.length) {
+ // case 0:
+ // binding = bootstrapMethod.invoke(caller, name, type);
+ // break;
+ // case 1:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0]);
+ // break;
+ // case 2:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1]);
+ // break;
+ // case 3:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1], argv[2]);
+ // break;
+ // case 4:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1], argv[2], argv[3]);
+ // break;
+ // case 5:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1], argv[2], argv[3], argv[4]);
+ // break;
+ // case 6:
+ // binding = bootstrapMethod.invoke(caller, name, type,
+ // argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
+ // break;
+ // default:
+ // final int NON_SPREAD_ARG_COUNT = 3; // (caller, name, type)
+ // if (NON_SPREAD_ARG_COUNT + argv.length > MethodType.MAX_MH_ARITY)
+ // throw new BootstrapMethodError("too many bootstrap method arguments");
+ // MethodType bsmType = bootstrapMethod.type();
+ // MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length);
+ // MethodHandle typedBSM = bootstrapMethod.asType(invocationType);
+ // MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
+ // binding = spreader.invokeExact(typedBSM, (Object)caller, (Object)name, (Object)type, argv);
+ // }
+ // }
+ // //System.out.println("BSM for "+name+type+" => "+binding);
+ // if (binding instanceof CallSite) {
+ // site = (CallSite) binding;
+ // } else {
+ // throw new ClassCastException("bootstrap method failed to produce a CallSite");
+ // }
+ // if (!site.getTarget().type().equals(type))
+ // throw wrongTargetType(site.getTarget(), type);
+ // } catch (Throwable ex) {
+ // BootstrapMethodError bex;
+ // if (ex instanceof BootstrapMethodError)
+ // bex = (BootstrapMethodError) ex;
+ // else
+ // bex = new BootstrapMethodError("call site initialization exception", ex);
+ // throw bex;
+ // }
+ // return site;
+ // }
+
+ // private static Object maybeReBox(Object x) {
+ // if (x instanceof Integer) {
+ // int xi = (int) x;
+ // if (xi == (byte) xi)
+ // x = xi; // must rebox; see JLS 5.1.7
+ // }
+ // return x;
+ // }
+ // private static void maybeReBoxElements(Object[] xa) {
+ // for (int i = 0; i < xa.length; i++) {
+ // xa[i] = maybeReBox(xa[i]);
+ // }
+ // }
}
diff --git a/java/lang/invoke/MethodHandle.java b/java/lang/invoke/MethodHandle.java
index 159f9dd7..af3db103 100644
--- a/java/lang/invoke/MethodHandle.java
+++ b/java/lang/invoke/MethodHandle.java
@@ -25,28 +25,1353 @@
package java.lang.invoke;
+
+import dalvik.system.EmulatedStackFrame;
+
+import static java.lang.invoke.MethodHandleStatics.*;
+
+/**
+ * A method handle is a typed, directly executable reference to an underlying method,
+ * constructor, field, or similar low-level operation, with optional
+ * transformations of arguments or return values.
+ * These transformations are quite general, and include such patterns as
+ * {@linkplain #asType conversion},
+ * {@linkplain #bindTo insertion},
+ * {@linkplain java.lang.invoke.MethodHandles#dropArguments deletion},
+ * and {@linkplain java.lang.invoke.MethodHandles#filterArguments substitution}.
+ *
+ * <h1>Method handle contents</h1>
+ * Method handles are dynamically and strongly typed according to their parameter and return types.
+ * They are not distinguished by the name or the defining class of their underlying methods.
+ * A method handle must be invoked using a symbolic type descriptor which matches
+ * the method handle's own {@linkplain #type type descriptor}.
+ * <p>
+ * Every method handle reports its type descriptor via the {@link #type type} accessor.
+ * This type descriptor is a {@link java.lang.invoke.MethodType MethodType} object,
+ * whose structure is a series of classes, one of which is
+ * the return type of the method (or {@code void.class} if none).
+ * <p>
+ * A method handle's type controls the types of invocations it accepts,
+ * and the kinds of transformations that apply to it.
+ * <p>
+ * A method handle contains a pair of special invoker methods
+ * called {@link #invokeExact invokeExact} and {@link #invoke invoke}.
+ * Both invoker methods provide direct access to the method handle's
+ * underlying method, constructor, field, or other operation,
+ * as modified by transformations of arguments and return values.
+ * Both invokers accept calls which exactly match the method handle's own type.
+ * The plain, inexact invoker also accepts a range of other call types.
+ * <p>
+ * Method handles are immutable and have no visible state.
+ * Of course, they can be bound to underlying methods or data which exhibit state.
+ * With respect to the Java Memory Model, any method handle will behave
+ * as if all of its (internal) fields are final variables. This means that any method
+ * handle made visible to the application will always be fully formed.
+ * This is true even if the method handle is published through a shared
+ * variable in a data race.
+ * <p>
+ * Method handles cannot be subclassed by the user.
+ * Implementations may (or may not) create internal subclasses of {@code MethodHandle}
+ * which may be visible via the {@link java.lang.Object#getClass Object.getClass}
+ * operation. The programmer should not draw conclusions about a method handle
+ * from its specific class, as the method handle class hierarchy (if any)
+ * may change from time to time or across implementations from different vendors.
+ *
+ * <h1>Method handle compilation</h1>
+ * A Java method call expression naming {@code invokeExact} or {@code invoke}
+ * can invoke a method handle from Java source code.
+ * From the viewpoint of source code, these methods can take any arguments
+ * and their result can be cast to any return type.
+ * Formally this is accomplished by giving the invoker methods
+ * {@code Object} return types and variable arity {@code Object} arguments,
+ * but they have an additional quality called <em>signature polymorphism</em>
+ * which connects this freedom of invocation directly to the JVM execution stack.
+ * <p>
+ * As is usual with virtual methods, source-level calls to {@code invokeExact}
+ * and {@code invoke} compile to an {@code invokevirtual} instruction.
+ * More unusually, the compiler must record the actual argument types,
+ * and may not perform method invocation conversions on the arguments.
+ * Instead, it must push them on the stack according to their own unconverted types.
+ * The method handle object itself is pushed on the stack before the arguments.
+ * The compiler then calls the method handle with a symbolic type descriptor which
+ * describes the argument and return types.
+ * <p>
+ * To issue a complete symbolic type descriptor, the compiler must also determine
+ * the return type. This is based on a cast on the method invocation expression,
+ * if there is one, or else {@code Object} if the invocation is an expression
+ * or else {@code void} if the invocation is a statement.
+ * The cast may be to a primitive type (but not {@code void}).
+ * <p>
+ * As a corner case, an uncasted {@code null} argument is given
+ * a symbolic type descriptor of {@code java.lang.Void}.
+ * The ambiguity with the type {@code Void} is harmless, since there are no references of type
+ * {@code Void} except the null reference.
+ *
+ * <h1>Method handle invocation</h1>
+ * The first time a {@code invokevirtual} instruction is executed
+ * it is linked, by symbolically resolving the names in the instruction
+ * and verifying that the method call is statically legal.
+ * This is true of calls to {@code invokeExact} and {@code invoke}.
+ * In this case, the symbolic type descriptor emitted by the compiler is checked for
+ * correct syntax and names it contains are resolved.
+ * Thus, an {@code invokevirtual} instruction which invokes
+ * a method handle will always link, as long
+ * as the symbolic type descriptor is syntactically well-formed
+ * and the types exist.
+ * <p>
+ * When the {@code invokevirtual} is executed after linking,
+ * the receiving method handle's type is first checked by the JVM
+ * to ensure that it matches the symbolic type descriptor.
+ * If the type match fails, it means that the method which the
+ * caller is invoking is not present on the individual
+ * method handle being invoked.
+ * <p>
+ * In the case of {@code invokeExact}, the type descriptor of the invocation
+ * (after resolving symbolic type names) must exactly match the method type
+ * of the receiving method handle.
+ * In the case of plain, inexact {@code invoke}, the resolved type descriptor
+ * must be a valid argument to the receiver's {@link #asType asType} method.
+ * Thus, plain {@code invoke} is more permissive than {@code invokeExact}.
+ * <p>
+ * After type matching, a call to {@code invokeExact} directly
+ * and immediately invoke the method handle's underlying method
+ * (or other behavior, as the case may be).
+ * <p>
+ * A call to plain {@code invoke} works the same as a call to
+ * {@code invokeExact}, if the symbolic type descriptor specified by the caller
+ * exactly matches the method handle's own type.
+ * If there is a type mismatch, {@code invoke} attempts
+ * to adjust the type of the receiving method handle,
+ * as if by a call to {@link #asType asType},
+ * to obtain an exactly invokable method handle {@code M2}.
+ * This allows a more powerful negotiation of method type
+ * between caller and callee.
+ * <p>
+ * (<em>Note:</em> The adjusted method handle {@code M2} is not directly observable,
+ * and implementations are therefore not required to materialize it.)
+ *
+ * <h1>Invocation checking</h1>
+ * In typical programs, method handle type matching will usually succeed.
+ * But if a match fails, the JVM will throw a {@link WrongMethodTypeException},
+ * either directly (in the case of {@code invokeExact}) or indirectly as if
+ * by a failed call to {@code asType} (in the case of {@code invoke}).
+ * <p>
+ * Thus, a method type mismatch which might show up as a linkage error
+ * in a statically typed program can show up as
+ * a dynamic {@code WrongMethodTypeException}
+ * in a program which uses method handles.
+ * <p>
+ * Because method types contain "live" {@code Class} objects,
+ * method type matching takes into account both types names and class loaders.
+ * Thus, even if a method handle {@code M} is created in one
+ * class loader {@code L1} and used in another {@code L2},
+ * method handle calls are type-safe, because the caller's symbolic type
+ * descriptor, as resolved in {@code L2},
+ * is matched against the original callee method's symbolic type descriptor,
+ * as resolved in {@code L1}.
+ * The resolution in {@code L1} happens when {@code M} is created
+ * and its type is assigned, while the resolution in {@code L2} happens
+ * when the {@code invokevirtual} instruction is linked.
+ * <p>
+ * Apart from the checking of type descriptors,
+ * a method handle's capability to call its underlying method is unrestricted.
+ * If a method handle is formed on a non-public method by a class
+ * that has access to that method, the resulting handle can be used
+ * in any place by any caller who receives a reference to it.
+ * <p>
+ * Unlike with the Core Reflection API, where access is checked every time
+ * a reflective method is invoked,
+ * method handle access checking is performed
+ * <a href="MethodHandles.Lookup.html#access">when the method handle is created</a>.
+ * In the case of {@code ldc} (see below), access checking is performed as part of linking
+ * the constant pool entry underlying the constant method handle.
+ * <p>
+ * Thus, handles to non-public methods, or to methods in non-public classes,
+ * should generally be kept secret.
+ * They should not be passed to untrusted code unless their use from
+ * the untrusted code would be harmless.
+ *
+ * <h1>Method handle creation</h1>
+ * Java code can create a method handle that directly accesses
+ * any method, constructor, or field that is accessible to that code.
+ * This is done via a reflective, capability-based API called
+ * {@link java.lang.invoke.MethodHandles.Lookup MethodHandles.Lookup}
+ * For example, a static method handle can be obtained
+ * from {@link java.lang.invoke.MethodHandles.Lookup#findStatic Lookup.findStatic}.
+ * There are also conversion methods from Core Reflection API objects,
+ * such as {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
+ * <p>
+ * Like classes and strings, method handles that correspond to accessible
+ * fields, methods, and constructors can also be represented directly
+ * in a class file's constant pool as constants to be loaded by {@code ldc} bytecodes.
+ * A new type of constant pool entry, {@code CONSTANT_MethodHandle},
+ * refers directly to an associated {@code CONSTANT_Methodref},
+ * {@code CONSTANT_InterfaceMethodref}, or {@code CONSTANT_Fieldref}
+ * constant pool entry.
+ * (For full details on method handle constants,
+ * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
+ * <p>
+ * Method handles produced by lookups or constant loads from methods or
+ * constructors with the variable arity modifier bit ({@code 0x0080})
+ * have a corresponding variable arity, as if they were defined with
+ * the help of {@link #asVarargsCollector asVarargsCollector}.
+ * <p>
+ * A method reference may refer either to a static or non-static method.
+ * In the non-static case, the method handle type includes an explicit
+ * receiver argument, prepended before any other arguments.
+ * In the method handle's type, the initial receiver argument is typed
+ * according to the class under which the method was initially requested.
+ * (E.g., if a non-static method handle is obtained via {@code ldc},
+ * the type of the receiver is the class named in the constant pool entry.)
+ * <p>
+ * Method handle constants are subject to the same link-time access checks
+ * their corresponding bytecode instructions, and the {@code ldc} instruction
+ * will throw corresponding linkage errors if the bytecode behaviors would
+ * throw such errors.
+ * <p>
+ * As a corollary of this, access to protected members is restricted
+ * to receivers only of the accessing class, or one of its subclasses,
+ * and the accessing class must in turn be a subclass (or package sibling)
+ * of the protected member's defining class.
+ * If a method reference refers to a protected non-static method or field
+ * of a class outside the current package, the receiver argument will
+ * be narrowed to the type of the accessing class.
+ * <p>
+ * When a method handle to a virtual method is invoked, the method is
+ * always looked up in the receiver (that is, the first argument).
+ * <p>
+ * A non-virtual method handle to a specific virtual method implementation
+ * can also be created. These do not perform virtual lookup based on
+ * receiver type. Such a method handle simulates the effect of
+ * an {@code invokespecial} instruction to the same method.
+ *
+ * <h1>Usage examples</h1>
+ * Here are some examples of usage:
+ * <blockquote><pre>{@code
+Object x, y; String s; int i;
+MethodType mt; MethodHandle mh;
+MethodHandles.Lookup lookup = MethodHandles.lookup();
+// mt is (char,char)String
+mt = MethodType.methodType(String.class, char.class, char.class);
+mh = lookup.findVirtual(String.class, "replace", mt);
+s = (String) mh.invokeExact("daddy",'d','n');
+// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
+assertEquals(s, "nanny");
+// weakly typed invocation (using MHs.invoke)
+s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
+assertEquals(s, "savvy");
+// mt is (Object[])List
+mt = MethodType.methodType(java.util.List.class, Object[].class);
+mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
+assert(mh.isVarargsCollector());
+x = mh.invoke("one", "two");
+// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+assertEquals(x, java.util.Arrays.asList("one","two"));
+// mt is (Object,Object,Object)Object
+mt = MethodType.genericMethodType(3);
+mh = mh.asType(mt);
+x = mh.invokeExact((Object)1, (Object)2, (Object)3);
+// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+assertEquals(x, java.util.Arrays.asList(1,2,3));
+// mt is ()int
+mt = MethodType.methodType(int.class);
+mh = lookup.findVirtual(java.util.List.class, "size", mt);
+i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
+// invokeExact(Ljava/util/List;)I
+assert(i == 3);
+mt = MethodType.methodType(void.class, String.class);
+mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
+mh.invokeExact(System.out, "Hello, world.");
+// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
+ * }</pre></blockquote>
+ * Each of the above calls to {@code invokeExact} or plain {@code invoke}
+ * generates a single invokevirtual instruction with
+ * the symbolic type descriptor indicated in the following comment.
+ * In these examples, the helper method {@code assertEquals} is assumed to
+ * be a method which calls {@link java.util.Objects#equals(Object,Object) Objects.equals}
+ * on its arguments, and asserts that the result is true.
+ *
+ * <h1>Exceptions</h1>
+ * The methods {@code invokeExact} and {@code invoke} are declared
+ * to throw {@link java.lang.Throwable Throwable},
+ * which is to say that there is no static restriction on what a method handle
+ * can throw. Since the JVM does not distinguish between checked
+ * and unchecked exceptions (other than by their class, of course),
+ * there is no particular effect on bytecode shape from ascribing
+ * checked exceptions to method handle invocations. But in Java source
+ * code, methods which perform method handle calls must either explicitly
+ * throw {@code Throwable}, or else must catch all
+ * throwables locally, rethrowing only those which are legal in the context,
+ * and wrapping ones which are illegal.
+ *
+ * <h1><a name="sigpoly"></a>Signature polymorphism</h1>
+ * The unusual compilation and linkage behavior of
+ * {@code invokeExact} and plain {@code invoke}
+ * is referenced by the term <em>signature polymorphism</em>.
+ * As defined in the Java Language Specification,
+ * a signature polymorphic method is one which can operate with
+ * any of a wide range of call signatures and return types.
+ * <p>
+ * In source code, a call to a signature polymorphic method will
+ * compile, regardless of the requested symbolic type descriptor.
+ * As usual, the Java compiler emits an {@code invokevirtual}
+ * instruction with the given symbolic type descriptor against the named method.
+ * The unusual part is that the symbolic type descriptor is derived from
+ * the actual argument and return types, not from the method declaration.
+ * <p>
+ * When the JVM processes bytecode containing signature polymorphic calls,
+ * it will successfully link any such call, regardless of its symbolic type descriptor.
+ * (In order to retain type safety, the JVM will guard such calls with suitable
+ * dynamic type checks, as described elsewhere.)
+ * <p>
+ * Bytecode generators, including the compiler back end, are required to emit
+ * untransformed symbolic type descriptors for these methods.
+ * Tools which determine symbolic linkage are required to accept such
+ * untransformed descriptors, without reporting linkage errors.
+ *
+ * <h1>Interoperation between method handles and the Core Reflection API</h1>
+ * Using factory methods in the {@link java.lang.invoke.MethodHandles.Lookup Lookup} API,
+ * any class member represented by a Core Reflection API object
+ * can be converted to a behaviorally equivalent method handle.
+ * For example, a reflective {@link java.lang.reflect.Method Method} can
+ * be converted to a method handle using
+ * {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
+ * The resulting method handles generally provide more direct and efficient
+ * access to the underlying class members.
+ * <p>
+ * As a special case,
+ * when the Core Reflection API is used to view the signature polymorphic
+ * methods {@code invokeExact} or plain {@code invoke} in this class,
+ * they appear as ordinary non-polymorphic methods.
+ * Their reflective appearance, as viewed by
+ * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod},
+ * is unaffected by their special status in this API.
+ * For example, {@link java.lang.reflect.Method#getModifiers Method.getModifiers}
+ * will report exactly those modifier bits required for any similarly
+ * declared method, including in this case {@code native} and {@code varargs} bits.
+ * <p>
+ * As with any reflected method, these methods (when reflected) may be
+ * invoked via {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.
+ * However, such reflective calls do not result in method handle invocations.
+ * Such a call, if passed the required argument
+ * (a single one, of type {@code Object[]}), will ignore the argument and
+ * will throw an {@code UnsupportedOperationException}.
+ * <p>
+ * Since {@code invokevirtual} instructions can natively
+ * invoke method handles under any symbolic type descriptor, this reflective view conflicts
+ * with the normal presentation of these methods via bytecodes.
+ * Thus, these two native methods, when reflectively viewed by
+ * {@code Class.getDeclaredMethod}, may be regarded as placeholders only.
+ * <p>
+ * In order to obtain an invoker method for a particular type descriptor,
+ * use {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker},
+ * or {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}.
+ * The {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual}
+ * API is also able to return a method handle
+ * to call {@code invokeExact} or plain {@code invoke},
+ * for any specified type descriptor .
+ *
+ * <h1>Interoperation between method handles and Java generics</h1>
+ * A method handle can be obtained on a method, constructor, or field
+ * which is declared with Java generic types.
+ * As with the Core Reflection API, the type of the method handle
+ * will constructed from the erasure of the source-level type.
+ * When a method handle is invoked, the types of its arguments
+ * or the return value cast type may be generic types or type instances.
+ * If this occurs, the compiler will replace those
+ * types by their erasures when it constructs the symbolic type descriptor
+ * for the {@code invokevirtual} instruction.
+ * <p>
+ * Method handles do not represent
+ * their function-like types in terms of Java parameterized (generic) types,
+ * because there are three mismatches between function-like types and parameterized
+ * Java types.
+ * <ul>
+ * <li>Method types range over all possible arities,
+ * from no arguments to up to the <a href="MethodHandle.html#maxarity">maximum number</a> of allowed arguments.
+ * Generics are not variadic, and so cannot represent this.</li>
+ * <li>Method types can specify arguments of primitive types,
+ * which Java generic types cannot range over.</li>
+ * <li>Higher order functions over method handles (combinators) are
+ * often generic across a wide range of function types, including
+ * those of multiple arities. It is impossible to represent such
+ * genericity with a Java type parameter.</li>
+ * </ul>
+ *
+ * <h1><a name="maxarity"></a>Arity limits</h1>
+ * The JVM imposes on all methods and constructors of any kind an absolute
+ * limit of 255 stacked arguments. This limit can appear more restrictive
+ * in certain cases:
+ * <ul>
+ * <li>A {@code long} or {@code double} argument counts (for purposes of arity limits) as two argument slots.
+ * <li>A non-static method consumes an extra argument for the object on which the method is called.
+ * <li>A constructor consumes an extra argument for the object which is being constructed.
+ * <li>Since a method handle&rsquo;s {@code invoke} method (or other signature-polymorphic method) is non-virtual,
+ * it consumes an extra argument for the method handle itself, in addition to any non-virtual receiver object.
+ * </ul>
+ * These limits imply that certain method handles cannot be created, solely because of the JVM limit on stacked arguments.
+ * For example, if a static JVM method accepts exactly 255 arguments, a method handle cannot be created for it.
+ * Attempts to create method handles with impossible method types lead to an {@link IllegalArgumentException}.
+ * In particular, a method handle&rsquo;s type must not have an arity of the exact maximum 255.
+ *
+ * @see MethodType
+ * @see MethodHandles
+ * @author John Rose, JSR 292 EG
+ */
public abstract class MethodHandle {
+ // Android-changed:
+ //
+ // static { MethodHandleImpl.initStatics(); }
+ //
+ // LambdaForm and customizationCount are currently unused in our implementation
+ // and will be substituted with appropriate implementation / delegate classes.
+ //
+ // /*private*/ final LambdaForm form;
+ // form is not private so that invokers can easily fetch it
+ // /*non-public*/ byte customizationCount;
+ // customizationCount should be accessible from invokers
+
+
+ /**
+ * Internal marker interface which distinguishes (to the Java compiler)
+ * those methods which are <a href="MethodHandle.html#sigpoly">signature polymorphic</a>.
+ *
+ * @hide
+ */
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+ public @interface PolymorphicSignature { }
+
+ /**
+ * The type of this method handle, this corresponds to the exact type of the method
+ * being invoked.
+ */
+ private final MethodType type;
+
+ /**
+ * The nominal type of this method handle, will be non-null if a method handle declares
+ * a different type from its "real" type, which is either the type of the method being invoked
+ * or the type of the emulated stackframe expected by an underyling adapter.
+ */
+ private MethodType nominalType;
+
+ /**
+ * The spread invoker associated with this type with zero trailing arguments.
+ * This is used to speed up invokeWithArguments.
+ */
+ private MethodHandle cachedSpreadInvoker;
+
+ /**
+ * The INVOKE* constants and SGET/SPUT and IGET/IPUT constants specify the behaviour of this
+ * method handle with respect to the ArtField* or the ArtMethod* that it operates on. These
+ * behaviours are equivalent to the dex bytecode behaviour on the respective method_id or
+ * field_id in the equivalent instruction.
+ *
+ * INVOKE_TRANSFORM is a special type of handle which doesn't encode any dex bytecode behaviour,
+ * instead it transforms the list of input arguments or performs other higher order operations
+ * before (optionally) delegating to another method handle.
+ *
+ * INVOKE_CALLSITE_TRANSFORM is a variation on INVOKE_TRANSFORM where the method type of
+ * a MethodHandle dynamically varies based on the callsite. This is used by
+ * the VarargsCollector implementation which places any number of trailing arguments
+ * into an array before invoking an arity method. The "any number of trailing arguments" means
+ * it would otherwise generate WrongMethodTypeExceptions as the callsite method type and
+ * VarargsCollector method type appear incompatible.
+ */
+
+ /** @hide */ public static final int INVOKE_VIRTUAL = 0;
+ /** @hide */ public static final int INVOKE_SUPER = 1;
+ /** @hide */ public static final int INVOKE_DIRECT = 2;
+ /** @hide */ public static final int INVOKE_STATIC = 3;
+ /** @hide */ public static final int INVOKE_INTERFACE = 4;
+ /** @hide */ public static final int INVOKE_TRANSFORM = 5;
+ /** @hide */ public static final int INVOKE_CALLSITE_TRANSFORM = 6;
+ /** @hide */ public static final int IGET = 7;
+ /** @hide */ public static final int IPUT = 8;
+ /** @hide */ public static final int SGET = 9;
+ /** @hide */ public static final int SPUT = 10;
+
+ // The kind of this method handle (used by the runtime). This is one of the INVOKE_*
+ // constants or SGET/SPUT, IGET/IPUT.
+ /** @hide */ protected final int handleKind;
+
+ // The ArtMethod* or ArtField* associated with this method handle (used by the runtime).
+ /** @hide */ protected final long artFieldOrMethod;
+
+ /** @hide */
+ protected MethodHandle(long artFieldOrMethod, int handleKind, MethodType type) {
+ this.artFieldOrMethod = artFieldOrMethod;
+ this.handleKind = handleKind;
+ this.type = type;
+ }
+
+ /**
+ * Reports the type of this method handle.
+ * Every invocation of this method handle via {@code invokeExact} must exactly match this type.
+ * @return the method handle type
+ */
+ public MethodType type() {
+ if (nominalType != null) {
+ return nominalType;
+ }
+
+ return type;
+ }
+
+ /**
+ * Invokes the method handle, allowing any caller type descriptor, but requiring an exact type match.
+ * The symbolic type descriptor at the call site of {@code invokeExact} must
+ * exactly match this method handle's {@link #type type}.
+ * No conversions are allowed on arguments or return values.
+ * <p>
+ * When this method is observed via the Core Reflection API,
+ * it will appear as a single native method, taking an object array and returning an object.
+ * If this native method is invoked directly via
+ * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
+ * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
+ * it will throw an {@code UnsupportedOperationException}.
+ * @param args the signature-polymorphic parameter list, statically represented using varargs
+ * @return the signature-polymorphic result, statically represented using {@code Object}
+ * @throws WrongMethodTypeException if the target's type is not identical with the caller's symbolic type descriptor
+ * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
+ */
+ public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable;
+
+ /**
+ * Invokes the method handle, allowing any caller type descriptor,
+ * and optionally performing conversions on arguments and return values.
+ * <p>
+ * If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type},
+ * the call proceeds as if by {@link #invokeExact invokeExact}.
+ * <p>
+ * Otherwise, the call proceeds as if this method handle were first
+ * adjusted by calling {@link #asType asType} to adjust this method handle
+ * to the required type, and then the call proceeds as if by
+ * {@link #invokeExact invokeExact} on the adjusted method handle.
+ * <p>
+ * There is no guarantee that the {@code asType} call is actually made.
+ * If the JVM can predict the results of making the call, it may perform
+ * adaptations directly on the caller's arguments,
+ * and call the target method handle according to its own exact type.
+ * <p>
+ * The resolved type descriptor at the call site of {@code invoke} must
+ * be a valid argument to the receivers {@code asType} method.
+ * In particular, the caller must specify the same argument arity
+ * as the callee's type,
+ * if the callee is not a {@linkplain #asVarargsCollector variable arity collector}.
+ * <p>
+ * When this method is observed via the Core Reflection API,
+ * it will appear as a single native method, taking an object array and returning an object.
+ * If this native method is invoked directly via
+ * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
+ * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
+ * it will throw an {@code UnsupportedOperationException}.
+ * @param args the signature-polymorphic parameter list, statically represented using varargs
+ * @return the signature-polymorphic result, statically represented using {@code Object}
+ * @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor
+ * @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails
+ * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
+ */
+ public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;
+
+ // Android-changed: Removed implementation details.
+ //
+ // /*non-public*/ final native @PolymorphicSignature Object invokeBasic(Object... args)
+ // /*non-public*/ static native @PolymorphicSignature Object linkToVirtual(Object... args)
+ // /*non-public*/ static native @PolymorphicSignature Object linkToStatic(Object... args)
+ // /*non-public*/ static native @PolymorphicSignature Object linkToSpecial(Object... args)
+ // /*non-public*/ static native @PolymorphicSignature Object linkToInterface(Object... args)
+
+ /**
+ * Performs a variable arity invocation, passing the arguments in the given list
+ * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
+ * which mentions only the type {@code Object}, and whose arity is the length
+ * of the argument list.
+ * <p>
+ * Specifically, execution proceeds as if by the following steps,
+ * although the methods are not guaranteed to be called if the JVM
+ * can predict their effects.
+ * <ul>
+ * <li>Determine the length of the argument array as {@code N}.
+ * For a null reference, {@code N=0}. </li>
+ * <li>Determine the general type {@code TN} of {@code N} arguments as
+ * as {@code TN=MethodType.genericMethodType(N)}.</li>
+ * <li>Force the original target method handle {@code MH0} to the
+ * required type, as {@code MH1 = MH0.asType(TN)}. </li>
+ * <li>Spread the array into {@code N} separate arguments {@code A0, ...}. </li>
+ * <li>Invoke the type-adjusted method handle on the unpacked arguments:
+ * MH1.invokeExact(A0, ...). </li>
+ * <li>Take the return value as an {@code Object} reference. </li>
+ * </ul>
+ * <p>
+ * Because of the action of the {@code asType} step, the following argument
+ * conversions are applied as necessary:
+ * <ul>
+ * <li>reference casting
+ * <li>unboxing
+ * <li>widening primitive conversions
+ * </ul>
+ * <p>
+ * The result returned by the call is boxed if it is a primitive,
+ * or forced to null if the return type is void.
+ * <p>
+ * This call is equivalent to the following code:
+ * <blockquote><pre>{@code
+ * MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0);
+ * Object result = invoker.invokeExact(this, arguments);
+ * }</pre></blockquote>
+ * <p>
+ * Unlike the signature polymorphic methods {@code invokeExact} and {@code invoke},
+ * {@code invokeWithArguments} can be accessed normally via the Core Reflection API and JNI.
+ * It can therefore be used as a bridge between native or reflective code and method handles.
+ *
+ * @param arguments the arguments to pass to the target
+ * @return the result returned by the target
+ * @throws ClassCastException if an argument cannot be converted by reference casting
+ * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments
+ * @throws Throwable anything thrown by the target method invocation
+ * @see MethodHandles#spreadInvoker
+ */
+ public Object invokeWithArguments(Object... arguments) throws Throwable {
+ MethodHandle invoker = null;
+ synchronized (this) {
+ if (cachedSpreadInvoker == null) {
+ cachedSpreadInvoker = MethodHandles.spreadInvoker(this.type(), 0);
+ }
+
+ invoker = cachedSpreadInvoker;
+ }
+
+ return invoker.invoke(this, arguments);
+ }
+
+ /**
+ * Performs a variable arity invocation, passing the arguments in the given array
+ * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
+ * which mentions only the type {@code Object}, and whose arity is the length
+ * of the argument array.
+ * <p>
+ * This method is also equivalent to the following code:
+ * <blockquote><pre>{@code
+ * invokeWithArguments(arguments.toArray()
+ * }</pre></blockquote>
+ *
+ * @param arguments the arguments to pass to the target
+ * @return the result returned by the target
+ * @throws NullPointerException if {@code arguments} is a null reference
+ * @throws ClassCastException if an argument cannot be converted by reference casting
+ * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments
+ * @throws Throwable anything thrown by the target method invocation
+ */
+ public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable {
+ return invokeWithArguments(arguments.toArray());
+ }
+
+ /**
+ * Produces an adapter method handle which adapts the type of the
+ * current method handle to a new type.
+ * The resulting method handle is guaranteed to report a type
+ * which is equal to the desired new type.
+ * <p>
+ * If the original type and new type are equal, returns {@code this}.
+ * <p>
+ * The new method handle, when invoked, will perform the following
+ * steps:
+ * <ul>
+ * <li>Convert the incoming argument list to match the original
+ * method handle's argument list.
+ * <li>Invoke the original method handle on the converted argument list.
+ * <li>Convert any result returned by the original method handle
+ * to the return type of new method handle.
+ * </ul>
+ * <p>
+ * This method provides the crucial behavioral difference between
+ * {@link #invokeExact invokeExact} and plain, inexact {@link #invoke invoke}.
+ * The two methods
+ * perform the same steps when the caller's type descriptor exactly m atches
+ * the callee's, but when the types differ, plain {@link #invoke invoke}
+ * also calls {@code asType} (or some internal equivalent) in order
+ * to match up the caller's and callee's types.
+ * <p>
+ * If the current method is a variable arity method handle
+ * argument list conversion may involve the conversion and collection
+ * of several arguments into an array, as
+ * {@linkplain #asVarargsCollector described elsewhere}.
+ * In every other case, all conversions are applied <em>pairwise</em>,
+ * which means that each argument or return value is converted to
+ * exactly one argument or return value (or no return value).
+ * The applied conversions are defined by consulting the
+ * the corresponding component types of the old and new
+ * method handle types.
+ * <p>
+ * Let <em>T0</em> and <em>T1</em> be corresponding new and old parameter types,
+ * or old and new return types. Specifically, for some valid index {@code i}, let
+ * <em>T0</em>{@code =newType.parameterType(i)} and <em>T1</em>{@code =this.type().parameterType(i)}.
+ * Or else, going the other way for return values, let
+ * <em>T0</em>{@code =this.type().returnType()} and <em>T1</em>{@code =newType.returnType()}.
+ * If the types are the same, the new method handle makes no change
+ * to the corresponding argument or return value (if any).
+ * Otherwise, one of the following conversions is applied
+ * if possible:
+ * <ul>
+ * <li>If <em>T0</em> and <em>T1</em> are references, then a cast to <em>T1</em> is applied.
+ * (The types do not need to be related in any particular way.
+ * This is because a dynamic value of null can convert to any reference type.)
+ * <li>If <em>T0</em> and <em>T1</em> are primitives, then a Java method invocation
+ * conversion (JLS 5.3) is applied, if one exists.
+ * (Specifically, <em>T0</em> must convert to <em>T1</em> by a widening primitive conversion.)
+ * <li>If <em>T0</em> is a primitive and <em>T1</em> a reference,
+ * a Java casting conversion (JLS 5.5) is applied if one exists.
+ * (Specifically, the value is boxed from <em>T0</em> to its wrapper class,
+ * which is then widened as needed to <em>T1</em>.)
+ * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing
+ * conversion will be applied at runtime, possibly followed
+ * by a Java method invocation conversion (JLS 5.3)
+ * on the primitive value. (These are the primitive widening conversions.)
+ * <em>T0</em> must be a wrapper class or a supertype of one.
+ * (In the case where <em>T0</em> is Object, these are the conversions
+ * allowed by {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.)
+ * The unboxing conversion must have a possibility of success, which means that
+ * if <em>T0</em> is not itself a wrapper class, there must exist at least one
+ * wrapper class <em>TW</em> which is a subtype of <em>T0</em> and whose unboxed
+ * primitive value can be widened to <em>T1</em>.
+ * <li>If the return type <em>T1</em> is marked as void, any returned value is discarded
+ * <li>If the return type <em>T0</em> is void and <em>T1</em> a reference, a null value is introduced.
+ * <li>If the return type <em>T0</em> is void and <em>T1</em> a primitive,
+ * a zero value is introduced.
+ * </ul>
+ * (<em>Note:</em> Both <em>T0</em> and <em>T1</em> may be regarded as static types,
+ * because neither corresponds specifically to the <em>dynamic type</em> of any
+ * actual argument or return value.)
+ * <p>
+ * The method handle conversion cannot be made if any one of the required
+ * pairwise conversions cannot be made.
+ * <p>
+ * At runtime, the conversions applied to reference arguments
+ * or return values may require additional runtime checks which can fail.
+ * An unboxing operation may fail because the original reference is null,
+ * causing a {@link java.lang.NullPointerException NullPointerException}.
+ * An unboxing operation or a reference cast may also fail on a reference
+ * to an object of the wrong type,
+ * causing a {@link java.lang.ClassCastException ClassCastException}.
+ * Although an unboxing operation may accept several kinds of wrappers,
+ * if none are available, a {@code ClassCastException} will be thrown.
+ *
+ * @param newType the expected type of the new method handle
+ * @return a method handle which delegates to {@code this} after performing
+ * any necessary argument conversions, and arranges for any
+ * necessary return value conversions
+ * @throws NullPointerException if {@code newType} is a null reference
+ * @throws WrongMethodTypeException if the conversion cannot be made
+ * @see MethodHandles#explicitCastArguments
+ */
+ public MethodHandle asType(MethodType newType) {
+ // Fast path alternative to a heavyweight {@code asType} call.
+ // Return 'this' if the conversion will be a no-op.
+ if (newType == type) {
+ return this;
+ }
+
+ if (!type.isConvertibleTo(newType)) {
+ throw new WrongMethodTypeException("cannot convert " + this + " to " + newType);
+ }
+
+ MethodHandle mh = duplicate();
+ mh.nominalType = newType;
+ return mh;
+ }
+
+ /**
+ * Makes an <em>array-spreading</em> method handle, which accepts a trailing array argument
+ * and spreads its elements as positional arguments.
+ * The new method handle adapts, as its <i>target</i>,
+ * the current method handle. The type of the adapter will be
+ * the same as the type of the target, except that the final
+ * {@code arrayLength} parameters of the target's type are replaced
+ * by a single array parameter of type {@code arrayType}.
+ * <p>
+ * If the array element type differs from any of the corresponding
+ * argument types on the original target,
+ * the original target is adapted to take the array elements directly,
+ * as if by a call to {@link #asType asType}.
+ * <p>
+ * When called, the adapter replaces a trailing array argument
+ * by the array's elements, each as its own argument to the target.
+ * (The order of the arguments is preserved.)
+ * They are converted pairwise by casting and/or unboxing
+ * to the types of the trailing parameters of the target.
+ * Finally the target is called.
+ * What the target eventually returns is returned unchanged by the adapter.
+ * <p>
+ * Before calling the target, the adapter verifies that the array
+ * contains exactly enough elements to provide a correct argument count
+ * to the target method handle.
+ * (The array may also be null when zero elements are required.)
+ * <p>
+ * If, when the adapter is called, the supplied array argument does
+ * not have the correct number of elements, the adapter will throw
+ * an {@link IllegalArgumentException} instead of invoking the target.
+ * <p>
+ * Here are some simple examples of array-spreading method handles:
+ * <blockquote><pre>{@code
+MethodHandle equals = publicLookup()
+ .findVirtual(String.class, "equals", methodType(boolean.class, Object.class));
+assert( (boolean) equals.invokeExact("me", (Object)"me"));
+assert(!(boolean) equals.invokeExact("me", (Object)"thee"));
+// spread both arguments from a 2-array:
+MethodHandle eq2 = equals.asSpreader(Object[].class, 2);
+assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" }));
+assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" }));
+// try to spread from anything but a 2-array:
+for (int n = 0; n <= 10; n++) {
+ Object[] badArityArgs = (n == 2 ? null : new Object[n]);
+ try { assert((boolean) eq2.invokeExact(badArityArgs) && false); }
+ catch (IllegalArgumentException ex) { } // OK
+}
+// spread both arguments from a String array:
+MethodHandle eq2s = equals.asSpreader(String[].class, 2);
+assert( (boolean) eq2s.invokeExact(new String[]{ "me", "me" }));
+assert(!(boolean) eq2s.invokeExact(new String[]{ "me", "thee" }));
+// spread second arguments from a 1-array:
+MethodHandle eq1 = equals.asSpreader(Object[].class, 1);
+assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" }));
+assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" }));
+// spread no arguments from a 0-array or null:
+MethodHandle eq0 = equals.asSpreader(Object[].class, 0);
+assert( (boolean) eq0.invokeExact("me", (Object)"me", new Object[0]));
+assert(!(boolean) eq0.invokeExact("me", (Object)"thee", (Object[])null));
+// asSpreader and asCollector are approximate inverses:
+for (int n = 0; n <= 2; n++) {
+ for (Class<?> a : new Class<?>[]{Object[].class, String[].class, CharSequence[].class}) {
+ MethodHandle equals2 = equals.asSpreader(a, n).asCollector(a, n);
+ assert( (boolean) equals2.invokeWithArguments("me", "me"));
+ assert(!(boolean) equals2.invokeWithArguments("me", "thee"));
+ }
+}
+MethodHandle caToString = publicLookup()
+ .findStatic(Arrays.class, "toString", methodType(String.class, char[].class));
+assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray()));
+MethodHandle caString3 = caToString.asCollector(char[].class, 3);
+assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C'));
+MethodHandle caToString2 = caString3.asSpreader(char[].class, 2);
+assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray()));
+ * }</pre></blockquote>
+ * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments
+ * @param arrayLength the number of arguments to spread from an incoming array argument
+ * @return a new method handle which spreads its final array argument,
+ * before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type,
+ * or if target does not have at least
+ * {@code arrayLength} parameter types,
+ * or if {@code arrayLength} is negative,
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ * @throws WrongMethodTypeException if the implied {@code asType} call fails
+ * @see #asCollector
+ */
+ public MethodHandle asSpreader(Class<?> arrayType, int arrayLength) {
+ MethodType postSpreadType = asSpreaderChecks(arrayType, arrayLength);
+
+ final int targetParamCount = postSpreadType.parameterCount();
+ MethodType dropArrayArgs = postSpreadType.dropParameterTypes(
+ (targetParamCount - arrayLength), targetParamCount);
+ MethodType adapterType = dropArrayArgs.appendParameterTypes(arrayType);
+
+ return new Transformers.Spreader(this, adapterType, arrayLength);
+ }
+
+ /**
+ * See if {@code asSpreader} can be validly called with the given arguments.
+ * Return the type of the method handle call after spreading but before conversions.
+ */
+ private MethodType asSpreaderChecks(Class<?> arrayType, int arrayLength) {
+ spreadArrayChecks(arrayType, arrayLength);
+ int nargs = type().parameterCount();
+ if (nargs < arrayLength || arrayLength < 0)
+ throw newIllegalArgumentException("bad spread array length");
+ Class<?> arrayElement = arrayType.getComponentType();
+ MethodType mtype = type();
+ boolean match = true, fail = false;
+ for (int i = nargs - arrayLength; i < nargs; i++) {
+ Class<?> ptype = mtype.parameterType(i);
+ if (ptype != arrayElement) {
+ match = false;
+ if (!MethodType.canConvert(arrayElement, ptype)) {
+ fail = true;
+ break;
+ }
+ }
+ }
+ if (match) return mtype;
+ MethodType needType = mtype.asSpreaderType(arrayType, arrayLength);
+ if (!fail) return needType;
+ // elicit an error:
+ this.asType(needType);
+ throw newInternalError("should not return", null);
+ }
+
+ private void spreadArrayChecks(Class<?> arrayType, int arrayLength) {
+ Class<?> arrayElement = arrayType.getComponentType();
+ if (arrayElement == null)
+ throw newIllegalArgumentException("not an array type", arrayType);
+ if ((arrayLength & 0x7F) != arrayLength) {
+ if ((arrayLength & 0xFF) != arrayLength)
+ throw newIllegalArgumentException("array length is not legal", arrayLength);
+ assert(arrayLength >= 128);
+ if (arrayElement == long.class ||
+ arrayElement == double.class)
+ throw newIllegalArgumentException("array length is not legal for long[] or double[]", arrayLength);
+ }
+ }
+
+ /**
+ * Makes an <em>array-collecting</em> method handle, which accepts a given number of trailing
+ * positional arguments and collects them into an array argument.
+ * The new method handle adapts, as its <i>target</i>,
+ * the current method handle. The type of the adapter will be
+ * the same as the type of the target, except that a single trailing
+ * parameter (usually of type {@code arrayType}) is replaced by
+ * {@code arrayLength} parameters whose type is element type of {@code arrayType}.
+ * <p>
+ * If the array type differs from the final argument type on the original target,
+ * the original target is adapted to take the array type directly,
+ * as if by a call to {@link #asType asType}.
+ * <p>
+ * When called, the adapter replaces its trailing {@code arrayLength}
+ * arguments by a single new array of type {@code arrayType}, whose elements
+ * comprise (in order) the replaced arguments.
+ * Finally the target is called.
+ * What the target eventually returns is returned unchanged by the adapter.
+ * <p>
+ * (The array may also be a shared constant when {@code arrayLength} is zero.)
+ * <p>
+ * (<em>Note:</em> The {@code arrayType} is often identical to the last
+ * parameter type of the original target.
+ * It is an explicit argument for symmetry with {@code asSpreader}, and also
+ * to allow the target to use a simple {@code Object} as its last parameter type.)
+ * <p>
+ * In order to create a collecting adapter which is not restricted to a particular
+ * number of collected arguments, use {@link #asVarargsCollector asVarargsCollector} instead.
+ * <p>
+ * Here are some examples of array-collecting method handles:
+ * <blockquote><pre>{@code
+MethodHandle deepToString = publicLookup()
+ .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+assertEquals("[won]", (String) deepToString.invokeExact(new Object[]{"won"}));
+MethodHandle ts1 = deepToString.asCollector(Object[].class, 1);
+assertEquals(methodType(String.class, Object.class), ts1.type());
+//assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"})); //FAIL
+assertEquals("[[won]]", (String) ts1.invokeExact((Object) new Object[]{"won"}));
+// arrayType can be a subtype of Object[]
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals(methodType(String.class, String.class, String.class), ts2.type());
+assertEquals("[two, too]", (String) ts2.invokeExact("two", "too"));
+MethodHandle ts0 = deepToString.asCollector(Object[].class, 0);
+assertEquals("[]", (String) ts0.invokeExact());
+// collectors can be nested, Lisp-style
+MethodHandle ts22 = deepToString.asCollector(Object[].class, 3).asCollector(String[].class, 2);
+assertEquals("[A, B, [C, D]]", ((String) ts22.invokeExact((Object)'A', (Object)"B", "C", "D")));
+// arrayType can be any primitive array type
+MethodHandle bytesToString = publicLookup()
+ .findStatic(Arrays.class, "toString", methodType(String.class, byte[].class))
+ .asCollector(byte[].class, 3);
+assertEquals("[1, 2, 3]", (String) bytesToString.invokeExact((byte)1, (byte)2, (byte)3));
+MethodHandle longsToString = publicLookup()
+ .findStatic(Arrays.class, "toString", methodType(String.class, long[].class))
+ .asCollector(long[].class, 1);
+assertEquals("[123]", (String) longsToString.invokeExact((long)123));
+ * }</pre></blockquote>
+ * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
+ * @param arrayLength the number of arguments to collect into a new array argument
+ * @return a new method handle which collects some trailing argument
+ * into an array, before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type
+ * or {@code arrayType} is not assignable to this method handle's trailing parameter type,
+ * or {@code arrayLength} is not a legal array size,
+ * or the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ * @throws WrongMethodTypeException if the implied {@code asType} call fails
+ * @see #asSpreader
+ * @see #asVarargsCollector
+ */
+ public MethodHandle asCollector(Class<?> arrayType, int arrayLength) {
+ asCollectorChecks(arrayType, arrayLength);
+
+ return new Transformers.Collector(this, arrayType, arrayLength);
+ }
+
+ /**
+ * See if {@code asCollector} can be validly called with the given arguments.
+ * Return false if the last parameter is not an exact match to arrayType.
+ */
+ /*non-public*/ boolean asCollectorChecks(Class<?> arrayType, int arrayLength) {
+ spreadArrayChecks(arrayType, arrayLength);
+ int nargs = type().parameterCount();
+ if (nargs != 0) {
+ Class<?> lastParam = type().parameterType(nargs-1);
+ if (lastParam == arrayType) return true;
+ if (lastParam.isAssignableFrom(arrayType)) return false;
+ }
+ throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType);
+ }
+
+ /**
+ * Makes a <em>variable arity</em> adapter which is able to accept
+ * any number of trailing positional arguments and collect them
+ * into an array argument.
+ * <p>
+ * The type and behavior of the adapter will be the same as
+ * the type and behavior of the target, except that certain
+ * {@code invoke} and {@code asType} requests can lead to
+ * trailing positional arguments being collected into target's
+ * trailing parameter.
+ * Also, the last parameter type of the adapter will be
+ * {@code arrayType}, even if the target has a different
+ * last parameter type.
+ * <p>
+ * This transformation may return {@code this} if the method handle is
+ * already of variable arity and its trailing parameter type
+ * is identical to {@code arrayType}.
+ * <p>
+ * When called with {@link #invokeExact invokeExact}, the adapter invokes
+ * the target with no argument changes.
+ * (<em>Note:</em> This behavior is different from a
+ * {@linkplain #asCollector fixed arity collector},
+ * since it accepts a whole array of indeterminate length,
+ * rather than a fixed number of arguments.)
+ * <p>
+ * When called with plain, inexact {@link #invoke invoke}, if the caller
+ * type is the same as the adapter, the adapter invokes the target as with
+ * {@code invokeExact}.
+ * (This is the normal behavior for {@code invoke} when types match.)
+ * <p>
+ * Otherwise, if the caller and adapter arity are the same, and the
+ * trailing parameter type of the caller is a reference type identical to
+ * or assignable to the trailing parameter type of the adapter,
+ * the arguments and return values are converted pairwise,
+ * as if by {@link #asType asType} on a fixed arity
+ * method handle.
+ * <p>
+ * Otherwise, the arities differ, or the adapter's trailing parameter
+ * type is not assignable from the corresponding caller type.
+ * In this case, the adapter replaces all trailing arguments from
+ * the original trailing argument position onward, by
+ * a new array of type {@code arrayType}, whose elements
+ * comprise (in order) the replaced arguments.
+ * <p>
+ * The caller type must provides as least enough arguments,
+ * and of the correct type, to satisfy the target's requirement for
+ * positional arguments before the trailing array argument.
+ * Thus, the caller must supply, at a minimum, {@code N-1} arguments,
+ * where {@code N} is the arity of the target.
+ * Also, there must exist conversions from the incoming arguments
+ * to the target's arguments.
+ * As with other uses of plain {@code invoke}, if these basic
+ * requirements are not fulfilled, a {@code WrongMethodTypeException}
+ * may be thrown.
+ * <p>
+ * In all cases, what the target eventually returns is returned unchanged by the adapter.
+ * <p>
+ * In the final case, it is exactly as if the target method handle were
+ * temporarily adapted with a {@linkplain #asCollector fixed arity collector}
+ * to the arity required by the caller type.
+ * (As with {@code asCollector}, if the array length is zero,
+ * a shared constant may be used instead of a new array.
+ * If the implied call to {@code asCollector} would throw
+ * an {@code IllegalArgumentException} or {@code WrongMethodTypeException},
+ * the call to the variable arity adapter must throw
+ * {@code WrongMethodTypeException}.)
+ * <p>
+ * The behavior of {@link #asType asType} is also specialized for
+ * variable arity adapters, to maintain the invariant that
+ * plain, inexact {@code invoke} is always equivalent to an {@code asType}
+ * call to adjust the target type, followed by {@code invokeExact}.
+ * Therefore, a variable arity adapter responds
+ * to an {@code asType} request by building a fixed arity collector,
+ * if and only if the adapter and requested type differ either
+ * in arity or trailing argument type.
+ * The resulting fixed arity collector has its type further adjusted
+ * (if necessary) to the requested type by pairwise conversion,
+ * as if by another application of {@code asType}.
+ * <p>
+ * When a method handle is obtained by executing an {@code ldc} instruction
+ * of a {@code CONSTANT_MethodHandle} constant, and the target method is marked
+ * as a variable arity method (with the modifier bit {@code 0x0080}),
+ * the method handle will accept multiple arities, as if the method handle
+ * constant were created by means of a call to {@code asVarargsCollector}.
+ * <p>
+ * In order to create a collecting adapter which collects a predetermined
+ * number of arguments, and whose type reflects this predetermined number,
+ * use {@link #asCollector asCollector} instead.
+ * <p>
+ * No method handle transformations produce new method handles with
+ * variable arity, unless they are documented as doing so.
+ * Therefore, besides {@code asVarargsCollector},
+ * all methods in {@code MethodHandle} and {@code MethodHandles}
+ * will return a method handle with fixed arity,
+ * except in the cases where they are specified to return their original
+ * operand (e.g., {@code asType} of the method handle's own type).
+ * <p>
+ * Calling {@code asVarargsCollector} on a method handle which is already
+ * of variable arity will produce a method handle with the same type and behavior.
+ * It may (or may not) return the original variable arity method handle.
+ * <p>
+ * Here is an example, of a list-making variable arity method handle:
+ * <blockquote><pre>{@code
+MethodHandle deepToString = publicLookup()
+ .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+MethodHandle ts1 = deepToString.asVarargsCollector(Object[].class);
+assertEquals("[won]", (String) ts1.invokeExact( new Object[]{"won"}));
+assertEquals("[won]", (String) ts1.invoke( new Object[]{"won"}));
+assertEquals("[won]", (String) ts1.invoke( "won" ));
+assertEquals("[[won]]", (String) ts1.invoke((Object) new Object[]{"won"}));
+// findStatic of Arrays.asList(...) produces a variable arity method handle:
+MethodHandle asList = publicLookup()
+ .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class));
+assertEquals(methodType(List.class, Object[].class), asList.type());
+assert(asList.isVarargsCollector());
+assertEquals("[]", asList.invoke().toString());
+assertEquals("[1]", asList.invoke(1).toString());
+assertEquals("[two, too]", asList.invoke("two", "too").toString());
+String[] argv = { "three", "thee", "tee" };
+assertEquals("[three, thee, tee]", asList.invoke(argv).toString());
+assertEquals("[three, thee, tee]", asList.invoke((Object[])argv).toString());
+List ls = (List) asList.invoke((Object)argv);
+assertEquals(1, ls.size());
+assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
+ * }</pre></blockquote>
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * These rules are designed as a dynamically-typed variation
+ * of the Java rules for variable arity methods.
+ * In both cases, callers to a variable arity method or method handle
+ * can either pass zero or more positional arguments, or else pass
+ * pre-collected arrays of any length. Users should be aware of the
+ * special role of the final argument, and of the effect of a
+ * type match on that final argument, which determines whether
+ * or not a single trailing argument is interpreted as a whole
+ * array or a single element of an array to be collected.
+ * Note that the dynamic type of the trailing argument has no
+ * effect on this decision, only a comparison between the symbolic
+ * type descriptor of the call site and the type descriptor of the method handle.)
+ *
+ * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
+ * @return a new method handle which can collect any number of trailing arguments
+ * into an array, before calling the original method handle
+ * @throws NullPointerException if {@code arrayType} is a null reference
+ * @throws IllegalArgumentException if {@code arrayType} is not an array type
+ * or {@code arrayType} is not assignable to this method handle's trailing parameter type
+ * @see #asCollector
+ * @see #isVarargsCollector
+ * @see #asFixedArity
+ */
+ public MethodHandle asVarargsCollector(Class<?> arrayType) {
+ arrayType.getClass(); // explicit NPE
+ boolean lastMatch = asCollectorChecks(arrayType, 0);
+ if (isVarargsCollector() && lastMatch)
+ return this;
- public MethodType type() { return null; }
+ return new Transformers.VarargsCollector(this);
+ }
- public final Object invokeExact(Object... args) throws Throwable { return null; }
+ /**
+ * Determines if this method handle
+ * supports {@linkplain #asVarargsCollector variable arity} calls.
+ * Such method handles arise from the following sources:
+ * <ul>
+ * <li>a call to {@linkplain #asVarargsCollector asVarargsCollector}
+ * <li>a call to a {@linkplain java.lang.invoke.MethodHandles.Lookup lookup method}
+ * which resolves to a variable arity Java method or constructor
+ * <li>an {@code ldc} instruction of a {@code CONSTANT_MethodHandle}
+ * which resolves to a variable arity Java method or constructor
+ * </ul>
+ * @return true if this method handle accepts more than one arity of plain, inexact {@code invoke} calls
+ * @see #asVarargsCollector
+ * @see #asFixedArity
+ */
+ public boolean isVarargsCollector() {
+ return false;
+ }
- public final Object invoke(Object... args) throws Throwable { return null; }
+ /**
+ * Makes a <em>fixed arity</em> method handle which is otherwise
+ * equivalent to the current method handle.
+ * <p>
+ * If the current method handle is not of
+ * {@linkplain #asVarargsCollector variable arity},
+ * the current method handle is returned.
+ * This is true even if the current method handle
+ * could not be a valid input to {@code asVarargsCollector}.
+ * <p>
+ * Otherwise, the resulting fixed-arity method handle has the same
+ * type and behavior of the current method handle,
+ * except that {@link #isVarargsCollector isVarargsCollector}
+ * will be false.
+ * The fixed-arity method handle may (or may not) be the
+ * a previous argument to {@code asVarargsCollector}.
+ * <p>
+ * Here is an example, of a list-making variable arity method handle:
+ * <blockquote><pre>{@code
+MethodHandle asListVar = publicLookup()
+ .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class))
+ .asVarargsCollector(Object[].class);
+MethodHandle asListFix = asListVar.asFixedArity();
+assertEquals("[1]", asListVar.invoke(1).toString());
+Exception caught = null;
+try { asListFix.invoke((Object)1); }
+catch (Exception ex) { caught = ex; }
+assert(caught instanceof ClassCastException);
+assertEquals("[two, too]", asListVar.invoke("two", "too").toString());
+try { asListFix.invoke("two", "too"); }
+catch (Exception ex) { caught = ex; }
+assert(caught instanceof WrongMethodTypeException);
+Object[] argv = { "three", "thee", "tee" };
+assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString());
+assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString());
+assertEquals(1, ((List) asListVar.invoke((Object)argv)).size());
+assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
+ * }</pre></blockquote>
+ *
+ * @return a new method handle which accepts only a fixed number of arguments
+ * @see #asVarargsCollector
+ * @see #isVarargsCollector
+ */
+ public MethodHandle asFixedArity() {
+ // Android-changed: implementation specific.
+ MethodHandle mh = this;
+ if (mh.isVarargsCollector()) {
+ mh = ((Transformers.VarargsCollector) mh).asFixedArity();
+ }
+ assert(!mh.isVarargsCollector());
+ return mh;
+ }
- public Object invokeWithArguments(Object... arguments) throws Throwable { return null; }
+ /**
+ * Binds a value {@code x} to the first argument of a method handle, without invoking it.
+ * The new method handle adapts, as its <i>target</i>,
+ * the current method handle by binding it to the given argument.
+ * The type of the bound handle will be
+ * the same as the type of the target, except that a single leading
+ * reference parameter will be omitted.
+ * <p>
+ * When called, the bound handle inserts the given value {@code x}
+ * as a new leading argument to the target. The other arguments are
+ * also passed unchanged.
+ * What the target eventually returns is returned unchanged by the bound handle.
+ * <p>
+ * The reference {@code x} must be convertible to the first parameter
+ * type of the target.
+ * <p>
+ * (<em>Note:</em> Because method handles are immutable, the target method handle
+ * retains its original type and behavior.)
+ * @param x the value to bind to the first argument of the target
+ * @return a new method handle which prepends the given value to the incoming
+ * argument list, before calling the original method handle
+ * @throws IllegalArgumentException if the target does not have a
+ * leading parameter type that is a reference type
+ * @throws ClassCastException if {@code x} cannot be converted
+ * to the leading parameter type of the target
+ * @see MethodHandles#insertArguments
+ */
+ public MethodHandle bindTo(Object x) {
+ x = type.leadingReferenceParameter().cast(x); // throw CCE if needed
- public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable { return null; }
+ return new Transformers.BindTo(this, x);
+ }
- public MethodHandle asType(MethodType newType) { return null; }
+ /**
+ * Returns a string representation of the method handle,
+ * starting with the string {@code "MethodHandle"} and
+ * ending with the string representation of the method handle's type.
+ * In other words, this method returns a string equal to the value of:
+ * <blockquote><pre>{@code
+ * "MethodHandle" + type().toString()
+ * }</pre></blockquote>
+ * <p>
+ * (<em>Note:</em> Future releases of this API may add further information
+ * to the string representation.
+ * Therefore, the present syntax should not be parsed by applications.)
+ *
+ * @return a string representation of the method handle
+ */
+ @Override
+ public String toString() {
+ // Android-changed: Removed debugging support.
+ return "MethodHandle"+type;
+ }
- public MethodHandle asCollector(Class<?> arrayType, int arrayLength) { return null; }
+ /** @hide */
+ public int getHandleKind() {
+ return handleKind;
+ }
- public MethodHandle asVarargsCollector(Class<?> arrayType) { return null; }
+ /** @hide */
+ protected void transform(EmulatedStackFrame arguments) throws Throwable {
+ throw new AssertionError("MethodHandle.transform should never be called.");
+ }
- public boolean isVarargsCollector() { return false; }
+ /**
+ * Creates a copy of this method handle, copying all relevant data.
+ *
+ * @hide
+ */
+ protected MethodHandle duplicate() {
+ try {
+ return (MethodHandle) this.clone();
+ } catch (CloneNotSupportedException cnse) {
+ throw new AssertionError("Subclass of Transformer is not cloneable");
+ }
+ }
- public MethodHandle asFixedArity() { return null; }
- public MethodHandle bindTo(Object x) { return null; }
+ /**
+ * This is the entry point for all transform calls, and dispatches to the protected
+ * transform method. This layer of indirection exists purely for convenience, because
+ * we can invoke-direct on a fixed ArtMethod for all transform variants.
+ *
+ * NOTE: If this extra layer of indirection proves to be a problem, we can get rid
+ * of this layer of indirection at the cost of some additional ugliness.
+ */
+ private void transformInternal(EmulatedStackFrame arguments) throws Throwable {
+ transform(arguments);
+ }
+ // Android-changed: Removed implementation details :
+ //
+ // String standardString();
+ // String debugString();
+ //
+ //// Implementation methods.
+ //// Sub-classes can override these default implementations.
+ //// All these methods assume arguments are already validated.
+ //
+ // Other transforms to do: convert, explicitCast, permute, drop, filter, fold, GWT, catch
+ //
+ // BoundMethodHandle bindArgumentL(int pos, Object value);
+ // /*non-public*/ MethodHandle setVarargs(MemberName member);
+ // /*non-public*/ MethodHandle viewAsType(MethodType newType, boolean strict);
+ // /*non-public*/ boolean viewAsTypeChecks(MethodType newType, boolean strict);
+ //
+ // Decoding
+ //
+ // /*non-public*/ LambdaForm internalForm();
+ // /*non-public*/ MemberName internalMemberName();
+ // /*non-public*/ Class<?> internalCallerClass();
+ // /*non-public*/ MethodHandleImpl.Intrinsic intrinsicName();
+ // /*non-public*/ MethodHandle withInternalMemberName(MemberName member, boolean isInvokeSpecial);
+ // /*non-public*/ boolean isInvokeSpecial();
+ // /*non-public*/ Object internalValues();
+ // /*non-public*/ Object internalProperties();
+ //
+ //// Method handle implementation methods.
+ //// Sub-classes can override these default implementations.
+ //// All these methods assume arguments are already validated.
+ //
+ // /*non-public*/ abstract MethodHandle copyWith(MethodType mt, LambdaForm lf);
+ // abstract BoundMethodHandle rebind();
+ // /*non-public*/ void updateForm(LambdaForm newForm);
+ // /*non-public*/ void customize();
+ // private static final long FORM_OFFSET;
}
diff --git a/java/lang/invoke/MethodHandles.java b/java/lang/invoke/MethodHandles.java
index f27ad988..88ce6e08 100644
--- a/java/lang/invoke/MethodHandles.java
+++ b/java/lang/invoke/MethodHandles.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,129 +25,3410 @@
package java.lang.invoke;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
+import java.lang.reflect.*;
+import java.nio.ByteOrder;
import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import dalvik.system.VMStack;
+import sun.invoke.util.VerifyAccess;
+import sun.invoke.util.Wrapper;
+import static java.lang.invoke.MethodHandleStatics.*;
+
+/**
+ * This class consists exclusively of static methods that operate on or return
+ * method handles. They fall into several categories:
+ * <ul>
+ * <li>Lookup methods which help create method handles for methods and fields.
+ * <li>Combinator methods, which combine or transform pre-existing method handles into new ones.
+ * <li>Other factory methods to create method handles that emulate other common JVM operations or control flow patterns.
+ * </ul>
+ * <p>
+ * @author John Rose, JSR 292 EG
+ * @since 1.7
+ */
public class MethodHandles {
- public static Lookup lookup() { return null; }
+ private MethodHandles() { } // do not instantiate
+
+ // BEGIN Android-added: unsupported() helper function.
+ // TODO(b/65872996): Remove when complete.
+ private static void unsupported(String msg) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(msg);
+ }
+ // END Android-added: unsupported() helper function.
+
+ // Android-changed: We do not use MemberName / MethodHandleImpl.
+ //
+ // private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory();
+ // static { MethodHandleImpl.initStatics(); }
+ // See IMPL_LOOKUP below.
- public static Lookup publicLookup() { return null; }
+ //// Method handle creation from ordinary methods.
+ /**
+ * Returns a {@link Lookup lookup object} with
+ * full capabilities to emulate all supported bytecode behaviors of the caller.
+ * These capabilities include <a href="MethodHandles.Lookup.html#privacc">private access</a> to the caller.
+ * Factory methods on the lookup object can create
+ * <a href="MethodHandleInfo.html#directmh">direct method handles</a>
+ * for any member that the caller has access to via bytecodes,
+ * including protected and private fields and methods.
+ * This lookup object is a <em>capability</em> which may be delegated to trusted agents.
+ * Do not store it in place where untrusted code can access it.
+ * <p>
+ * This method is caller sensitive, which means that it may return different
+ * values to different callers.
+ * <p>
+ * For any given caller class {@code C}, the lookup object returned by this call
+ * has equivalent capabilities to any lookup object
+ * supplied by the JVM to the bootstrap method of an
+ * <a href="package-summary.html#indyinsn">invokedynamic instruction</a>
+ * executing in the same caller class {@code C}.
+ * @return a lookup object for the caller of this method, with private access
+ */
+ // Android-changed: Remove caller sensitive.
+ // @CallerSensitive
+ public static Lookup lookup() {
+ // Android-changed: Do not use Reflection.getCallerClass().
+ return new Lookup(VMStack.getStackClass1());
+ }
+
+ /**
+ * Returns a {@link Lookup lookup object} which is trusted minimally.
+ * It can only be used to create method handles to
+ * publicly accessible fields and methods.
+ * <p>
+ * As a matter of pure convention, the {@linkplain Lookup#lookupClass lookup class}
+ * of this lookup object will be {@link java.lang.Object}.
+ *
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * The lookup class can be changed to any other class {@code C} using an expression of the form
+ * {@link Lookup#in publicLookup().in(C.class)}.
+ * Since all classes have equal access to public names,
+ * such a change would confer no new access rights.
+ * A public lookup object is always subject to
+ * <a href="MethodHandles.Lookup.html#secmgr">security manager checks</a>.
+ * Also, it cannot access
+ * <a href="MethodHandles.Lookup.html#callsens">caller sensitive methods</a>.
+ * @return a lookup object which is trusted minimally
+ */
+ public static Lookup publicLookup() {
+ return Lookup.PUBLIC_LOOKUP;
+ }
+
+ /**
+ * Performs an unchecked "crack" of a
+ * <a href="MethodHandleInfo.html#directmh">direct method handle</a>.
+ * The result is as if the user had obtained a lookup object capable enough
+ * to crack the target method handle, called
+ * {@link java.lang.invoke.MethodHandles.Lookup#revealDirect Lookup.revealDirect}
+ * on the target to obtain its symbolic reference, and then called
+ * {@link java.lang.invoke.MethodHandleInfo#reflectAs MethodHandleInfo.reflectAs}
+ * to resolve the symbolic reference to a member.
+ * <p>
+ * If there is a security manager, its {@code checkPermission} method
+ * is called with a {@code ReflectPermission("suppressAccessChecks")} permission.
+ * @param <T> the desired type of the result, either {@link Member} or a subtype
+ * @param target a direct method handle to crack into symbolic reference components
+ * @param expected a class object representing the desired result type {@code T}
+ * @return a reference to the method, constructor, or field object
+ * @exception SecurityException if the caller is not privileged to call {@code setAccessible}
+ * @exception NullPointerException if either argument is {@code null}
+ * @exception IllegalArgumentException if the target is not a direct method handle
+ * @exception ClassCastException if the member is not of the expected type
+ * @since 1.8
+ */
public static <T extends Member> T
- reflectAs(Class<T> expected, MethodHandle target) { return null; }
+ reflectAs(Class<T> expected, MethodHandle target) {
+ MethodHandleImpl directTarget = getMethodHandleImpl(target);
+ // Given that this is specified to be an "unchecked" crack, we can directly allocate
+ // a member from the underlying ArtField / Method and bypass all associated access checks.
+ return expected.cast(directTarget.getMemberInternal());
+ }
+ /**
+ * A <em>lookup object</em> is a factory for creating method handles,
+ * when the creation requires access checking.
+ * Method handles do not perform
+ * access checks when they are called, but rather when they are created.
+ * Therefore, method handle access
+ * restrictions must be enforced when a method handle is created.
+ * The caller class against which those restrictions are enforced
+ * is known as the {@linkplain #lookupClass lookup class}.
+ * <p>
+ * A lookup class which needs to create method handles will call
+ * {@link #lookup MethodHandles.lookup} to create a factory for itself.
+ * When the {@code Lookup} factory object is created, the identity of the lookup class is
+ * determined, and securely stored in the {@code Lookup} object.
+ * The lookup class (or its delegates) may then use factory methods
+ * on the {@code Lookup} object to create method handles for access-checked members.
+ * This includes all methods, constructors, and fields which are allowed to the lookup class,
+ * even private ones.
+ *
+ * <h1><a name="lookups"></a>Lookup Factory Methods</h1>
+ * The factory methods on a {@code Lookup} object correspond to all major
+ * use cases for methods, constructors, and fields.
+ * Each method handle created by a factory method is the functional
+ * equivalent of a particular <em>bytecode behavior</em>.
+ * (Bytecode behaviors are described in section 5.4.3.5 of the Java Virtual Machine Specification.)
+ * Here is a summary of the correspondence between these factory methods and
+ * the behavior the resulting method handles:
+ * <table border=1 cellpadding=5 summary="lookup method behaviors">
+ * <tr>
+ * <th><a name="equiv"></a>lookup expression</th>
+ * <th>member</th>
+ * <th>bytecode behavior</th>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findGetter lookup.findGetter(C.class,"f",FT.class)}</td>
+ * <td>{@code FT f;}</td><td>{@code (T) this.f;}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticGetter lookup.findStaticGetter(C.class,"f",FT.class)}</td>
+ * <td>{@code static}<br>{@code FT f;}</td><td>{@code (T) C.f;}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findSetter lookup.findSetter(C.class,"f",FT.class)}</td>
+ * <td>{@code FT f;}</td><td>{@code this.f = x;}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticSetter lookup.findStaticSetter(C.class,"f",FT.class)}</td>
+ * <td>{@code static}<br>{@code FT f;}</td><td>{@code C.f = arg;}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findVirtual lookup.findVirtual(C.class,"m",MT)}</td>
+ * <td>{@code T m(A*);}</td><td>{@code (T) this.m(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findStatic lookup.findStatic(C.class,"m",MT)}</td>
+ * <td>{@code static}<br>{@code T m(A*);}</td><td>{@code (T) C.m(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findSpecial lookup.findSpecial(C.class,"m",MT,this.class)}</td>
+ * <td>{@code T m(A*);}</td><td>{@code (T) super.m(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#findConstructor lookup.findConstructor(C.class,MT)}</td>
+ * <td>{@code C(A*);}</td><td>{@code new C(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectGetter lookup.unreflectGetter(aField)}</td>
+ * <td>({@code static})?<br>{@code FT f;}</td><td>{@code (FT) aField.get(thisOrNull);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectSetter lookup.unreflectSetter(aField)}</td>
+ * <td>({@code static})?<br>{@code FT f;}</td><td>{@code aField.set(thisOrNull, arg);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
+ * <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectConstructor lookup.unreflectConstructor(aConstructor)}</td>
+ * <td>{@code C(A*);}</td><td>{@code (C) aConstructor.newInstance(arg*);}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
+ * <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
+ * </tr>
+ * </table>
+ *
+ * Here, the type {@code C} is the class or interface being searched for a member,
+ * documented as a parameter named {@code refc} in the lookup methods.
+ * The method type {@code MT} is composed from the return type {@code T}
+ * and the sequence of argument types {@code A*}.
+ * The constructor also has a sequence of argument types {@code A*} and
+ * is deemed to return the newly-created object of type {@code C}.
+ * Both {@code MT} and the field type {@code FT} are documented as a parameter named {@code type}.
+ * The formal parameter {@code this} stands for the self-reference of type {@code C};
+ * if it is present, it is always the leading argument to the method handle invocation.
+ * (In the case of some {@code protected} members, {@code this} may be
+ * restricted in type to the lookup class; see below.)
+ * The name {@code arg} stands for all the other method handle arguments.
+ * In the code examples for the Core Reflection API, the name {@code thisOrNull}
+ * stands for a null reference if the accessed method or field is static,
+ * and {@code this} otherwise.
+ * The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand
+ * for reflective objects corresponding to the given members.
+ * <p>
+ * In cases where the given member is of variable arity (i.e., a method or constructor)
+ * the returned method handle will also be of {@linkplain MethodHandle#asVarargsCollector variable arity}.
+ * In all other cases, the returned method handle will be of fixed arity.
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * The equivalence between looked-up method handles and underlying
+ * class members and bytecode behaviors
+ * can break down in a few ways:
+ * <ul style="font-size:smaller;">
+ * <li>If {@code C} is not symbolically accessible from the lookup class's loader,
+ * the lookup can still succeed, even when there is no equivalent
+ * Java expression or bytecoded constant.
+ * <li>Likewise, if {@code T} or {@code MT}
+ * is not symbolically accessible from the lookup class's loader,
+ * the lookup can still succeed.
+ * For example, lookups for {@code MethodHandle.invokeExact} and
+ * {@code MethodHandle.invoke} will always succeed, regardless of requested type.
+ * <li>If there is a security manager installed, it can forbid the lookup
+ * on various grounds (<a href="MethodHandles.Lookup.html#secmgr">see below</a>).
+ * By contrast, the {@code ldc} instruction on a {@code CONSTANT_MethodHandle}
+ * constant is not subject to security manager checks.
+ * <li>If the looked-up method has a
+ * <a href="MethodHandle.html#maxarity">very large arity</a>,
+ * the method handle creation may fail, due to the method handle
+ * type having too many parameters.
+ * </ul>
+ *
+ * <h1><a name="access"></a>Access checking</h1>
+ * Access checks are applied in the factory methods of {@code Lookup},
+ * when a method handle is created.
+ * This is a key difference from the Core Reflection API, since
+ * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+ * performs access checking against every caller, on every call.
+ * <p>
+ * All access checks start from a {@code Lookup} object, which
+ * compares its recorded lookup class against all requests to
+ * create method handles.
+ * A single {@code Lookup} object can be used to create any number
+ * of access-checked method handles, all checked against a single
+ * lookup class.
+ * <p>
+ * A {@code Lookup} object can be shared with other trusted code,
+ * such as a metaobject protocol.
+ * A shared {@code Lookup} object delegates the capability
+ * to create method handles on private members of the lookup class.
+ * Even if privileged code uses the {@code Lookup} object,
+ * the access checking is confined to the privileges of the
+ * original lookup class.
+ * <p>
+ * A lookup can fail, because
+ * the containing class is not accessible to the lookup class, or
+ * because the desired class member is missing, or because the
+ * desired class member is not accessible to the lookup class, or
+ * because the lookup object is not trusted enough to access the member.
+ * In any of these cases, a {@code ReflectiveOperationException} will be
+ * thrown from the attempted lookup. The exact class will be one of
+ * the following:
+ * <ul>
+ * <li>NoSuchMethodException &mdash; if a method is requested but does not exist
+ * <li>NoSuchFieldException &mdash; if a field is requested but does not exist
+ * <li>IllegalAccessException &mdash; if the member exists but an access check fails
+ * </ul>
+ * <p>
+ * In general, the conditions under which a method handle may be
+ * looked up for a method {@code M} are no more restrictive than the conditions
+ * under which the lookup class could have compiled, verified, and resolved a call to {@code M}.
+ * Where the JVM would raise exceptions like {@code NoSuchMethodError},
+ * a method handle lookup will generally raise a corresponding
+ * checked exception, such as {@code NoSuchMethodException}.
+ * And the effect of invoking the method handle resulting from the lookup
+ * is <a href="MethodHandles.Lookup.html#equiv">exactly equivalent</a>
+ * to executing the compiled, verified, and resolved call to {@code M}.
+ * The same point is true of fields and constructors.
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * Access checks only apply to named and reflected methods,
+ * constructors, and fields.
+ * Other method handle creation methods, such as
+ * {@link MethodHandle#asType MethodHandle.asType},
+ * do not require any access checks, and are used
+ * independently of any {@code Lookup} object.
+ * <p>
+ * If the desired member is {@code protected}, the usual JVM rules apply,
+ * including the requirement that the lookup class must be either be in the
+ * same package as the desired member, or must inherit that member.
+ * (See the Java Virtual Machine Specification, sections 4.9.2, 5.4.3.5, and 6.4.)
+ * In addition, if the desired member is a non-static field or method
+ * in a different package, the resulting method handle may only be applied
+ * to objects of the lookup class or one of its subclasses.
+ * This requirement is enforced by narrowing the type of the leading
+ * {@code this} parameter from {@code C}
+ * (which will necessarily be a superclass of the lookup class)
+ * to the lookup class itself.
+ * <p>
+ * The JVM imposes a similar requirement on {@code invokespecial} instruction,
+ * that the receiver argument must match both the resolved method <em>and</em>
+ * the current class. Again, this requirement is enforced by narrowing the
+ * type of the leading parameter to the resulting method handle.
+ * (See the Java Virtual Machine Specification, section 4.10.1.9.)
+ * <p>
+ * The JVM represents constructors and static initializer blocks as internal methods
+ * with special names ({@code "<init>"} and {@code "<clinit>"}).
+ * The internal syntax of invocation instructions allows them to refer to such internal
+ * methods as if they were normal methods, but the JVM bytecode verifier rejects them.
+ * A lookup of such an internal method will produce a {@code NoSuchMethodException}.
+ * <p>
+ * In some cases, access between nested classes is obtained by the Java compiler by creating
+ * an wrapper method to access a private method of another class
+ * in the same top-level declaration.
+ * For example, a nested class {@code C.D}
+ * can access private members within other related classes such as
+ * {@code C}, {@code C.D.E}, or {@code C.B},
+ * but the Java compiler may need to generate wrapper methods in
+ * those related classes. In such cases, a {@code Lookup} object on
+ * {@code C.E} would be unable to those private members.
+ * A workaround for this limitation is the {@link Lookup#in Lookup.in} method,
+ * which can transform a lookup on {@code C.E} into one on any of those other
+ * classes, without special elevation of privilege.
+ * <p>
+ * The accesses permitted to a given lookup object may be limited,
+ * according to its set of {@link #lookupModes lookupModes},
+ * to a subset of members normally accessible to the lookup class.
+ * For example, the {@link #publicLookup publicLookup}
+ * method produces a lookup object which is only allowed to access
+ * public members in public classes.
+ * The caller sensitive method {@link #lookup lookup}
+ * produces a lookup object with full capabilities relative to
+ * its caller class, to emulate all supported bytecode behaviors.
+ * Also, the {@link Lookup#in Lookup.in} method may produce a lookup object
+ * with fewer access modes than the original lookup object.
+ *
+ * <p style="font-size:smaller;">
+ * <a name="privacc"></a>
+ * <em>Discussion of private access:</em>
+ * We say that a lookup has <em>private access</em>
+ * if its {@linkplain #lookupModes lookup modes}
+ * include the possibility of accessing {@code private} members.
+ * As documented in the relevant methods elsewhere,
+ * only lookups with private access possess the following capabilities:
+ * <ul style="font-size:smaller;">
+ * <li>access private fields, methods, and constructors of the lookup class
+ * <li>create method handles which invoke <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a> methods,
+ * such as {@code Class.forName}
+ * <li>create method handles which {@link Lookup#findSpecial emulate invokespecial} instructions
+ * <li>avoid <a href="MethodHandles.Lookup.html#secmgr">package access checks</a>
+ * for classes accessible to the lookup class
+ * <li>create {@link Lookup#in delegated lookup objects} which have private access to other classes
+ * within the same package member
+ * </ul>
+ * <p style="font-size:smaller;">
+ * Each of these permissions is a consequence of the fact that a lookup object
+ * with private access can be securely traced back to an originating class,
+ * whose <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> and Java language access permissions
+ * can be reliably determined and emulated by method handles.
+ *
+ * <h1><a name="secmgr"></a>Security manager interactions</h1>
+ * Although bytecode instructions can only refer to classes in
+ * a related class loader, this API can search for methods in any
+ * class, as long as a reference to its {@code Class} object is
+ * available. Such cross-loader references are also possible with the
+ * Core Reflection API, and are impossible to bytecode instructions
+ * such as {@code invokestatic} or {@code getfield}.
+ * There is a {@linkplain java.lang.SecurityManager security manager API}
+ * to allow applications to check such cross-loader references.
+ * These checks apply to both the {@code MethodHandles.Lookup} API
+ * and the Core Reflection API
+ * (as found on {@link java.lang.Class Class}).
+ * <p>
+ * If a security manager is present, member lookups are subject to
+ * additional checks.
+ * From one to three calls are made to the security manager.
+ * Any of these calls can refuse access by throwing a
+ * {@link java.lang.SecurityException SecurityException}.
+ * Define {@code smgr} as the security manager,
+ * {@code lookc} as the lookup class of the current lookup object,
+ * {@code refc} as the containing class in which the member
+ * is being sought, and {@code defc} as the class in which the
+ * member is actually defined.
+ * The value {@code lookc} is defined as <em>not present</em>
+ * if the current lookup object does not have
+ * <a href="MethodHandles.Lookup.html#privacc">private access</a>.
+ * The calls are made according to the following rules:
+ * <ul>
+ * <li><b>Step 1:</b>
+ * If {@code lookc} is not present, or if its class loader is not
+ * the same as or an ancestor of the class loader of {@code refc},
+ * then {@link SecurityManager#checkPackageAccess
+ * smgr.checkPackageAccess(refcPkg)} is called,
+ * where {@code refcPkg} is the package of {@code refc}.
+ * <li><b>Step 2:</b>
+ * If the retrieved member is not public and
+ * {@code lookc} is not present, then
+ * {@link SecurityManager#checkPermission smgr.checkPermission}
+ * with {@code RuntimePermission("accessDeclaredMembers")} is called.
+ * <li><b>Step 3:</b>
+ * If the retrieved member is not public,
+ * and if {@code lookc} is not present,
+ * and if {@code defc} and {@code refc} are different,
+ * then {@link SecurityManager#checkPackageAccess
+ * smgr.checkPackageAccess(defcPkg)} is called,
+ * where {@code defcPkg} is the package of {@code defc}.
+ * </ul>
+ * Security checks are performed after other access checks have passed.
+ * Therefore, the above rules presuppose a member that is public,
+ * or else that is being accessed from a lookup class that has
+ * rights to access the member.
+ *
+ * <h1><a name="callsens"></a>Caller sensitive methods</h1>
+ * A small number of Java methods have a special property called caller sensitivity.
+ * A <em>caller-sensitive</em> method can behave differently depending on the
+ * identity of its immediate caller.
+ * <p>
+ * If a method handle for a caller-sensitive method is requested,
+ * the general rules for <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> apply,
+ * but they take account of the lookup class in a special way.
+ * The resulting method handle behaves as if it were called
+ * from an instruction contained in the lookup class,
+ * so that the caller-sensitive method detects the lookup class.
+ * (By contrast, the invoker of the method handle is disregarded.)
+ * Thus, in the case of caller-sensitive methods,
+ * different lookup classes may give rise to
+ * differently behaving method handles.
+ * <p>
+ * In cases where the lookup object is
+ * {@link #publicLookup publicLookup()},
+ * or some other lookup object without
+ * <a href="MethodHandles.Lookup.html#privacc">private access</a>,
+ * the lookup class is disregarded.
+ * In such cases, no caller-sensitive method handle can be created,
+ * access is forbidden, and the lookup fails with an
+ * {@code IllegalAccessException}.
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * For example, the caller-sensitive method
+ * {@link java.lang.Class#forName(String) Class.forName(x)}
+ * can return varying classes or throw varying exceptions,
+ * depending on the class loader of the class that calls it.
+ * A public lookup of {@code Class.forName} will fail, because
+ * there is no reasonable way to determine its bytecode behavior.
+ * <p style="font-size:smaller;">
+ * If an application caches method handles for broad sharing,
+ * it should use {@code publicLookup()} to create them.
+ * If there is a lookup of {@code Class.forName}, it will fail,
+ * and the application must take appropriate action in that case.
+ * It may be that a later lookup, perhaps during the invocation of a
+ * bootstrap method, can incorporate the specific identity
+ * of the caller, making the method accessible.
+ * <p style="font-size:smaller;">
+ * The function {@code MethodHandles.lookup} is caller sensitive
+ * so that there can be a secure foundation for lookups.
+ * Nearly all other methods in the JSR 292 API rely on lookup
+ * objects to check access requests.
+ */
+ // Android-changed: Change link targets from MethodHandles#[public]Lookup to
+ // #[public]Lookup to work around complaints from javadoc.
public static final
class Lookup {
- public static final int PUBLIC = 0;
+ /** The class on behalf of whom the lookup is being performed. */
+ /* @NonNull */ private final Class<?> lookupClass;
+
+ /** The allowed sorts of members which may be looked up (PUBLIC, etc.). */
+ private final int allowedModes;
+
+ /** A single-bit mask representing {@code public} access,
+ * which may contribute to the result of {@link #lookupModes lookupModes}.
+ * The value, {@code 0x01}, happens to be the same as the value of the
+ * {@code public} {@linkplain java.lang.reflect.Modifier#PUBLIC modifier bit}.
+ */
+ public static final int PUBLIC = Modifier.PUBLIC;
+
+ /** A single-bit mask representing {@code private} access,
+ * which may contribute to the result of {@link #lookupModes lookupModes}.
+ * The value, {@code 0x02}, happens to be the same as the value of the
+ * {@code private} {@linkplain java.lang.reflect.Modifier#PRIVATE modifier bit}.
+ */
+ public static final int PRIVATE = Modifier.PRIVATE;
+
+ /** A single-bit mask representing {@code protected} access,
+ * which may contribute to the result of {@link #lookupModes lookupModes}.
+ * The value, {@code 0x04}, happens to be the same as the value of the
+ * {@code protected} {@linkplain java.lang.reflect.Modifier#PROTECTED modifier bit}.
+ */
+ public static final int PROTECTED = Modifier.PROTECTED;
+
+ /** A single-bit mask representing {@code package} access (default access),
+ * which may contribute to the result of {@link #lookupModes lookupModes}.
+ * The value is {@code 0x08}, which does not correspond meaningfully to
+ * any particular {@linkplain java.lang.reflect.Modifier modifier bit}.
+ */
+ public static final int PACKAGE = Modifier.STATIC;
+
+ private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE);
+
+ // Android-note: Android has no notion of a trusted lookup. If required, such lookups
+ // are performed by the runtime. As a result, we always use lookupClass, which will always
+ // be non-null in our implementation.
+ //
+ // private static final int TRUSTED = -1;
+
+ private static int fixmods(int mods) {
+ mods &= (ALL_MODES - PACKAGE);
+ return (mods != 0) ? mods : PACKAGE;
+ }
+
+ /** Tells which class is performing the lookup. It is this class against
+ * which checks are performed for visibility and access permissions.
+ * <p>
+ * The class implies a maximum level of access permission,
+ * but the permissions may be additionally limited by the bitmask
+ * {@link #lookupModes lookupModes}, which controls whether non-public members
+ * can be accessed.
+ * @return the lookup class, on behalf of which this lookup object finds members
+ */
+ public Class<?> lookupClass() {
+ return lookupClass;
+ }
+
+ /** Tells which access-protection classes of members this lookup object can produce.
+ * The result is a bit-mask of the bits
+ * {@linkplain #PUBLIC PUBLIC (0x01)},
+ * {@linkplain #PRIVATE PRIVATE (0x02)},
+ * {@linkplain #PROTECTED PROTECTED (0x04)},
+ * and {@linkplain #PACKAGE PACKAGE (0x08)}.
+ * <p>
+ * A freshly-created lookup object
+ * on the {@linkplain java.lang.invoke.MethodHandles#lookup() caller's class}
+ * has all possible bits set, since the caller class can access all its own members.
+ * A lookup object on a new lookup class
+ * {@linkplain java.lang.invoke.MethodHandles.Lookup#in created from a previous lookup object}
+ * may have some mode bits set to zero.
+ * The purpose of this is to restrict access via the new lookup object,
+ * so that it can access only names which can be reached by the original
+ * lookup object, and also by the new lookup class.
+ * @return the lookup modes, which limit the kinds of access performed by this lookup object
+ */
+ public int lookupModes() {
+ return allowedModes & ALL_MODES;
+ }
+
+ /** Embody the current class (the lookupClass) as a lookup class
+ * for method handle creation.
+ * Must be called by from a method in this package,
+ * which in turn is called by a method not in this package.
+ */
+ Lookup(Class<?> lookupClass) {
+ this(lookupClass, ALL_MODES);
+ // make sure we haven't accidentally picked up a privileged class:
+ checkUnprivilegedlookupClass(lookupClass, ALL_MODES);
+ }
+
+ private Lookup(Class<?> lookupClass, int allowedModes) {
+ this.lookupClass = lookupClass;
+ this.allowedModes = allowedModes;
+ }
- public static final int PRIVATE = 0;
+ /**
+ * Creates a lookup on the specified new lookup class.
+ * The resulting object will report the specified
+ * class as its own {@link #lookupClass lookupClass}.
+ * <p>
+ * However, the resulting {@code Lookup} object is guaranteed
+ * to have no more access capabilities than the original.
+ * In particular, access capabilities can be lost as follows:<ul>
+ * <li>If the new lookup class differs from the old one,
+ * protected members will not be accessible by virtue of inheritance.
+ * (Protected members may continue to be accessible because of package sharing.)
+ * <li>If the new lookup class is in a different package
+ * than the old one, protected and default (package) members will not be accessible.
+ * <li>If the new lookup class is not within the same package member
+ * as the old one, private members will not be accessible.
+ * <li>If the new lookup class is not accessible to the old lookup class,
+ * then no members, not even public members, will be accessible.
+ * (In all other cases, public members will continue to be accessible.)
+ * </ul>
+ *
+ * @param requestedLookupClass the desired lookup class for the new lookup object
+ * @return a lookup object which reports the desired lookup class
+ * @throws NullPointerException if the argument is null
+ */
+ public Lookup in(Class<?> requestedLookupClass) {
+ requestedLookupClass.getClass(); // null check
+ // Android-changed: There's no notion of a trusted lookup.
+ // if (allowedModes == TRUSTED) // IMPL_LOOKUP can make any lookup at all
+ // return new Lookup(requestedLookupClass, ALL_MODES);
- public static final int PROTECTED = 0;
+ if (requestedLookupClass == this.lookupClass)
+ return this; // keep same capabilities
+ int newModes = (allowedModes & (ALL_MODES & ~PROTECTED));
+ if ((newModes & PACKAGE) != 0
+ && !VerifyAccess.isSamePackage(this.lookupClass, requestedLookupClass)) {
+ newModes &= ~(PACKAGE|PRIVATE);
+ }
+ // Allow nestmate lookups to be created without special privilege:
+ if ((newModes & PRIVATE) != 0
+ && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) {
+ newModes &= ~PRIVATE;
+ }
+ if ((newModes & PUBLIC) != 0
+ && !VerifyAccess.isClassAccessible(requestedLookupClass, this.lookupClass, allowedModes)) {
+ // The requested class it not accessible from the lookup class.
+ // No permissions.
+ newModes = 0;
+ }
+ checkUnprivilegedlookupClass(requestedLookupClass, newModes);
+ return new Lookup(requestedLookupClass, newModes);
+ }
- public static final int PACKAGE = 0;
+ // Make sure outer class is initialized first.
+ //
+ // Android-changed: Removed unnecessary reference to IMPL_NAMES.
+ // static { IMPL_NAMES.getClass(); }
- public Class<?> lookupClass() { return null; }
+ /** Version of lookup which is trusted minimally.
+ * It can only be used to create method handles to
+ * publicly accessible members.
+ */
+ static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, PUBLIC);
- public int lookupModes() { return 0; }
+ /** Package-private version of lookup which is trusted. */
+ static final Lookup IMPL_LOOKUP = new Lookup(Object.class, ALL_MODES);
- public Lookup in(Class<?> requestedLookupClass) { return null; }
+ private static void checkUnprivilegedlookupClass(Class<?> lookupClass, int allowedModes) {
+ String name = lookupClass.getName();
+ if (name.startsWith("java.lang.invoke."))
+ throw newIllegalArgumentException("illegal lookupClass: "+lookupClass);
+ // For caller-sensitive MethodHandles.lookup()
+ // disallow lookup more restricted packages
+ //
+ // Android-changed: The bootstrap classloader isn't null.
+ if (allowedModes == ALL_MODES &&
+ lookupClass.getClassLoader() == Object.class.getClassLoader()) {
+ if (name.startsWith("java.") ||
+ (name.startsWith("sun.")
+ && !name.startsWith("sun.invoke.")
+ && !name.equals("sun.reflect.ReflectionFactory"))) {
+ throw newIllegalArgumentException("illegal lookupClass: " + lookupClass);
+ }
+ }
+ }
+
+ /**
+ * Displays the name of the class from which lookups are to be made.
+ * (The name is the one reported by {@link java.lang.Class#getName() Class.getName}.)
+ * If there are restrictions on the access permitted to this lookup,
+ * this is indicated by adding a suffix to the class name, consisting
+ * of a slash and a keyword. The keyword represents the strongest
+ * allowed access, and is chosen as follows:
+ * <ul>
+ * <li>If no access is allowed, the suffix is "/noaccess".
+ * <li>If only public access is allowed, the suffix is "/public".
+ * <li>If only public and package access are allowed, the suffix is "/package".
+ * <li>If only public, package, and private access are allowed, the suffix is "/private".
+ * </ul>
+ * If none of the above cases apply, it is the case that full
+ * access (public, package, private, and protected) is allowed.
+ * In this case, no suffix is added.
+ * This is true only of an object obtained originally from
+ * {@link java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}.
+ * Objects created by {@link java.lang.invoke.MethodHandles.Lookup#in Lookup.in}
+ * always have restricted access, and will display a suffix.
+ * <p>
+ * (It may seem strange that protected access should be
+ * stronger than private access. Viewed independently from
+ * package access, protected access is the first to be lost,
+ * because it requires a direct subclass relationship between
+ * caller and callee.)
+ * @see #in
+ */
+ @Override
+ public String toString() {
+ String cname = lookupClass.getName();
+ switch (allowedModes) {
+ case 0: // no privileges
+ return cname + "/noaccess";
+ case PUBLIC:
+ return cname + "/public";
+ case PUBLIC|PACKAGE:
+ return cname + "/package";
+ case ALL_MODES & ~PROTECTED:
+ return cname + "/private";
+ case ALL_MODES:
+ return cname;
+ // Android-changed: No support for TRUSTED callers.
+ // case TRUSTED:
+ // return "/trusted"; // internal only; not exported
+ default: // Should not happen, but it's a bitfield...
+ cname = cname + "/" + Integer.toHexString(allowedModes);
+ assert(false) : cname;
+ return cname;
+ }
+ }
+
+ /**
+ * Produces a method handle for a static method.
+ * The type of the method handle will be that of the method.
+ * (Since static methods do not take receivers, there is no
+ * additional receiver argument inserted into the method handle type,
+ * as there would be with {@link #findVirtual findVirtual} or {@link #findSpecial findSpecial}.)
+ * The method and all its argument types must be accessible to the lookup object.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * If the returned method handle is invoked, the method's class will
+ * be initialized, if it has not already been initialized.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_asList = publicLookup().findStatic(Arrays.class,
+ "asList", methodType(List.class, Object[].class));
+assertEquals("[x, y]", MH_asList.invoke("x", "y").toString());
+ * }</pre></blockquote>
+ * @param refc the class from which the method is accessed
+ * @param name the name of the method
+ * @param type the type of the method
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws IllegalAccessException if access checking fails,
+ * or if the method is not {@code static},
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
public
- MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
+ MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ Method method = refc.getDeclaredMethod(name, type.ptypes());
+ final int modifiers = method.getModifiers();
+ if (!Modifier.isStatic(modifiers)) {
+ throw new IllegalAccessException("Method" + method + " is not static");
+ }
+ checkReturnType(method, type);
+ checkAccess(refc, method.getDeclaringClass(), modifiers, method.getName());
+ return createMethodHandle(method, MethodHandle.INVOKE_STATIC, type);
+ }
+
+ private MethodHandle findVirtualForMH(String name, MethodType type) {
+ // these names require special lookups because of the implicit MethodType argument
+ if ("invoke".equals(name))
+ return invoker(type);
+ if ("invokeExact".equals(name))
+ return exactInvoker(type);
+ return null;
+ }
+
+ private static MethodHandle createMethodHandle(Method method, int handleKind,
+ MethodType methodType) {
+ MethodHandle mh = new MethodHandleImpl(method.getArtMethod(), handleKind, methodType);
+ if (method.isVarArgs()) {
+ return new Transformers.VarargsCollector(mh);
+ } else {
+ return mh;
+ }
+ }
+
+ /**
+ * Produces a method handle for a virtual method.
+ * The type of the method handle will be that of the method,
+ * with the receiver type (usually {@code refc}) prepended.
+ * The method and all its argument types must be accessible to the lookup object.
+ * <p>
+ * When called, the handle will treat the first argument as a receiver
+ * and dispatch on the receiver's type to determine which method
+ * implementation to enter.
+ * (The dispatching action is identical with that performed by an
+ * {@code invokevirtual} or {@code invokeinterface} instruction.)
+ * <p>
+ * The first argument will be of type {@code refc} if the lookup
+ * class has full privileges to access the member. Otherwise
+ * the member must be {@code protected} and the first argument
+ * will be restricted in type to the lookup class.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * Because of the general <a href="MethodHandles.Lookup.html#equiv">equivalence</a> between {@code invokevirtual}
+ * instructions and method handles produced by {@code findVirtual},
+ * if the class is {@code MethodHandle} and the name string is
+ * {@code invokeExact} or {@code invoke}, the resulting
+ * method handle is equivalent to one produced by
+ * {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker} or
+ * {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}
+ * with the same {@code type} argument.
+ *
+ * <b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_concat = publicLookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+MethodHandle MH_hashCode = publicLookup().findVirtual(Object.class,
+ "hashCode", methodType(int.class));
+MethodHandle MH_hashCode_String = publicLookup().findVirtual(String.class,
+ "hashCode", methodType(int.class));
+assertEquals("xy", (String) MH_concat.invokeExact("x", "y"));
+assertEquals("xy".hashCode(), (int) MH_hashCode.invokeExact((Object)"xy"));
+assertEquals("xy".hashCode(), (int) MH_hashCode_String.invokeExact("xy"));
+// interface method:
+MethodHandle MH_subSequence = publicLookup().findVirtual(CharSequence.class,
+ "subSequence", methodType(CharSequence.class, int.class, int.class));
+assertEquals("def", MH_subSequence.invoke("abcdefghi", 3, 6).toString());
+// constructor "internal method" must be accessed differently:
+MethodType MT_newString = methodType(void.class); //()V for new String()
+try { assertEquals("impossible", lookup()
+ .findVirtual(String.class, "<init>", MT_newString));
+ } catch (NoSuchMethodException ex) { } // OK
+MethodHandle MH_newString = publicLookup()
+ .findConstructor(String.class, MT_newString);
+assertEquals("", (String) MH_newString.invokeExact());
+ * }</pre></blockquote>
+ *
+ * @param refc the class or interface from which the method is accessed
+ * @param name the name of the method
+ * @param type the type of the method, with the receiver argument omitted
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws IllegalAccessException if access checking fails,
+ * or if the method is {@code static}
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ // Special case : when we're looking up a virtual method on the MethodHandles class
+ // itself, we can return one of our specialized invokers.
+ if (refc == MethodHandle.class) {
+ MethodHandle mh = findVirtualForMH(name, type);
+ if (mh != null) {
+ return mh;
+ }
+ }
+ // BEGIN Android-changed: Added VarHandle case here.
+ // Implementation to follow. TODO(b/65872996)
+ if (refc == VarHandle.class) {
+ unsupported("MethodHandles.findVirtual with refc == VarHandle.class");
+ return null;
+ }
+ // END Android-changed: Added VarHandle handling here.
+
+ Method method = refc.getInstanceMethod(name, type.ptypes());
+ if (method == null) {
+ // This is pretty ugly and a consequence of the MethodHandles API. We have to throw
+ // an IAE and not an NSME if the method exists but is static (even though the RI's
+ // IAE has a message that says "no such method"). We confine the ugliness and
+ // slowness to the failure case, and allow getInstanceMethod to remain fairly
+ // general.
+ try {
+ Method m = refc.getDeclaredMethod(name, type.ptypes());
+ if (Modifier.isStatic(m.getModifiers())) {
+ throw new IllegalAccessException("Method" + m + " is static");
+ }
+ } catch (NoSuchMethodException ignored) {
+ }
+
+ throw new NoSuchMethodException(name + " " + Arrays.toString(type.ptypes()));
+ }
+ checkReturnType(method, type);
+
+ // We have a valid method, perform access checks.
+ checkAccess(refc, method.getDeclaringClass(), method.getModifiers(), method.getName());
+
+ // Insert the leading reference parameter.
+ MethodType handleType = type.insertParameterTypes(0, refc);
+ return createMethodHandle(method, MethodHandle.INVOKE_VIRTUAL, handleType);
+ }
+
+ /**
+ * Produces a method handle which creates an object and initializes it, using
+ * the constructor of the specified type.
+ * The parameter types of the method handle will be those of the constructor,
+ * while the return type will be a reference to the constructor's class.
+ * The constructor and all its argument types must be accessible to the lookup object.
+ * <p>
+ * The requested type must have a return type of {@code void}.
+ * (This is consistent with the JVM's treatment of constructor type descriptors.)
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the constructor's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * If the returned method handle is invoked, the constructor's class will
+ * be initialized, if it has not already been initialized.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_newArrayList = publicLookup().findConstructor(
+ ArrayList.class, methodType(void.class, Collection.class));
+Collection orig = Arrays.asList("x", "y");
+Collection copy = (ArrayList) MH_newArrayList.invokeExact(orig);
+assert(orig != copy);
+assertEquals(orig, copy);
+// a variable-arity constructor:
+MethodHandle MH_newProcessBuilder = publicLookup().findConstructor(
+ ProcessBuilder.class, methodType(void.class, String[].class));
+ProcessBuilder pb = (ProcessBuilder)
+ MH_newProcessBuilder.invoke("x", "y", "z");
+assertEquals("[x, y, z]", pb.command().toString());
+ * }</pre></blockquote>
+ * @param refc the class or interface from which the method is accessed
+ * @param type the type of the method, with the receiver argument omitted, and a void return type
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the constructor does not exist
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ if (refc.isArray()) {
+ throw new NoSuchMethodException("no constructor for array class: " + refc.getName());
+ }
+ // The queried |type| is (PT1,PT2,..)V
+ Constructor constructor = refc.getDeclaredConstructor(type.ptypes());
+ if (constructor == null) {
+ throw new NoSuchMethodException(
+ "No constructor for " + constructor.getDeclaringClass() + " matching " + type);
+ }
+ checkAccess(refc, constructor.getDeclaringClass(), constructor.getModifiers(),
+ constructor.getName());
+
+ return createMethodHandleForConstructor(constructor);
+ }
- public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
+ private MethodHandle createMethodHandleForConstructor(Constructor constructor) {
+ Class<?> refc = constructor.getDeclaringClass();
+ MethodType constructorType =
+ MethodType.methodType(refc, constructor.getParameterTypes());
+ MethodHandle mh;
+ if (refc == String.class) {
+ // String constructors have optimized StringFactory methods
+ // that matches returned type. These factory methods combine the
+ // memory allocation and initialization calls for String objects.
+ mh = new MethodHandleImpl(constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT,
+ constructorType);
+ } else {
+ // Constructors for all other classes use a Construct transformer to perform
+ // their memory allocation and call to <init>.
+ MethodType initType = initMethodType(constructorType);
+ MethodHandle initHandle = new MethodHandleImpl(
+ constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT, initType);
+ mh = new Transformers.Construct(initHandle, constructorType);
+ }
- public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
+ if (constructor.isVarArgs()) {
+ mh = new Transformers.VarargsCollector(mh);
+ }
+ return mh;
+ }
+ private static MethodType initMethodType(MethodType constructorType) {
+ // Returns a MethodType appropriate for class <init>
+ // methods. Constructor MethodTypes have the form
+ // (PT1,PT2,...)C and class <init> MethodTypes have the
+ // form (C,PT1,PT2,...)V.
+ assert constructorType.rtype() != void.class;
+
+ // Insert constructorType C as the first parameter type in
+ // the MethodType for <init>.
+ Class<?> [] initPtypes = new Class<?> [constructorType.ptypes().length + 1];
+ initPtypes[0] = constructorType.rtype();
+ System.arraycopy(constructorType.ptypes(), 0, initPtypes, 1,
+ constructorType.ptypes().length);
+
+ // Set the return type for the <init> MethodType to be void.
+ return MethodType.methodType(void.class, initPtypes);
+ }
+
+ /**
+ * Produces an early-bound method handle for a virtual method.
+ * It will bypass checks for overriding methods on the receiver,
+ * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
+ * instruction from within the explicitly specified {@code specialCaller}.
+ * The type of the method handle will be that of the method,
+ * with a suitably restricted receiver type prepended.
+ * (The receiver type will be {@code specialCaller} or a subtype.)
+ * The method and all its argument types must be accessible
+ * to the lookup object.
+ * <p>
+ * Before method resolution,
+ * if the explicitly specified caller class is not identical with the
+ * lookup class, or if this lookup object does not have
+ * <a href="MethodHandles.Lookup.html#privacc">private access</a>
+ * privileges, the access fails.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p style="font-size:smaller;">
+ * <em>(Note: JVM internal methods named {@code "<init>"} are not visible to this API,
+ * even though the {@code invokespecial} instruction can refer to them
+ * in special circumstances. Use {@link #findConstructor findConstructor}
+ * to access instance initialization methods in a safe manner.)</em>
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+static class Listie extends ArrayList {
+ public String toString() { return "[wee Listie]"; }
+ static Lookup lookup() { return MethodHandles.lookup(); }
+}
+...
+// no access to constructor via invokeSpecial:
+MethodHandle MH_newListie = Listie.lookup()
+ .findConstructor(Listie.class, methodType(void.class));
+Listie l = (Listie) MH_newListie.invokeExact();
+try { assertEquals("impossible", Listie.lookup().findSpecial(
+ Listie.class, "<init>", methodType(void.class), Listie.class));
+ } catch (NoSuchMethodException ex) { } // OK
+// access to super and self methods via invokeSpecial:
+MethodHandle MH_super = Listie.lookup().findSpecial(
+ ArrayList.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_this = Listie.lookup().findSpecial(
+ Listie.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_duper = Listie.lookup().findSpecial(
+ Object.class, "toString" , methodType(String.class), Listie.class);
+assertEquals("[]", (String) MH_super.invokeExact(l));
+assertEquals(""+l, (String) MH_this.invokeExact(l));
+assertEquals("[]", (String) MH_duper.invokeExact(l)); // ArrayList method
+try { assertEquals("inaccessible", Listie.lookup().findSpecial(
+ String.class, "toString", methodType(String.class), Listie.class));
+ } catch (IllegalAccessException ex) { } // OK
+Listie subl = new Listie() { public String toString() { return "[subclass]"; } };
+assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method
+ * }</pre></blockquote>
+ *
+ * @param refc the class or interface from which the method is accessed
+ * @param name the name of the method (which must not be "&lt;init&gt;")
+ * @param type the type of the method, with the receiver argument omitted
+ * @param specialCaller the proposed calling class to perform the {@code invokespecial}
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
public MethodHandle findSpecial(Class<?> refc, String name, MethodType type,
- Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException { return null; }
+ Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
+ if (specialCaller == null) {
+ throw new NullPointerException("specialCaller == null");
+ }
+
+ if (type == null) {
+ throw new NullPointerException("type == null");
+ }
+
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+
+ if (refc == null) {
+ throw new NullPointerException("ref == null");
+ }
+
+ // Make sure that the special caller is identical to the lookup class or that we have
+ // private access.
+ checkSpecialCaller(specialCaller);
+
+ // Even though constructors are invoked using a "special" invoke, handles to them can't
+ // be created using findSpecial. Callers must use findConstructor instead. Similarly,
+ // there is no path for calling static class initializers.
+ if (name.startsWith("<")) {
+ throw new NoSuchMethodException(name + " is not a valid method name.");
+ }
+
+ Method method = refc.getDeclaredMethod(name, type.ptypes());
+ checkReturnType(method, type);
+ return findSpecial(method, type, refc, specialCaller);
+ }
+
+ private MethodHandle findSpecial(Method method, MethodType type,
+ Class<?> refc, Class<?> specialCaller)
+ throws IllegalAccessException {
+ if (Modifier.isStatic(method.getModifiers())) {
+ throw new IllegalAccessException("expected a non-static method:" + method);
+ }
+
+ if (Modifier.isPrivate(method.getModifiers())) {
+ // Since this is a private method, we'll need to also make sure that the
+ // lookup class is the same as the refering class. We've already checked that
+ // the specialCaller is the same as the special lookup class, both of these must
+ // be the same as the declaring class(*) in order to access the private method.
+ //
+ // (*) Well, this isn't true for nested classes but OpenJDK doesn't support those
+ // either.
+ if (refc != lookupClass()) {
+ throw new IllegalAccessException("no private access for invokespecial : "
+ + refc + ", from" + this);
+ }
+
+ // This is a private method, so there's nothing special to do.
+ MethodType handleType = type.insertParameterTypes(0, refc);
+ return createMethodHandle(method, MethodHandle.INVOKE_DIRECT, handleType);
+ }
+
+ // This is a public, protected or package-private method, which means we're expecting
+ // invoke-super semantics. We'll have to restrict the receiver type appropriately on the
+ // handle once we check that there really is a "super" relationship between them.
+ if (!method.getDeclaringClass().isAssignableFrom(specialCaller)) {
+ throw new IllegalAccessException(refc + "is not assignable from " + specialCaller);
+ }
+
+ // Note that we restrict the receiver to "specialCaller" instances.
+ MethodType handleType = type.insertParameterTypes(0, specialCaller);
+ return createMethodHandle(method, MethodHandle.INVOKE_SUPER, handleType);
+ }
+
+ /**
+ * Produces a method handle giving read access to a non-static field.
+ * The type of the method handle will have a return type of the field's
+ * value type.
+ * The method handle's single argument will be the instance containing
+ * the field.
+ * Access checking is performed immediately on behalf of the lookup class.
+ * @param refc the class or interface from which the method is accessed
+ * @param name the field's name
+ * @param type the field's type
+ * @return a method handle which can load values from the field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ return findAccessor(refc, name, type, MethodHandle.IGET);
+ }
+
+ private MethodHandle findAccessor(Class<?> refc, String name, Class<?> type, int kind)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = refc.getDeclaredField(name);
+ final Class<?> fieldType = field.getType();
+ if (fieldType != type) {
+ throw new NoSuchFieldException(
+ "Field has wrong type: " + fieldType + " != " + type);
+ }
+
+ return findAccessor(field, refc, type, kind, true /* performAccessChecks */);
+ }
+
+ private MethodHandle findAccessor(Field field, Class<?> refc, Class<?> fieldType, int kind,
+ boolean performAccessChecks)
+ throws IllegalAccessException {
+ if (!performAccessChecks) {
+ checkAccess(refc, field.getDeclaringClass(), field.getModifiers(), field.getName());
+ }
+
+ final boolean isStaticKind = kind == MethodHandle.SGET || kind == MethodHandle.SPUT;
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) != isStaticKind) {
+ String reason = "Field " + field + " is " +
+ (isStaticKind ? "not " : "") + "static";
+ throw new IllegalAccessException(reason);
+ }
+
+ final boolean isSetterKind = kind == MethodHandle.IPUT || kind == MethodHandle.SPUT;
+ if (Modifier.isFinal(modifiers) && isSetterKind) {
+ throw new IllegalAccessException("Field " + field + " is final");
+ }
+
+ final MethodType methodType;
+ switch (kind) {
+ case MethodHandle.SGET:
+ methodType = MethodType.methodType(fieldType);
+ break;
+ case MethodHandle.SPUT:
+ methodType = MethodType.methodType(void.class, fieldType);
+ break;
+ case MethodHandle.IGET:
+ methodType = MethodType.methodType(fieldType, refc);
+ break;
+ case MethodHandle.IPUT:
+ methodType = MethodType.methodType(void.class, refc, fieldType);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid kind " + kind);
+ }
+ return new MethodHandleImpl(field.getArtField(), kind, methodType);
+ }
+
+ /**
+ * Produces a method handle giving write access to a non-static field.
+ * The type of the method handle will have a void return type.
+ * The method handle will take two arguments, the instance containing
+ * the field, and the value to be stored.
+ * The second argument will be of the field's value type.
+ * Access checking is performed immediately on behalf of the lookup class.
+ * @param refc the class or interface from which the method is accessed
+ * @param name the field's name
+ * @param type the field's type
+ * @return a method handle which can store values into the field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ return findAccessor(refc, name, type, MethodHandle.IPUT);
+ }
+
+ // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
+ /**
+ * Produces a VarHandle giving access to a non-static field {@code name}
+ * of type {@code type} declared in a class of type {@code recv}.
+ * The VarHandle's variable type is {@code type} and it has one
+ * coordinate type, {@code recv}.
+ * <p>
+ * Access checking is performed immediately on behalf of the lookup
+ * class.
+ * <p>
+ * Certain access modes of the returned VarHandle are unsupported under
+ * the following conditions:
+ * <ul>
+ * <li>if the field is declared {@code final}, then the write, atomic
+ * update, numeric atomic update, and bitwise atomic update access
+ * modes are unsupported.
+ * <li>if the field type is anything other than {@code byte},
+ * {@code short}, {@code char}, {@code int}, {@code long},
+ * {@code float}, or {@code double} then numeric atomic update
+ * access modes are unsupported.
+ * <li>if the field type is anything other than {@code boolean},
+ * {@code byte}, {@code short}, {@code char}, {@code int} or
+ * {@code long} then bitwise atomic update access modes are
+ * unsupported.
+ * </ul>
+ * <p>
+ * If the field is declared {@code volatile} then the returned VarHandle
+ * will override access to the field (effectively ignore the
+ * {@code volatile} declaration) in accordance to its specified
+ * access modes.
+ * <p>
+ * If the field type is {@code float} or {@code double} then numeric
+ * and atomic update access modes compare values using their bitwise
+ * representation (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @apiNote
+ * Bitwise comparison of {@code float} values or {@code double} values,
+ * as performed by the numeric and atomic update access modes, differ
+ * from the primitive {@code ==} operator and the {@link Float#equals}
+ * and {@link Double#equals} methods, specifically with respect to
+ * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+ * Care should be taken when performing a compare and set or a compare
+ * and exchange operation with such values since the operation may
+ * unexpectedly fail.
+ * There are many possible NaN values that are considered to be
+ * {@code NaN} in Java, although no IEEE 754 floating-point operation
+ * provided by Java can distinguish between them. Operation failure can
+ * occur if the expected or witness value is a NaN value and it is
+ * transformed (perhaps in a platform specific manner) into another NaN
+ * value, and thus has a different bitwise representation (see
+ * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+ * details).
+ * The values {@code -0.0} and {@code +0.0} have different bitwise
+ * representations but are considered equal when using the primitive
+ * {@code ==} operator. Operation failure can occur if, for example, a
+ * numeric algorithm computes an expected value to be say {@code -0.0}
+ * and previously computed the witness value to be say {@code +0.0}.
+ * @param recv the receiver class, of type {@code R}, that declares the
+ * non-static field
+ * @param name the field's name
+ * @param type the field's type, of type {@code T}
+ * @return a VarHandle giving access to non-static fields.
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ * @since 9
+ * @hide
+ */
+ public VarHandle findVarHandle(Class<?> recv, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ unsupported("MethodHandles.Lookup.findVarHandle()"); // TODO(b/65872996)
+ return null;
+ }
+ // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
+
+ /**
+ * Produces a method handle giving read access to a static field.
+ * The type of the method handle will have a return type of the field's
+ * value type.
+ * The method handle will take no arguments.
+ * Access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * If the returned method handle is invoked, the field's class will
+ * be initialized, if it has not already been initialized.
+ * @param refc the class or interface from which the method is accessed
+ * @param name the field's name
+ * @param type the field's type
+ * @return a method handle which can load values from the field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ return findAccessor(refc, name, type, MethodHandle.SGET);
+ }
+
+ /**
+ * Produces a method handle giving write access to a static field.
+ * The type of the method handle will have a void return type.
+ * The method handle will take a single
+ * argument, of the field's value type, the value to be stored.
+ * Access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * If the returned method handle is invoked, the field's class will
+ * be initialized, if it has not already been initialized.
+ * @param refc the class or interface from which the method is accessed
+ * @param name the field's name
+ * @param type the field's type
+ * @return a method handle which can store values into the field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ return findAccessor(refc, name, type, MethodHandle.SPUT);
+ }
+
+ // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
+ /**
+ * Produces a VarHandle giving access to a static field {@code name} of
+ * type {@code type} declared in a class of type {@code decl}.
+ * The VarHandle's variable type is {@code type} and it has no
+ * coordinate types.
+ * <p>
+ * Access checking is performed immediately on behalf of the lookup
+ * class.
+ * <p>
+ * If the returned VarHandle is operated on, the declaring class will be
+ * initialized, if it has not already been initialized.
+ * <p>
+ * Certain access modes of the returned VarHandle are unsupported under
+ * the following conditions:
+ * <ul>
+ * <li>if the field is declared {@code final}, then the write, atomic
+ * update, numeric atomic update, and bitwise atomic update access
+ * modes are unsupported.
+ * <li>if the field type is anything other than {@code byte},
+ * {@code short}, {@code char}, {@code int}, {@code long},
+ * {@code float}, or {@code double}, then numeric atomic update
+ * access modes are unsupported.
+ * <li>if the field type is anything other than {@code boolean},
+ * {@code byte}, {@code short}, {@code char}, {@code int} or
+ * {@code long} then bitwise atomic update access modes are
+ * unsupported.
+ * </ul>
+ * <p>
+ * If the field is declared {@code volatile} then the returned VarHandle
+ * will override access to the field (effectively ignore the
+ * {@code volatile} declaration) in accordance to its specified
+ * access modes.
+ * <p>
+ * If the field type is {@code float} or {@code double} then numeric
+ * and atomic update access modes compare values using their bitwise
+ * representation (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @apiNote
+ * Bitwise comparison of {@code float} values or {@code double} values,
+ * as performed by the numeric and atomic update access modes, differ
+ * from the primitive {@code ==} operator and the {@link Float#equals}
+ * and {@link Double#equals} methods, specifically with respect to
+ * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+ * Care should be taken when performing a compare and set or a compare
+ * and exchange operation with such values since the operation may
+ * unexpectedly fail.
+ * There are many possible NaN values that are considered to be
+ * {@code NaN} in Java, although no IEEE 754 floating-point operation
+ * provided by Java can distinguish between them. Operation failure can
+ * occur if the expected or witness value is a NaN value and it is
+ * transformed (perhaps in a platform specific manner) into another NaN
+ * value, and thus has a different bitwise representation (see
+ * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+ * details).
+ * The values {@code -0.0} and {@code +0.0} have different bitwise
+ * representations but are considered equal when using the primitive
+ * {@code ==} operator. Operation failure can occur if, for example, a
+ * numeric algorithm computes an expected value to be say {@code -0.0}
+ * and previously computed the witness value to be say {@code +0.0}.
+ * @param decl the class that declares the static field
+ * @param name the field's name
+ * @param type the field's type, of type {@code T}
+ * @return a VarHandle giving access to a static field
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ * @since 9
+ * @hide
+ */
+ public VarHandle findStaticVarHandle(Class<?> decl, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+ unsupported("MethodHandles.Lookup.findStaticVarHandle()"); // TODO(b/65872996)
+ return null;
+ }
+ // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
+
+ /**
+ * Produces an early-bound method handle for a non-static method.
+ * The receiver must have a supertype {@code defc} in which a method
+ * of the given name and type is accessible to the lookup class.
+ * The method and all its argument types must be accessible to the lookup object.
+ * The type of the method handle will be that of the method,
+ * without any insertion of an additional receiver parameter.
+ * The given receiver will be bound into the method handle,
+ * so that every call to the method handle will invoke the
+ * requested method on the given receiver.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set
+ * <em>and</em> the trailing array argument is not the only argument.
+ * (If the trailing array argument is the only argument,
+ * the given receiver value will be bound to it.)
+ * <p>
+ * This is equivalent to the following code:
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle mh0 = lookup().findVirtual(defc, name, type);
+MethodHandle mh1 = mh0.bindTo(receiver);
+MethodType mt1 = mh1.type();
+if (mh0.isVarargsCollector())
+ mh1 = mh1.asVarargsCollector(mt1.parameterType(mt1.parameterCount()-1));
+return mh1;
+ * }</pre></blockquote>
+ * where {@code defc} is either {@code receiver.getClass()} or a super
+ * type of that class, in which the requested method is accessible
+ * to the lookup class.
+ * (Note that {@code bindTo} does not preserve variable arity.)
+ * @param receiver the object from which the method is accessed
+ * @param name the name of the method
+ * @param type the type of the method, with the receiver argument omitted
+ * @return the desired method handle
+ * @throws NoSuchMethodException if the method does not exist
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws NullPointerException if any argument is null
+ * @see MethodHandle#bindTo
+ * @see #findVirtual
+ */
+ public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+ MethodHandle handle = findVirtual(receiver.getClass(), name, type);
+ MethodHandle adapter = handle.bindTo(receiver);
+ MethodType adapterType = adapter.type();
+ if (handle.isVarargsCollector()) {
+ adapter = adapter.asVarargsCollector(
+ adapterType.parameterType(adapterType.parameterCount() - 1));
+ }
+
+ return adapter;
+ }
+
+ /**
+ * Makes a <a href="MethodHandleInfo.html#directmh">direct method handle</a>
+ * to <i>m</i>, if the lookup class has permission.
+ * If <i>m</i> is non-static, the receiver argument is treated as an initial argument.
+ * If <i>m</i> is virtual, overriding is respected on every call.
+ * Unlike the Core Reflection API, exceptions are <em>not</em> wrapped.
+ * The type of the method handle will be that of the method,
+ * with the receiver type prepended (but only if it is non-static).
+ * If the method's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class.
+ * If <i>m</i> is not public, do not share the resulting handle with untrusted parties.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * If <i>m</i> is static, and
+ * if the returned method handle is invoked, the method's class will
+ * be initialized, if it has not already been initialized.
+ * @param m the reflected method
+ * @return a method handle which can invoke the reflected method
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @throws NullPointerException if the argument is null
+ */
+ public MethodHandle unreflect(Method m) throws IllegalAccessException {
+ if (m == null) {
+ throw new NullPointerException("m == null");
+ }
+
+ MethodType methodType = MethodType.methodType(m.getReturnType(),
+ m.getParameterTypes());
+
+ // We should only perform access checks if setAccessible hasn't been called yet.
+ if (!m.isAccessible()) {
+ checkAccess(m.getDeclaringClass(), m.getDeclaringClass(), m.getModifiers(),
+ m.getName());
+ }
+
+ if (Modifier.isStatic(m.getModifiers())) {
+ return createMethodHandle(m, MethodHandle.INVOKE_STATIC, methodType);
+ } else {
+ methodType = methodType.insertParameterTypes(0, m.getDeclaringClass());
+ return createMethodHandle(m, MethodHandle.INVOKE_VIRTUAL, methodType);
+ }
+ }
+
+ /**
+ * Produces a method handle for a reflected method.
+ * It will bypass checks for overriding methods on the receiver,
+ * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
+ * instruction from within the explicitly specified {@code specialCaller}.
+ * The type of the method handle will be that of the method,
+ * with a suitably restricted receiver type prepended.
+ * (The receiver type will be {@code specialCaller} or a subtype.)
+ * If the method's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class,
+ * as if {@code invokespecial} instruction were being linked.
+ * <p>
+ * Before method resolution,
+ * if the explicitly specified caller class is not identical with the
+ * lookup class, or if this lookup object does not have
+ * <a href="MethodHandles.Lookup.html#privacc">private access</a>
+ * privileges, the access fails.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the method's variable arity modifier bit ({@code 0x0080}) is set.
+ * @param m the reflected method
+ * @param specialCaller the class nominally calling the method
+ * @return a method handle which can invoke the reflected method
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @throws NullPointerException if any argument is null
+ */
+ public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException {
+ if (m == null) {
+ throw new NullPointerException("m == null");
+ }
+
+ if (specialCaller == null) {
+ throw new NullPointerException("specialCaller == null");
+ }
+
+ if (!m.isAccessible()) {
+ checkSpecialCaller(specialCaller);
+ }
+
+ final MethodType methodType = MethodType.methodType(m.getReturnType(),
+ m.getParameterTypes());
+ return findSpecial(m, methodType, m.getDeclaringClass() /* refc */, specialCaller);
+ }
+
+ /**
+ * Produces a method handle for a reflected constructor.
+ * The type of the method handle will be that of the constructor,
+ * with the return type changed to the declaring class.
+ * The method handle will perform a {@code newInstance} operation,
+ * creating a new instance of the constructor's class on the
+ * arguments passed to the method handle.
+ * <p>
+ * If the constructor's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * The returned method handle will have
+ * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+ * the constructor's variable arity modifier bit ({@code 0x0080}) is set.
+ * <p>
+ * If the returned method handle is invoked, the constructor's class will
+ * be initialized, if it has not already been initialized.
+ * @param c the reflected constructor
+ * @return a method handle which can invoke the reflected constructor
+ * @throws IllegalAccessException if access checking fails
+ * or if the method's variable arity modifier bit
+ * is set and {@code asVarargsCollector} fails
+ * @throws NullPointerException if the argument is null
+ */
+ public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException {
+ if (c == null) {
+ throw new NullPointerException("c == null");
+ }
+
+ if (!c.isAccessible()) {
+ checkAccess(c.getDeclaringClass(), c.getDeclaringClass(), c.getModifiers(),
+ c.getName());
+ }
+
+ return createMethodHandleForConstructor(c);
+ }
+
+ /**
+ * Produces a method handle giving read access to a reflected field.
+ * The type of the method handle will have a return type of the field's
+ * value type.
+ * If the field is static, the method handle will take no arguments.
+ * Otherwise, its single argument will be the instance containing
+ * the field.
+ * If the field's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * If the field is static, and
+ * if the returned method handle is invoked, the field's class will
+ * be initialized, if it has not already been initialized.
+ * @param f the reflected field
+ * @return a method handle which can load values from the reflected field
+ * @throws IllegalAccessException if access checking fails
+ * @throws NullPointerException if the argument is null
+ */
+ public MethodHandle unreflectGetter(Field f) throws IllegalAccessException {
+ return findAccessor(f, f.getDeclaringClass(), f.getType(),
+ Modifier.isStatic(f.getModifiers()) ? MethodHandle.SGET : MethodHandle.IGET,
+ f.isAccessible() /* performAccessChecks */);
+ }
+
+ /**
+ * Produces a method handle giving write access to a reflected field.
+ * The type of the method handle will have a void return type.
+ * If the field is static, the method handle will take a single
+ * argument, of the field's value type, the value to be stored.
+ * Otherwise, the two arguments will be the instance containing
+ * the field, and the value to be stored.
+ * If the field's {@code accessible} flag is not set,
+ * access checking is performed immediately on behalf of the lookup class.
+ * <p>
+ * If the field is static, and
+ * if the returned method handle is invoked, the field's class will
+ * be initialized, if it has not already been initialized.
+ * @param f the reflected field
+ * @return a method handle which can store values into the reflected field
+ * @throws IllegalAccessException if access checking fails
+ * @throws NullPointerException if the argument is null
+ */
+ public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
+ return findAccessor(f, f.getDeclaringClass(), f.getType(),
+ Modifier.isStatic(f.getModifiers()) ? MethodHandle.SPUT : MethodHandle.IPUT,
+ f.isAccessible() /* performAccessChecks */);
+ }
+
+ // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
+ /**
+ * Produces a VarHandle giving access to a reflected field {@code f}
+ * of type {@code T} declared in a class of type {@code R}.
+ * The VarHandle's variable type is {@code T}.
+ * If the field is non-static the VarHandle has one coordinate type,
+ * {@code R}. Otherwise, the field is static, and the VarHandle has no
+ * coordinate types.
+ * <p>
+ * Access checking is performed immediately on behalf of the lookup
+ * class, regardless of the value of the field's {@code accessible}
+ * flag.
+ * <p>
+ * If the field is static, and if the returned VarHandle is operated
+ * on, the field's declaring class will be initialized, if it has not
+ * already been initialized.
+ * <p>
+ * Certain access modes of the returned VarHandle are unsupported under
+ * the following conditions:
+ * <ul>
+ * <li>if the field is declared {@code final}, then the write, atomic
+ * update, numeric atomic update, and bitwise atomic update access
+ * modes are unsupported.
+ * <li>if the field type is anything other than {@code byte},
+ * {@code short}, {@code char}, {@code int}, {@code long},
+ * {@code float}, or {@code double} then numeric atomic update
+ * access modes are unsupported.
+ * <li>if the field type is anything other than {@code boolean},
+ * {@code byte}, {@code short}, {@code char}, {@code int} or
+ * {@code long} then bitwise atomic update access modes are
+ * unsupported.
+ * </ul>
+ * <p>
+ * If the field is declared {@code volatile} then the returned VarHandle
+ * will override access to the field (effectively ignore the
+ * {@code volatile} declaration) in accordance to its specified
+ * access modes.
+ * <p>
+ * If the field type is {@code float} or {@code double} then numeric
+ * and atomic update access modes compare values using their bitwise
+ * representation (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @apiNote
+ * Bitwise comparison of {@code float} values or {@code double} values,
+ * as performed by the numeric and atomic update access modes, differ
+ * from the primitive {@code ==} operator and the {@link Float#equals}
+ * and {@link Double#equals} methods, specifically with respect to
+ * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+ * Care should be taken when performing a compare and set or a compare
+ * and exchange operation with such values since the operation may
+ * unexpectedly fail.
+ * There are many possible NaN values that are considered to be
+ * {@code NaN} in Java, although no IEEE 754 floating-point operation
+ * provided by Java can distinguish between them. Operation failure can
+ * occur if the expected or witness value is a NaN value and it is
+ * transformed (perhaps in a platform specific manner) into another NaN
+ * value, and thus has a different bitwise representation (see
+ * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+ * details).
+ * The values {@code -0.0} and {@code +0.0} have different bitwise
+ * representations but are considered equal when using the primitive
+ * {@code ==} operator. Operation failure can occur if, for example, a
+ * numeric algorithm computes an expected value to be say {@code -0.0}
+ * and previously computed the witness value to be say {@code +0.0}.
+ * @param f the reflected field, with a field of type {@code T}, and
+ * a declaring class of type {@code R}
+ * @return a VarHandle giving access to non-static fields or a static
+ * field
+ * @throws IllegalAccessException if access checking fails
+ * @throws NullPointerException if the argument is null
+ * @since 9
+ * @hide
+ */
+ public VarHandle unreflectVarHandle(Field f) throws IllegalAccessException {
+ unsupported("MethodHandles.Lookup.unreflectVarHandle()"); // TODO(b/65872996)
+ return null;
+ }
+ // END Android-changed: OpenJDK 9+181 VarHandle API factory method for bring up purposes.
+
+ /**
+ * Cracks a <a href="MethodHandleInfo.html#directmh">direct method handle</a>
+ * created by this lookup object or a similar one.
+ * Security and access checks are performed to ensure that this lookup object
+ * is capable of reproducing the target method handle.
+ * This means that the cracking may fail if target is a direct method handle
+ * but was created by an unrelated lookup object.
+ * This can happen if the method handle is <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a>
+ * and was created by a lookup object for a different class.
+ * @param target a direct method handle to crack into symbolic reference components
+ * @return a symbolic reference which can be used to reconstruct this method handle from this lookup object
+ * @exception SecurityException if a security manager is present and it
+ * <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+ * @throws IllegalArgumentException if the target is not a direct method handle or if access checking fails
+ * @exception NullPointerException if the target is {@code null}
+ * @see MethodHandleInfo
+ * @since 1.8
+ */
+ public MethodHandleInfo revealDirect(MethodHandle target) {
+ MethodHandleImpl directTarget = getMethodHandleImpl(target);
+ MethodHandleInfo info = directTarget.reveal();
+
+ try {
+ checkAccess(lookupClass(), info.getDeclaringClass(), info.getModifiers(),
+ info.getName());
+ } catch (IllegalAccessException exception) {
+ throw new IllegalArgumentException("Unable to access memeber.", exception);
+ }
+
+ return info;
+ }
+
+ private boolean hasPrivateAccess() {
+ return (allowedModes & PRIVATE) != 0;
+ }
+
+ /** Check public/protected/private bits on the symbolic reference class and its member. */
+ void checkAccess(Class<?> refc, Class<?> defc, int mods, String methName)
+ throws IllegalAccessException {
+ int allowedModes = this.allowedModes;
+
+ if (Modifier.isProtected(mods) &&
+ defc == Object.class &&
+ "clone".equals(methName) &&
+ refc.isArray()) {
+ // The JVM does this hack also.
+ // (See ClassVerifier::verify_invoke_instructions
+ // and LinkResolver::check_method_accessability.)
+ // Because the JVM does not allow separate methods on array types,
+ // there is no separate method for int[].clone.
+ // All arrays simply inherit Object.clone.
+ // But for access checking logic, we make Object.clone
+ // (normally protected) appear to be public.
+ // Later on, when the DirectMethodHandle is created,
+ // its leading argument will be restricted to the
+ // requested array type.
+ // N.B. The return type is not adjusted, because
+ // that is *not* the bytecode behavior.
+ mods ^= Modifier.PROTECTED | Modifier.PUBLIC;
+ }
+
+ if (Modifier.isProtected(mods) && Modifier.isConstructor(mods)) {
+ // cannot "new" a protected ctor in a different package
+ mods ^= Modifier.PROTECTED;
+ }
+
+ if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers()) && allowedModes != 0)
+ return; // common case
+ int requestedModes = fixmods(mods); // adjust 0 => PACKAGE
+ if ((requestedModes & allowedModes) != 0) {
+ if (VerifyAccess.isMemberAccessible(refc, defc, mods, lookupClass(), allowedModes))
+ return;
+ } else {
+ // Protected members can also be checked as if they were package-private.
+ if ((requestedModes & PROTECTED) != 0 && (allowedModes & PACKAGE) != 0
+ && VerifyAccess.isSamePackage(defc, lookupClass()))
+ return;
+ }
+
+ throwMakeAccessException(accessFailedMessage(refc, defc, mods), this);
+ }
+
+ String accessFailedMessage(Class<?> refc, Class<?> defc, int mods) {
+ // check the class first:
+ boolean classOK = (Modifier.isPublic(defc.getModifiers()) &&
+ (defc == refc ||
+ Modifier.isPublic(refc.getModifiers())));
+ if (!classOK && (allowedModes & PACKAGE) != 0) {
+ classOK = (VerifyAccess.isClassAccessible(defc, lookupClass(), ALL_MODES) &&
+ (defc == refc ||
+ VerifyAccess.isClassAccessible(refc, lookupClass(), ALL_MODES)));
+ }
+ if (!classOK)
+ return "class is not public";
+ if (Modifier.isPublic(mods))
+ return "access to public member failed"; // (how?)
+ if (Modifier.isPrivate(mods))
+ return "member is private";
+ if (Modifier.isProtected(mods))
+ return "member is protected";
+ return "member is private to package";
+ }
+
+ // Android-changed: checkSpecialCaller assumes that ALLOW_NESTMATE_ACCESS = false,
+ // as in upstream OpenJDK.
+ //
+ // private static final boolean ALLOW_NESTMATE_ACCESS = false;
- public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
+ private void checkSpecialCaller(Class<?> specialCaller) throws IllegalAccessException {
+ // Android-changed: No support for TRUSTED lookups. Also construct the
+ // IllegalAccessException by hand because the upstream code implicitly assumes
+ // that the lookupClass == specialCaller.
+ //
+ // if (allowedModes == TRUSTED) return;
+ if (!hasPrivateAccess() || (specialCaller != lookupClass())) {
+ throw new IllegalAccessException("no private access for invokespecial : "
+ + specialCaller + ", from" + this);
+ }
+ }
- public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
+ private void throwMakeAccessException(String message, Object from) throws
+ IllegalAccessException{
+ message = message + ": "+ toString();
+ if (from != null) message += ", from " + from;
+ throw new IllegalAccessException(message);
+ }
- public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
+ private void checkReturnType(Method method, MethodType methodType)
+ throws NoSuchMethodException {
+ if (method.getReturnType() != methodType.rtype()) {
+ throw new NoSuchMethodException(method.getName() + methodType);
+ }
+ }
+ }
+
+ /**
+ * "Cracks" {@code target} to reveal the underlying {@code MethodHandleImpl}.
+ */
+ private static MethodHandleImpl getMethodHandleImpl(MethodHandle target) {
+ // Special case : We implement handles to constructors as transformers,
+ // so we must extract the underlying handle from the transformer.
+ if (target instanceof Transformers.Construct) {
+ target = ((Transformers.Construct) target).getConstructorHandle();
+ }
- public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
+ // Special case: Var-args methods are also implemented as Transformers,
+ // so we should get the underlying handle in that case as well.
+ if (target instanceof Transformers.VarargsCollector) {
+ target = target.asFixedArity();
+ }
- public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
+ if (target instanceof MethodHandleImpl) {
+ return (MethodHandleImpl) target;
+ }
- public MethodHandle unreflect(Method m) throws IllegalAccessException { return null; }
+ throw new IllegalArgumentException(target + " is not a direct handle");
+ }
- public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException { return null; }
+ /**
+ * Produces a method handle giving read access to elements of an array.
+ * The type of the method handle will have a return type of the array's
+ * element type. Its first argument will be the array type,
+ * and the second will be {@code int}.
+ * @param arrayClass an array type
+ * @return a method handle which can load values from the given array type
+ * @throws NullPointerException if the argument is null
+ * @throws IllegalArgumentException if arrayClass is not an array type
+ */
+ public static
+ MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException {
+ final Class<?> componentType = arrayClass.getComponentType();
+ if (componentType == null) {
+ throw new IllegalArgumentException("Not an array type: " + arrayClass);
+ }
- public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException { return null; }
+ if (componentType.isPrimitive()) {
+ try {
+ return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class,
+ "arrayElementGetter",
+ MethodType.methodType(componentType, arrayClass, int.class));
+ } catch (NoSuchMethodException | IllegalAccessException exception) {
+ throw new AssertionError(exception);
+ }
+ }
- public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { return null; }
+ return new Transformers.ReferenceArrayElementGetter(arrayClass);
+ }
- public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { return null; }
+ /** @hide */ public static byte arrayElementGetter(byte[] array, int i) { return array[i]; }
+ /** @hide */ public static boolean arrayElementGetter(boolean[] array, int i) { return array[i]; }
+ /** @hide */ public static char arrayElementGetter(char[] array, int i) { return array[i]; }
+ /** @hide */ public static short arrayElementGetter(short[] array, int i) { return array[i]; }
+ /** @hide */ public static int arrayElementGetter(int[] array, int i) { return array[i]; }
+ /** @hide */ public static long arrayElementGetter(long[] array, int i) { return array[i]; }
+ /** @hide */ public static float arrayElementGetter(float[] array, int i) { return array[i]; }
+ /** @hide */ public static double arrayElementGetter(double[] array, int i) { return array[i]; }
- public MethodHandleInfo revealDirect(MethodHandle target) { return null; }
+ /**
+ * Produces a method handle giving write access to elements of an array.
+ * The type of the method handle will have a void return type.
+ * Its last argument will be the array's element type.
+ * The first and second arguments will be the array type and int.
+ * @param arrayClass the class of an array
+ * @return a method handle which can store values into the array type
+ * @throws NullPointerException if the argument is null
+ * @throws IllegalArgumentException if arrayClass is not an array type
+ */
+ public static
+ MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException {
+ final Class<?> componentType = arrayClass.getComponentType();
+ if (componentType == null) {
+ throw new IllegalArgumentException("Not an array type: " + arrayClass);
+ }
+ if (componentType.isPrimitive()) {
+ try {
+ return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class,
+ "arrayElementSetter",
+ MethodType.methodType(void.class, arrayClass, int.class, componentType));
+ } catch (NoSuchMethodException | IllegalAccessException exception) {
+ throw new AssertionError(exception);
+ }
+ }
+
+ return new Transformers.ReferenceArrayElementSetter(arrayClass);
+ }
+
+ /** @hide */
+ public static void arrayElementSetter(byte[] array, int i, byte val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(boolean[] array, int i, boolean val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(char[] array, int i, char val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(short[] array, int i, short val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(int[] array, int i, int val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(long[] array, int i, long val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(float[] array, int i, float val) { array[i] = val; }
+ /** @hide */
+ public static void arrayElementSetter(double[] array, int i, double val) { array[i] = val; }
+
+ // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory methods for bring up purposes.
+ /**
+ * Produces a VarHandle giving access to elements of an array of type
+ * {@code arrayClass}. The VarHandle's variable type is the component type
+ * of {@code arrayClass} and the list of coordinate types is
+ * {@code (arrayClass, int)}, where the {@code int} coordinate type
+ * corresponds to an argument that is an index into an array.
+ * <p>
+ * Certain access modes of the returned VarHandle are unsupported under
+ * the following conditions:
+ * <ul>
+ * <li>if the component type is anything other than {@code byte},
+ * {@code short}, {@code char}, {@code int}, {@code long},
+ * {@code float}, or {@code double} then numeric atomic update access
+ * modes are unsupported.
+ * <li>if the field type is anything other than {@code boolean},
+ * {@code byte}, {@code short}, {@code char}, {@code int} or
+ * {@code long} then bitwise atomic update access modes are
+ * unsupported.
+ * </ul>
+ * <p>
+ * If the component type is {@code float} or {@code double} then numeric
+ * and atomic update access modes compare values using their bitwise
+ * representation (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @apiNote
+ * Bitwise comparison of {@code float} values or {@code double} values,
+ * as performed by the numeric and atomic update access modes, differ
+ * from the primitive {@code ==} operator and the {@link Float#equals}
+ * and {@link Double#equals} methods, specifically with respect to
+ * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+ * Care should be taken when performing a compare and set or a compare
+ * and exchange operation with such values since the operation may
+ * unexpectedly fail.
+ * There are many possible NaN values that are considered to be
+ * {@code NaN} in Java, although no IEEE 754 floating-point operation
+ * provided by Java can distinguish between them. Operation failure can
+ * occur if the expected or witness value is a NaN value and it is
+ * transformed (perhaps in a platform specific manner) into another NaN
+ * value, and thus has a different bitwise representation (see
+ * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+ * details).
+ * The values {@code -0.0} and {@code +0.0} have different bitwise
+ * representations but are considered equal when using the primitive
+ * {@code ==} operator. Operation failure can occur if, for example, a
+ * numeric algorithm computes an expected value to be say {@code -0.0}
+ * and previously computed the witness value to be say {@code +0.0}.
+ * @param arrayClass the class of an array, of type {@code T[]}
+ * @return a VarHandle giving access to elements of an array
+ * @throws NullPointerException if the arrayClass is null
+ * @throws IllegalArgumentException if arrayClass is not an array type
+ * @since 9
+ * @hide
+ */
+ public static
+ VarHandle arrayElementVarHandle(Class<?> arrayClass) throws IllegalArgumentException {
+ unsupported("MethodHandles.arrayElementVarHandle()"); // TODO(b/65872996)
+ return null;
}
+ /**
+ * Produces a VarHandle giving access to elements of a {@code byte[]} array
+ * viewed as if it were a different primitive array type, such as
+ * {@code int[]} or {@code long[]}.
+ * The VarHandle's variable type is the component type of
+ * {@code viewArrayClass} and the list of coordinate types is
+ * {@code (byte[], int)}, where the {@code int} coordinate type
+ * corresponds to an argument that is an index into a {@code byte[]} array.
+ * The returned VarHandle accesses bytes at an index in a {@code byte[]}
+ * array, composing bytes to or from a value of the component type of
+ * {@code viewArrayClass} according to the given endianness.
+ * <p>
+ * The supported component types (variables types) are {@code short},
+ * {@code char}, {@code int}, {@code long}, {@code float} and
+ * {@code double}.
+ * <p>
+ * Access of bytes at a given index will result in an
+ * {@code IndexOutOfBoundsException} if the index is less than {@code 0}
+ * or greater than the {@code byte[]} array length minus the size (in bytes)
+ * of {@code T}.
+ * <p>
+ * Access of bytes at an index may be aligned or misaligned for {@code T},
+ * with respect to the underlying memory address, {@code A} say, associated
+ * with the array and index.
+ * If access is misaligned then access for anything other than the
+ * {@code get} and {@code set} access modes will result in an
+ * {@code IllegalStateException}. In such cases atomic access is only
+ * guaranteed with respect to the largest power of two that divides the GCD
+ * of {@code A} and the size (in bytes) of {@code T}.
+ * If access is aligned then following access modes are supported and are
+ * guaranteed to support atomic access:
+ * <ul>
+ * <li>read write access modes for all {@code T}, with the exception of
+ * access modes {@code get} and {@code set} for {@code long} and
+ * {@code double} on 32-bit platforms.
+ * <li>atomic update access modes for {@code int}, {@code long},
+ * {@code float} or {@code double}.
+ * (Future major platform releases of the JDK may support additional
+ * types for certain currently unsupported access modes.)
+ * <li>numeric atomic update access modes for {@code int} and {@code long}.
+ * (Future major platform releases of the JDK may support additional
+ * numeric types for certain currently unsupported access modes.)
+ * <li>bitwise atomic update access modes for {@code int} and {@code long}.
+ * (Future major platform releases of the JDK may support additional
+ * numeric types for certain currently unsupported access modes.)
+ * </ul>
+ * <p>
+ * Misaligned access, and therefore atomicity guarantees, may be determined
+ * for {@code byte[]} arrays without operating on a specific array. Given
+ * an {@code index}, {@code T} and it's corresponding boxed type,
+ * {@code T_BOX}, misalignment may be determined as follows:
+ * <pre>{@code
+ * int sizeOfT = T_BOX.BYTES; // size in bytes of T
+ * int misalignedAtZeroIndex = ByteBuffer.wrap(new byte[0]).
+ * alignmentOffset(0, sizeOfT);
+ * int misalignedAtIndex = (misalignedAtZeroIndex + index) % sizeOfT;
+ * boolean isMisaligned = misalignedAtIndex != 0;
+ * }</pre>
+ * <p>
+ * If the variable type is {@code float} or {@code double} then atomic
+ * update access modes compare values using their bitwise representation
+ * (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @param viewArrayClass the view array class, with a component type of
+ * type {@code T}
+ * @param byteOrder the endianness of the view array elements, as
+ * stored in the underlying {@code byte} array
+ * @return a VarHandle giving access to elements of a {@code byte[]} array
+ * viewed as if elements corresponding to the components type of the view
+ * array class
+ * @throws NullPointerException if viewArrayClass or byteOrder is null
+ * @throws IllegalArgumentException if viewArrayClass is not an array type
+ * @throws UnsupportedOperationException if the component type of
+ * viewArrayClass is not supported as a variable type
+ * @since 9
+ * @hide
+ */
public static
- MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
+ VarHandle byteArrayViewVarHandle(Class<?> viewArrayClass,
+ ByteOrder byteOrder) throws IllegalArgumentException {
+ unsupported("MethodHandles.byteArrayViewVarHandle()"); // TODO(b/65872996)
+ return null;
+ }
+ /**
+ * Produces a VarHandle giving access to elements of a {@code ByteBuffer}
+ * viewed as if it were an array of elements of a different primitive
+ * component type to that of {@code byte}, such as {@code int[]} or
+ * {@code long[]}.
+ * The VarHandle's variable type is the component type of
+ * {@code viewArrayClass} and the list of coordinate types is
+ * {@code (ByteBuffer, int)}, where the {@code int} coordinate type
+ * corresponds to an argument that is an index into a {@code byte[]} array.
+ * The returned VarHandle accesses bytes at an index in a
+ * {@code ByteBuffer}, composing bytes to or from a value of the component
+ * type of {@code viewArrayClass} according to the given endianness.
+ * <p>
+ * The supported component types (variables types) are {@code short},
+ * {@code char}, {@code int}, {@code long}, {@code float} and
+ * {@code double}.
+ * <p>
+ * Access will result in a {@code ReadOnlyBufferException} for anything
+ * other than the read access modes if the {@code ByteBuffer} is read-only.
+ * <p>
+ * Access of bytes at a given index will result in an
+ * {@code IndexOutOfBoundsException} if the index is less than {@code 0}
+ * or greater than the {@code ByteBuffer} limit minus the size (in bytes) of
+ * {@code T}.
+ * <p>
+ * Access of bytes at an index may be aligned or misaligned for {@code T},
+ * with respect to the underlying memory address, {@code A} say, associated
+ * with the {@code ByteBuffer} and index.
+ * If access is misaligned then access for anything other than the
+ * {@code get} and {@code set} access modes will result in an
+ * {@code IllegalStateException}. In such cases atomic access is only
+ * guaranteed with respect to the largest power of two that divides the GCD
+ * of {@code A} and the size (in bytes) of {@code T}.
+ * If access is aligned then following access modes are supported and are
+ * guaranteed to support atomic access:
+ * <ul>
+ * <li>read write access modes for all {@code T}, with the exception of
+ * access modes {@code get} and {@code set} for {@code long} and
+ * {@code double} on 32-bit platforms.
+ * <li>atomic update access modes for {@code int}, {@code long},
+ * {@code float} or {@code double}.
+ * (Future major platform releases of the JDK may support additional
+ * types for certain currently unsupported access modes.)
+ * <li>numeric atomic update access modes for {@code int} and {@code long}.
+ * (Future major platform releases of the JDK may support additional
+ * numeric types for certain currently unsupported access modes.)
+ * <li>bitwise atomic update access modes for {@code int} and {@code long}.
+ * (Future major platform releases of the JDK may support additional
+ * numeric types for certain currently unsupported access modes.)
+ * </ul>
+ * <p>
+ * Misaligned access, and therefore atomicity guarantees, may be determined
+ * for a {@code ByteBuffer}, {@code bb} (direct or otherwise), an
+ * {@code index}, {@code T} and it's corresponding boxed type,
+ * {@code T_BOX}, as follows:
+ * <pre>{@code
+ * int sizeOfT = T_BOX.BYTES; // size in bytes of T
+ * ByteBuffer bb = ...
+ * int misalignedAtIndex = bb.alignmentOffset(index, sizeOfT);
+ * boolean isMisaligned = misalignedAtIndex != 0;
+ * }</pre>
+ * <p>
+ * If the variable type is {@code float} or {@code double} then atomic
+ * update access modes compare values using their bitwise representation
+ * (see {@link Float#floatToRawIntBits} and
+ * {@link Double#doubleToRawLongBits}, respectively).
+ * @param viewArrayClass the view array class, with a component type of
+ * type {@code T}
+ * @param byteOrder the endianness of the view array elements, as
+ * stored in the underlying {@code ByteBuffer} (Note this overrides the
+ * endianness of a {@code ByteBuffer})
+ * @return a VarHandle giving access to elements of a {@code ByteBuffer}
+ * viewed as if elements corresponding to the components type of the view
+ * array class
+ * @throws NullPointerException if viewArrayClass or byteOrder is null
+ * @throws IllegalArgumentException if viewArrayClass is not an array type
+ * @throws UnsupportedOperationException if the component type of
+ * viewArrayClass is not supported as a variable type
+ * @since 9
+ * @hide
+ */
public static
- MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
+ VarHandle byteBufferViewVarHandle(Class<?> viewArrayClass,
+ ByteOrder byteOrder) throws IllegalArgumentException {
+
+ unsupported("MethodHandles.byteBufferViewVarHandle()"); // TODO(b/65872996)
+ return null;
+ }
+ // END Android-changed: OpenJDK 9+181 VarHandle API factory methods for bring up purposes.
+
+ /// method handle invocation (reflective style)
+
+ /**
+ * Produces a method handle which will invoke any method handle of the
+ * given {@code type}, with a given number of trailing arguments replaced by
+ * a single trailing {@code Object[]} array.
+ * The resulting invoker will be a method handle with the following
+ * arguments:
+ * <ul>
+ * <li>a single {@code MethodHandle} target
+ * <li>zero or more leading values (counted by {@code leadingArgCount})
+ * <li>an {@code Object[]} array containing trailing arguments
+ * </ul>
+ * <p>
+ * The invoker will invoke its target like a call to {@link MethodHandle#invoke invoke} with
+ * the indicated {@code type}.
+ * That is, if the target is exactly of the given {@code type}, it will behave
+ * like {@code invokeExact}; otherwise it behave as if {@link MethodHandle#asType asType}
+ * is used to convert the target to the required {@code type}.
+ * <p>
+ * The type of the returned invoker will not be the given {@code type}, but rather
+ * will have all parameters except the first {@code leadingArgCount}
+ * replaced by a single array of type {@code Object[]}, which will be
+ * the final parameter.
+ * <p>
+ * Before invoking its target, the invoker will spread the final array, apply
+ * reference casts as necessary, and unbox and widen primitive arguments.
+ * If, when the invoker is called, the supplied array argument does
+ * not have the correct number of elements, the invoker will throw
+ * an {@link IllegalArgumentException} instead of invoking the target.
+ * <p>
+ * This method is equivalent to the following code (though it may be more efficient):
+ * <blockquote><pre>{@code
+MethodHandle invoker = MethodHandles.invoker(type);
+int spreadArgCount = type.parameterCount() - leadingArgCount;
+invoker = invoker.asSpreader(Object[].class, spreadArgCount);
+return invoker;
+ * }</pre></blockquote>
+ * This method throws no reflective or security exceptions.
+ * @param type the desired target type
+ * @param leadingArgCount number of fixed arguments, to be passed unchanged to the target
+ * @return a method handle suitable for invoking any method handle of the given type
+ * @throws NullPointerException if {@code type} is null
+ * @throws IllegalArgumentException if {@code leadingArgCount} is not in
+ * the range from 0 to {@code type.parameterCount()} inclusive,
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
+ static public
+ MethodHandle spreadInvoker(MethodType type, int leadingArgCount) {
+ if (leadingArgCount < 0 || leadingArgCount > type.parameterCount())
+ throw newIllegalArgumentException("bad argument count", leadingArgCount);
+
+ MethodHandle invoker = MethodHandles.invoker(type);
+ int spreadArgCount = type.parameterCount() - leadingArgCount;
+ invoker = invoker.asSpreader(Object[].class, spreadArgCount);
+ return invoker;
+ }
+
+ /**
+ * Produces a special <em>invoker method handle</em> which can be used to
+ * invoke any method handle of the given type, as if by {@link MethodHandle#invokeExact invokeExact}.
+ * The resulting invoker will have a type which is
+ * exactly equal to the desired type, except that it will accept
+ * an additional leading argument of type {@code MethodHandle}.
+ * <p>
+ * This method is equivalent to the following code (though it may be more efficient):
+ * {@code publicLookup().findVirtual(MethodHandle.class, "invokeExact", type)}
+ *
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * Invoker method handles can be useful when working with variable method handles
+ * of unknown types.
+ * For example, to emulate an {@code invokeExact} call to a variable method
+ * handle {@code M}, extract its type {@code T},
+ * look up the invoker method {@code X} for {@code T},
+ * and call the invoker method, as {@code X.invoke(T, A...)}.
+ * (It would not work to call {@code X.invokeExact}, since the type {@code T}
+ * is unknown.)
+ * If spreading, collecting, or other argument transformations are required,
+ * they can be applied once to the invoker {@code X} and reused on many {@code M}
+ * method handle values, as long as they are compatible with the type of {@code X}.
+ * <p style="font-size:smaller;">
+ * <em>(Note: The invoker method is not available via the Core Reflection API.
+ * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+ * on the declared {@code invokeExact} or {@code invoke} method will raise an
+ * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em>
+ * <p>
+ * This method throws no reflective or security exceptions.
+ * @param type the desired target type
+ * @return a method handle suitable for invoking any method handle of the given type
+ * @throws IllegalArgumentException if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
+ static public
+ MethodHandle exactInvoker(MethodType type) {
+ return new Transformers.Invoker(type, true /* isExactInvoker */);
+ }
+ /**
+ * Produces a special <em>invoker method handle</em> which can be used to
+ * invoke any method handle compatible with the given type, as if by {@link MethodHandle#invoke invoke}.
+ * The resulting invoker will have a type which is
+ * exactly equal to the desired type, except that it will accept
+ * an additional leading argument of type {@code MethodHandle}.
+ * <p>
+ * Before invoking its target, if the target differs from the expected type,
+ * the invoker will apply reference casts as
+ * necessary and box, unbox, or widen primitive values, as if by {@link MethodHandle#asType asType}.
+ * Similarly, the return value will be converted as necessary.
+ * If the target is a {@linkplain MethodHandle#asVarargsCollector variable arity method handle},
+ * the required arity conversion will be made, again as if by {@link MethodHandle#asType asType}.
+ * <p>
+ * This method is equivalent to the following code (though it may be more efficient):
+ * {@code publicLookup().findVirtual(MethodHandle.class, "invoke", type)}
+ * <p style="font-size:smaller;">
+ * <em>Discussion:</em>
+ * A {@linkplain MethodType#genericMethodType general method type} is one which
+ * mentions only {@code Object} arguments and return values.
+ * An invoker for such a type is capable of calling any method handle
+ * of the same arity as the general type.
+ * <p style="font-size:smaller;">
+ * <em>(Note: The invoker method is not available via the Core Reflection API.
+ * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+ * on the declared {@code invokeExact} or {@code invoke} method will raise an
+ * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em>
+ * <p>
+ * This method throws no reflective or security exceptions.
+ * @param type the desired target type
+ * @return a method handle suitable for invoking any method handle convertible to the given type
+ * @throws IllegalArgumentException if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
static public
- MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { return null; }
+ MethodHandle invoker(MethodType type) {
+ return new Transformers.Invoker(type, false /* isExactInvoker */);
+ }
+ /**
+ * Produces a special <em>invoker method handle</em> which can be used to
+ * invoke a signature-polymorphic access mode method on any VarHandle whose
+ * associated access mode type is compatible with the given type.
+ * The resulting invoker will have a type which is exactly equal to the
+ * desired given type, except that it will accept an additional leading
+ * argument of type {@code VarHandle}.
+ *
+ * @param accessMode the VarHandle access mode
+ * @param type the desired target type
+ * @return a method handle suitable for invoking an access mode method of
+ * any VarHandle whose access mode type is of the given type.
+ * @since 9
+ * @hide
+ */
static public
- MethodHandle exactInvoker(MethodType type) { return null; }
+ MethodHandle varHandleExactInvoker(VarHandle.AccessMode accessMode, MethodType type) {
+ unsupported("MethodHandles.varHandleExactInvoker()"); // TODO(b/65872996)
+ return null;
+ }
+ /**
+ * Produces a special <em>invoker method handle</em> which can be used to
+ * invoke a signature-polymorphic access mode method on any VarHandle whose
+ * associated access mode type is compatible with the given type.
+ * The resulting invoker will have a type which is exactly equal to the
+ * desired given type, except that it will accept an additional leading
+ * argument of type {@code VarHandle}.
+ * <p>
+ * Before invoking its target, if the access mode type differs from the
+ * desired given type, the invoker will apply reference casts as necessary
+ * and box, unbox, or widen primitive values, as if by
+ * {@link MethodHandle#asType asType}. Similarly, the return value will be
+ * converted as necessary.
+ * <p>
+ * This method is equivalent to the following code (though it may be more
+ * efficient): {@code publicLookup().findVirtual(VarHandle.class, accessMode.name(), type)}
+ *
+ * @param accessMode the VarHandle access mode
+ * @param type the desired target type
+ * @return a method handle suitable for invoking an access mode method of
+ * any VarHandle whose access mode type is convertible to the given
+ * type.
+ * @since 9
+ * @hide
+ */
static public
- MethodHandle invoker(MethodType type) { return null; }
+ MethodHandle varHandleInvoker(VarHandle.AccessMode accessMode, MethodType type) {
+ unsupported("MethodHandles.varHandleInvoker()"); // TODO(b/65872996)
+ return null;
+ }
+ // Android-changed: Basic invokers are not supported.
+ //
+ // static /*non-public*/
+ // MethodHandle basicInvoker(MethodType type) {
+ // return type.invokers().basicInvoker();
+ // }
+
+ /// method handle modification (creation from other method handles)
+
+ /**
+ * Produces a method handle which adapts the type of the
+ * given method handle to a new type by pairwise argument and return type conversion.
+ * The original type and new type must have the same number of arguments.
+ * The resulting method handle is guaranteed to report a type
+ * which is equal to the desired new type.
+ * <p>
+ * If the original type and new type are equal, returns target.
+ * <p>
+ * The same conversions are allowed as for {@link MethodHandle#asType MethodHandle.asType},
+ * and some additional conversions are also applied if those conversions fail.
+ * Given types <em>T0</em>, <em>T1</em>, one of the following conversions is applied
+ * if possible, before or instead of any conversions done by {@code asType}:
+ * <ul>
+ * <li>If <em>T0</em> and <em>T1</em> are references, and <em>T1</em> is an interface type,
+ * then the value of type <em>T0</em> is passed as a <em>T1</em> without a cast.
+ * (This treatment of interfaces follows the usage of the bytecode verifier.)
+ * <li>If <em>T0</em> is boolean and <em>T1</em> is another primitive,
+ * the boolean is converted to a byte value, 1 for true, 0 for false.
+ * (This treatment follows the usage of the bytecode verifier.)
+ * <li>If <em>T1</em> is boolean and <em>T0</em> is another primitive,
+ * <em>T0</em> is converted to byte via Java casting conversion (JLS 5.5),
+ * and the low order bit of the result is tested, as if by {@code (x & 1) != 0}.
+ * <li>If <em>T0</em> and <em>T1</em> are primitives other than boolean,
+ * then a Java casting conversion (JLS 5.5) is applied.
+ * (Specifically, <em>T0</em> will convert to <em>T1</em> by
+ * widening and/or narrowing.)
+ * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing
+ * conversion will be applied at runtime, possibly followed
+ * by a Java casting conversion (JLS 5.5) on the primitive value,
+ * possibly followed by a conversion from byte to boolean by testing
+ * the low-order bit.
+ * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive,
+ * and if the reference is null at runtime, a zero value is introduced.
+ * </ul>
+ * @param target the method handle to invoke after arguments are retyped
+ * @param newType the expected type of the new method handle
+ * @return a method handle which delegates to the target after performing
+ * any necessary argument conversions, and arranges for any
+ * necessary return value conversions
+ * @throws NullPointerException if either argument is null
+ * @throws WrongMethodTypeException if the conversion cannot be made
+ * @see MethodHandle#asType
+ */
public static
- MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { return null; }
+ MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) {
+ explicitCastArgumentsChecks(target, newType);
+ // use the asTypeCache when possible:
+ MethodType oldType = target.type();
+ if (oldType == newType) return target;
+ if (oldType.explicitCastEquivalentToAsType(newType)) {
+ return target.asFixedArity().asType(newType);
+ }
+ return new Transformers.ExplicitCastArguments(target, newType);
+ }
+
+ private static void explicitCastArgumentsChecks(MethodHandle target, MethodType newType) {
+ if (target.type().parameterCount() != newType.parameterCount()) {
+ throw new WrongMethodTypeException("cannot explicitly cast " + target + " to " + newType);
+ }
+ }
+
+ /**
+ * Produces a method handle which adapts the calling sequence of the
+ * given method handle to a new type, by reordering the arguments.
+ * The resulting method handle is guaranteed to report a type
+ * which is equal to the desired new type.
+ * <p>
+ * The given array controls the reordering.
+ * Call {@code #I} the number of incoming parameters (the value
+ * {@code newType.parameterCount()}, and call {@code #O} the number
+ * of outgoing parameters (the value {@code target.type().parameterCount()}).
+ * Then the length of the reordering array must be {@code #O},
+ * and each element must be a non-negative number less than {@code #I}.
+ * For every {@code N} less than {@code #O}, the {@code N}-th
+ * outgoing argument will be taken from the {@code I}-th incoming
+ * argument, where {@code I} is {@code reorder[N]}.
+ * <p>
+ * No argument or return value conversions are applied.
+ * The type of each incoming argument, as determined by {@code newType},
+ * must be identical to the type of the corresponding outgoing parameter
+ * or parameters in the target method handle.
+ * The return type of {@code newType} must be identical to the return
+ * type of the original target.
+ * <p>
+ * The reordering array need not specify an actual permutation.
+ * An incoming argument will be duplicated if its index appears
+ * more than once in the array, and an incoming argument will be dropped
+ * if its index does not appear in the array.
+ * As in the case of {@link #dropArguments(MethodHandle,int,List) dropArguments},
+ * incoming arguments which are not mentioned in the reordering array
+ * are may be any type, as determined only by {@code newType}.
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodType intfn1 = methodType(int.class, int.class);
+MethodType intfn2 = methodType(int.class, int.class, int.class);
+MethodHandle sub = ... (int x, int y) -> (x-y) ...;
+assert(sub.type().equals(intfn2));
+MethodHandle sub1 = permuteArguments(sub, intfn2, 0, 1);
+MethodHandle rsub = permuteArguments(sub, intfn2, 1, 0);
+assert((int)rsub.invokeExact(1, 100) == 99);
+MethodHandle add = ... (int x, int y) -> (x+y) ...;
+assert(add.type().equals(intfn2));
+MethodHandle twice = permuteArguments(add, intfn1, 0, 0);
+assert(twice.type().equals(intfn1));
+assert((int)twice.invokeExact(21) == 42);
+ * }</pre></blockquote>
+ * @param target the method handle to invoke after arguments are reordered
+ * @param newType the expected type of the new method handle
+ * @param reorder an index array which controls the reordering
+ * @return a method handle which delegates to the target after it
+ * drops unused arguments and moves and/or duplicates the other arguments
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if the index array length is not equal to
+ * the arity of the target, or if any index array element
+ * not a valid index for a parameter of {@code newType},
+ * or if two corresponding parameter types in
+ * {@code target.type()} and {@code newType} are not identical,
+ */
public static
- MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { return null; }
+ MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) {
+ reorder = reorder.clone(); // get a private copy
+ MethodType oldType = target.type();
+ permuteArgumentChecks(reorder, newType, oldType);
+
+ return new Transformers.PermuteArguments(newType, target, reorder);
+ }
+ // Android-changed: findFirstDupOrDrop is unused and removed.
+ // private static int findFirstDupOrDrop(int[] reorder, int newArity);
+
+ private static boolean permuteArgumentChecks(int[] reorder, MethodType newType, MethodType oldType) {
+ if (newType.returnType() != oldType.returnType())
+ throw newIllegalArgumentException("return types do not match",
+ oldType, newType);
+ if (reorder.length == oldType.parameterCount()) {
+ int limit = newType.parameterCount();
+ boolean bad = false;
+ for (int j = 0; j < reorder.length; j++) {
+ int i = reorder[j];
+ if (i < 0 || i >= limit) {
+ bad = true; break;
+ }
+ Class<?> src = newType.parameterType(i);
+ Class<?> dst = oldType.parameterType(j);
+ if (src != dst)
+ throw newIllegalArgumentException("parameter types do not match after reorder",
+ oldType, newType);
+ }
+ if (!bad) return true;
+ }
+ throw newIllegalArgumentException("bad reorder array: "+Arrays.toString(reorder));
+ }
+
+ /**
+ * Produces a method handle of the requested return type which returns the given
+ * constant value every time it is invoked.
+ * <p>
+ * Before the method handle is returned, the passed-in value is converted to the requested type.
+ * If the requested type is primitive, widening primitive conversions are attempted,
+ * else reference conversions are attempted.
+ * <p>The returned method handle is equivalent to {@code identity(type).bindTo(value)}.
+ * @param type the return type of the desired method handle
+ * @param value the value to return
+ * @return a method handle of the given return type and no arguments, which always returns the given value
+ * @throws NullPointerException if the {@code type} argument is null
+ * @throws ClassCastException if the value cannot be converted to the required return type
+ * @throws IllegalArgumentException if the given type is {@code void.class}
+ */
public static
- MethodHandle constant(Class<?> type, Object value) { return null; }
+ MethodHandle constant(Class<?> type, Object value) {
+ if (type.isPrimitive()) {
+ if (type == void.class)
+ throw newIllegalArgumentException("void type");
+ Wrapper w = Wrapper.forPrimitiveType(type);
+ value = w.convert(value, type);
+ }
+ return new Transformers.Constant(type, value);
+ }
+
+ /**
+ * Produces a method handle which returns its sole argument when invoked.
+ * @param type the type of the sole parameter and return value of the desired method handle
+ * @return a unary method handle which accepts and returns the given type
+ * @throws NullPointerException if the argument is null
+ * @throws IllegalArgumentException if the given type is {@code void.class}
+ */
public static
- MethodHandle identity(Class<?> type) { return null; }
+ MethodHandle identity(Class<?> type) {
+ if (type == null) {
+ throw new NullPointerException("type == null");
+ }
+
+ if (type.isPrimitive()) {
+ try {
+ return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class, "identity",
+ MethodType.methodType(type, type));
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ return new Transformers.ReferenceIdentity(type);
+ }
+
+ /** @hide */ public static byte identity(byte val) { return val; }
+ /** @hide */ public static boolean identity(boolean val) { return val; }
+ /** @hide */ public static char identity(char val) { return val; }
+ /** @hide */ public static short identity(short val) { return val; }
+ /** @hide */ public static int identity(int val) { return val; }
+ /** @hide */ public static long identity(long val) { return val; }
+ /** @hide */ public static float identity(float val) { return val; }
+ /** @hide */ public static double identity(double val) { return val; }
+
+ /**
+ * Provides a target method handle with one or more <em>bound arguments</em>
+ * in advance of the method handle's invocation.
+ * The formal parameters to the target corresponding to the bound
+ * arguments are called <em>bound parameters</em>.
+ * Returns a new method handle which saves away the bound arguments.
+ * When it is invoked, it receives arguments for any non-bound parameters,
+ * binds the saved arguments to their corresponding parameters,
+ * and calls the original target.
+ * <p>
+ * The type of the new method handle will drop the types for the bound
+ * parameters from the original target type, since the new method handle
+ * will no longer require those arguments to be supplied by its callers.
+ * <p>
+ * Each given argument object must match the corresponding bound parameter type.
+ * If a bound parameter type is a primitive, the argument object
+ * must be a wrapper, and will be unboxed to produce the primitive value.
+ * <p>
+ * The {@code pos} argument selects which parameters are to be bound.
+ * It may range between zero and <i>N-L</i> (inclusively),
+ * where <i>N</i> is the arity of the target method handle
+ * and <i>L</i> is the length of the values array.
+ * @param target the method handle to invoke after the argument is inserted
+ * @param pos where to insert the argument (zero for the first)
+ * @param values the series of arguments to insert
+ * @return a method handle which inserts an additional argument,
+ * before calling the original method handle
+ * @throws NullPointerException if the target or the {@code values} array is null
+ * @see MethodHandle#bindTo
+ */
public static
- MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { return null; }
+ MethodHandle insertArguments(MethodHandle target, int pos, Object... values) {
+ int insCount = values.length;
+ Class<?>[] ptypes = insertArgumentsChecks(target, insCount, pos);
+ if (insCount == 0) {
+ return target;
+ }
+ // Throw ClassCastExceptions early if we can't cast any of the provided values
+ // to the required type.
+ for (int i = 0; i < insCount; i++) {
+ final Class<?> ptype = ptypes[pos + i];
+ if (!ptype.isPrimitive()) {
+ ptypes[pos + i].cast(values[i]);
+ } else {
+ // Will throw a ClassCastException if something terrible happens.
+ values[i] = Wrapper.forPrimitiveType(ptype).convert(values[i], ptype);
+ }
+ }
+
+ return new Transformers.InsertArguments(target, pos, values);
+ }
+
+ // Android-changed: insertArgumentPrimitive is unused.
+ //
+ // private static BoundMethodHandle insertArgumentPrimitive(BoundMethodHandle result, int pos,
+ // Class<?> ptype, Object value) {
+ // Wrapper w = Wrapper.forPrimitiveType(ptype);
+ // // perform unboxing and/or primitive conversion
+ // value = w.convert(value, ptype);
+ // switch (w) {
+ // case INT: return result.bindArgumentI(pos, (int)value);
+ // case LONG: return result.bindArgumentJ(pos, (long)value);
+ // case FLOAT: return result.bindArgumentF(pos, (float)value);
+ // case DOUBLE: return result.bindArgumentD(pos, (double)value);
+ // default: return result.bindArgumentI(pos, ValueConversions.widenSubword(value));
+ // }
+ // }
+
+ private static Class<?>[] insertArgumentsChecks(MethodHandle target, int insCount, int pos) throws RuntimeException {
+ MethodType oldType = target.type();
+ int outargs = oldType.parameterCount();
+ int inargs = outargs - insCount;
+ if (inargs < 0)
+ throw newIllegalArgumentException("too many values to insert");
+ if (pos < 0 || pos > inargs)
+ throw newIllegalArgumentException("no argument type to append");
+ return oldType.ptypes();
+ }
+
+ /**
+ * Produces a method handle which will discard some dummy arguments
+ * before calling some other specified <i>target</i> method handle.
+ * The type of the new method handle will be the same as the target's type,
+ * except it will also include the dummy argument types,
+ * at some given position.
+ * <p>
+ * The {@code pos} argument may range between zero and <i>N</i>,
+ * where <i>N</i> is the arity of the target.
+ * If {@code pos} is zero, the dummy arguments will precede
+ * the target's real arguments; if {@code pos} is <i>N</i>
+ * they will come after.
+ * <p>
+ * <b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodType bigType = cat.type().insertParameterTypes(0, int.class, String.class);
+MethodHandle d0 = dropArguments(cat, 0, bigType.parameterList().subList(0,2));
+assertEquals(bigType, d0.type());
+assertEquals("yz", (String) d0.invokeExact(123, "x", "y", "z"));
+ * }</pre></blockquote>
+ * <p>
+ * This method is also equivalent to the following code:
+ * <blockquote><pre>
+ * {@link #dropArguments(MethodHandle,int,Class...) dropArguments}{@code (target, pos, valueTypes.toArray(new Class[0]))}
+ * </pre></blockquote>
+ * @param target the method handle to invoke after the arguments are dropped
+ * @param valueTypes the type(s) of the argument(s) to drop
+ * @param pos position of first argument to drop (zero for the leftmost)
+ * @return a method handle which drops arguments of the given types,
+ * before calling the original method handle
+ * @throws NullPointerException if the target is null,
+ * or if the {@code valueTypes} list or any of its elements is null
+ * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
+ * or if {@code pos} is negative or greater than the arity of the target,
+ * or if the new method handle's type would have too many parameters
+ */
public static
- MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) { return null; }
+ MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) {
+ valueTypes = copyTypes(valueTypes);
+ MethodType oldType = target.type(); // get NPE
+ int dropped = dropArgumentChecks(oldType, pos, valueTypes);
+
+ MethodType newType = oldType.insertParameterTypes(pos, valueTypes);
+ if (dropped == 0) {
+ return target;
+ }
+
+ return new Transformers.DropArguments(newType, target, pos, valueTypes.size());
+ }
+
+ private static List<Class<?>> copyTypes(List<Class<?>> types) {
+ Object[] a = types.toArray();
+ return Arrays.asList(Arrays.copyOf(a, a.length, Class[].class));
+ }
+
+ private static int dropArgumentChecks(MethodType oldType, int pos, List<Class<?>> valueTypes) {
+ int dropped = valueTypes.size();
+ MethodType.checkSlotCount(dropped);
+ int outargs = oldType.parameterCount();
+ int inargs = outargs + dropped;
+ if (pos < 0 || pos > outargs)
+ throw newIllegalArgumentException("no argument type to remove"
+ + Arrays.asList(oldType, pos, valueTypes, inargs, outargs)
+ );
+ return dropped;
+ }
+ /**
+ * Produces a method handle which will discard some dummy arguments
+ * before calling some other specified <i>target</i> method handle.
+ * The type of the new method handle will be the same as the target's type,
+ * except it will also include the dummy argument types,
+ * at some given position.
+ * <p>
+ * The {@code pos} argument may range between zero and <i>N</i>,
+ * where <i>N</i> is the arity of the target.
+ * If {@code pos} is zero, the dummy arguments will precede
+ * the target's real arguments; if {@code pos} is <i>N</i>
+ * they will come after.
+ * <p>
+ * <b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodHandle d0 = dropArguments(cat, 0, String.class);
+assertEquals("yz", (String) d0.invokeExact("x", "y", "z"));
+MethodHandle d1 = dropArguments(cat, 1, String.class);
+assertEquals("xz", (String) d1.invokeExact("x", "y", "z"));
+MethodHandle d2 = dropArguments(cat, 2, String.class);
+assertEquals("xy", (String) d2.invokeExact("x", "y", "z"));
+MethodHandle d12 = dropArguments(cat, 1, int.class, boolean.class);
+assertEquals("xz", (String) d12.invokeExact("x", 12, true, "z"));
+ * }</pre></blockquote>
+ * <p>
+ * This method is also equivalent to the following code:
+ * <blockquote><pre>
+ * {@link #dropArguments(MethodHandle,int,List) dropArguments}{@code (target, pos, Arrays.asList(valueTypes))}
+ * </pre></blockquote>
+ * @param target the method handle to invoke after the arguments are dropped
+ * @param valueTypes the type(s) of the argument(s) to drop
+ * @param pos position of first argument to drop (zero for the leftmost)
+ * @return a method handle which drops arguments of the given types,
+ * before calling the original method handle
+ * @throws NullPointerException if the target is null,
+ * or if the {@code valueTypes} array or any of its elements is null
+ * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
+ * or if {@code pos} is negative or greater than the arity of the target,
+ * or if the new method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
public static
- MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) { return null; }
+ MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) {
+ return dropArguments(target, pos, Arrays.asList(valueTypes));
+ }
+ /**
+ * Adapts a target method handle by pre-processing
+ * one or more of its arguments, each with its own unary filter function,
+ * and then calling the target with each pre-processed argument
+ * replaced by the result of its corresponding filter function.
+ * <p>
+ * The pre-processing is performed by one or more method handles,
+ * specified in the elements of the {@code filters} array.
+ * The first element of the filter array corresponds to the {@code pos}
+ * argument of the target, and so on in sequence.
+ * <p>
+ * Null arguments in the array are treated as identity functions,
+ * and the corresponding arguments left unchanged.
+ * (If there are no non-null elements in the array, the original target is returned.)
+ * Each filter is applied to the corresponding argument of the adapter.
+ * <p>
+ * If a filter {@code F} applies to the {@code N}th argument of
+ * the target, then {@code F} must be a method handle which
+ * takes exactly one argument. The type of {@code F}'s sole argument
+ * replaces the corresponding argument type of the target
+ * in the resulting adapted method handle.
+ * The return type of {@code F} must be identical to the corresponding
+ * parameter type of the target.
+ * <p>
+ * It is an error if there are elements of {@code filters}
+ * (null or not)
+ * which do not correspond to argument positions in the target.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+MethodHandle upcase = lookup().findVirtual(String.class,
+ "toUpperCase", methodType(String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodHandle f0 = filterArguments(cat, 0, upcase);
+assertEquals("Xy", (String) f0.invokeExact("x", "y")); // Xy
+MethodHandle f1 = filterArguments(cat, 1, upcase);
+assertEquals("xY", (String) f1.invokeExact("x", "y")); // xY
+MethodHandle f2 = filterArguments(cat, 0, upcase, upcase);
+assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY
+ * }</pre></blockquote>
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * V target(P... p, A[i]... a[i], B... b);
+ * A[i] filter[i](V[i]);
+ * T adapter(P... p, V[i]... v[i], B... b) {
+ * return target(p..., f[i](v[i])..., b...);
+ * }
+ * }</pre></blockquote>
+ *
+ * @param target the method handle to invoke after arguments are filtered
+ * @param pos the position of the first argument to filter
+ * @param filters method handles to call initially on filtered arguments
+ * @return method handle which incorporates the specified argument filtering logic
+ * @throws NullPointerException if the target is null
+ * or if the {@code filters} array is null
+ * @throws IllegalArgumentException if a non-null element of {@code filters}
+ * does not match a corresponding argument type of target as described above,
+ * or if the {@code pos+filters.length} is greater than {@code target.type().parameterCount()},
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ */
public static
- MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) { return null; }
+ MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) {
+ filterArgumentsCheckArity(target, pos, filters);
+
+ for (int i = 0; i < filters.length; ++i) {
+ filterArgumentChecks(target, i + pos, filters[i]);
+ }
+
+ return new Transformers.FilterArguments(target, pos, filters);
+ }
+
+ private static void filterArgumentsCheckArity(MethodHandle target, int pos, MethodHandle[] filters) {
+ MethodType targetType = target.type();
+ int maxPos = targetType.parameterCount();
+ if (pos + filters.length > maxPos)
+ throw newIllegalArgumentException("too many filters");
+ }
+
+ private static void filterArgumentChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException {
+ MethodType targetType = target.type();
+ MethodType filterType = filter.type();
+ if (filterType.parameterCount() != 1
+ || filterType.returnType() != targetType.parameterType(pos))
+ throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+ }
+
+ /**
+ * Adapts a target method handle by pre-processing
+ * a sub-sequence of its arguments with a filter (another method handle).
+ * The pre-processed arguments are replaced by the result (if any) of the
+ * filter function.
+ * The target is then called on the modified (usually shortened) argument list.
+ * <p>
+ * If the filter returns a value, the target must accept that value as
+ * its argument in position {@code pos}, preceded and/or followed by
+ * any arguments not passed to the filter.
+ * If the filter returns void, the target must accept all arguments
+ * not passed to the filter.
+ * No arguments are reordered, and a result returned from the filter
+ * replaces (in order) the whole subsequence of arguments originally
+ * passed to the adapter.
+ * <p>
+ * The argument types (if any) of the filter
+ * replace zero or one argument types of the target, at position {@code pos},
+ * in the resulting adapted method handle.
+ * The return type of the filter (if any) must be identical to the
+ * argument type of the target at position {@code pos}, and that target argument
+ * is supplied by the return value of the filter.
+ * <p>
+ * In all cases, {@code pos} must be greater than or equal to zero, and
+ * {@code pos} must also be less than or equal to the target's arity.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle deepToString = publicLookup()
+ .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+
+MethodHandle ts1 = deepToString.asCollector(String[].class, 1);
+assertEquals("[strange]", (String) ts1.invokeExact("strange"));
+
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals("[up, down]", (String) ts2.invokeExact("up", "down"));
+MethodHandle ts3 = deepToString.asCollector(String[].class, 3);
+MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2);
+assertEquals("[top, [up, down], strange]",
+ (String) ts3_ts2.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1);
+assertEquals("[top, [up, down], [strange]]",
+ (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3);
+assertEquals("[top, [[up, down, strange], charm], bottom]",
+ (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom"));
+ * }</pre></blockquote>
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * T target(A...,V,C...);
+ * V filter(B...);
+ * T adapter(A... a,B... b,C... c) {
+ * V v = filter(b...);
+ * return target(a...,v,c...);
+ * }
+ * // and if the filter has no arguments:
+ * T target2(A...,V,C...);
+ * V filter2();
+ * T adapter2(A... a,C... c) {
+ * V v = filter2();
+ * return target2(a...,v,c...);
+ * }
+ * // and if the filter has a void return:
+ * T target3(A...,C...);
+ * void filter3(B...);
+ * void adapter3(A... a,B... b,C... c) {
+ * filter3(b...);
+ * return target3(a...,c...);
+ * }
+ * }</pre></blockquote>
+ * <p>
+ * A collection adapter {@code collectArguments(mh, 0, coll)} is equivalent to
+ * one which first "folds" the affected arguments, and then drops them, in separate
+ * steps as follows:
+ * <blockquote><pre>{@code
+ * mh = MethodHandles.dropArguments(mh, 1, coll.type().parameterList()); //step 2
+ * mh = MethodHandles.foldArguments(mh, coll); //step 1
+ * }</pre></blockquote>
+ * If the target method handle consumes no arguments besides than the result
+ * (if any) of the filter {@code coll}, then {@code collectArguments(mh, 0, coll)}
+ * is equivalent to {@code filterReturnValue(coll, mh)}.
+ * If the filter method handle {@code coll} consumes one argument and produces
+ * a non-void result, then {@code collectArguments(mh, N, coll)}
+ * is equivalent to {@code filterArguments(mh, N, coll)}.
+ * Other equivalences are possible but would require argument permutation.
+ *
+ * @param target the method handle to invoke after filtering the subsequence of arguments
+ * @param pos the position of the first adapter argument to pass to the filter,
+ * and/or the target argument which receives the result of the filter
+ * @param filter method handle to call on the subsequence of arguments
+ * @return method handle which incorporates the specified argument subsequence filtering logic
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if the return type of {@code filter}
+ * is non-void and is not the same as the {@code pos} argument of the target,
+ * or if {@code pos} is not between 0 and the target's arity, inclusive,
+ * or if the resulting method handle's type would have
+ * <a href="MethodHandle.html#maxarity">too many parameters</a>
+ * @see MethodHandles#foldArguments
+ * @see MethodHandles#filterArguments
+ * @see MethodHandles#filterReturnValue
+ */
public static
- MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { return null; }
+ MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) {
+ MethodType newType = collectArgumentsChecks(target, pos, filter);
+ return new Transformers.CollectArguments(target, filter, pos, newType);
+ }
+ private static MethodType collectArgumentsChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException {
+ MethodType targetType = target.type();
+ MethodType filterType = filter.type();
+ Class<?> rtype = filterType.returnType();
+ List<Class<?>> filterArgs = filterType.parameterList();
+ if (rtype == void.class) {
+ return targetType.insertParameterTypes(pos, filterArgs);
+ }
+ if (rtype != targetType.parameterType(pos)) {
+ throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+ }
+ return targetType.dropParameterTypes(pos, pos+1).insertParameterTypes(pos, filterArgs);
+ }
+
+ /**
+ * Adapts a target method handle by post-processing
+ * its return value (if any) with a filter (another method handle).
+ * The result of the filter is returned from the adapter.
+ * <p>
+ * If the target returns a value, the filter must accept that value as
+ * its only argument.
+ * If the target returns void, the filter must accept no arguments.
+ * <p>
+ * The return type of the filter
+ * replaces the return type of the target
+ * in the resulting adapted method handle.
+ * The argument type of the filter (if any) must be identical to the
+ * return type of the target.
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+MethodHandle length = lookup().findVirtual(String.class,
+ "length", methodType(int.class));
+System.out.println((String) cat.invokeExact("x", "y")); // xy
+MethodHandle f0 = filterReturnValue(cat, length);
+System.out.println((int) f0.invokeExact("x", "y")); // 2
+ * }</pre></blockquote>
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * V target(A...);
+ * T filter(V);
+ * T adapter(A... a) {
+ * V v = target(a...);
+ * return filter(v);
+ * }
+ * // and if the target has a void return:
+ * void target2(A...);
+ * T filter2();
+ * T adapter2(A... a) {
+ * target2(a...);
+ * return filter2();
+ * }
+ * // and if the filter has a void return:
+ * V target3(A...);
+ * void filter3(V);
+ * void adapter3(A... a) {
+ * V v = target3(a...);
+ * filter3(v);
+ * }
+ * }</pre></blockquote>
+ * @param target the method handle to invoke before filtering the return value
+ * @param filter method handle to call on the return value
+ * @return method handle which incorporates the specified return value filtering logic
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if the argument list of {@code filter}
+ * does not match the return type of target as described above
+ */
public static
- MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { return null; }
+ MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) {
+ MethodType targetType = target.type();
+ MethodType filterType = filter.type();
+ filterReturnValueChecks(targetType, filterType);
+ return new Transformers.FilterReturnValue(target, filter);
+ }
+
+ private static void filterReturnValueChecks(MethodType targetType, MethodType filterType) throws RuntimeException {
+ Class<?> rtype = targetType.returnType();
+ int filterValues = filterType.parameterCount();
+ if (filterValues == 0
+ ? (rtype != void.class)
+ : (rtype != filterType.parameterType(0) || filterValues != 1))
+ throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+ }
+
+ /**
+ * Adapts a target method handle by pre-processing
+ * some of its arguments, and then calling the target with
+ * the result of the pre-processing, inserted into the original
+ * sequence of arguments.
+ * <p>
+ * The pre-processing is performed by {@code combiner}, a second method handle.
+ * Of the arguments passed to the adapter, the first {@code N} arguments
+ * are copied to the combiner, which is then called.
+ * (Here, {@code N} is defined as the parameter count of the combiner.)
+ * After this, control passes to the target, with any result
+ * from the combiner inserted before the original {@code N} incoming
+ * arguments.
+ * <p>
+ * If the combiner returns a value, the first parameter type of the target
+ * must be identical with the return type of the combiner, and the next
+ * {@code N} parameter types of the target must exactly match the parameters
+ * of the combiner.
+ * <p>
+ * If the combiner has a void return, no result will be inserted,
+ * and the first {@code N} parameter types of the target
+ * must exactly match the parameters of the combiner.
+ * <p>
+ * The resulting adapter is the same type as the target, except that the
+ * first parameter type is dropped,
+ * if it corresponds to the result of the combiner.
+ * <p>
+ * (Note that {@link #dropArguments(MethodHandle,int,List) dropArguments} can be used to remove any arguments
+ * that either the combiner or the target does not wish to receive.
+ * If some of the incoming arguments are destined only for the combiner,
+ * consider using {@link MethodHandle#asCollector asCollector} instead, since those
+ * arguments will not need to be live on the stack on entry to the
+ * target.)
+ * <p><b>Example:</b>
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
+ "println", methodType(void.class, String.class))
+ .bindTo(System.out);
+MethodHandle cat = lookup().findVirtual(String.class,
+ "concat", methodType(String.class, String.class));
+assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
+MethodHandle catTrace = foldArguments(cat, trace);
+// also prints "boo":
+assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
+ * }</pre></blockquote>
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * // there are N arguments in A...
+ * T target(V, A[N]..., B...);
+ * V combiner(A...);
+ * T adapter(A... a, B... b) {
+ * V v = combiner(a...);
+ * return target(v, a..., b...);
+ * }
+ * // and if the combiner has a void return:
+ * T target2(A[N]..., B...);
+ * void combiner2(A...);
+ * T adapter2(A... a, B... b) {
+ * combiner2(a...);
+ * return target2(a..., b...);
+ * }
+ * }</pre></blockquote>
+ * @param target the method handle to invoke after arguments are combined
+ * @param combiner method handle to call initially on the incoming arguments
+ * @return method handle which incorporates the specified argument folding logic
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if {@code combiner}'s return type
+ * is non-void and not the same as the first argument type of
+ * the target, or if the initial {@code N} argument types
+ * of the target
+ * (skipping one matching the {@code combiner}'s return type)
+ * are not identical with the argument types of {@code combiner}
+ */
public static
- MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { return null; }
+ MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) {
+ int foldPos = 0;
+ MethodType targetType = target.type();
+ MethodType combinerType = combiner.type();
+ Class<?> rtype = foldArgumentChecks(foldPos, targetType, combinerType);
+
+ return new Transformers.FoldArguments(target, combiner);
+ }
+ private static Class<?> foldArgumentChecks(int foldPos, MethodType targetType, MethodType combinerType) {
+ int foldArgs = combinerType.parameterCount();
+ Class<?> rtype = combinerType.returnType();
+ int foldVals = rtype == void.class ? 0 : 1;
+ int afterInsertPos = foldPos + foldVals;
+ boolean ok = (targetType.parameterCount() >= afterInsertPos + foldArgs);
+ if (ok && !(combinerType.parameterList()
+ .equals(targetType.parameterList().subList(afterInsertPos,
+ afterInsertPos + foldArgs))))
+ ok = false;
+ if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(0))
+ ok = false;
+ if (!ok)
+ throw misMatchedTypes("target and combiner types", targetType, combinerType);
+ return rtype;
+ }
+
+ /**
+ * Makes a method handle which adapts a target method handle,
+ * by guarding it with a test, a boolean-valued method handle.
+ * If the guard fails, a fallback handle is called instead.
+ * All three method handles must have the same corresponding
+ * argument and return types, except that the return type
+ * of the test must be boolean, and the test is allowed
+ * to have fewer arguments than the other two method handles.
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * boolean test(A...);
+ * T target(A...,B...);
+ * T fallback(A...,B...);
+ * T adapter(A... a,B... b) {
+ * if (test(a...))
+ * return target(a..., b...);
+ * else
+ * return fallback(a..., b...);
+ * }
+ * }</pre></blockquote>
+ * Note that the test arguments ({@code a...} in the pseudocode) cannot
+ * be modified by execution of the test, and so are passed unchanged
+ * from the caller to the target or fallback as appropriate.
+ * @param test method handle used for test, must return boolean
+ * @param target method handle to call if test passes
+ * @param fallback method handle to call if test fails
+ * @return method handle which incorporates the specified if/then/else logic
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if {@code test} does not return boolean,
+ * or if all three method types do not match (with the return
+ * type of {@code test} changed to match that of the target).
+ */
public static
MethodHandle guardWithTest(MethodHandle test,
MethodHandle target,
- MethodHandle fallback) { return null; }
+ MethodHandle fallback) {
+ MethodType gtype = test.type();
+ MethodType ttype = target.type();
+ MethodType ftype = fallback.type();
+ if (!ttype.equals(ftype))
+ throw misMatchedTypes("target and fallback types", ttype, ftype);
+ if (gtype.returnType() != boolean.class)
+ throw newIllegalArgumentException("guard type is not a predicate "+gtype);
+ List<Class<?>> targs = ttype.parameterList();
+ List<Class<?>> gargs = gtype.parameterList();
+ if (!targs.equals(gargs)) {
+ int gpc = gargs.size(), tpc = targs.size();
+ if (gpc >= tpc || !targs.subList(0, gpc).equals(gargs))
+ throw misMatchedTypes("target and test types", ttype, gtype);
+ test = dropArguments(test, gpc, targs.subList(gpc, tpc));
+ gtype = test.type();
+ }
+
+ return new Transformers.GuardWithTest(test, target, fallback);
+ }
+
+ static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) {
+ return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2);
+ }
+ /**
+ * Makes a method handle which adapts a target method handle,
+ * by running it inside an exception handler.
+ * If the target returns normally, the adapter returns that value.
+ * If an exception matching the specified type is thrown, the fallback
+ * handle is called instead on the exception, plus the original arguments.
+ * <p>
+ * The target and handler must have the same corresponding
+ * argument and return types, except that handler may omit trailing arguments
+ * (similarly to the predicate in {@link #guardWithTest guardWithTest}).
+ * Also, the handler must have an extra leading parameter of {@code exType} or a supertype.
+ * <p> Here is pseudocode for the resulting adapter:
+ * <blockquote><pre>{@code
+ * T target(A..., B...);
+ * T handler(ExType, A...);
+ * T adapter(A... a, B... b) {
+ * try {
+ * return target(a..., b...);
+ * } catch (ExType ex) {
+ * return handler(ex, a...);
+ * }
+ * }
+ * }</pre></blockquote>
+ * Note that the saved arguments ({@code a...} in the pseudocode) cannot
+ * be modified by execution of the target, and so are passed unchanged
+ * from the caller to the handler, if the handler is invoked.
+ * <p>
+ * The target and handler must return the same type, even if the handler
+ * always throws. (This might happen, for instance, because the handler
+ * is simulating a {@code finally} clause).
+ * To create such a throwing handler, compose the handler creation logic
+ * with {@link #throwException throwException},
+ * in order to create a method handle of the correct return type.
+ * @param target method handle to call
+ * @param exType the type of exception which the handler will catch
+ * @param handler method handle to call if a matching exception is thrown
+ * @return method handle which incorporates the specified try/catch logic
+ * @throws NullPointerException if any argument is null
+ * @throws IllegalArgumentException if {@code handler} does not accept
+ * the given exception type, or if the method handle types do
+ * not match in their return types and their
+ * corresponding parameters
+ */
public static
MethodHandle catchException(MethodHandle target,
Class<? extends Throwable> exType,
- MethodHandle handler) { return null; }
+ MethodHandle handler) {
+ MethodType ttype = target.type();
+ MethodType htype = handler.type();
+ if (htype.parameterCount() < 1 ||
+ !htype.parameterType(0).isAssignableFrom(exType))
+ throw newIllegalArgumentException("handler does not accept exception type "+exType);
+ if (htype.returnType() != ttype.returnType())
+ throw misMatchedTypes("target and handler return types", ttype, htype);
+ List<Class<?>> targs = ttype.parameterList();
+ List<Class<?>> hargs = htype.parameterList();
+ hargs = hargs.subList(1, hargs.size()); // omit leading parameter from handler
+ if (!targs.equals(hargs)) {
+ int hpc = hargs.size(), tpc = targs.size();
+ if (hpc >= tpc || !targs.subList(0, hpc).equals(hargs))
+ throw misMatchedTypes("target and handler types", ttype, htype);
+ }
+
+ return new Transformers.CatchException(target, handler, exType);
+ }
+ /**
+ * Produces a method handle which will throw exceptions of the given {@code exType}.
+ * The method handle will accept a single argument of {@code exType},
+ * and immediately throw it as an exception.
+ * The method type will nominally specify a return of {@code returnType}.
+ * The return type may be anything convenient: It doesn't matter to the
+ * method handle's behavior, since it will never return normally.
+ * @param returnType the return type of the desired method handle
+ * @param exType the parameter type of the desired method handle
+ * @return method handle which can throw the given exceptions
+ * @throws NullPointerException if either argument is null
+ */
public static
- MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) { return null; }
+ MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) {
+ if (!Throwable.class.isAssignableFrom(exType))
+ throw new ClassCastException(exType.getName());
+
+ return new Transformers.AlwaysThrow(returnType, exType);
+ }
}
diff --git a/java/lang/invoke/MethodType.java b/java/lang/invoke/MethodType.java
index 4cb5c226..bfa7ccd5 100644
--- a/java/lang/invoke/MethodType.java
+++ b/java/lang/invoke/MethodType.java
@@ -25,78 +25,1227 @@
package java.lang.invoke;
+import sun.invoke.util.Wrapper;
+import java.lang.ref.WeakReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import sun.invoke.util.BytecodeDescriptor;
+import static java.lang.invoke.MethodHandleStatics.*;
+/**
+ * A method type represents the arguments and return type accepted and
+ * returned by a method handle, or the arguments and return type passed
+ * and expected by a method handle caller. Method types must be properly
+ * matched between a method handle and all its callers,
+ * and the JVM's operations enforce this matching at, specifically
+ * during calls to {@link MethodHandle#invokeExact MethodHandle.invokeExact}
+ * and {@link MethodHandle#invoke MethodHandle.invoke}, and during execution
+ * of {@code invokedynamic} instructions.
+ * <p>
+ * The structure is a return type accompanied by any number of parameter types.
+ * The types (primitive, {@code void}, and reference) are represented by {@link Class} objects.
+ * (For ease of exposition, we treat {@code void} as if it were a type.
+ * In fact, it denotes the absence of a return type.)
+ * <p>
+ * All instances of {@code MethodType} are immutable.
+ * Two instances are completely interchangeable if they compare equal.
+ * Equality depends on pairwise correspondence of the return and parameter types and on nothing else.
+ * <p>
+ * This type can be created only by factory methods.
+ * All factory methods may cache values, though caching is not guaranteed.
+ * Some factory methods are static, while others are virtual methods which
+ * modify precursor method types, e.g., by changing a selected parameter.
+ * <p>
+ * Factory methods which operate on groups of parameter types
+ * are systematically presented in two versions, so that both Java arrays and
+ * Java lists can be used to work with groups of parameter types.
+ * The query methods {@code parameterArray} and {@code parameterList}
+ * also provide a choice between arrays and lists.
+ * <p>
+ * {@code MethodType} objects are sometimes derived from bytecode instructions
+ * such as {@code invokedynamic}, specifically from the type descriptor strings associated
+ * with the instructions in a class file's constant pool.
+ * <p>
+ * Like classes and strings, method types can also be represented directly
+ * in a class file's constant pool as constants.
+ * A method type may be loaded by an {@code ldc} instruction which refers
+ * to a suitable {@code CONSTANT_MethodType} constant pool entry.
+ * The entry refers to a {@code CONSTANT_Utf8} spelling for the descriptor string.
+ * (For full details on method type constants,
+ * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
+ * <p>
+ * When the JVM materializes a {@code MethodType} from a descriptor string,
+ * all classes named in the descriptor must be accessible, and will be loaded.
+ * (But the classes need not be initialized, as is the case with a {@code CONSTANT_Class}.)
+ * This loading may occur at any time before the {@code MethodType} object is first derived.
+ * @author John Rose, JSR 292 EG
+ */
public final
class MethodType implements java.io.Serializable {
+ private static final long serialVersionUID = 292L; // {rtype, {ptype...}}
+
+ // The rtype and ptypes fields define the structural identity of the method type:
+ private final Class<?> rtype;
+ private final Class<?>[] ptypes;
+
+ // The remaining fields are caches of various sorts:
+ private @Stable MethodTypeForm form; // erased form, plus cached data about primitives
+ private @Stable MethodType wrapAlt; // alternative wrapped/unwrapped version
+ // Android-changed: Remove adapter cache. We're not dynamically generating any
+ // adapters at this point.
+ // private @Stable Invokers invokers; // cache of handy higher-order adapters
+ private @Stable String methodDescriptor; // cache for toMethodDescriptorString
+
+ /**
+ * Check the given parameters for validity and store them into the final fields.
+ */
+ private MethodType(Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
+ checkRtype(rtype);
+ checkPtypes(ptypes);
+ this.rtype = rtype;
+ // defensively copy the array passed in by the user
+ this.ptypes = trusted ? ptypes : Arrays.copyOf(ptypes, ptypes.length);
+ }
+
+ /**
+ * Construct a temporary unchecked instance of MethodType for use only as a key to the intern table.
+ * Does not check the given parameters for validity, and must be discarded after it is used as a searching key.
+ * The parameters are reversed for this constructor, so that is is not accidentally used.
+ */
+ private MethodType(Class<?>[] ptypes, Class<?> rtype) {
+ this.rtype = rtype;
+ this.ptypes = ptypes;
+ }
+
+ /*trusted*/ MethodTypeForm form() { return form; }
+ /*trusted*/ /** @hide */ public Class<?> rtype() { return rtype; }
+ /*trusted*/ /** @hide */ public Class<?>[] ptypes() { return ptypes; }
+ // Android-changed: Removed method setForm. It's unused in the JDK and there's no
+ // good reason to allow the form to be set externally.
+ //
+ // void setForm(MethodTypeForm f) { form = f; }
+
+ /** This number, mandated by the JVM spec as 255,
+ * is the maximum number of <em>slots</em>
+ * that any Java method can receive in its argument list.
+ * It limits both JVM signatures and method type objects.
+ * The longest possible invocation will look like
+ * {@code staticMethod(arg1, arg2, ..., arg255)} or
+ * {@code x.virtualMethod(arg1, arg2, ..., arg254)}.
+ */
+ /*non-public*/ static final int MAX_JVM_ARITY = 255; // this is mandated by the JVM spec.
+
+ /** This number is the maximum arity of a method handle, 254.
+ * It is derived from the absolute JVM-imposed arity by subtracting one,
+ * which is the slot occupied by the method handle itself at the
+ * beginning of the argument list used to invoke the method handle.
+ * The longest possible invocation will look like
+ * {@code mh.invoke(arg1, arg2, ..., arg254)}.
+ */
+ // Issue: Should we allow MH.invokeWithArguments to go to the full 255?
+ /*non-public*/ static final int MAX_MH_ARITY = MAX_JVM_ARITY-1; // deduct one for mh receiver
+
+ /** This number is the maximum arity of a method handle invoker, 253.
+ * It is derived from the absolute JVM-imposed arity by subtracting two,
+ * which are the slots occupied by invoke method handle, and the
+ * target method handle, which are both at the beginning of the argument
+ * list used to invoke the target method handle.
+ * The longest possible invocation will look like
+ * {@code invokermh.invoke(targetmh, arg1, arg2, ..., arg253)}.
+ */
+ /*non-public*/ static final int MAX_MH_INVOKER_ARITY = MAX_MH_ARITY-1; // deduct one more for invoker
+
+ private static void checkRtype(Class<?> rtype) {
+ Objects.requireNonNull(rtype);
+ }
+ private static void checkPtype(Class<?> ptype) {
+ Objects.requireNonNull(ptype);
+ if (ptype == void.class)
+ throw newIllegalArgumentException("parameter type cannot be void");
+ }
+ /** Return number of extra slots (count of long/double args). */
+ private static int checkPtypes(Class<?>[] ptypes) {
+ int slots = 0;
+ for (Class<?> ptype : ptypes) {
+ checkPtype(ptype);
+ if (ptype == double.class || ptype == long.class) {
+ slots++;
+ }
+ }
+ checkSlotCount(ptypes.length + slots);
+ return slots;
+ }
+ static void checkSlotCount(int count) {
+ assert((MAX_JVM_ARITY & (MAX_JVM_ARITY+1)) == 0);
+ // MAX_JVM_ARITY must be power of 2 minus 1 for following code trick to work:
+ if ((count & MAX_JVM_ARITY) != count)
+ throw newIllegalArgumentException("bad parameter count "+count);
+ }
+ private static IndexOutOfBoundsException newIndexOutOfBoundsException(Object num) {
+ if (num instanceof Integer) num = "bad index: "+num;
+ return new IndexOutOfBoundsException(num.toString());
+ }
+
+ static final ConcurrentWeakInternSet<MethodType> internTable = new ConcurrentWeakInternSet<>();
+
+ static final Class<?>[] NO_PTYPES = {};
+
+ /**
+ * Finds or creates an instance of the given method type.
+ * @param rtype the return type
+ * @param ptypes the parameter types
+ * @return a method type with the given components
+ * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null
+ * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class}
+ */
public static
MethodType methodType(Class<?> rtype, Class<?>[] ptypes) {
- return null;
+ return makeImpl(rtype, ptypes, false);
}
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param rtype the return type
+ * @param ptypes the parameter types
+ * @return a method type with the given components
+ * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null
+ * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class}
+ */
public static
MethodType methodType(Class<?> rtype, List<Class<?>> ptypes) {
- return null;
+ boolean notrust = false; // random List impl. could return evil ptypes array
+ return makeImpl(rtype, listToArray(ptypes), notrust);
}
+ private static Class<?>[] listToArray(List<Class<?>> ptypes) {
+ // sanity check the size before the toArray call, since size might be huge
+ checkSlotCount(ptypes.size());
+ return ptypes.toArray(NO_PTYPES);
+ }
+
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * The leading parameter type is prepended to the remaining array.
+ * @param rtype the return type
+ * @param ptype0 the first parameter type
+ * @param ptypes the remaining parameter types
+ * @return a method type with the given components
+ * @throws NullPointerException if {@code rtype} or {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is null
+ * @throws IllegalArgumentException if {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is {@code void.class}
+ */
public static
- MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) { return null; }
+ MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) {
+ Class<?>[] ptypes1 = new Class<?>[1+ptypes.length];
+ ptypes1[0] = ptype0;
+ System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length);
+ return makeImpl(rtype, ptypes1, true);
+ }
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * The resulting method has no parameter types.
+ * @param rtype the return type
+ * @return a method type with the given return value
+ * @throws NullPointerException if {@code rtype} is null
+ */
public static
- MethodType methodType(Class<?> rtype) { return null; }
+ MethodType methodType(Class<?> rtype) {
+ return makeImpl(rtype, NO_PTYPES, true);
+ }
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * The resulting method has the single given parameter type.
+ * @param rtype the return type
+ * @param ptype0 the parameter type
+ * @return a method type with the given return value and parameter type
+ * @throws NullPointerException if {@code rtype} or {@code ptype0} is null
+ * @throws IllegalArgumentException if {@code ptype0} is {@code void.class}
+ */
public static
- MethodType methodType(Class<?> rtype, Class<?> ptype0) { return null; }
+ MethodType methodType(Class<?> rtype, Class<?> ptype0) {
+ return makeImpl(rtype, new Class<?>[]{ ptype0 }, true);
+ }
+ /**
+ * Finds or creates a method type with the given components.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * The resulting method has the same parameter types as {@code ptypes},
+ * and the specified return type.
+ * @param rtype the return type
+ * @param ptypes the method type which supplies the parameter types
+ * @return a method type with the given components
+ * @throws NullPointerException if {@code rtype} or {@code ptypes} is null
+ */
public static
- MethodType methodType(Class<?> rtype, MethodType ptypes) { return null; }
+ MethodType methodType(Class<?> rtype, MethodType ptypes) {
+ return makeImpl(rtype, ptypes.ptypes, true);
+ }
+
+ /**
+ * Sole factory method to find or create an interned method type.
+ * @param rtype desired return type
+ * @param ptypes desired parameter types
+ * @param trusted whether the ptypes can be used without cloning
+ * @return the unique method type of the desired structure
+ */
+ /*trusted*/ static
+ MethodType makeImpl(Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
+ MethodType mt = internTable.get(new MethodType(ptypes, rtype));
+ if (mt != null)
+ return mt;
+ if (ptypes.length == 0) {
+ ptypes = NO_PTYPES; trusted = true;
+ }
+ mt = new MethodType(rtype, ptypes, trusted);
+ // promote the object to the Real Thing, and reprobe
+ mt.form = MethodTypeForm.findForm(mt);
+ return internTable.add(mt);
+ }
+ private static final MethodType[] objectOnlyTypes = new MethodType[20];
+ /**
+ * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All parameters and the return type will be {@code Object},
+ * except the final array parameter if any, which will be {@code Object[]}.
+ * @param objectArgCount number of parameters (excluding the final array parameter if any)
+ * @param finalArray whether there will be a trailing array parameter, of type {@code Object[]}
+ * @return a generally applicable method type, for all calls of the given fixed argument count and a collected array of further arguments
+ * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 (or 254, if {@code finalArray} is true)
+ * @see #genericMethodType(int)
+ */
public static
- MethodType genericMethodType(int objectArgCount, boolean finalArray) { return null; }
+ MethodType genericMethodType(int objectArgCount, boolean finalArray) {
+ MethodType mt;
+ checkSlotCount(objectArgCount);
+ int ivarargs = (!finalArray ? 0 : 1);
+ int ootIndex = objectArgCount*2 + ivarargs;
+ if (ootIndex < objectOnlyTypes.length) {
+ mt = objectOnlyTypes[ootIndex];
+ if (mt != null) return mt;
+ }
+ Class<?>[] ptypes = new Class<?>[objectArgCount + ivarargs];
+ Arrays.fill(ptypes, Object.class);
+ if (ivarargs != 0) ptypes[objectArgCount] = Object[].class;
+ mt = makeImpl(Object.class, ptypes, true);
+ if (ootIndex < objectOnlyTypes.length) {
+ objectOnlyTypes[ootIndex] = mt; // cache it here also!
+ }
+ return mt;
+ }
+ /**
+ * Finds or creates a method type whose components are all {@code Object}.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All parameters and the return type will be Object.
+ * @param objectArgCount number of parameters
+ * @return a generally applicable method type, for all calls of the given argument count
+ * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255
+ * @see #genericMethodType(int, boolean)
+ */
public static
- MethodType genericMethodType(int objectArgCount) { return null; }
+ MethodType genericMethodType(int objectArgCount) {
+ return genericMethodType(objectArgCount, false);
+ }
+
+ /**
+ * Finds or creates a method type with a single different parameter type.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param num the index (zero-based) of the parameter type to change
+ * @param nptype a new parameter type to replace the old one with
+ * @return the same type, except with the selected parameter changed
+ * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()}
+ * @throws IllegalArgumentException if {@code nptype} is {@code void.class}
+ * @throws NullPointerException if {@code nptype} is null
+ */
+ public MethodType changeParameterType(int num, Class<?> nptype) {
+ if (parameterType(num) == nptype) return this;
+ checkPtype(nptype);
+ Class<?>[] nptypes = ptypes.clone();
+ nptypes[num] = nptype;
+ return makeImpl(rtype, nptypes, true);
+ }
+
+ /**
+ * Finds or creates a method type with additional parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param num the position (zero-based) of the inserted parameter type(s)
+ * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+ * @return the same type, except with the selected parameter(s) inserted
+ * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()}
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) {
+ int len = ptypes.length;
+ if (num < 0 || num > len)
+ throw newIndexOutOfBoundsException(num);
+ int ins = checkPtypes(ptypesToInsert);
+ checkSlotCount(parameterSlotCount() + ptypesToInsert.length + ins);
+ int ilen = ptypesToInsert.length;
+ if (ilen == 0) return this;
+ Class<?>[] nptypes = Arrays.copyOfRange(ptypes, 0, len+ilen);
+ System.arraycopy(nptypes, num, nptypes, num+ilen, len-num);
+ System.arraycopy(ptypesToInsert, 0, nptypes, num, ilen);
+ return makeImpl(rtype, nptypes, true);
+ }
+
+ /**
+ * Finds or creates a method type with additional parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list
+ * @return the same type, except with the selected parameter(s) appended
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ public MethodType appendParameterTypes(Class<?>... ptypesToInsert) {
+ return insertParameterTypes(parameterCount(), ptypesToInsert);
+ }
+
+ /**
+ * Finds or creates a method type with additional parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param num the position (zero-based) of the inserted parameter type(s)
+ * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+ * @return the same type, except with the selected parameter(s) inserted
+ * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()}
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) {
+ return insertParameterTypes(num, listToArray(ptypesToInsert));
+ }
+
+ /**
+ * Finds or creates a method type with additional parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list
+ * @return the same type, except with the selected parameter(s) appended
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) {
+ return insertParameterTypes(parameterCount(), ptypesToInsert);
+ }
+
+ /**
+ * Finds or creates a method type with modified parameter types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param start the position (zero-based) of the first replaced parameter type(s)
+ * @param end the position (zero-based) after the last replaced parameter type(s)
+ * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+ * @return the same type, except with the selected parameter(s) replaced
+ * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
+ * or if {@code end} is negative or greater than {@code parameterCount()}
+ * or if {@code start} is greater than {@code end}
+ * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+ * or if the resulting method type would have more than 255 parameter slots
+ * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+ */
+ /*non-public*/ MethodType replaceParameterTypes(int start, int end, Class<?>... ptypesToInsert) {
+ if (start == end)
+ return insertParameterTypes(start, ptypesToInsert);
+ int len = ptypes.length;
+ if (!(0 <= start && start <= end && end <= len))
+ throw newIndexOutOfBoundsException("start="+start+" end="+end);
+ int ilen = ptypesToInsert.length;
+ if (ilen == 0)
+ return dropParameterTypes(start, end);
+ return dropParameterTypes(start, end).insertParameterTypes(start, ptypesToInsert);
+ }
+
+ /** Replace the last arrayLength parameter types with the component type of arrayType.
+ * @param arrayType any array type
+ * @param arrayLength the number of parameter types to change
+ * @return the resulting type
+ */
+ /*non-public*/ MethodType asSpreaderType(Class<?> arrayType, int arrayLength) {
+ assert(parameterCount() >= arrayLength);
+ int spreadPos = ptypes.length - arrayLength;
+ if (arrayLength == 0) return this; // nothing to change
+ if (arrayType == Object[].class) {
+ if (isGeneric()) return this; // nothing to change
+ if (spreadPos == 0) {
+ // no leading arguments to preserve; go generic
+ MethodType res = genericMethodType(arrayLength);
+ if (rtype != Object.class) {
+ res = res.changeReturnType(rtype);
+ }
+ return res;
+ }
+ }
+ Class<?> elemType = arrayType.getComponentType();
+ assert(elemType != null);
+ for (int i = spreadPos; i < ptypes.length; i++) {
+ if (ptypes[i] != elemType) {
+ Class<?>[] fixedPtypes = ptypes.clone();
+ Arrays.fill(fixedPtypes, i, ptypes.length, elemType);
+ return methodType(rtype, fixedPtypes);
+ }
+ }
+ return this; // arguments check out; no change
+ }
+
+ /** Return the leading parameter type, which must exist and be a reference.
+ * @return the leading parameter type, after error checks
+ */
+ /*non-public*/ Class<?> leadingReferenceParameter() {
+ Class<?> ptype;
+ if (ptypes.length == 0 ||
+ (ptype = ptypes[0]).isPrimitive())
+ throw newIllegalArgumentException("no leading reference parameter");
+ return ptype;
+ }
+
+ /** Delete the last parameter type and replace it with arrayLength copies of the component type of arrayType.
+ * @param arrayType any array type
+ * @param arrayLength the number of parameter types to insert
+ * @return the resulting type
+ */
+ /*non-public*/ MethodType asCollectorType(Class<?> arrayType, int arrayLength) {
+ assert(parameterCount() >= 1);
+ assert(lastParameterType().isAssignableFrom(arrayType));
+ MethodType res;
+ if (arrayType == Object[].class) {
+ res = genericMethodType(arrayLength);
+ if (rtype != Object.class) {
+ res = res.changeReturnType(rtype);
+ }
+ } else {
+ Class<?> elemType = arrayType.getComponentType();
+ assert(elemType != null);
+ res = methodType(rtype, Collections.nCopies(arrayLength, elemType));
+ }
+ if (ptypes.length == 1) {
+ return res;
+ } else {
+ return res.insertParameterTypes(0, parameterList().subList(0, ptypes.length-1));
+ }
+ }
+
+ /**
+ * Finds or creates a method type with some parameter types omitted.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param start the index (zero-based) of the first parameter type to remove
+ * @param end the index (greater than {@code start}) of the first parameter type after not to remove
+ * @return the same type, except with the selected parameter(s) removed
+ * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
+ * or if {@code end} is negative or greater than {@code parameterCount()}
+ * or if {@code start} is greater than {@code end}
+ */
+ public MethodType dropParameterTypes(int start, int end) {
+ int len = ptypes.length;
+ if (!(0 <= start && start <= end && end <= len))
+ throw newIndexOutOfBoundsException("start="+start+" end="+end);
+ if (start == end) return this;
+ Class<?>[] nptypes;
+ if (start == 0) {
+ if (end == len) {
+ // drop all parameters
+ nptypes = NO_PTYPES;
+ } else {
+ // drop initial parameter(s)
+ nptypes = Arrays.copyOfRange(ptypes, end, len);
+ }
+ } else {
+ if (end == len) {
+ // drop trailing parameter(s)
+ nptypes = Arrays.copyOfRange(ptypes, 0, start);
+ } else {
+ int tail = len - end;
+ nptypes = Arrays.copyOfRange(ptypes, 0, start + tail);
+ System.arraycopy(ptypes, end, nptypes, start, tail);
+ }
+ }
+ return makeImpl(rtype, nptypes, true);
+ }
+
+ /**
+ * Finds or creates a method type with a different return type.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * @param nrtype a return parameter type to replace the old one with
+ * @return the same type, except with the return type change
+ * @throws NullPointerException if {@code nrtype} is null
+ */
+ public MethodType changeReturnType(Class<?> nrtype) {
+ if (returnType() == nrtype) return this;
+ return makeImpl(nrtype, ptypes, true);
+ }
+
+ /**
+ * Reports if this type contains a primitive argument or return value.
+ * The return type {@code void} counts as a primitive.
+ * @return true if any of the types are primitives
+ */
+ public boolean hasPrimitives() {
+ return form.hasPrimitives();
+ }
+
+ /**
+ * Reports if this type contains a wrapper argument or return value.
+ * Wrappers are types which box primitive values, such as {@link Integer}.
+ * The reference type {@code java.lang.Void} counts as a wrapper,
+ * if it occurs as a return type.
+ * @return true if any of the types are wrappers
+ */
+ public boolean hasWrappers() {
+ return unwrap() != this;
+ }
+
+ /**
+ * Erases all reference types to {@code Object}.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All primitive types (including {@code void}) will remain unchanged.
+ * @return a version of the original type with all reference types replaced
+ */
+ public MethodType erase() {
+ return form.erasedType();
+ }
+
+ /**
+ * Erases all reference types to {@code Object}, and all subword types to {@code int}.
+ * This is the reduced type polymorphism used by private methods
+ * such as {@link MethodHandle#invokeBasic invokeBasic}.
+ * @return a version of the original type with all reference and subword types replaced
+ */
+ /*non-public*/ MethodType basicType() {
+ return form.basicType();
+ }
+
+ /**
+ * @return a version of the original type with MethodHandle prepended as the first argument
+ */
+ /*non-public*/ MethodType invokerType() {
+ return insertParameterTypes(0, MethodHandle.class);
+ }
- public MethodType changeParameterType(int num, Class<?> nptype) { return null; }
+ /**
+ * Converts all types, both reference and primitive, to {@code Object}.
+ * Convenience method for {@link #genericMethodType(int) genericMethodType}.
+ * The expression {@code type.wrap().erase()} produces the same value
+ * as {@code type.generic()}.
+ * @return a version of the original type with all types replaced
+ */
+ public MethodType generic() {
+ return genericMethodType(parameterCount());
+ }
- public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) { return null; }
+ /*non-public*/ boolean isGeneric() {
+ return this == erase() && !hasPrimitives();
+ }
- public MethodType appendParameterTypes(Class<?>... ptypesToInsert) { return null; }
+ /**
+ * Converts all primitive types to their corresponding wrapper types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All reference types (including wrapper types) will remain unchanged.
+ * A {@code void} return type is changed to the type {@code java.lang.Void}.
+ * The expression {@code type.wrap().erase()} produces the same value
+ * as {@code type.generic()}.
+ * @return a version of the original type with all primitive types replaced
+ */
+ public MethodType wrap() {
+ return hasPrimitives() ? wrapWithPrims(this) : this;
+ }
- public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) { return null; }
+ /**
+ * Converts all wrapper types to their corresponding primitive types.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * All primitive types (including {@code void}) will remain unchanged.
+ * A return type of {@code java.lang.Void} is changed to {@code void}.
+ * @return a version of the original type with all wrapper types replaced
+ */
+ public MethodType unwrap() {
+ MethodType noprims = !hasPrimitives() ? this : wrapWithPrims(this);
+ return unwrapWithNoPrims(noprims);
+ }
- public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) { return null; }
+ private static MethodType wrapWithPrims(MethodType pt) {
+ assert(pt.hasPrimitives());
+ MethodType wt = pt.wrapAlt;
+ if (wt == null) {
+ // fill in lazily
+ wt = MethodTypeForm.canonicalize(pt, MethodTypeForm.WRAP, MethodTypeForm.WRAP);
+ assert(wt != null);
+ pt.wrapAlt = wt;
+ }
+ return wt;
+ }
- public MethodType dropParameterTypes(int start, int end) { return null; }
+ private static MethodType unwrapWithNoPrims(MethodType wt) {
+ assert(!wt.hasPrimitives());
+ MethodType uwt = wt.wrapAlt;
+ if (uwt == null) {
+ // fill in lazily
+ uwt = MethodTypeForm.canonicalize(wt, MethodTypeForm.UNWRAP, MethodTypeForm.UNWRAP);
+ if (uwt == null)
+ uwt = wt; // type has no wrappers or prims at all
+ wt.wrapAlt = uwt;
+ }
+ return uwt;
+ }
- public MethodType changeReturnType(Class<?> nrtype) { return null; }
+ /**
+ * Returns the parameter type at the specified index, within this method type.
+ * @param num the index (zero-based) of the desired parameter type
+ * @return the selected parameter type
+ * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()}
+ */
+ public Class<?> parameterType(int num) {
+ return ptypes[num];
+ }
+ /**
+ * Returns the number of parameter types in this method type.
+ * @return the number of parameter types
+ */
+ public int parameterCount() {
+ return ptypes.length;
+ }
+ /**
+ * Returns the return type of this method type.
+ * @return the return type
+ */
+ public Class<?> returnType() {
+ return rtype;
+ }
- public boolean hasPrimitives() { return false; }
+ /**
+ * Presents the parameter types as a list (a convenience method).
+ * The list will be immutable.
+ * @return the parameter types (as an immutable list)
+ */
+ public List<Class<?>> parameterList() {
+ return Collections.unmodifiableList(Arrays.asList(ptypes.clone()));
+ }
- public boolean hasWrappers() { return false; }
+ /*non-public*/ Class<?> lastParameterType() {
+ int len = ptypes.length;
+ return len == 0 ? void.class : ptypes[len-1];
+ }
- public MethodType erase() { return null; }
+ /**
+ * Presents the parameter types as an array (a convenience method).
+ * Changes to the array will not result in changes to the type.
+ * @return the parameter types (as a fresh copy if necessary)
+ */
+ public Class<?>[] parameterArray() {
+ return ptypes.clone();
+ }
- public MethodType generic() { return null; }
+ /**
+ * Compares the specified object with this type for equality.
+ * That is, it returns <tt>true</tt> if and only if the specified object
+ * is also a method type with exactly the same parameters and return type.
+ * @param x object to compare
+ * @see Object#equals(Object)
+ */
+ @Override
+ public boolean equals(Object x) {
+ return this == x || x instanceof MethodType && equals((MethodType)x);
+ }
- public MethodType wrap() { return null; }
+ private boolean equals(MethodType that) {
+ return this.rtype == that.rtype
+ && Arrays.equals(this.ptypes, that.ptypes);
+ }
- public MethodType unwrap() { return null; }
+ /**
+ * Returns the hash code value for this method type.
+ * It is defined to be the same as the hashcode of a List
+ * whose elements are the return type followed by the
+ * parameter types.
+ * @return the hash code value for this method type
+ * @see Object#hashCode()
+ * @see #equals(Object)
+ * @see List#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ int hashCode = 31 + rtype.hashCode();
+ for (Class<?> ptype : ptypes)
+ hashCode = 31*hashCode + ptype.hashCode();
+ return hashCode;
+ }
- public Class<?> parameterType(int num) { return null; }
+ /**
+ * Returns a string representation of the method type,
+ * of the form {@code "(PT0,PT1...)RT"}.
+ * The string representation of a method type is a
+ * parenthesis enclosed, comma separated list of type names,
+ * followed immediately by the return type.
+ * <p>
+ * Each type is represented by its
+ * {@link java.lang.Class#getSimpleName simple name}.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ for (int i = 0; i < ptypes.length; i++) {
+ if (i > 0) sb.append(",");
+ sb.append(ptypes[i].getSimpleName());
+ }
+ sb.append(")");
+ sb.append(rtype.getSimpleName());
+ return sb.toString();
+ }
- public int parameterCount() { return 0; }
+ /** True if the old return type can always be viewed (w/o casting) under new return type,
+ * and the new parameters can be viewed (w/o casting) under the old parameter types.
+ */
+ // Android-changed: Removed implementation details.
+ // boolean isViewableAs(MethodType newType, boolean keepInterfaces);
+ // boolean parametersAreViewableAs(MethodType newType, boolean keepInterfaces);
+ /*non-public*/
+ boolean isConvertibleTo(MethodType newType) {
+ MethodTypeForm oldForm = this.form();
+ MethodTypeForm newForm = newType.form();
+ if (oldForm == newForm)
+ // same parameter count, same primitive/object mix
+ return true;
+ if (!canConvert(returnType(), newType.returnType()))
+ return false;
+ Class<?>[] srcTypes = newType.ptypes;
+ Class<?>[] dstTypes = ptypes;
+ if (srcTypes == dstTypes)
+ return true;
+ int argc;
+ if ((argc = srcTypes.length) != dstTypes.length)
+ return false;
+ if (argc <= 1) {
+ if (argc == 1 && !canConvert(srcTypes[0], dstTypes[0]))
+ return false;
+ return true;
+ }
+ if ((oldForm.primitiveParameterCount() == 0 && oldForm.erasedType == this) ||
+ (newForm.primitiveParameterCount() == 0 && newForm.erasedType == newType)) {
+ // Somewhat complicated test to avoid a loop of 2 or more trips.
+ // If either type has only Object parameters, we know we can convert.
+ assert(canConvertParameters(srcTypes, dstTypes));
+ return true;
+ }
+ return canConvertParameters(srcTypes, dstTypes);
+ }
+
+ /** Returns true if MHs.explicitCastArguments produces the same result as MH.asType.
+ * If the type conversion is impossible for either, the result should be false.
+ */
+ /*non-public*/
+ boolean explicitCastEquivalentToAsType(MethodType newType) {
+ if (this == newType) return true;
+ if (!explicitCastEquivalentToAsType(rtype, newType.rtype)) {
+ return false;
+ }
+ Class<?>[] srcTypes = newType.ptypes;
+ Class<?>[] dstTypes = ptypes;
+ if (dstTypes == srcTypes) {
+ return true;
+ }
+ assert(dstTypes.length == srcTypes.length);
+ for (int i = 0; i < dstTypes.length; i++) {
+ if (!explicitCastEquivalentToAsType(srcTypes[i], dstTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Reports true if the src can be converted to the dst, by both asType and MHs.eCE,
+ * and with the same effect.
+ * MHs.eCA has the following "upgrades" to MH.asType:
+ * 1. interfaces are unchecked (that is, treated as if aliased to Object)
+ * Therefore, {@code Object->CharSequence} is possible in both cases but has different semantics
+ * 2a. the full matrix of primitive-to-primitive conversions is supported
+ * Narrowing like {@code long->byte} and basic-typing like {@code boolean->int}
+ * are not supported by asType, but anything supported by asType is equivalent
+ * with MHs.eCE.
+ * 2b. conversion of void->primitive means explicit cast has to insert zero/false/null.
+ * 3a. unboxing conversions can be followed by the full matrix of primitive conversions
+ * 3b. unboxing of null is permitted (creates a zero primitive value)
+ * Other than interfaces, reference-to-reference conversions are the same.
+ * Boxing primitives to references is the same for both operators.
+ */
+ private static boolean explicitCastEquivalentToAsType(Class<?> src, Class<?> dst) {
+ if (src == dst || dst == Object.class || dst == void.class) {
+ return true;
+ } else if (src.isPrimitive() && src != void.class) {
+ // Could be a prim/prim conversion, where casting is a strict superset.
+ // Or a boxing conversion, which is always to an exact wrapper class.
+ return canConvert(src, dst);
+ } else if (dst.isPrimitive()) {
+ // Unboxing behavior is different between MHs.eCA & MH.asType (see 3b).
+ return false;
+ } else {
+ // R->R always works, but we have to avoid a check-cast to an interface.
+ return !dst.isInterface() || dst.isAssignableFrom(src);
+ }
+ }
- public Class<?> returnType() { return null; }
+ private boolean canConvertParameters(Class<?>[] srcTypes, Class<?>[] dstTypes) {
+ for (int i = 0; i < srcTypes.length; i++) {
+ if (!canConvert(srcTypes[i], dstTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
- public List<Class<?>> parameterList() { return null; }
+ /*non-public*/
+ static boolean canConvert(Class<?> src, Class<?> dst) {
+ // short-circuit a few cases:
+ if (src == dst || src == Object.class || dst == Object.class) return true;
+ // the remainder of this logic is documented in MethodHandle.asType
+ if (src.isPrimitive()) {
+ // can force void to an explicit null, a la reflect.Method.invoke
+ // can also force void to a primitive zero, by analogy
+ if (src == void.class) return true; //or !dst.isPrimitive()?
+ Wrapper sw = Wrapper.forPrimitiveType(src);
+ if (dst.isPrimitive()) {
+ // P->P must widen
+ return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw);
+ } else {
+ // P->R must box and widen
+ return dst.isAssignableFrom(sw.wrapperType());
+ }
+ } else if (dst.isPrimitive()) {
+ // any value can be dropped
+ if (dst == void.class) return true;
+ Wrapper dw = Wrapper.forPrimitiveType(dst);
+ // R->P must be able to unbox (from a dynamically chosen type) and widen
+ // For example:
+ // Byte/Number/Comparable/Object -> dw:Byte -> byte.
+ // Character/Comparable/Object -> dw:Character -> char
+ // Boolean/Comparable/Object -> dw:Boolean -> boolean
+ // This means that dw must be cast-compatible with src.
+ if (src.isAssignableFrom(dw.wrapperType())) {
+ return true;
+ }
+ // The above does not work if the source reference is strongly typed
+ // to a wrapper whose primitive must be widened. For example:
+ // Byte -> unbox:byte -> short/int/long/float/double
+ // Character -> unbox:char -> int/long/float/double
+ if (Wrapper.isWrapperType(src) &&
+ dw.isConvertibleFrom(Wrapper.forWrapperType(src))) {
+ // can unbox from src and then widen to dst
+ return true;
+ }
+ // We have already covered cases which arise due to runtime unboxing
+ // of a reference type which covers several wrapper types:
+ // Object -> cast:Integer -> unbox:int -> long/float/double
+ // Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double
+ // An marginal case is Number -> dw:Character -> char, which would be OK if there were a
+ // subclass of Number which wraps a value that can convert to char.
+ // Since there is none, we don't need an extra check here to cover char or boolean.
+ return false;
+ } else {
+ // R->R always works, since null is always valid dynamically
+ return true;
+ }
+ }
- public Class<?>[] parameterArray() { return null; }
+ /** Reports the number of JVM stack slots required to invoke a method
+ * of this type. Note that (for historical reasons) the JVM requires
+ * a second stack slot to pass long and double arguments.
+ * So this method returns {@link #parameterCount() parameterCount} plus the
+ * number of long and double parameters (if any).
+ * <p>
+ * This method is included for the benefit of applications that must
+ * generate bytecodes that process method handles and invokedynamic.
+ * @return the number of JVM stack slots for this type's parameters
+ */
+ /*non-public*/ int parameterSlotCount() {
+ return form.parameterSlotCount();
+ }
+ /// Queries which have to do with the bytecode architecture
+
+ // Android-changed: These methods aren't needed on Android and are unused within the JDK.
+ //
+ // int parameterSlotDepth(int num);
+ // int returnSlotCount();
+ //
+ // Android-changed: Removed cache of higher order adapters.
+ //
+ // Invokers invokers();
+
+ /**
+ * Finds or creates an instance of a method type, given the spelling of its bytecode descriptor.
+ * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+ * Any class or interface name embedded in the descriptor string
+ * will be resolved by calling {@link ClassLoader#loadClass(java.lang.String)}
+ * on the given loader (or if it is null, on the system class loader).
+ * <p>
+ * Note that it is possible to encounter method types which cannot be
+ * constructed by this method, because their component types are
+ * not all reachable from a common class loader.
+ * <p>
+ * This method is included for the benefit of applications that must
+ * generate bytecodes that process method handles and {@code invokedynamic}.
+ * @param descriptor a bytecode-level type descriptor string "(T...)T"
+ * @param loader the class loader in which to look up the types
+ * @return a method type matching the bytecode-level type descriptor
+ * @throws NullPointerException if the string is null
+ * @throws IllegalArgumentException if the string is not well-formed
+ * @throws TypeNotPresentException if a named type cannot be found
+ */
public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)
- throws IllegalArgumentException, TypeNotPresentException { return null; }
+ throws IllegalArgumentException, TypeNotPresentException
+ {
+ if (!descriptor.startsWith("(") || // also generates NPE if needed
+ descriptor.indexOf(')') < 0 ||
+ descriptor.indexOf('.') >= 0)
+ throw newIllegalArgumentException("not a method descriptor: "+descriptor);
+ List<Class<?>> types = BytecodeDescriptor.parseMethod(descriptor, loader);
+ Class<?> rtype = types.remove(types.size() - 1);
+ checkSlotCount(types.size());
+ Class<?>[] ptypes = listToArray(types);
+ return makeImpl(rtype, ptypes, true);
+ }
+
+ /**
+ * Produces a bytecode descriptor representation of the method type.
+ * <p>
+ * Note that this is not a strict inverse of {@link #fromMethodDescriptorString fromMethodDescriptorString}.
+ * Two distinct classes which share a common name but have different class loaders
+ * will appear identical when viewed within descriptor strings.
+ * <p>
+ * This method is included for the benefit of applications that must
+ * generate bytecodes that process method handles and {@code invokedynamic}.
+ * {@link #fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) fromMethodDescriptorString},
+ * because the latter requires a suitable class loader argument.
+ * @return the bytecode type descriptor representation
+ */
+ public String toMethodDescriptorString() {
+ String desc = methodDescriptor;
+ if (desc == null) {
+ desc = BytecodeDescriptor.unparse(this);
+ methodDescriptor = desc;
+ }
+ return desc;
+ }
+
+ /*non-public*/ static String toFieldDescriptorString(Class<?> cls) {
+ return BytecodeDescriptor.unparse(cls);
+ }
- public String toMethodDescriptorString() { return null; }
+ /// Serialization.
+
+ /**
+ * There are no serializable fields for {@code MethodType}.
+ */
+ private static final java.io.ObjectStreamField[] serialPersistentFields = { };
+
+ /**
+ * Save the {@code MethodType} instance to a stream.
+ *
+ * @serialData
+ * For portability, the serialized format does not refer to named fields.
+ * Instead, the return type and parameter type arrays are written directly
+ * from the {@code writeObject} method, using two calls to {@code s.writeObject}
+ * as follows:
+ * <blockquote><pre>{@code
+s.writeObject(this.returnType());
+s.writeObject(this.parameterArray());
+ * }</pre></blockquote>
+ * <p>
+ * The deserialized field values are checked as if they were
+ * provided to the factory method {@link #methodType(Class,Class[]) methodType}.
+ * For example, null values, or {@code void} parameter types,
+ * will lead to exceptions during deserialization.
+ * @param s the stream to write the object to
+ * @throws java.io.IOException if there is a problem writing the object
+ */
+ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
+ s.defaultWriteObject(); // requires serialPersistentFields to be an empty array
+ s.writeObject(returnType());
+ s.writeObject(parameterArray());
+ }
+
+ /**
+ * Reconstitute the {@code MethodType} instance from a stream (that is,
+ * deserialize it).
+ * This instance is a scratch object with bogus final fields.
+ * It provides the parameters to the factory method called by
+ * {@link #readResolve readResolve}.
+ * After that call it is discarded.
+ * @param s the stream to read the object from
+ * @throws java.io.IOException if there is a problem reading the object
+ * @throws ClassNotFoundException if one of the component classes cannot be resolved
+ * @see #MethodType()
+ * @see #readResolve
+ * @see #writeObject
+ */
+ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
+ s.defaultReadObject(); // requires serialPersistentFields to be an empty array
+
+ Class<?> returnType = (Class<?>) s.readObject();
+ Class<?>[] parameterArray = (Class<?>[]) s.readObject();
+
+ // Probably this object will never escape, but let's check
+ // the field values now, just to be sure.
+ checkRtype(returnType);
+ checkPtypes(parameterArray);
+
+ parameterArray = parameterArray.clone(); // make sure it is unshared
+ MethodType_init(returnType, parameterArray);
+ }
+
+ /**
+ * For serialization only.
+ * Sets the final fields to null, pending {@code Unsafe.putObject}.
+ */
+ private MethodType() {
+ this.rtype = null;
+ this.ptypes = null;
+ }
+ private void MethodType_init(Class<?> rtype, Class<?>[] ptypes) {
+ // In order to communicate these values to readResolve, we must
+ // store them into the implementation-specific final fields.
+ checkRtype(rtype);
+ checkPtypes(ptypes);
+ UNSAFE.putObject(this, rtypeOffset, rtype);
+ UNSAFE.putObject(this, ptypesOffset, ptypes);
+ }
+
+ // Support for resetting final fields while deserializing
+ private static final long rtypeOffset, ptypesOffset;
+ static {
+ try {
+ rtypeOffset = UNSAFE.objectFieldOffset
+ (MethodType.class.getDeclaredField("rtype"));
+ ptypesOffset = UNSAFE.objectFieldOffset
+ (MethodType.class.getDeclaredField("ptypes"));
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ /**
+ * Resolves and initializes a {@code MethodType} object
+ * after serialization.
+ * @return the fully initialized {@code MethodType} object
+ */
+ private Object readResolve() {
+ // Do not use a trusted path for deserialization:
+ //return makeImpl(rtype, ptypes, true);
+ // Verify all operands, and make sure ptypes is unshared:
+ return methodType(rtype, ptypes);
+ }
+
+ /**
+ * Simple implementation of weak concurrent intern set.
+ *
+ * @param <T> interned type
+ */
+ private static class ConcurrentWeakInternSet<T> {
+
+ private final ConcurrentMap<WeakEntry<T>, WeakEntry<T>> map;
+ private final ReferenceQueue<T> stale;
+
+ public ConcurrentWeakInternSet() {
+ this.map = new ConcurrentHashMap<>();
+ this.stale = new ReferenceQueue<>();
+ }
+
+ /**
+ * Get the existing interned element.
+ * This method returns null if no element is interned.
+ *
+ * @param elem element to look up
+ * @return the interned element
+ */
+ public T get(T elem) {
+ if (elem == null) throw new NullPointerException();
+ expungeStaleElements();
+
+ WeakEntry<T> value = map.get(new WeakEntry<>(elem));
+ if (value != null) {
+ T res = value.get();
+ if (res != null) {
+ return res;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Interns the element.
+ * Always returns non-null element, matching the one in the intern set.
+ * Under the race against another add(), it can return <i>different</i>
+ * element, if another thread beats us to interning it.
+ *
+ * @param elem element to add
+ * @return element that was actually added
+ */
+ public T add(T elem) {
+ if (elem == null) throw new NullPointerException();
+
+ // Playing double race here, and so spinloop is required.
+ // First race is with two concurrent updaters.
+ // Second race is with GC purging weak ref under our feet.
+ // Hopefully, we almost always end up with a single pass.
+ T interned;
+ WeakEntry<T> e = new WeakEntry<>(elem, stale);
+ do {
+ expungeStaleElements();
+ WeakEntry<T> exist = map.putIfAbsent(e, e);
+ interned = (exist == null) ? elem : exist.get();
+ } while (interned == null);
+ return interned;
+ }
+
+ private void expungeStaleElements() {
+ Reference<? extends T> reference;
+ while ((reference = stale.poll()) != null) {
+ map.remove(reference);
+ }
+ }
+
+ private static class WeakEntry<T> extends WeakReference<T> {
+
+ public final int hashcode;
+
+ public WeakEntry(T key, ReferenceQueue<T> queue) {
+ super(key, queue);
+ hashcode = key.hashCode();
+ }
+
+ public WeakEntry(T key) {
+ super(key);
+ hashcode = key.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof WeakEntry) {
+ Object that = ((WeakEntry) obj).get();
+ Object mine = get();
+ return (that == null || mine == null) ? (this == obj) : mine.equals(that);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashcode;
+ }
+
+ }
+ }
}
diff --git a/java/lang/invoke/VarHandle.java b/java/lang/invoke/VarHandle.java
new file mode 100644
index 00000000..bb93fcf5
--- /dev/null
+++ b/java/lang/invoke/VarHandle.java
@@ -0,0 +1,2161 @@
+/*
+ * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang.invoke;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A VarHandle is a dynamically strongly typed reference to a variable, or to a
+ * parametrically-defined family of variables, including static fields,
+ * non-static fields, array elements, or components of an off-heap data
+ * structure. Access to such variables is supported under various
+ * <em>access modes</em>, including plain read/write access, volatile
+ * read/write access, and compare-and-swap.
+ *
+ * <p>VarHandles are immutable and have no visible state. VarHandles cannot be
+ * subclassed by the user.
+ *
+ * <p>A VarHandle has:
+ * <ul>
+ * <li>a {@link #varType variable type} T, the type of every variable referenced
+ * by this VarHandle; and
+ * <li>a list of {@link #coordinateTypes coordinate types}
+ * {@code CT1, CT2, ..., CTn}, the types of <em>coordinate expressions</em> that
+ * jointly locate a variable referenced by this VarHandle.
+ * </ul>
+ * Variable and coordinate types may be primitive or reference, and are
+ * represented by {@code Class} objects. The list of coordinate types may be
+ * empty.
+ *
+ * <p>Factory methods that produce or {@link java.lang.invoke.MethodHandles.Lookup
+ * lookup} VarHandle instances document the supported variable type and the list
+ * of coordinate types.
+ *
+ * <p>Each access mode is associated with one <em>access mode method</em>, a
+ * <a href="MethodHandle.html#sigpoly">signature polymorphic</a> method named
+ * for the access mode. When an access mode method is invoked on a VarHandle
+ * instance, the initial arguments to the invocation are coordinate expressions
+ * that indicate in precisely which object the variable is to be accessed.
+ * Trailing arguments to the invocation represent values of importance to the
+ * access mode. For example, the various compare-and-set or compare-and-exchange
+ * access modes require two trailing arguments for the variable's expected value
+ * and new value.
+ *
+ * <p>The arity and types of arguments to the invocation of an access mode
+ * method are not checked statically. Instead, each access mode method
+ * specifies an {@link #accessModeType(AccessMode) access mode type},
+ * represented as an instance of {@link MethodType}, that serves as a kind of
+ * method signature against which the arguments are checked dynamically. An
+ * access mode type gives formal parameter types in terms of the coordinate
+ * types of a VarHandle instance and the types for values of importance to the
+ * access mode. An access mode type also gives a return type, often in terms of
+ * the variable type of a VarHandle instance. When an access mode method is
+ * invoked on a VarHandle instance, the symbolic type descriptor at the
+ * call site, the run time types of arguments to the invocation, and the run
+ * time type of the return value, must <a href="#invoke">match</a> the types
+ * given in the access mode type. A runtime exception will be thrown if the
+ * match fails.
+ *
+ * For example, the access mode method {@link #compareAndSet} specifies that if
+ * its receiver is a VarHandle instance with coordinate types
+ * {@code CT1, ..., CTn} and variable type {@code T}, then its access mode type
+ * is {@code (CT1 c1, ..., CTn cn, T expectedValue, T newValue)boolean}.
+ * Suppose that a VarHandle instance can access array elements, and that its
+ * coordinate types are {@code String[]} and {@code int} while its variable type
+ * is {@code String}. The access mode type for {@code compareAndSet} on this
+ * VarHandle instance would be
+ * {@code (String[] c1, int c2, String expectedValue, String newValue)boolean}.
+ * Such a VarHandle instance may produced by the
+ * {@link MethodHandles#arrayElementVarHandle(Class) array factory method} and
+ * access array elements as follows:
+ * <pre> {@code
+ * String[] sa = ...
+ * VarHandle avh = MethodHandles.arrayElementVarHandle(String[].class);
+ * boolean r = avh.compareAndSet(sa, 10, "expected", "new");
+ * }</pre>
+ *
+ * <p>Access modes control atomicity and consistency properties.
+ * <em>Plain</em> read ({@code get}) and write ({@code set})
+ * accesses are guaranteed to be bitwise atomic only for references
+ * and for primitive values of at most 32 bits, and impose no observable
+ * ordering constraints with respect to threads other than the
+ * executing thread. <em>Opaque</em> operations are bitwise atomic and
+ * coherently ordered with respect to accesses to the same variable.
+ * In addition to obeying Opaque properties, <em>Acquire</em> mode
+ * reads and their subsequent accesses are ordered after matching
+ * <em>Release</em> mode writes and their previous accesses. In
+ * addition to obeying Acquire and Release properties, all
+ * <em>Volatile</em> operations are totally ordered with respect to
+ * each other.
+ *
+ * <p>Access modes are grouped into the following categories:
+ * <ul>
+ * <li>read access modes that get the value of a variable under specified
+ * memory ordering effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #get get},
+ * {@link #getVolatile getVolatile},
+ * {@link #getAcquire getAcquire},
+ * {@link #getOpaque getOpaque}.
+ * <li>write access modes that set the value of a variable under specified
+ * memory ordering effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #set set},
+ * {@link #setVolatile setVolatile},
+ * {@link #setRelease setRelease},
+ * {@link #setOpaque setOpaque}.
+ * <li>atomic update access modes that, for example, atomically compare and set
+ * the value of a variable under specified memory ordering effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #compareAndSet compareAndSet},
+ * {@link #weakCompareAndSetPlain weakCompareAndSetPlain},
+ * {@link #weakCompareAndSet weakCompareAndSet},
+ * {@link #weakCompareAndSetAcquire weakCompareAndSetAcquire},
+ * {@link #weakCompareAndSetRelease weakCompareAndSetRelease},
+ * {@link #compareAndExchangeAcquire compareAndExchangeAcquire},
+ * {@link #compareAndExchange compareAndExchange},
+ * {@link #compareAndExchangeRelease compareAndExchangeRelease},
+ * {@link #getAndSet getAndSet},
+ * {@link #getAndSetAcquire getAndSetAcquire},
+ * {@link #getAndSetRelease getAndSetRelease}.
+ * <li>numeric atomic update access modes that, for example, atomically get and
+ * set with addition the value of a variable under specified memory ordering
+ * effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #getAndAdd getAndAdd},
+ * {@link #getAndAddAcquire getAndAddAcquire},
+ * {@link #getAndAddRelease getAndAddRelease},
+ * <li>bitwise atomic update access modes that, for example, atomically get and
+ * bitwise OR the value of a variable under specified memory ordering
+ * effects.
+ * The set of corresponding access mode methods belonging to this group
+ * consists of the methods
+ * {@link #getAndBitwiseOr getAndBitwiseOr},
+ * {@link #getAndBitwiseOrAcquire getAndBitwiseOrAcquire},
+ * {@link #getAndBitwiseOrRelease getAndBitwiseOrRelease},
+ * {@link #getAndBitwiseAnd getAndBitwiseAnd},
+ * {@link #getAndBitwiseAndAcquire getAndBitwiseAndAcquire},
+ * {@link #getAndBitwiseAndRelease getAndBitwiseAndRelease},
+ * {@link #getAndBitwiseXor getAndBitwiseXor},
+ * {@link #getAndBitwiseXorAcquire getAndBitwiseXorAcquire},
+ * {@link #getAndBitwiseXorRelease getAndBitwiseXorRelease}.
+ * </ul>
+ *
+ * <p>Factory methods that produce or {@link java.lang.invoke.MethodHandles.Lookup
+ * lookup} VarHandle instances document the set of access modes that are
+ * supported, which may also include documenting restrictions based on the
+ * variable type and whether a variable is read-only. If an access mode is not
+ * supported then the corresponding access mode method will on invocation throw
+ * an {@code UnsupportedOperationException}. Factory methods should document
+ * any additional undeclared exceptions that may be thrown by access mode
+ * methods.
+ * The {@link #get get} access mode is supported for all
+ * VarHandle instances and the corresponding method never throws
+ * {@code UnsupportedOperationException}.
+ * If a VarHandle references a read-only variable (for example a {@code final}
+ * field) then write, atomic update, numeric atomic update, and bitwise atomic
+ * update access modes are not supported and corresponding methods throw
+ * {@code UnsupportedOperationException}.
+ * Read/write access modes (if supported), with the exception of
+ * {@code get} and {@code set}, provide atomic access for
+ * reference types and all primitive types.
+ * Unless stated otherwise in the documentation of a factory method, the access
+ * modes {@code get} and {@code set} (if supported) provide atomic access for
+ * reference types and all primitives types, with the exception of {@code long}
+ * and {@code double} on 32-bit platforms.
+ *
+ * <p>Access modes will override any memory ordering effects specified at
+ * the declaration site of a variable. For example, a VarHandle accessing a
+ * a field using the {@code get} access mode will access the field as
+ * specified <em>by its access mode</em> even if that field is declared
+ * {@code volatile}. When mixed access is performed extreme care should be
+ * taken since the Java Memory Model may permit surprising results.
+ *
+ * <p>In addition to supporting access to variables under various access modes,
+ * a set of static methods, referred to as memory fence methods, is also
+ * provided for fine-grained control of memory ordering.
+ *
+ * The Java Language Specification permits other threads to observe operations
+ * as if they were executed in orders different than are apparent in program
+ * source code, subject to constraints arising, for example, from the use of
+ * locks, {@code volatile} fields or VarHandles. The static methods,
+ * {@link #fullFence fullFence}, {@link #acquireFence acquireFence},
+ * {@link #releaseFence releaseFence}, {@link #loadLoadFence loadLoadFence} and
+ * {@link #storeStoreFence storeStoreFence}, can also be used to impose
+ * constraints. Their specifications, as is the case for certain access modes,
+ * are phrased in terms of the lack of "reorderings" -- observable ordering
+ * effects that might otherwise occur if the fence was not present. More
+ * precise phrasing of the specification of access mode methods and memory fence
+ * methods may accompany future updates of the Java Language Specification.
+ *
+ * <h1>Compiling invocation of access mode methods</h1>
+ * A Java method call expression naming an access mode method can invoke a
+ * VarHandle from Java source code. From the viewpoint of source code, these
+ * methods can take any arguments and their polymorphic result (if expressed)
+ * can be cast to any return type. Formally this is accomplished by giving the
+ * access mode methods variable arity {@code Object} arguments and
+ * {@code Object} return types (if the return type is polymorphic), but they
+ * have an additional quality called <em>signature polymorphism</em> which
+ * connects this freedom of invocation directly to the JVM execution stack.
+ * <p>
+ * As is usual with virtual methods, source-level calls to access mode methods
+ * compile to an {@code invokevirtual} instruction. More unusually, the
+ * compiler must record the actual argument types, and may not perform method
+ * invocation conversions on the arguments. Instead, it must generate
+ * instructions to push them on the stack according to their own unconverted
+ * types. The VarHandle object itself will be pushed on the stack before the
+ * arguments. The compiler then generates an {@code invokevirtual} instruction
+ * that invokes the access mode method with a symbolic type descriptor which
+ * describes the argument and return types.
+ * <p>
+ * To issue a complete symbolic type descriptor, the compiler must also
+ * determine the return type (if polymorphic). This is based on a cast on the
+ * method invocation expression, if there is one, or else {@code Object} if the
+ * invocation is an expression, or else {@code void} if the invocation is a
+ * statement. The cast may be to a primitive type (but not {@code void}).
+ * <p>
+ * As a corner case, an uncasted {@code null} argument is given a symbolic type
+ * descriptor of {@code java.lang.Void}. The ambiguity with the type
+ * {@code Void} is harmless, since there are no references of type {@code Void}
+ * except the null reference.
+ *
+ *
+ * <h1><a id="invoke">Performing invocation of access mode methods</a></h1>
+ * The first time an {@code invokevirtual} instruction is executed it is linked
+ * by symbolically resolving the names in the instruction and verifying that
+ * the method call is statically legal. This also holds for calls to access mode
+ * methods. In this case, the symbolic type descriptor emitted by the compiler
+ * is checked for correct syntax, and names it contains are resolved. Thus, an
+ * {@code invokevirtual} instruction which invokes an access mode method will
+ * always link, as long as the symbolic type descriptor is syntactically
+ * well-formed and the types exist.
+ * <p>
+ * When the {@code invokevirtual} is executed after linking, the receiving
+ * VarHandle's access mode type is first checked by the JVM to ensure that it
+ * matches the symbolic type descriptor. If the type
+ * match fails, it means that the access mode method which the caller is
+ * invoking is not present on the individual VarHandle being invoked.
+ *
+ * <p>
+ * Invocation of an access mode method behaves as if an invocation of
+ * {@link MethodHandle#invoke}, where the receiving method handle accepts the
+ * VarHandle instance as the leading argument. More specifically, the
+ * following, where {@code {access-mode}} corresponds to the access mode method
+ * name:
+ * <pre> {@code
+ * VarHandle vh = ..
+ * R r = (R) vh.{access-mode}(p1, p2, ..., pN);
+ * }</pre>
+ * behaves as if:
+ * <pre> {@code
+ * VarHandle vh = ..
+ * VarHandle.AccessMode am = VarHandle.AccessMode.valueFromMethodName("{access-mode}");
+ * MethodHandle mh = MethodHandles.varHandleExactInvoker(
+ * am,
+ * vh.accessModeType(am));
+ *
+ * R r = (R) mh.invoke(vh, p1, p2, ..., pN)
+ * }</pre>
+ * (modulo access mode methods do not declare throwing of {@code Throwable}).
+ * This is equivalent to:
+ * <pre> {@code
+ * MethodHandle mh = MethodHandles.lookup().findVirtual(
+ * VarHandle.class,
+ * "{access-mode}",
+ * MethodType.methodType(R, p1, p2, ..., pN));
+ *
+ * R r = (R) mh.invokeExact(vh, p1, p2, ..., pN)
+ * }</pre>
+ * where the desired method type is the symbolic type descriptor and a
+ * {@link MethodHandle#invokeExact} is performed, since before invocation of the
+ * target, the handle will apply reference casts as necessary and box, unbox, or
+ * widen primitive values, as if by {@link MethodHandle#asType asType} (see also
+ * {@link MethodHandles#varHandleInvoker}).
+ *
+ * More concisely, such behaviour is equivalent to:
+ * <pre> {@code
+ * VarHandle vh = ..
+ * VarHandle.AccessMode am = VarHandle.AccessMode.valueFromMethodName("{access-mode}");
+ * MethodHandle mh = vh.toMethodHandle(am);
+ *
+ * R r = (R) mh.invoke(p1, p2, ..., pN)
+ * }</pre>
+ * Where, in this case, the method handle is bound to the VarHandle instance.
+ *
+ *
+ * <h1>Invocation checking</h1>
+ * In typical programs, VarHandle access mode type matching will usually
+ * succeed. But if a match fails, the JVM will throw a
+ * {@link WrongMethodTypeException}.
+ * <p>
+ * Thus, an access mode type mismatch which might show up as a linkage error
+ * in a statically typed program can show up as a dynamic
+ * {@code WrongMethodTypeException} in a program which uses VarHandles.
+ * <p>
+ * Because access mode types contain "live" {@code Class} objects, method type
+ * matching takes into account both type names and class loaders.
+ * Thus, even if a VarHandle {@code VH} is created in one class loader
+ * {@code L1} and used in another {@code L2}, VarHandle access mode method
+ * calls are type-safe, because the caller's symbolic type descriptor, as
+ * resolved in {@code L2}, is matched against the original callee method's
+ * symbolic type descriptor, as resolved in {@code L1}. The resolution in
+ * {@code L1} happens when {@code VH} is created and its access mode types are
+ * assigned, while the resolution in {@code L2} happens when the
+ * {@code invokevirtual} instruction is linked.
+ * <p>
+ * Apart from type descriptor checks, a VarHandles's capability to
+ * access it's variables is unrestricted.
+ * If a VarHandle is formed on a non-public variable by a class that has access
+ * to that variable, the resulting VarHandle can be used in any place by any
+ * caller who receives a reference to it.
+ * <p>
+ * Unlike with the Core Reflection API, where access is checked every time a
+ * reflective method is invoked, VarHandle access checking is performed
+ * <a href="MethodHandles.Lookup.html#access">when the VarHandle is
+ * created</a>.
+ * Thus, VarHandles to non-public variables, or to variables in non-public
+ * classes, should generally be kept secret. They should not be passed to
+ * untrusted code unless their use from the untrusted code would be harmless.
+ *
+ *
+ * <h1>VarHandle creation</h1>
+ * Java code can create a VarHandle that directly accesses any field that is
+ * accessible to that code. This is done via a reflective, capability-based
+ * API called {@link java.lang.invoke.MethodHandles.Lookup
+ * MethodHandles.Lookup}.
+ * For example, a VarHandle for a non-static field can be obtained
+ * from {@link java.lang.invoke.MethodHandles.Lookup#findVarHandle
+ * Lookup.findVarHandle}.
+ * There is also a conversion method from Core Reflection API objects,
+ * {@link java.lang.invoke.MethodHandles.Lookup#unreflectVarHandle
+ * Lookup.unreflectVarHandle}.
+ * <p>
+ * Access to protected field members is restricted to receivers only of the
+ * accessing class, or one of its subclasses, and the accessing class must in
+ * turn be a subclass (or package sibling) of the protected member's defining
+ * class. If a VarHandle refers to a protected non-static field of a declaring
+ * class outside the current package, the receiver argument will be narrowed to
+ * the type of the accessing class.
+ *
+ * <h1>Interoperation between VarHandles and the Core Reflection API</h1>
+ * Using factory methods in the {@link java.lang.invoke.MethodHandles.Lookup
+ * Lookup} API, any field represented by a Core Reflection API object
+ * can be converted to a behaviorally equivalent VarHandle.
+ * For example, a reflective {@link java.lang.reflect.Field Field} can
+ * be converted to a VarHandle using
+ * {@link java.lang.invoke.MethodHandles.Lookup#unreflectVarHandle
+ * Lookup.unreflectVarHandle}.
+ * The resulting VarHandles generally provide more direct and efficient
+ * access to the underlying fields.
+ * <p>
+ * As a special case, when the Core Reflection API is used to view the
+ * signature polymorphic access mode methods in this class, they appear as
+ * ordinary non-polymorphic methods. Their reflective appearance, as viewed by
+ * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod},
+ * is unaffected by their special status in this API.
+ * For example, {@link java.lang.reflect.Method#getModifiers
+ * Method.getModifiers}
+ * will report exactly those modifier bits required for any similarly
+ * declared method, including in this case {@code native} and {@code varargs}
+ * bits.
+ * <p>
+ * As with any reflected method, these methods (when reflected) may be invoked
+ * directly via {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke},
+ * via JNI, or indirectly via
+ * {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
+ * However, such reflective calls do not result in access mode method
+ * invocations. Such a call, if passed the required argument (a single one, of
+ * type {@code Object[]}), will ignore the argument and will throw an
+ * {@code UnsupportedOperationException}.
+ * <p>
+ * Since {@code invokevirtual} instructions can natively invoke VarHandle
+ * access mode methods under any symbolic type descriptor, this reflective view
+ * conflicts with the normal presentation of these methods via bytecodes.
+ * Thus, these native methods, when reflectively viewed by
+ * {@code Class.getDeclaredMethod}, may be regarded as placeholders only.
+ * <p>
+ * In order to obtain an invoker method for a particular access mode type,
+ * use {@link java.lang.invoke.MethodHandles#varHandleExactInvoker} or
+ * {@link java.lang.invoke.MethodHandles#varHandleInvoker}. The
+ * {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual}
+ * API is also able to return a method handle to call an access mode method for
+ * any specified access mode type and is equivalent in behaviour to
+ * {@link java.lang.invoke.MethodHandles#varHandleInvoker}.
+ *
+ * <h1>Interoperation between VarHandles and Java generics</h1>
+ * A VarHandle can be obtained for a variable, such as a a field, which is
+ * declared with Java generic types. As with the Core Reflection API, the
+ * VarHandle's variable type will be constructed from the erasure of the
+ * source-level type. When a VarHandle access mode method is invoked, the
+ * types
+ * of its arguments or the return value cast type may be generic types or type
+ * instances. If this occurs, the compiler will replace those types by their
+ * erasures when it constructs the symbolic type descriptor for the
+ * {@code invokevirtual} instruction.
+ *
+ * @see MethodHandle
+ * @see MethodHandles
+ * @see MethodType
+ * @since 9
+ * @hide
+ */
+public abstract class VarHandle {
+ // Android-added: Using sun.misc.Unsafe for fence implementation.
+ private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe();
+
+ // BEGIN Android-removed: No VarForm in Android implementation.
+ /*
+ final VarForm vform;
+
+ VarHandle(VarForm vform) {
+ this.vform = vform;
+ }
+ */
+ // END Android-removed: No VarForm in Android implementation.
+
+ RuntimeException unsupported() {
+ return new UnsupportedOperationException();
+ }
+
+ // Plain accessors
+
+ /**
+ * Returns the value of a variable, with memory semantics of reading as
+ * if the variable was declared non-{@code volatile}. Commonly referred to
+ * as plain read access.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code get}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET)} on this VarHandle.
+ *
+ * <p>This access mode is supported by all VarHandle instances and never
+ * throws {@code UnsupportedOperationException}.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the value of the
+ * variable
+ * , statically represented using {@code Object}.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object get(Object... args);
+
+ /**
+ * Sets the value of a variable to the {@code newValue}, with memory
+ * semantics of setting as if the variable was declared non-{@code volatile}
+ * and non-{@code final}. Commonly referred to as plain write access.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)void}
+ *
+ * <p>The symbolic type descriptor at the call site of {@code set}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.SET)} on this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+ * , statically represented using varargs.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ void set(Object... args);
+
+
+ // Volatile accessors
+
+ /**
+ * Returns the value of a variable, with memory semantics of reading as if
+ * the variable was declared {@code volatile}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getVolatile}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_VOLATILE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the value of the
+ * variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getVolatile(Object... args);
+
+ /**
+ * Sets the value of a variable to the {@code newValue}, with memory
+ * semantics of setting as if the variable was declared {@code volatile}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)void}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code setVolatile}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.SET_VOLATILE)} on this
+ * VarHandle.
+ *
+ * @apiNote
+ * Ignoring the many semantic differences from C and C++, this method has
+ * memory ordering effects compatible with {@code memory_order_seq_cst}.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+ * , statically represented using varargs.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ void setVolatile(Object... args);
+
+
+ /**
+ * Returns the value of a variable, accessed in program order, but with no
+ * assurance of memory ordering effects with respect to other threads.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getOpaque}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_OPAQUE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the value of the
+ * variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getOpaque(Object... args);
+
+ /**
+ * Sets the value of a variable to the {@code newValue}, in program order,
+ * but with no assurance of memory ordering effects with respect to other
+ * threads.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)void}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code setOpaque}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.SET_OPAQUE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+ * , statically represented using varargs.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ void setOpaque(Object... args);
+
+
+ // Lazy accessors
+
+ /**
+ * Returns the value of a variable, and ensures that subsequent loads and
+ * stores are not reordered before this access.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAcquire}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_ACQUIRE)} on this
+ * VarHandle.
+ *
+ * @apiNote
+ * Ignoring the many semantic differences from C and C++, this method has
+ * memory ordering effects compatible with {@code memory_order_acquire}
+ * ordering.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the value of the
+ * variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAcquire(Object... args);
+
+ /**
+ * Sets the value of a variable to the {@code newValue}, and ensures that
+ * prior loads and stores are not reordered after this access.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)void}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code setRelease}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.SET_RELEASE)} on this
+ * VarHandle.
+ *
+ * @apiNote
+ * Ignoring the many semantic differences from C and C++, this method has
+ * memory ordering effects compatible with {@code memory_order_release}
+ * ordering.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+ * , statically represented using varargs.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ void setRelease(Object... args);
+
+
+ // Compare and set accessors
+
+ /**
+ * Atomically sets the value of a variable to the {@code newValue} with the
+ * memory semantics of {@link #setVolatile} if the variable's current value,
+ * referred to as the <em>witness value</em>, {@code ==} the
+ * {@code expectedValue}, as accessed with the memory semantics of
+ * {@link #getVolatile}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code
+ * compareAndSet} must match the access mode type that is the result of
+ * calling {@code accessModeType(VarHandle.AccessMode.COMPARE_AND_SET)} on
+ * this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+ * , statically represented using varargs.
+ * @return {@code true} if successful, otherwise {@code false} if the
+ * witness value was not the same as the {@code expectedValue}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ boolean compareAndSet(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the {@code newValue} with the
+ * memory semantics of {@link #setVolatile} if the variable's current value,
+ * referred to as the <em>witness value</em>, {@code ==} the
+ * {@code expectedValue}, as accessed with the memory semantics of
+ * {@link #getVolatile}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code
+ * compareAndExchange}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.COMPARE_AND_EXCHANGE)}
+ * on this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the witness value, which
+ * will be the same as the {@code expectedValue} if successful
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type is not
+ * compatible with the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type is compatible with the
+ * caller's symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object compareAndExchange(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the {@code newValue} with the
+ * memory semantics of {@link #set} if the variable's current value,
+ * referred to as the <em>witness value</em>, {@code ==} the
+ * {@code expectedValue}, as accessed with the memory semantics of
+ * {@link #getAcquire}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code
+ * compareAndExchangeAcquire}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.COMPARE_AND_EXCHANGE_ACQUIRE)} on
+ * this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the witness value, which
+ * will be the same as the {@code expectedValue} if successful
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #set(Object...)
+ * @see #getAcquire(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object compareAndExchangeAcquire(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the {@code newValue} with the
+ * memory semantics of {@link #setRelease} if the variable's current value,
+ * referred to as the <em>witness value</em>, {@code ==} the
+ * {@code expectedValue}, as accessed with the memory semantics of
+ * {@link #get}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code
+ * compareAndExchangeRelease}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.COMPARE_AND_EXCHANGE_RELEASE)}
+ * on this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the witness value, which
+ * will be the same as the {@code expectedValue} if successful
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setRelease(Object...)
+ * @see #get(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object compareAndExchangeRelease(Object... args);
+
+ // Weak (spurious failures allowed)
+
+ /**
+ * Possibly atomically sets the value of a variable to the {@code newValue}
+ * with the semantics of {@link #set} if the variable's current value,
+ * referred to as the <em>witness value</em>, {@code ==} the
+ * {@code expectedValue}, as accessed with the memory semantics of
+ * {@link #get}.
+ *
+ * <p>This operation may fail spuriously (typically, due to memory
+ * contention) even if the witness value does match the expected value.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code
+ * weakCompareAndSetPlain} must match the access mode type that is the result of
+ * calling {@code accessModeType(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_PLAIN)}
+ * on this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+ * , statically represented using varargs.
+ * @return {@code true} if successful, otherwise {@code false} if the
+ * witness value was not the same as the {@code expectedValue} or if this
+ * operation spuriously failed.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #set(Object...)
+ * @see #get(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ boolean weakCompareAndSetPlain(Object... args);
+
+ /**
+ * Possibly atomically sets the value of a variable to the {@code newValue}
+ * with the memory semantics of {@link #setVolatile} if the variable's
+ * current value, referred to as the <em>witness value</em>, {@code ==} the
+ * {@code expectedValue}, as accessed with the memory semantics of
+ * {@link #getVolatile}.
+ *
+ * <p>This operation may fail spuriously (typically, due to memory
+ * contention) even if the witness value does match the expected value.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code
+ * weakCompareAndSet} must match the access mode type that is the
+ * result of calling {@code accessModeType(VarHandle.AccessMode.WEAK_COMPARE_AND_SET)}
+ * on this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+ * , statically represented using varargs.
+ * @return {@code true} if successful, otherwise {@code false} if the
+ * witness value was not the same as the {@code expectedValue} or if this
+ * operation spuriously failed.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ boolean weakCompareAndSet(Object... args);
+
+ /**
+ * Possibly atomically sets the value of a variable to the {@code newValue}
+ * with the semantics of {@link #set} if the variable's current value,
+ * referred to as the <em>witness value</em>, {@code ==} the
+ * {@code expectedValue}, as accessed with the memory semantics of
+ * {@link #getAcquire}.
+ *
+ * <p>This operation may fail spuriously (typically, due to memory
+ * contention) even if the witness value does match the expected value.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code
+ * weakCompareAndSetAcquire}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_ACQUIRE)}
+ * on this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+ * , statically represented using varargs.
+ * @return {@code true} if successful, otherwise {@code false} if the
+ * witness value was not the same as the {@code expectedValue} or if this
+ * operation spuriously failed.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #set(Object...)
+ * @see #getAcquire(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ boolean weakCompareAndSetAcquire(Object... args);
+
+ /**
+ * Possibly atomically sets the value of a variable to the {@code newValue}
+ * with the semantics of {@link #setRelease} if the variable's current
+ * value, referred to as the <em>witness value</em>, {@code ==} the
+ * {@code expectedValue}, as accessed with the memory semantics of
+ * {@link #get}.
+ *
+ * <p>This operation may fail spuriously (typically, due to memory
+ * contention) even if the witness value does match the expected value.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)boolean}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code
+ * weakCompareAndSetRelease}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_RELEASE)}
+ * on this VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T expectedValue, T newValue)}
+ * , statically represented using varargs.
+ * @return {@code true} if successful, otherwise {@code false} if the
+ * witness value was not the same as the {@code expectedValue} or if this
+ * operation spuriously failed.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setRelease(Object...)
+ * @see #get(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ boolean weakCompareAndSetRelease(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the {@code newValue} with the
+ * memory semantics of {@link #setVolatile} and returns the variable's
+ * previous value, as accessed with the memory semantics of
+ * {@link #getVolatile}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndSet}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_SET)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndSet(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the {@code newValue} with the
+ * memory semantics of {@link #set} and returns the variable's
+ * previous value, as accessed with the memory semantics of
+ * {@link #getAcquire}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndSetAcquire}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_SET_ACQUIRE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndSetAcquire(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the {@code newValue} with the
+ * memory semantics of {@link #setRelease} and returns the variable's
+ * previous value, as accessed with the memory semantics of
+ * {@link #get}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T newValue)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndSetRelease}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_SET_RELEASE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T newValue)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndSetRelease(Object... args);
+
+ // Primitive adders
+ // Throw UnsupportedOperationException for refs
+
+ /**
+ * Atomically adds the {@code value} to the current value of a variable with
+ * the memory semantics of {@link #setVolatile}, and returns the variable's
+ * previous value, as accessed with the memory semantics of
+ * {@link #getVolatile}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T value)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndAdd}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_ADD)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T value)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndAdd(Object... args);
+
+ /**
+ * Atomically adds the {@code value} to the current value of a variable with
+ * the memory semantics of {@link #set}, and returns the variable's
+ * previous value, as accessed with the memory semantics of
+ * {@link #getAcquire}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T value)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndAddAcquire}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_ADD_ACQUIRE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T value)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndAddAcquire(Object... args);
+
+ /**
+ * Atomically adds the {@code value} to the current value of a variable with
+ * the memory semantics of {@link #setRelease}, and returns the variable's
+ * previous value, as accessed with the memory semantics of
+ * {@link #get}.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T value)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndAddRelease}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_ADD_RELEASE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T value)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndAddRelease(Object... args);
+
+
+ // Bitwise operations
+ // Throw UnsupportedOperationException for refs
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise OR between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #setVolatile} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #getVolatile}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical OR is performed instead of a bitwise OR.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseOr}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_OR)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseOr(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise OR between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #set} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #getAcquire}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical OR is performed instead of a bitwise OR.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseOrAcquire}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_OR_ACQUIRE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #set(Object...)
+ * @see #getAcquire(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseOrAcquire(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise OR between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #setRelease} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #get}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical OR is performed instead of a bitwise OR.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseOrRelease}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_OR_RELEASE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setRelease(Object...)
+ * @see #get(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseOrRelease(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise AND between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #setVolatile} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #getVolatile}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical AND is performed instead of a bitwise AND.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseAnd}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_AND)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseAnd(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise AND between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #set} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #getAcquire}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical AND is performed instead of a bitwise AND.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseAndAcquire}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_AND_ACQUIRE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #set(Object...)
+ * @see #getAcquire(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseAndAcquire(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise AND between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #setRelease} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #get}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical AND is performed instead of a bitwise AND.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseAndRelease}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_AND_RELEASE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setRelease(Object...)
+ * @see #get(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseAndRelease(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise XOR between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #setVolatile} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #getVolatile}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical XOR is performed instead of a bitwise XOR.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseXor}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_XOR)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setVolatile(Object...)
+ * @see #getVolatile(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseXor(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise XOR between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #set} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #getAcquire}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical XOR is performed instead of a bitwise XOR.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseXorAcquire}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_XOR_ACQUIRE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #set(Object...)
+ * @see #getAcquire(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseXorAcquire(Object... args);
+
+ /**
+ * Atomically sets the value of a variable to the result of
+ * bitwise XOR between the variable's current value and the {@code mask}
+ * with the memory semantics of {@link #setRelease} and returns the
+ * variable's previous value, as accessed with the memory semantics of
+ * {@link #get}.
+ *
+ * <p>If the variable type is the non-integral {@code boolean} type then a
+ * logical XOR is performed instead of a bitwise XOR.
+ *
+ * <p>The method signature is of the form {@code (CT1 ct1, ..., CTn ctn, T mask)T}.
+ *
+ * <p>The symbolic type descriptor at the call site of {@code getAndBitwiseXorRelease}
+ * must match the access mode type that is the result of calling
+ * {@code accessModeType(VarHandle.AccessMode.GET_AND_BITWISE_XOR_RELEASE)} on this
+ * VarHandle.
+ *
+ * @param args the signature-polymorphic parameter list of the form
+ * {@code (CT1 ct1, ..., CTn ctn, T mask)}
+ * , statically represented using varargs.
+ * @return the signature-polymorphic result that is the previous value of
+ * the variable
+ * , statically represented using {@code Object}.
+ * @throws UnsupportedOperationException if the access mode is unsupported
+ * for this VarHandle.
+ * @throws WrongMethodTypeException if the access mode type does not
+ * match the caller's symbolic type descriptor.
+ * @throws ClassCastException if the access mode type matches the caller's
+ * symbolic type descriptor, but a reference cast fails.
+ * @see #setRelease(Object...)
+ * @see #get(Object...)
+ */
+ public final native
+ // Android-removed: unsupported annotations.
+ // @MethodHandle.PolymorphicSignature
+ // @HotSpotIntrinsicCandidate
+ Object getAndBitwiseXorRelease(Object... args);
+
+
+ enum AccessType {
+ GET(Object.class),
+ SET(void.class),
+ COMPARE_AND_SWAP(boolean.class),
+ COMPARE_AND_EXCHANGE(Object.class),
+ GET_AND_UPDATE(Object.class);
+
+ final Class<?> returnType;
+ final boolean isMonomorphicInReturnType;
+
+ AccessType(Class<?> returnType) {
+ this.returnType = returnType;
+ isMonomorphicInReturnType = returnType != Object.class;
+ }
+
+ MethodType accessModeType(Class<?> receiver, Class<?> value,
+ Class<?>... intermediate) {
+ Class<?>[] ps;
+ int i;
+ switch (this) {
+ case GET:
+ ps = allocateParameters(0, receiver, intermediate);
+ fillParameters(ps, receiver, intermediate);
+ return MethodType.methodType(value, ps);
+ case SET:
+ ps = allocateParameters(1, receiver, intermediate);
+ i = fillParameters(ps, receiver, intermediate);
+ ps[i] = value;
+ return MethodType.methodType(void.class, ps);
+ case COMPARE_AND_SWAP:
+ ps = allocateParameters(2, receiver, intermediate);
+ i = fillParameters(ps, receiver, intermediate);
+ ps[i++] = value;
+ ps[i] = value;
+ return MethodType.methodType(boolean.class, ps);
+ case COMPARE_AND_EXCHANGE:
+ ps = allocateParameters(2, receiver, intermediate);
+ i = fillParameters(ps, receiver, intermediate);
+ ps[i++] = value;
+ ps[i] = value;
+ return MethodType.methodType(value, ps);
+ case GET_AND_UPDATE:
+ ps = allocateParameters(1, receiver, intermediate);
+ i = fillParameters(ps, receiver, intermediate);
+ ps[i] = value;
+ return MethodType.methodType(value, ps);
+ default:
+ throw new InternalError("Unknown AccessType");
+ }
+ }
+
+ private static Class<?>[] allocateParameters(int values,
+ Class<?> receiver, Class<?>... intermediate) {
+ int size = ((receiver != null) ? 1 : 0) + intermediate.length + values;
+ return new Class<?>[size];
+ }
+
+ private static int fillParameters(Class<?>[] ps,
+ Class<?> receiver, Class<?>... intermediate) {
+ int i = 0;
+ if (receiver != null)
+ ps[i++] = receiver;
+ for (int j = 0; j < intermediate.length; j++)
+ ps[i++] = intermediate[j];
+ return i;
+ }
+ }
+
+ /**
+ * The set of access modes that specify how a variable, referenced by a
+ * VarHandle, is accessed.
+ */
+ public enum AccessMode {
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#get VarHandle.get}
+ */
+ GET("get", AccessType.GET),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#set VarHandle.set}
+ */
+ SET("set", AccessType.SET),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getVolatile VarHandle.getVolatile}
+ */
+ GET_VOLATILE("getVolatile", AccessType.GET),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#setVolatile VarHandle.setVolatile}
+ */
+ SET_VOLATILE("setVolatile", AccessType.SET),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAcquire VarHandle.getAcquire}
+ */
+ GET_ACQUIRE("getAcquire", AccessType.GET),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#setRelease VarHandle.setRelease}
+ */
+ SET_RELEASE("setRelease", AccessType.SET),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getOpaque VarHandle.getOpaque}
+ */
+ GET_OPAQUE("getOpaque", AccessType.GET),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#setOpaque VarHandle.setOpaque}
+ */
+ SET_OPAQUE("setOpaque", AccessType.SET),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#compareAndSet VarHandle.compareAndSet}
+ */
+ COMPARE_AND_SET("compareAndSet", AccessType.COMPARE_AND_SWAP),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#compareAndExchange VarHandle.compareAndExchange}
+ */
+ COMPARE_AND_EXCHANGE("compareAndExchange", AccessType.COMPARE_AND_EXCHANGE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#compareAndExchangeAcquire VarHandle.compareAndExchangeAcquire}
+ */
+ COMPARE_AND_EXCHANGE_ACQUIRE("compareAndExchangeAcquire", AccessType.COMPARE_AND_EXCHANGE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#compareAndExchangeRelease VarHandle.compareAndExchangeRelease}
+ */
+ COMPARE_AND_EXCHANGE_RELEASE("compareAndExchangeRelease", AccessType.COMPARE_AND_EXCHANGE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#weakCompareAndSetPlain VarHandle.weakCompareAndSetPlain}
+ */
+ WEAK_COMPARE_AND_SET_PLAIN("weakCompareAndSetPlain", AccessType.COMPARE_AND_SWAP),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#weakCompareAndSet VarHandle.weakCompareAndSet}
+ */
+ WEAK_COMPARE_AND_SET("weakCompareAndSet", AccessType.COMPARE_AND_SWAP),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#weakCompareAndSetAcquire VarHandle.weakCompareAndSetAcquire}
+ */
+ WEAK_COMPARE_AND_SET_ACQUIRE("weakCompareAndSetAcquire", AccessType.COMPARE_AND_SWAP),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#weakCompareAndSetRelease VarHandle.weakCompareAndSetRelease}
+ */
+ WEAK_COMPARE_AND_SET_RELEASE("weakCompareAndSetRelease", AccessType.COMPARE_AND_SWAP),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndSet VarHandle.getAndSet}
+ */
+ GET_AND_SET("getAndSet", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndSetAcquire VarHandle.getAndSetAcquire}
+ */
+ GET_AND_SET_ACQUIRE("getAndSetAcquire", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndSetRelease VarHandle.getAndSetRelease}
+ */
+ GET_AND_SET_RELEASE("getAndSetRelease", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndAdd VarHandle.getAndAdd}
+ */
+ GET_AND_ADD("getAndAdd", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndAddAcquire VarHandle.getAndAddAcquire}
+ */
+ GET_AND_ADD_ACQUIRE("getAndAddAcquire", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndAddRelease VarHandle.getAndAddRelease}
+ */
+ GET_AND_ADD_RELEASE("getAndAddRelease", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseOr VarHandle.getAndBitwiseOr}
+ */
+ GET_AND_BITWISE_OR("getAndBitwiseOr", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseOrRelease VarHandle.getAndBitwiseOrRelease}
+ */
+ GET_AND_BITWISE_OR_RELEASE("getAndBitwiseOrRelease", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseOrAcquire VarHandle.getAndBitwiseOrAcquire}
+ */
+ GET_AND_BITWISE_OR_ACQUIRE("getAndBitwiseOrAcquire", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseAnd VarHandle.getAndBitwiseAnd}
+ */
+ GET_AND_BITWISE_AND("getAndBitwiseAnd", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseAndRelease VarHandle.getAndBitwiseAndRelease}
+ */
+ GET_AND_BITWISE_AND_RELEASE("getAndBitwiseAndRelease", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseAndAcquire VarHandle.getAndBitwiseAndAcquire}
+ */
+ GET_AND_BITWISE_AND_ACQUIRE("getAndBitwiseAndAcquire", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseXor VarHandle.getAndBitwiseXor}
+ */
+ GET_AND_BITWISE_XOR("getAndBitwiseXor", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseXorRelease VarHandle.getAndBitwiseXorRelease}
+ */
+ GET_AND_BITWISE_XOR_RELEASE("getAndBitwiseXorRelease", AccessType.GET_AND_UPDATE),
+ /**
+ * The access mode whose access is specified by the corresponding
+ * method
+ * {@link VarHandle#getAndBitwiseXorAcquire VarHandle.getAndBitwiseXorAcquire}
+ */
+ GET_AND_BITWISE_XOR_ACQUIRE("getAndBitwiseXorAcquire", AccessType.GET_AND_UPDATE),
+ ;
+
+ static final Map<String, AccessMode> methodNameToAccessMode;
+ static {
+ // Initial capacity of # values is sufficient to avoid resizes
+ // for the smallest table size (32)
+ methodNameToAccessMode = new HashMap<>(AccessMode.values().length);
+ for (AccessMode am : AccessMode.values()) {
+ methodNameToAccessMode.put(am.methodName, am);
+ }
+ }
+
+ final String methodName;
+ final AccessType at;
+
+ AccessMode(final String methodName, AccessType at) {
+ this.methodName = methodName;
+ this.at = at;
+ }
+
+ /**
+ * Returns the {@code VarHandle} signature-polymorphic method name
+ * associated with this {@code AccessMode} value.
+ *
+ * @return the signature-polymorphic method name
+ * @see #valueFromMethodName
+ */
+ public String methodName() {
+ return methodName;
+ }
+
+ /**
+ * Returns the {@code AccessMode} value associated with the specified
+ * {@code VarHandle} signature-polymorphic method name.
+ *
+ * @param methodName the signature-polymorphic method name
+ * @return the {@code AccessMode} value
+ * @throws IllegalArgumentException if there is no {@code AccessMode}
+ * value associated with method name (indicating the method
+ * name does not correspond to a {@code VarHandle}
+ * signature-polymorphic method name).
+ * @see #methodName
+ */
+ public static AccessMode valueFromMethodName(String methodName) {
+ AccessMode am = methodNameToAccessMode.get(methodName);
+ if (am != null) return am;
+ throw new IllegalArgumentException("No AccessMode value for method name " + methodName);
+ }
+
+ // BEGIN Android-removed: MemberName and VarForm are not used in the Android implementation.
+ /*
+ @ForceInline
+ static MemberName getMemberName(int ordinal, VarForm vform) {
+ return vform.memberName_table[ordinal];
+ }
+ */
+ // END Android-removed: MemberName and VarForm are not used in the Android implementation.
+ }
+
+ // BEGIN Android-removed: AccessDescriptor not used in Android implementation.
+ /*
+ static final class AccessDescriptor {
+ final MethodType symbolicMethodTypeErased;
+ final MethodType symbolicMethodTypeInvoker;
+ final Class<?> returnType;
+ final int type;
+ final int mode;
+
+ public AccessDescriptor(MethodType symbolicMethodType, int type, int mode) {
+ this.symbolicMethodTypeErased = symbolicMethodType.erase();
+ this.symbolicMethodTypeInvoker = symbolicMethodType.insertParameterTypes(0, VarHandle.class);
+ this.returnType = symbolicMethodType.returnType();
+ this.type = type;
+ this.mode = mode;
+ }
+ }
+ */
+ // END Android-removed: AccessDescriptor not used in Android implementation.
+
+ /**
+ * Returns the variable type of variables referenced by this VarHandle.
+ *
+ * @return the variable type of variables referenced by this VarHandle
+ */
+ public final Class<?> varType() {
+ MethodType typeSet = accessModeType(AccessMode.SET);
+ return typeSet.parameterType(typeSet.parameterCount() - 1);
+ }
+
+ /**
+ * Returns the coordinate types for this VarHandle.
+ *
+ * @return the coordinate types for this VarHandle. The returned
+ * list is unmodifiable
+ */
+ public final List<Class<?>> coordinateTypes() {
+ MethodType typeGet = accessModeType(AccessMode.GET);
+ return typeGet.parameterList();
+ }
+
+ /**
+ * Obtains the access mode type for this VarHandle and a given access mode.
+ *
+ * <p>The access mode type's parameter types will consist of a prefix that
+ * is the coordinate types of this VarHandle followed by further
+ * types as defined by the access mode method.
+ * The access mode type's return type is defined by the return type of the
+ * access mode method.
+ *
+ * @param accessMode the access mode, corresponding to the
+ * signature-polymorphic method of the same name
+ * @return the access mode type for the given access mode
+ */
+ public final MethodType accessModeType(AccessMode accessMode) {
+ // BEGIN Android-removed: Relies on internal class that is not part of the
+ // Android implementation.
+ /*
+ TypesAndInvokers tis = getTypesAndInvokers();
+ MethodType mt = tis.methodType_table[accessMode.at.ordinal()];
+ if (mt == null) {
+ mt = tis.methodType_table[accessMode.at.ordinal()] =
+ accessModeTypeUncached(accessMode);
+ }
+ return mt;
+ */
+ // END Android-removed: Relies on internal class that is not part of the
+ // Android implementation.
+ // Android-added: Throw an exception until implemented.
+ unsupported(); // TODO(b/65872996)
+ return null;
+ }
+
+ // Android-removed: Not part of the Android implementation.
+ // abstract MethodType accessModeTypeUncached(AccessMode accessMode);
+
+ /**
+ * Returns {@code true} if the given access mode is supported, otherwise
+ * {@code false}.
+ *
+ * <p>The return of a {@code false} value for a given access mode indicates
+ * that an {@code UnsupportedOperationException} is thrown on invocation
+ * of the corresponding access mode method.
+ *
+ * @param accessMode the access mode, corresponding to the
+ * signature-polymorphic method of the same name
+ * @return {@code true} if the given access mode is supported, otherwise
+ * {@code false}.
+ */
+ public final boolean isAccessModeSupported(AccessMode accessMode) {
+ // Android-removed: Refers to unused field vform.
+ // return AccessMode.getMemberName(accessMode.ordinal(), vform) != null;
+ // Android-added: Throw an exception until implemented.
+ unsupported(); // TODO(b/65872996)
+ return false;
+ }
+
+ /**
+ * Obtains a method handle bound to this VarHandle and the given access
+ * mode.
+ *
+ * @apiNote This method, for a VarHandle {@code vh} and access mode
+ * {@code {access-mode}}, returns a method handle that is equivalent to
+ * method handle {@code bmh} in the following code (though it may be more
+ * efficient):
+ * <pre>{@code
+ * MethodHandle mh = MethodHandles.varHandleExactInvoker(
+ * vh.accessModeType(VarHandle.AccessMode.{access-mode}));
+ *
+ * MethodHandle bmh = mh.bindTo(vh);
+ * }</pre>
+ *
+ * @param accessMode the access mode, corresponding to the
+ * signature-polymorphic method of the same name
+ * @return a method handle bound to this VarHandle and the given access mode
+ */
+ public final MethodHandle toMethodHandle(AccessMode accessMode) {
+ // BEGIN Android-removed: no vform field in Android implementation.
+ /*
+ MemberName mn = AccessMode.getMemberName(accessMode.ordinal(), vform);
+ if (mn != null) {
+ MethodHandle mh = getMethodHandle(accessMode.ordinal());
+ return mh.bindTo(this);
+ }
+ else {
+ // Ensure an UnsupportedOperationException is thrown
+ return MethodHandles.varHandleInvoker(accessMode, accessModeType(accessMode)).
+ bindTo(this);
+ }
+ */
+ // Android-added: Throw an exception until implemented.
+ unsupported(); // TODO(b/65872996)
+ return null;
+ }
+
+ // BEGIN Android-removed: Not used in Android implementation.
+ /*
+ @Stable
+ TypesAndInvokers typesAndInvokers;
+
+ static class TypesAndInvokers {
+ final @Stable
+ MethodType[] methodType_table =
+ new MethodType[VarHandle.AccessType.values().length];
+
+ final @Stable
+ MethodHandle[] methodHandle_table =
+ new MethodHandle[AccessMode.values().length];
+ }
+
+ @ForceInline
+ private final TypesAndInvokers getTypesAndInvokers() {
+ TypesAndInvokers tis = typesAndInvokers;
+ if (tis == null) {
+ tis = typesAndInvokers = new TypesAndInvokers();
+ }
+ return tis;
+ }
+
+ @ForceInline
+ final MethodHandle getMethodHandle(int mode) {
+ TypesAndInvokers tis = getTypesAndInvokers();
+ MethodHandle mh = tis.methodHandle_table[mode];
+ if (mh == null) {
+ mh = tis.methodHandle_table[mode] = getMethodHandleUncached(mode);
+ }
+ return mh;
+ }
+ private final MethodHandle getMethodHandleUncached(int mode) {
+ MethodType mt = accessModeType(AccessMode.values()[mode]).
+ insertParameterTypes(0, VarHandle.class);
+ MemberName mn = vform.getMemberName(mode);
+ DirectMethodHandle dmh = DirectMethodHandle.make(mn);
+ // Such a method handle must not be publically exposed directly
+ // otherwise it can be cracked, it must be transformed or rebound
+ // before exposure
+ MethodHandle mh = dmh.copyWith(mt, dmh.form);
+ assert mh.type().erase() == mn.getMethodType().erase();
+ return mh;
+ }
+ */
+ // END Android-removed: Not used in Android implementation.
+
+ /*non-public*/
+ // BEGIN Android-removed: No VarForm in Android implementation.
+ /*
+ final void updateVarForm(VarForm newVForm) {
+ if (vform == newVForm) return;
+ UNSAFE.putObject(this, VFORM_OFFSET, newVForm);
+ UNSAFE.fullFence();
+ }
+
+ static final BiFunction<String, List<Integer>, ArrayIndexOutOfBoundsException>
+ AIOOBE_SUPPLIER = Preconditions.outOfBoundsExceptionFormatter(
+ new Function<String, ArrayIndexOutOfBoundsException>() {
+ @Override
+ public ArrayIndexOutOfBoundsException apply(String s) {
+ return new ArrayIndexOutOfBoundsException(s);
+ }
+ });
+
+ private static final long VFORM_OFFSET;
+
+ static {
+ try {
+ VFORM_OFFSET = UNSAFE.objectFieldOffset(VarHandle.class.getDeclaredField("vform"));
+ }
+ catch (ReflectiveOperationException e) {
+ throw newInternalError(e);
+ }
+
+ // The VarHandleGuards must be initialized to ensure correct
+ // compilation of the guard methods
+ UNSAFE.ensureClassInitialized(VarHandleGuards.class);
+ }
+ */
+ // END Android-removed: No VarForm in Android implementation.
+
+ // Fence methods
+
+ /**
+ * Ensures that loads and stores before the fence will not be reordered
+ * with
+ * loads and stores after the fence.
+ *
+ * @apiNote Ignoring the many semantic differences from C and C++, this
+ * method has memory ordering effects compatible with
+ * {@code atomic_thread_fence(memory_order_seq_cst)}
+ */
+ // Android-removed: @ForceInline is an unsupported attribute.
+ // @ForceInline
+ public static void fullFence() {
+ UNSAFE.fullFence();
+ }
+
+ /**
+ * Ensures that loads before the fence will not be reordered with loads and
+ * stores after the fence.
+ *
+ * @apiNote Ignoring the many semantic differences from C and C++, this
+ * method has memory ordering effects compatible with
+ * {@code atomic_thread_fence(memory_order_acquire)}
+ */
+ // Android-removed: @ForceInline is an unsupported attribute.
+ // @ForceInline
+ public static void acquireFence() {
+ UNSAFE.loadFence();
+ }
+
+ /**
+ * Ensures that loads and stores before the fence will not be
+ * reordered with stores after the fence.
+ *
+ * @apiNote Ignoring the many semantic differences from C and C++, this
+ * method has memory ordering effects compatible with
+ * {@code atomic_thread_fence(memory_order_release)}
+ */
+ // Android-removed: @ForceInline is an unsupported attribute.
+ // @ForceInline
+ public static void releaseFence() {
+ UNSAFE.storeFence();
+ }
+
+ /**
+ * Ensures that loads before the fence will not be reordered with
+ * loads after the fence.
+ */
+ // Android-removed: @ForceInline is an unsupported attribute.
+ // @ForceInline
+ public static void loadLoadFence() {
+ // Android-changed: Not using UNSAFE.loadLoadFence() as not present on Android.
+ // NB The compiler recognizes all the fences here as intrinsics.
+ UNSAFE.loadFence();
+ }
+
+ /**
+ * Ensures that stores before the fence will not be reordered with
+ * stores after the fence.
+ */
+ // Android-removed: @ForceInline is an unsupported attribute.
+ // @ForceInline
+ public static void storeStoreFence() {
+ // Android-changed: Not using UNSAFE.storeStoreFence() as not present on Android.
+ // NB The compiler recognizes all the fences here as intrinsics.
+ UNSAFE.storeFence();
+ }
+}
diff --git a/java/net/Inet6AddressImpl.java b/java/net/Inet6AddressImpl.java
index 2a897f73..cfc2d132 100644
--- a/java/net/Inet6AddressImpl.java
+++ b/java/net/Inet6AddressImpl.java
@@ -48,11 +48,18 @@ import static android.system.OsConstants.ICMP6_ECHO_REPLY;
import static android.system.OsConstants.ICMP_ECHOREPLY;
import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_IPV6;
-import static android.system.OsConstants.IPV6_UNICAST_HOPS;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_STREAM;
+// Android-note: Android-specific behavior and Linux-based implementation
+// http://b/36933260 Implement root-less ICMP for isReachable()
+// http://b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
+// http://b/25861497 Add BlockGuard checks.
+// http://b/26700324 Fix odd dependency chains of the static InetAddress.
+// anyLocalAddress() Let anyLocalAddress() always return an IPv6 address.
+// Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
+// Rewrote hostname lookup methods on top of Libcore.os. Merge implementation from InetAddress
+// and remove native methods in this class
/*
* Package private implementation of InetAddressImpl for dual
* IPv4/IPv6 stack. {@code #anyLocalAddress()} will always return an IPv6 address.
@@ -69,6 +76,14 @@ class Inet6AddressImpl implements InetAddressImpl {
private static final AddressCache addressCache = new AddressCache();
+ // BEGIN Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
+ /*
+ public native String getLocalHostName() throws UnknownHostException;
+ public native InetAddress[]
+ lookupAllHostAddr(String hostname) throws UnknownHostException;
+ public native String getHostByAddr(byte[] addr) throws UnknownHostException;
+ private native boolean isReachable0(byte[] addr, int scope, int timeout, byte[] inf, int ttl, int if_scope) throws IOException;
+ */
@Override
public InetAddress[] lookupAllHostAddr(String host, int netId) throws UnknownHostException {
if (host == null || host.isEmpty()) {
@@ -154,10 +169,11 @@ class Inet6AddressImpl implements InetAddressImpl {
public void clearAddressCache() {
addressCache.clear();
}
+ // END Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
@Override
public boolean isReachable(InetAddress addr, int timeout, NetworkInterface netif, int ttl) throws IOException {
- // Android-changed: rewritten on the top of IoBridge and Libcore.os
+ // Android-changed: rewritten on the top of IoBridge and Libcore.os.
InetAddress sourceAddr = null;
if (netif != null) {
/*
@@ -183,6 +199,12 @@ class Inet6AddressImpl implements InetAddressImpl {
}
}
+ // Android-changed: http://b/36933260 Implement root-less ICMP for isReachable().
+ /*
+ if (addr instanceof Inet6Address)
+ scope = ((Inet6Address) addr).getScopeId();
+ return isReachable0(addr.getAddress(), scope, timeout, ifaddr, ttl, netif_scope);
+ */
// Try ICMP first
if (icmpEcho(addr, timeout, sourceAddr, ttl)) {
return true;
@@ -192,6 +214,7 @@ class Inet6AddressImpl implements InetAddressImpl {
return tcpEcho(addr, timeout, sourceAddr, ttl);
}
+ // BEGIN Android-added: http://b/36933260 Implement root-less ICMP for isReachable().
private boolean tcpEcho(InetAddress addr, int timeout, InetAddress sourceAddr, int ttl)
throws IOException {
FileDescriptor fd = null;
@@ -274,14 +297,16 @@ class Inet6AddressImpl implements InetAddressImpl {
return false;
}
+ // END Android-added: http://b/36933260 Implement root-less ICMP for isReachable().
+ // BEGIN Android-changed: Let anyLocalAddress() always return an IPv6 address.
@Override
public InetAddress anyLocalAddress() {
synchronized (Inet6AddressImpl.class) {
// We avoid initializing anyLocalAddress during <clinit> to avoid issues
// caused by the dependency chains of these classes. InetAddress depends on
// InetAddressImpl, but Inet6Address & Inet4Address are its subclasses.
- // Also see {@code loopbackAddresses).
+ // Also see {@code loopbackAddresses). http://b/26700324
if (anyLocalAddress == null) {
Inet6Address anyAddress = new Inet6Address();
anyAddress.holder().hostName = "::";
@@ -291,7 +316,9 @@ class Inet6AddressImpl implements InetAddressImpl {
return anyLocalAddress;
}
}
+ // END Android-changed: Let anyLocalAddress() always return an IPv6 address.
+ // BEGIN Android-changed: Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
@Override
public InetAddress[] loopbackAddresses() {
synchronized (Inet6AddressImpl.class) {
@@ -306,7 +333,9 @@ class Inet6AddressImpl implements InetAddressImpl {
return loopbackAddresses;
}
}
+ // END Android-changed: Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
+ // BEGIN Android-changed: b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
private String getHostByAddr0(byte[] addr) throws UnknownHostException {
// Android-changed: Rewritten on the top of Libcore.os
InetAddress hostaddr = InetAddress.getByAddress(addr);
@@ -318,4 +347,5 @@ class Inet6AddressImpl implements InetAddressImpl {
throw uhe;
}
}
+ // END Android-changed: b/28609551 Rewrite getHostByAddr0 using POSIX library Libcore.os.
}
diff --git a/java/net/InetAddress.java b/java/net/InetAddress.java
index 1468b2d3..40b46d2d 100644
--- a/java/net/InetAddress.java
+++ b/java/net/InetAddress.java
@@ -181,6 +181,24 @@ import static android.system.OsConstants.*;
*/
public
class InetAddress implements java.io.Serializable {
+ // BEGIN Android-removed: Android uses linux-based OsConstants.
+ /*
+ * Specify the address family: Internet Protocol, Version 4
+ * @since 1.4
+ *
+ static final int IPv4 = 1;
+
+ /**
+ * Specify the address family: Internet Protocol, Version 6
+ * @since 1.4
+ *
+ static final int IPv6 = 2;
+ */
+ // END Android-removed: Android uses linux-based OsConstants.
+
+ // Android-removed: Android doesn't support the preference.
+ // /* Specify address family preference */
+ //static transient boolean preferIPv6Address = false;
static class InetAddressHolder {
/**
@@ -235,6 +253,7 @@ class InetAddress implements java.io.Serializable {
return address;
}
+ // Android-changed: Documentation: use Linux-based OsConstants.
/**
* Specifies the address family type, for instance, AF_INET for IPv4
* addresses, and AF_INET6 for IPv6 addresses.
@@ -256,6 +275,9 @@ class InetAddress implements java.io.Serializable {
static final InetAddressImpl impl = new Inet6AddressImpl();
/* Used to store the name service provider */
+ // Android-changed: Android has only one name service.
+ // Android doesn't allow user to provide custom name services.
+ // private static List<NameService> nameServices = null;
private static final NameService nameService = new NameService() {
public InetAddress[] lookupAllHostAddr(String host, int netId)
throws UnknownHostException {
@@ -273,6 +295,26 @@ class InetAddress implements java.io.Serializable {
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = 3286316764910316507L;
+
+ // BEGIN Android-removed: Android doesn't need to load native library.
+ /*
+ * Load net library into runtime, and perform initializations.
+ *
+ static {
+ preferIPv6Address = java.security.AccessController.doPrivileged(
+ new GetBooleanAction("java.net.preferIPv6Addresses")).booleanValue();
+ AccessController.doPrivileged(
+ new java.security.PrivilegedAction<Void>() {
+ public Void run() {
+ System.loadLibrary("net");
+ return null;
+ }
+ });
+ init();
+ }
+ */
+ // END Android-removed: Android doesn't need to load native library.
+
/**
* Constructor for the Socket.accept() method.
* This creates an empty InetAddress, which is filled in by
@@ -433,9 +475,12 @@ class InetAddress implements java.io.Serializable {
* @since 1.5
*/
public boolean isReachable(int timeout) throws IOException {
- return isReachable(null, 0, timeout);
+ return isReachable(null, 0 , timeout);
}
+ // Android-changed: Document that impl tries ICMP ECHO REQUESTs first.
+ // The sole implementation, Inet6AddressImpl.isReachable(), tries ICMP ECHO REQUESTs before
+ // TCP ECHO REQUESTs on Android. On Android, these are both possible without root access.
/**
* Test whether that address is reachable. Best effort is made by the
* implementation to try to reach the host, but firewalls and server
@@ -478,12 +523,14 @@ class InetAddress implements java.io.Serializable {
return impl.isReachable(this, timeout, netif, ttl);
}
+ // BEGIN Android-added: isReachableByICMP(timeout).
/**
* @hide For testing only
*/
public boolean isReachableByICMP(int timeout) throws IOException {
return ((Inet6AddressImpl) impl).icmpEcho(this, timeout, null, 0);
}
+ // END Android-added: isReachableByICMP(timeout).
/**
* Gets the host name for this IP address.
@@ -511,12 +558,46 @@ class InetAddress implements java.io.Serializable {
* @see SecurityManager#checkConnect
*/
public String getHostName() {
+ // Android-changed: Remove SecurityManager check.
if (holder().getHostName() == null) {
holder().hostName = InetAddress.getHostFromNameService(this);
}
return holder().getHostName();
}
+ // BEGIN Android-removed: Android doesn't support SecurityManager.
+ /*
+ * Returns the hostname for this address.
+ * If the host is equal to null, then this address refers to any
+ * of the local machine's available network addresses.
+ * this is package private so SocketPermission can make calls into
+ * here without a security check.
+ *
+ * <p>If there is a security manager, this method first
+ * calls its {@code checkConnect} method
+ * with the hostname and {@code -1}
+ * as its arguments to see if the calling code is allowed to know
+ * the hostname for this IP address, i.e., to connect to the host.
+ * If the operation is not allowed, it will return
+ * the textual representation of the IP address.
+ *
+ * @return the host name for this IP address, or if the operation
+ * is not allowed by the security check, the textual
+ * representation of the IP address.
+ *
+ * @param check make security check if true
+ *
+ * @see SecurityManager#checkConnect
+ *
+ String getHostName(boolean check) {
+ if (holder().getHostName() == null) {
+ holder().hostName = InetAddress.getHostFromNameService(this, check);
+ }
+ return holder().getHostName();
+ }
+ */
+ // END Android-removed: Android doesn't support SecurityManager.
+
/**
* Gets the fully qualified domain name for this IP address.
* Best effort method, meaning we may not be able to return
@@ -539,12 +620,15 @@ class InetAddress implements java.io.Serializable {
* @since 1.4
*/
public String getCanonicalHostName() {
+ // Android-changed: Remove SecurityManager check.
if (canonicalHostName == null) {
canonicalHostName = InetAddress.getHostFromNameService(this);
}
return canonicalHostName;
}
+ // Android-changed: Remove SecurityManager check.
+ // * @param check make security check if true
/**
* Returns the hostname for this address.
*
@@ -566,6 +650,7 @@ class InetAddress implements java.io.Serializable {
String host = null;
try {
// first lookup the hostname
+ // Android-changed: Android has only one name service.
host = nameService.getHostByAddr(addr.getAddress());
/* now get all the IP addresses for this hostname,
@@ -659,6 +744,280 @@ class InetAddress implements java.io.Serializable {
+ "/" + getHostAddress();
}
+ // BEGIN Android-removed: Resolves a hostname using Libcore.os.
+ /*
+ * Cached addresses - our own litle nis, not!
+ *
+ private static Cache addressCache = new Cache(Cache.Type.Positive);
+
+ private static Cache negativeCache = new Cache(Cache.Type.Negative);
+
+ private static boolean addressCacheInit = false;
+
+ static InetAddress[] unknown_array; // put THIS in cache
+
+ static InetAddressImpl impl;
+
+ private static final HashMap<String, Void> lookupTable = new HashMap<>();
+
+ /**
+ * Represents a cache entry
+ *
+ static final class CacheEntry {
+
+ CacheEntry(InetAddress[] addresses, long expiration) {
+ this.addresses = addresses;
+ this.expiration = expiration;
+ }
+
+ InetAddress[] addresses;
+ long expiration;
+ }
+
+ /**
+ * A cache that manages entries based on a policy specified
+ * at creation time.
+ *
+ static final class Cache {
+ private LinkedHashMap<String, CacheEntry> cache;
+ private Type type;
+
+ enum Type {Positive, Negative};
+
+ /**
+ * Create cache
+ *
+ public Cache(Type type) {
+ this.type = type;
+ cache = new LinkedHashMap<String, CacheEntry>();
+ }
+
+ private int getPolicy() {
+ if (type == Type.Positive) {
+ return InetAddressCachePolicy.get();
+ } else {
+ return InetAddressCachePolicy.getNegative();
+ }
+ }
+
+ /**
+ * Add an entry to the cache. If there's already an
+ * entry then for this host then the entry will be
+ * replaced.
+ *
+ public Cache put(String host, InetAddress[] addresses) {
+ int policy = getPolicy();
+ if (policy == InetAddressCachePolicy.NEVER) {
+ return this;
+ }
+
+ // purge any expired entries
+
+ if (policy != InetAddressCachePolicy.FOREVER) {
+
+ // As we iterate in insertion order we can
+ // terminate when a non-expired entry is found.
+ LinkedList<String> expired = new LinkedList<>();
+ long now = System.currentTimeMillis();
+ for (String key : cache.keySet()) {
+ CacheEntry entry = cache.get(key);
+
+ if (entry.expiration >= 0 && entry.expiration < now) {
+ expired.add(key);
+ } else {
+ break;
+ }
+ }
+
+ for (String key : expired) {
+ cache.remove(key);
+ }
+ }
+
+ // create new entry and add it to the cache
+ // -- as a HashMap replaces existing entries we
+ // don't need to explicitly check if there is
+ // already an entry for this host.
+ long expiration;
+ if (policy == InetAddressCachePolicy.FOREVER) {
+ expiration = -1;
+ } else {
+ expiration = System.currentTimeMillis() + (policy * 1000);
+ }
+ CacheEntry entry = new CacheEntry(addresses, expiration);
+ cache.put(host, entry);
+ return this;
+ }
+
+ /**
+ * Query the cache for the specific host. If found then
+ * return its CacheEntry, or null if not found.
+ *
+ public CacheEntry get(String host) {
+ int policy = getPolicy();
+ if (policy == InetAddressCachePolicy.NEVER) {
+ return null;
+ }
+ CacheEntry entry = cache.get(host);
+
+ // check if entry has expired
+ if (entry != null && policy != InetAddressCachePolicy.FOREVER) {
+ if (entry.expiration >= 0 &&
+ entry.expiration < System.currentTimeMillis()) {
+ cache.remove(host);
+ entry = null;
+ }
+ }
+
+ return entry;
+ }
+ }
+
+ /*
+ * Initialize cache and insert anyLocalAddress into the
+ * unknown array with no expiry.
+ *
+ private static void cacheInitIfNeeded() {
+ assert Thread.holdsLock(addressCache);
+ if (addressCacheInit) {
+ return;
+ }
+ unknown_array = new InetAddress[1];
+ unknown_array[0] = impl.anyLocalAddress();
+
+ addressCache.put(impl.anyLocalAddress().getHostName(),
+ unknown_array);
+
+ addressCacheInit = true;
+ }
+
+ /*
+ * Cache the given hostname and addresses.
+ *
+ private static void cacheAddresses(String hostname,
+ InetAddress[] addresses,
+ boolean success) {
+ hostname = hostname.toLowerCase();
+ synchronized (addressCache) {
+ cacheInitIfNeeded();
+ if (success) {
+ addressCache.put(hostname, addresses);
+ } else {
+ negativeCache.put(hostname, addresses);
+ }
+ }
+ }
+
+ /*
+ * Lookup hostname in cache (positive & negative cache). If
+ * found return addresses, null if not found.
+ *
+ private static InetAddress[] getCachedAddresses(String hostname) {
+ hostname = hostname.toLowerCase();
+
+ // search both positive & negative caches
+
+ synchronized (addressCache) {
+ cacheInitIfNeeded();
+
+ CacheEntry entry = addressCache.get(hostname);
+ if (entry == null) {
+ entry = negativeCache.get(hostname);
+ }
+
+ if (entry != null) {
+ return entry.addresses;
+ }
+ }
+
+ // not found
+ return null;
+ }
+
+ private static NameService createNSProvider(String provider) {
+ if (provider == null)
+ return null;
+
+ NameService nameService = null;
+ if (provider.equals("default")) {
+ // initialize the default name service
+ nameService = new NameService() {
+ public InetAddress[] lookupAllHostAddr(String host)
+ throws UnknownHostException {
+ return impl.lookupAllHostAddr(host);
+ }
+ public String getHostByAddr(byte[] addr)
+ throws UnknownHostException {
+ return impl.getHostByAddr(addr);
+ }
+ };
+ } else {
+ final String providerName = provider;
+ try {
+ nameService = java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedExceptionAction<NameService>() {
+ public NameService run() {
+ Iterator<NameServiceDescriptor> itr =
+ ServiceLoader.load(NameServiceDescriptor.class)
+ .iterator();
+ while (itr.hasNext()) {
+ NameServiceDescriptor nsd = itr.next();
+ if (providerName.
+ equalsIgnoreCase(nsd.getType()+","
+ +nsd.getProviderName())) {
+ try {
+ return nsd.createNameService();
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.err.println(
+ "Cannot create name service:"
+ +providerName+": " + e);
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+ );
+ } catch (java.security.PrivilegedActionException e) {
+ }
+ }
+
+ return nameService;
+ }
+
+ static {
+ // create the impl
+ impl = InetAddressImplFactory.create();
+
+ // get name service if provided and requested
+ String provider = null;;
+ String propPrefix = "sun.net.spi.nameservice.provider.";
+ int n = 1;
+ nameServices = new ArrayList<NameService>();
+ provider = AccessController.doPrivileged(
+ new GetPropertyAction(propPrefix + n));
+ while (provider != null) {
+ NameService ns = createNSProvider(provider);
+ if (ns != null)
+ nameServices.add(ns);
+
+ n++;
+ provider = AccessController.doPrivileged(
+ new GetPropertyAction(propPrefix + n));
+ }
+
+ // if not designate any name services provider,
+ // create a default one
+ if (nameServices.size() == 0) {
+ NameService ns = createNSProvider("default");
+ nameServices.add(ns);
+ }
+ }
+ */
+ // END Android-removed: Resolves a hostname using Libcore.os.
+
/**
* Creates an InetAddress based on the provided host name and IP address.
* No name service is checked for the validity of the address.
@@ -685,6 +1044,7 @@ class InetAddress implements java.io.Serializable {
return getByAddress(host, addr, -1 /* scopeId */);
}
+ // Android-added: Called by native code in Libcore.io.
// Do not delete. Called from native code.
private static InetAddress getByAddress(String host, byte[] addr, int scopeId)
throws UnknownHostException {
@@ -740,6 +1100,7 @@ class InetAddress implements java.io.Serializable {
*/
public static InetAddress getByName(String host)
throws UnknownHostException {
+ // Android-changed: Rewritten on the top of Libcore.os.
return impl.lookupAllHostAddr(host, NETID_UNSET)[0];
}
@@ -784,6 +1145,8 @@ class InetAddress implements java.io.Serializable {
*/
public static InetAddress[] getAllByName(String host)
throws UnknownHostException {
+ // Android-changed: Resolves a hostname using Libcore.os.
+ // Also, returns both the Inet4 and Inet6 loopback for null/empty host
return impl.lookupAllHostAddr(host, NETID_UNSET).clone();
}
@@ -799,9 +1162,223 @@ class InetAddress implements java.io.Serializable {
* @since 1.7
*/
public static InetAddress getLoopbackAddress() {
+ // Android-changed: Always returns IPv6 loopback address in Android.
return impl.loopbackAddresses()[0];
}
+ // BEGIN Android-removed: Resolves a hostname using Libcore.os.
+ /*
+ * check if the literal address string has %nn appended
+ * returns -1 if not, or the numeric value otherwise.
+ *
+ * %nn may also be a string that represents the displayName of
+ * a currently available NetworkInterface.
+ *
+ private static int checkNumericZone (String s) throws UnknownHostException {
+ int percent = s.indexOf ('%');
+ int slen = s.length();
+ int digit, zone=0;
+ if (percent == -1) {
+ return -1;
+ }
+ for (int i=percent+1; i<slen; i++) {
+ char c = s.charAt(i);
+ if (c == ']') {
+ if (i == percent+1) {
+ /* empty per-cent field *
+ return -1;
+ }
+ break;
+ }
+ if ((digit = Character.digit (c, 10)) < 0) {
+ return -1;
+ }
+ zone = (zone * 10) + digit;
+ }
+ return zone;
+ }
+
+ private static InetAddress[] getAllByName0 (String host)
+ throws UnknownHostException
+ {
+ return getAllByName0(host, true);
+ }
+
+ /**
+ * package private so SocketPermission can call it
+ *
+ static InetAddress[] getAllByName0 (String host, boolean check)
+ throws UnknownHostException {
+ return getAllByName0 (host, null, check);
+ }
+
+ private static InetAddress[] getAllByName0 (String host, InetAddress reqAddr, boolean check)
+ throws UnknownHostException {
+
+ /* If it gets here it is presumed to be a hostname */
+ /* Cache.get can return: null, unknownAddress, or InetAddress[] */
+
+ /* make sure the connection to the host is allowed, before we
+ * give out a hostname
+ *
+ if (check) {
+ SecurityManager security = System.getSecurityManager();
+ if (security != null) {
+ security.checkConnect(host, -1);
+ }
+ }
+
+ InetAddress[] addresses = getCachedAddresses(host);
+
+ /* If no entry in cache, then do the host lookup *
+ if (addresses == null) {
+ addresses = getAddressesFromNameService(host, reqAddr);
+ }
+
+ if (addresses == unknown_array)
+ throw new UnknownHostException(host);
+
+ return addresses.clone();
+ }
+
+ private static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr)
+ throws UnknownHostException
+ {
+ InetAddress[] addresses = null;
+ boolean success = false;
+ UnknownHostException ex = null;
+
+ // Check whether the host is in the lookupTable.
+ // 1) If the host isn't in the lookupTable when
+ // checkLookupTable() is called, checkLookupTable()
+ // would add the host in the lookupTable and
+ // return null. So we will do the lookup.
+ // 2) If the host is in the lookupTable when
+ // checkLookupTable() is called, the current thread
+ // would be blocked until the host is removed
+ // from the lookupTable. Then this thread
+ // should try to look up the addressCache.
+ // i) if it found the addresses in the
+ // addressCache, checkLookupTable() would
+ // return the addresses.
+ // ii) if it didn't find the addresses in the
+ // addressCache for any reason,
+ // it should add the host in the
+ // lookupTable and return null so the
+ // following code would do a lookup itself.
+ if ((addresses = checkLookupTable(host)) == null) {
+ try {
+ // This is the first thread which looks up the addresses
+ // this host or the cache entry for this host has been
+ // expired so this thread should do the lookup.
+ for (NameService nameService : nameServices) {
+ try {
+ /*
+ * Do not put the call to lookup() inside the
+ * constructor. if you do you will still be
+ * allocating space when the lookup fails.
+ *
+
+ addresses = nameService.lookupAllHostAddr(host);
+ success = true;
+ break;
+ } catch (UnknownHostException uhe) {
+ if (host.equalsIgnoreCase("localhost")) {
+ InetAddress[] local = new InetAddress[] { impl.loopbackAddress() };
+ addresses = local;
+ success = true;
+ break;
+ }
+ else {
+ addresses = unknown_array;
+ success = false;
+ ex = uhe;
+ }
+ }
+ }
+
+ // More to do?
+ if (reqAddr != null && addresses.length > 1 && !addresses[0].equals(reqAddr)) {
+ // Find it?
+ int i = 1;
+ for (; i < addresses.length; i++) {
+ if (addresses[i].equals(reqAddr)) {
+ break;
+ }
+ }
+ // Rotate
+ if (i < addresses.length) {
+ InetAddress tmp, tmp2 = reqAddr;
+ for (int j = 0; j < i; j++) {
+ tmp = addresses[j];
+ addresses[j] = tmp2;
+ tmp2 = tmp;
+ }
+ addresses[i] = tmp2;
+ }
+ }
+ // Cache the address.
+ cacheAddresses(host, addresses, success);
+
+ if (!success && ex != null)
+ throw ex;
+
+ } finally {
+ // Delete host from the lookupTable and notify
+ // all threads waiting on the lookupTable monitor.
+ updateLookupTable(host);
+ }
+ }
+
+ return addresses;
+ }
+
+
+ private static InetAddress[] checkLookupTable(String host) {
+ synchronized (lookupTable) {
+ // If the host isn't in the lookupTable, add it in the
+ // lookuptable and return null. The caller should do
+ // the lookup.
+ if (lookupTable.containsKey(host) == false) {
+ lookupTable.put(host, null);
+ return null;
+ }
+
+ // If the host is in the lookupTable, it means that another
+ // thread is trying to look up the addresses of this host.
+ // This thread should wait.
+ while (lookupTable.containsKey(host)) {
+ try {
+ lookupTable.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ // The other thread has finished looking up the addresses of
+ // the host. This thread should retry to get the addresses
+ // from the addressCache. If it doesn't get the addresses from
+ // the cache, it will try to look up the addresses itself.
+ InetAddress[] addresses = getCachedAddresses(host);
+ if (addresses == null) {
+ synchronized (lookupTable) {
+ lookupTable.put(host, null);
+ return null;
+ }
+ }
+
+ return addresses;
+ }
+
+ private static void updateLookupTable(String host) {
+ synchronized (lookupTable) {
+ lookupTable.remove(host);
+ lookupTable.notifyAll();
+ }
+ }
+ */
+ // END Android-removed: Resolves a hostname using Libcore.os.
+
/**
* Returns an {@code InetAddress} object given the raw IP address .
* The argument is in network byte order: the highest order
@@ -823,6 +1400,15 @@ class InetAddress implements java.io.Serializable {
return getByAddress(null, addr);
}
+ // BEGIN Android-removed: Resolves a hostname using Libcore.os.
+ /*
+ private static InetAddress cachedLocalHost = null;
+ private static long cacheTime = 0;
+ private static final long maxCacheTime = 5000L;
+ private static final Object cacheLock = new Object();
+ */
+ // END Android-removed: Resolves a hostname using Libcore.os.
+
/**
* Returns the address of the local host. This is achieved by retrieving
* the name of the host from the system, then resolving that name into
@@ -847,10 +1433,68 @@ class InetAddress implements java.io.Serializable {
* @see java.net.InetAddress#getByName(java.lang.String)
*/
public static InetAddress getLocalHost() throws UnknownHostException {
+ // BEGIN Android-changed: Resolves a hostname using Libcore.os.
+ /*
+ SecurityManager security = System.getSecurityManager();
+ try {
+ String local = impl.getLocalHostName();
+
+ if (security != null) {
+ security.checkConnect(local, -1);
+ }
+
+ if (local.equals("localhost")) {
+ return impl.loopbackAddress();
+ }
+
+ InetAddress ret = null;
+ synchronized (cacheLock) {
+ long now = System.currentTimeMillis();
+ if (cachedLocalHost != null) {
+ if ((now - cacheTime) < maxCacheTime) // Less than 5s old?
+ ret = cachedLocalHost;
+ else
+ cachedLocalHost = null;
+ }
+
+ // we are calling getAddressesFromNameService directly
+ // to avoid getting localHost from cache
+ if (ret == null) {
+ InetAddress[] localAddrs;
+ try {
+ localAddrs =
+ InetAddress.getAddressesFromNameService(local, null);
+ } catch (UnknownHostException uhe) {
+ // Rethrow with a more informative error message.
+ UnknownHostException uhe2 =
+ new UnknownHostException(local + ": " +
+ uhe.getMessage());
+ uhe2.initCause(uhe);
+ throw uhe2;
+ }
+ cachedLocalHost = localAddrs[0];
+ cacheTime = now;
+ ret = localAddrs[0];
+ }
+ }
+ return ret;
+ } catch (java.lang.SecurityException e) {
+ return impl.loopbackAddress();
+ }
+ */
String local = Libcore.os.uname().nodename;
return impl.lookupAllHostAddr(local, NETID_UNSET)[0];
+ // END Android-changed: Resolves a hostname using Libcore.os.
}
+ // BEGIN Android-removed: Android doesn't need to call native init.
+ /**
+ * Perform class load-time initializations.
+ *
+ private static native void init();
+ */
+ // END Android-removed: Android doesn't need to call native init.
+
/*
* Returns the InetAddress representing anyLocalAddress
* (typically 0.0.0.0 or ::0)
@@ -859,6 +1503,51 @@ class InetAddress implements java.io.Serializable {
return impl.anyLocalAddress();
}
+ // BEGIN Android-removed: Android doesn't load user-provided implementation.
+ /*
+ * Load and instantiate an underlying impl class
+ *
+ static InetAddressImpl loadImpl(String implName) {
+ Object impl = null;
+
+ /*
+ * Property "impl.prefix" will be prepended to the classname
+ * of the implementation object we instantiate, to which we
+ * delegate the real work (like native methods). This
+ * property can vary across implementations of the java.
+ * classes. The default is an empty String "".
+ *
+ String prefix = AccessController.doPrivileged(
+ new GetPropertyAction("impl.prefix", ""));
+ try {
+ impl = Class.forName("java.net." + prefix + implName).newInstance();
+ } catch (ClassNotFoundException e) {
+ System.err.println("Class not found: java.net." + prefix +
+ implName + ":\ncheck impl.prefix property " +
+ "in your properties file.");
+ } catch (InstantiationException e) {
+ System.err.println("Could not instantiate: java.net." + prefix +
+ implName + ":\ncheck impl.prefix property " +
+ "in your properties file.");
+ } catch (IllegalAccessException e) {
+ System.err.println("Cannot access class: java.net." + prefix +
+ implName + ":\ncheck impl.prefix property " +
+ "in your properties file.");
+ }
+
+ if (impl == null) {
+ try {
+ impl = Class.forName(implName).newInstance();
+ } catch (Exception e) {
+ throw new Error("System property impl.prefix incorrect");
+ }
+ }
+
+ return (InetAddressImpl) impl;
+ }
+ */
+ // END Android-removed: Android doesn't load user-provided implementation.
+
private void readObjectNoData (ObjectInputStream s) throws
IOException, ClassNotFoundException {
// Android-changed: Don't use null to mean the boot classloader.
@@ -912,6 +1601,8 @@ class InetAddress implements java.io.Serializable {
static final int NETID_UNSET = 0;
+ // BEGIN Android-added: Add methods required by frameworks/base.
+ // Particularly those required to deal with net-ids and scope ids.
/**
* Returns true if the string is a valid numeric IPv4 or IPv6 address (such as "192.168.0.1").
* This copes with all forms of address that Java supports, detailed in the {@link InetAddress}
@@ -1007,6 +1698,7 @@ class InetAddress implements java.io.Serializable {
public static InetAddress[] getAllByNameOnNet(String host, int netId) throws UnknownHostException {
return impl.lookupAllHostAddr(host, netId).clone();
}
+ // END Android-added: Add methods required by frameworks/base.
// Only called by java.net.SocketPermission.
static InetAddress[] getAllByName0(String authHost, boolean check) throws UnknownHostException {
@@ -1018,3 +1710,18 @@ class InetAddress implements java.io.Serializable {
throw new UnsupportedOperationException();
}
}
+// BEGIN Android-removed: Android doesn't load user-provided implementation.
+/*
+ * Simple factory to create the impl
+ *
+class InetAddressImplFactory {
+
+ static InetAddressImpl create() {
+ return InetAddress.loadImpl(isIPv6Supported() ?
+ "Inet6AddressImpl" : "Inet4AddressImpl");
+ }
+
+ static native boolean isIPv6Supported();
+}
+*/
+// END Android-removed: Android doesn't load user-provided implementation.
diff --git a/java/net/InetAddressImpl.java b/java/net/InetAddressImpl.java
index e5821a38..a636c1e4 100644
--- a/java/net/InetAddressImpl.java
+++ b/java/net/InetAddressImpl.java
@@ -35,6 +35,13 @@ import java.io.IOException;
* @since 1.4
*/
interface InetAddressImpl {
+
+ // BEGIN Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
+ /*
+ String getLocalHostName() throws UnknownHostException;
+ InetAddress[]
+ lookupAllHostAddr(String hostname) throws UnknownHostException;
+ */
/**
* Lookup all addresses for {@code hostname} on the given {@code netId}.
*/
@@ -49,12 +56,15 @@ interface InetAddressImpl {
* Clear address caches (if any).
*/
public void clearAddressCache();
+ // END Android-changed: Rewrote hostname lookup methods on top of Libcore.os.
/**
* Return the "any" local address.
*/
InetAddress anyLocalAddress();
+ // Android-changed: Let loopbackAddresses() return both Inet4 and Inet6 loopbacks.
+ // InetAddress loopbackAddress();
/**
* Return a list of loop back adresses for this implementation.
*/
diff --git a/java/net/InetSocketAddress.java b/java/net/InetSocketAddress.java
index 407c7224..74b559bc 100644
--- a/java/net/InetSocketAddress.java
+++ b/java/net/InetSocketAddress.java
@@ -151,6 +151,7 @@ public class InetSocketAddress
return hostname;
}
+ // BEGIN Android-added: InetSocketAddress() ctor used by IoBridge.
/**
* @hide internal use only
*/
@@ -158,6 +159,7 @@ public class InetSocketAddress
// These will be filled in the native implementation of recvfrom.
holder = new InetSocketAddressHolder(null, null, 0);
}
+ // END Android-added: InetSocketAddress() ctor used by IoBridge.
/**
* Creates a socket address where the IP address is the wildcard address
@@ -172,7 +174,9 @@ public class InetSocketAddress
* range of valid port values.
*/
public InetSocketAddress(int port) {
- this((InetAddress)null, port);
+ // Android-changed: Defaults to IPv6.
+ // this(InetAddress.anyLocalAddress(), port);
+ this((InetAddress)null, port);
}
/**
@@ -193,7 +197,7 @@ public class InetSocketAddress
public InetSocketAddress(InetAddress addr, int port) {
holder = new InetSocketAddressHolder(
null,
- // Android-changed: Return IPv4 address
+ // Android-changed: Defaults to IPv6 if addr is null.
// addr == null ? InetAddress.anyLocalAddress() : addr,
addr == null ? Inet6Address.ANY : addr,
checkPort(port));
@@ -207,7 +211,7 @@ public class InetSocketAddress
* If that attempt fails, the address will be flagged as <I>unresolved</I>.
* <p>
* If there is a security manager, its {@code checkConnect} method
- * is called with the host name as its argument to check the permissiom
+ * is called with the host name as its argument to check the permission
* to resolve it. This could result in a SecurityException.
* <P>
* A valid port value is between 0 and 65535.
@@ -400,7 +404,7 @@ public class InetSocketAddress
* Two instances of {@code InetSocketAddress} represent the same
* address if both the InetAddresses (or hostnames if it is unresolved) and port
* numbers are equal.
- * If both addresses are unresolved, then the hostname & the port number
+ * If both addresses are unresolved, then the hostname and the port number
* are compared.
*
* Note: Hostnames are case insensitive. e.g. "FooBar" and "foobar" are
diff --git a/java/net/InterfaceAddress.java b/java/net/InterfaceAddress.java
index fbd0e483..85f9e587 100644
--- a/java/net/InterfaceAddress.java
+++ b/java/net/InterfaceAddress.java
@@ -46,6 +46,7 @@ public class InterfaceAddress {
InterfaceAddress() {
}
+ // BEGIN Android-added: Rewrote NetworkInterface on top of Libcore.io.
InterfaceAddress(InetAddress address, Inet4Address broadcast, InetAddress netmask) {
this.address = address;
this.broadcast = broadcast;
@@ -65,6 +66,7 @@ public class InterfaceAddress {
}
return count;
}
+ // END Android-added: Rewrote NetworkInterface on top of Libcore.io.
/**
* Returns an {@code InetAddress} for this address.
diff --git a/java/net/MulticastSocket.java b/java/net/MulticastSocket.java
index d4150a8c..d14500ac 100644
--- a/java/net/MulticastSocket.java
+++ b/java/net/MulticastSocket.java
@@ -567,6 +567,7 @@ class MulticastSocket extends DatagramSocket {
* @since 1.4
*/
public NetworkInterface getNetworkInterface() throws SocketException {
+ // Android-changed: Support Integer IP_MULTICAST_IF2 values for app compat.
Integer niIndex
= (Integer)getImpl().getOption(SocketOptions.IP_MULTICAST_IF2);
if (niIndex == 0) {
diff --git a/java/net/NetworkInterface.java b/java/net/NetworkInterface.java
index 216094ac..e465d0ba 100644
--- a/java/net/NetworkInterface.java
+++ b/java/net/NetworkInterface.java
@@ -45,6 +45,8 @@ import java.security.AccessController;
import static android.system.OsConstants.*;
+// Android-note: NetworkInterface has been rewritten to avoid native code.
+// Fix upstream bug not returning link-down interfaces. http://b/26238832
/**
* This class represents a Network Interface made up of a name,
* and a list of IP addresses assigned to this interface.
@@ -61,14 +63,30 @@ public final class NetworkInterface {
private int index;
private InetAddress addrs[];
private InterfaceAddress bindings[];
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
+ // private NetworkInterface childs[];
private List<NetworkInterface> childs;
private NetworkInterface parent = null;
private boolean virtual = false;
- private byte[] hardwareAddr;
private static final NetworkInterface defaultInterface;
private static final int defaultIndex; /* index of defaultInterface */
+ // Android-changed: Fix upstream bug not returning link-down interfaces. http://b/26238832
+ private byte[] hardwareAddr;
+
static {
+ // Android-removed: Android doesn't need to call native init.
+ /*
+ AccessController.doPrivileged(
+ new java.security.PrivilegedAction<Void>() {
+ public Void run() {
+ System.loadLibrary("net");
+ return null;
+ }
+ });
+
+ init();
+ */
defaultInterface = DefaultInterface.getDefault();
if (defaultInterface != null) {
defaultIndex = defaultInterface.getIndex();
@@ -203,6 +221,7 @@ public final class NetworkInterface {
* @since 1.6
*/
public Enumeration<NetworkInterface> getSubInterfaces() {
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
return Collections.enumeration(childs);
}
@@ -266,6 +285,7 @@ public final class NetworkInterface {
if (name == null)
throw new NullPointerException();
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
NetworkInterface[] nis = getAll();
for (NetworkInterface ni : nis) {
if (ni.getName().equals(name)) {
@@ -290,6 +310,7 @@ public final class NetworkInterface {
if (index < 0)
throw new IllegalArgumentException("Interface index can't be negative");
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
NetworkInterface[] nis = getAll();
for (NetworkInterface ni : nis) {
if (ni.getIndex() == index) {
@@ -329,6 +350,7 @@ public final class NetworkInterface {
throw new IllegalArgumentException ("invalid address type");
}
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
NetworkInterface[] nis = getAll();
for (NetworkInterface ni : nis) {
for (InetAddress inetAddress : Collections.list(ni.getInetAddresses())) {
@@ -357,6 +379,7 @@ public final class NetworkInterface {
throws SocketException {
final NetworkInterface[] netifs = getAll();
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
// specified to return null if no network interfaces
if (netifs.length == 0)
return null;
@@ -364,6 +387,9 @@ public final class NetworkInterface {
return Collections.enumeration(Arrays.asList(netifs));
}
+ // BEGIN Android-changed: Rewrote NetworkInterface on top of Libcore.io.
+ // private native static NetworkInterface[] getAll()
+ // throws SocketException;
private static NetworkInterface[] getAll() throws SocketException {
// Group Ifaddrs by interface name.
Map<String, List<StructIfaddrs>> inetMap = new HashMap<>();
@@ -439,6 +465,7 @@ public final class NetworkInterface {
return nis.values().toArray(new NetworkInterface[nis.size()]);
}
+ // END Android-changed: Rewrote NetworkInterface on top of Libcore.io.
/**
* Returns whether a network interface is up and running.
@@ -449,6 +476,7 @@ public final class NetworkInterface {
*/
public boolean isUp() throws SocketException {
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
return (getFlags() & IFF_UP) != 0;
}
@@ -461,6 +489,7 @@ public final class NetworkInterface {
*/
public boolean isLoopback() throws SocketException {
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
return (getFlags() & IFF_LOOPBACK) != 0;
}
@@ -476,6 +505,7 @@ public final class NetworkInterface {
*/
public boolean isPointToPoint() throws SocketException {
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
return (getFlags() & IFF_POINTOPOINT) != 0;
}
@@ -488,6 +518,7 @@ public final class NetworkInterface {
*/
public boolean supportsMulticast() throws SocketException {
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
return (getFlags() & IFF_MULTICAST) != 0;
}
@@ -506,13 +537,21 @@ public final class NetworkInterface {
* @since 1.6
*/
public byte[] getHardwareAddress() throws SocketException {
- // Android chage - do not use the cached address, fetch
- // the object again. NI might not be valid anymore.
+ // BEGIN Android-changed: Fix upstream not returning link-down interfaces. http://b/26238832
+ /*
+ for (InetAddress addr : addrs) {
+ if (addr instanceof Inet4Address) {
+ return getMacAddr0(((Inet4Address)addr).getAddress(), name, index);
+ }
+ }
+ return getMacAddr0(null, name, index);
+ */
NetworkInterface ni = getByName(name);
if (ni == null) {
throw new SocketException("NetworkInterface doesn't exist anymore");
}
return ni.hardwareAddr;
+ // END Android-changed: Fix upstream not returning link-down interfaces. http://b/26238832
}
/**
@@ -523,6 +562,8 @@ public final class NetworkInterface {
* @since 1.6
*/
public int getMTU() throws SocketException {
+ // Android-changed: Rewrote NetworkInterface on top of Libcore.io.
+ // return getMTU0(name, index);
FileDescriptor fd = null;
try {
fd = Libcore.rawOs.socket(AF_INET, SOCK_DGRAM, 0);
@@ -553,6 +594,18 @@ public final class NetworkInterface {
return virtual;
}
+ // BEGIN Android-removed: Rewrote NetworkInterface on top of Libcore.io.
+ /*
+ private native static boolean isUp0(String name, int ind) throws SocketException;
+ private native static boolean isLoopback0(String name, int ind) throws SocketException;
+ private native static boolean supportsMulticast0(String name, int ind) throws SocketException;
+ private native static boolean isP2P0(String name, int ind) throws SocketException;
+ private native static byte[] getMacAddr0(byte[] inAddr, String name, int ind) throws SocketException;
+ private native static int getMTU0(String name, int ind) throws SocketException;
+ */
+ // END Android-removed: Rewrote NetworkInterface on top of Libcore.io.
+
+ // BEGIN Android-added: Rewrote NetworkInterface on top of Libcore.io.
private int getFlags() throws SocketException {
FileDescriptor fd = null;
try {
@@ -566,6 +619,7 @@ public final class NetworkInterface {
IoUtils.closeQuietly(fd);
}
}
+ // END Android-added: Rewrote NetworkInterface on top of Libcore.io.
/**
* Compares this object against the specified object.
@@ -639,6 +693,9 @@ public final class NetworkInterface {
return result;
}
+ // Android-removed: Android doesn't need to call native init.
+ // private static native void init();
+
/**
* Returns the default network interface of this system
*
diff --git a/java/net/PlainDatagramSocketImpl.java b/java/net/PlainDatagramSocketImpl.java
index c6a44d56..d4b1f2cb 100644
--- a/java/net/PlainDatagramSocketImpl.java
+++ b/java/net/PlainDatagramSocketImpl.java
@@ -95,6 +95,7 @@ class PlainDatagramSocketImpl extends AbstractPlainDatagramSocketImpl
}
}
+ // BEGIN Android-changed: Rewrote on top of Libcore.io.
protected synchronized void bind0(int lport, InetAddress laddr) throws SocketException {
if (isClosed()) {
throw new SocketException("Socket closed");
@@ -245,6 +246,7 @@ class PlainDatagramSocketImpl extends AbstractPlainDatagramSocketImpl
IoBridge.connect(fd, inetAddressUnspec, 0);
} catch (SocketException ignored) { }
}
+ // END Android-changed: Rewrote on top of Libcore.io.
// Android-removed: JNI has been removed
// /**
diff --git a/java/net/PlainSocketImpl.java b/java/net/PlainSocketImpl.java
index 656defc4..f5923983 100644
--- a/java/net/PlainSocketImpl.java
+++ b/java/net/PlainSocketImpl.java
@@ -61,6 +61,13 @@ import static sun.net.ExtendedOptionsImpl.*;
class PlainSocketImpl extends AbstractPlainSocketImpl
{
+ // Android-removed: Android doesn't need to call native initProto.
+ /*
+ static {
+ initProto();
+ }
+ */
+
/**
* Constructs an empty instance.
*/
@@ -101,6 +108,7 @@ class PlainSocketImpl extends AbstractPlainSocketImpl
return (T)flow;
}
+ // BEGIN Android-changed: Rewrote on top of Libcore.io.
protected void socketSetOption(int opt, Object val) throws SocketException {
try {
socketSetOption0(opt, val);
@@ -293,5 +301,6 @@ class PlainSocketImpl extends AbstractPlainSocketImpl
throw errnoException.rethrowAsSocketException();
}
}
+ // END Android-changed: Rewrote on top of Libcore.io.
}
diff --git a/java/net/PortUnreachableException.java b/java/net/PortUnreachableException.java
index c5dae1b0..3e7558b8 100644
--- a/java/net/PortUnreachableException.java
+++ b/java/net/PortUnreachableException.java
@@ -51,6 +51,7 @@ public class PortUnreachableException extends SocketException {
*/
public PortUnreachableException() {}
+ // Android-added: PortUnreachableException ctor used by IoBridge.
/** @hide */
public PortUnreachableException(String msg, Throwable cause) {
super(msg, cause);
diff --git a/java/net/ProtocolException.java b/java/net/ProtocolException.java
index e5e66f76..03683503 100644
--- a/java/net/ProtocolException.java
+++ b/java/net/ProtocolException.java
@@ -55,6 +55,7 @@ class ProtocolException extends IOException {
public ProtocolException() {
}
+ // Android-added: ProtocolException ctor used by frameworks.
/** @hide */
public ProtocolException(String msg, Throwable cause) {
super(msg, cause);
diff --git a/java/net/ServerSocket.java b/java/net/ServerSocket.java
index 20ae95a9..bb495e6c 100644
--- a/java/net/ServerSocket.java
+++ b/java/net/ServerSocket.java
@@ -253,6 +253,7 @@ class ServerSocket implements java.io.Closeable {
* @since 1.4
* @hide
*/
+ // Android-changed: Make ctor public and @hide, for internal use.
public SocketImpl getImpl() throws SocketException {
if (!created)
createImpl();
@@ -446,8 +447,7 @@ class ServerSocket implements java.io.Closeable {
}
/**
- * Returns the address of the endpoint this socket is bound to, or
- * {@code null} if it is not bound yet.
+ * Returns the address of the endpoint this socket is bound to.
* <p>
* If the socket was bound prior to being {@link #close closed},
* then this method will continue to return the address of the endpoint
@@ -922,7 +922,7 @@ class ServerSocket implements java.io.Closeable {
/* Not implemented yet */
}
- // Android-added: for testing and internal use.
+ // Android-added: getFileDescriptor$(), for testing / internal use.
/**
* @hide internal use only
*/
diff --git a/java/net/URLConnection.java b/java/net/URLConnection.java
index b869c39f..1de7b3ca 100644
--- a/java/net/URLConnection.java
+++ b/java/net/URLConnection.java
@@ -288,6 +288,13 @@ public abstract class URLConnection {
*/
private static FileNameMap fileNameMap;
+ // BEGIN Android-removed: Android has its own mime table.
+ /*
+ * @since 1.2.2
+ *
+ private static boolean fileNameMapLoaded = false;
+ */
+ // END Android-removed: Android has its own mime table.
/**
* Loads filename map (a mimetable) from a data file. It will
* first try to load the user-specific table, defined
@@ -299,6 +306,7 @@ public abstract class URLConnection {
* @see #setFileNameMap(java.net.FileNameMap)
*/
public static synchronized FileNameMap getFileNameMap() {
+ // Android-changed: Android has its own mime table.
if (fileNameMap == null) {
fileNameMap = new DefaultFileNameMap();
}
@@ -352,6 +360,7 @@ public abstract class URLConnection {
*/
abstract public void connect() throws IOException;
+ // Android-changed: Add javadoc to specify Android's timeout behavior.
/**
* Sets a specified timeout value, in milliseconds, to be used
* when opening a communications link to the resource referenced
@@ -659,7 +668,7 @@ public abstract class URLConnection {
* Returns the key for the {@code n}<sup>th</sup> header field.
* It returns {@code null} if there are fewer than {@code n+1} fields.
*
- * @param n an index, where n>=0
+ * @param n an index, where {@code n>=0}
* @return the key for the {@code n}<sup>th</sup> header field,
* or {@code null} if there are fewer than {@code n+1}
* fields.
@@ -677,7 +686,7 @@ public abstract class URLConnection {
* {@link #getHeaderFieldKey(int) getHeaderFieldKey} method to iterate through all
* the headers in the message.
*
- * @param n an index, where n>=0
+ * @param n an index, where {@code n>=0}
* @return the value of the {@code n}<sup>th</sup> header field
* or {@code null} if there are fewer than {@code n+1} fields
* @see java.net.URLConnection#getHeaderFieldKey(int)
@@ -1234,6 +1243,7 @@ public abstract class URLConnection {
{
String contentType = stripOffParameters(getContentType());
ContentHandler handler = null;
+ // BEGIN Android-changed: App Compat. Android guesses content type from name and stream.
if (contentType == null) {
if ((contentType = guessContentTypeFromName(url.getFile())) == null) {
contentType = guessContentTypeFromStream(getInputStream());
@@ -1243,6 +1253,7 @@ public abstract class URLConnection {
if (contentType == null) {
return UnknownContentHandler.INSTANCE;
}
+ // END Android-changed: App Compat. Android guesses content type from name and stream.
try {
handler = handlers.get(contentType);
if (handler != null)
diff --git a/java/net/URLDecoder.java b/java/net/URLDecoder.java
index cdf04196..8b14eb86 100644
--- a/java/net/URLDecoder.java
+++ b/java/net/URLDecoder.java
@@ -67,7 +67,7 @@ import java.io.*;
* <p>
* There are two possible ways in which this decoder could deal with
* illegal strings. It could either leave illegal characters alone or
- * it could throw an {@code {@link java.lang.IllegalArgumentException}}.
+ * it could throw an {@link java.lang.IllegalArgumentException}.
* Which approach the decoder takes is left to the
* implementation.
*
@@ -172,14 +172,15 @@ public class URLDecoder {
while ( ((i+2) < numChars) &&
(c=='%')) {
- // BEGIN Android-changed
+ // BEGIN Android-changed: App compat. Forbid non-hex chars after '%'.
if (!isValidHexChar(s.charAt(i+1)) || !isValidHexChar(s.charAt(i+2))) {
throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern : "
+ s.substring(i, i + 3));
}
- // END Android-changed
+ // END Android-changed: App compat. Forbid non-hex chars after '%'.
int v = Integer.parseInt(s.substring(i+1,i+3),16);
if (v < 0)
+ // Android-changed: Improve error message by printing the string value.
throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern - negative value : "
+ s.substring(i, i + 3));
bytes[pos++] = (byte) v;
@@ -213,9 +214,9 @@ public class URLDecoder {
return (needToChange? sb.toString() : s);
}
- // BEGIN Android-changed
+ // BEGIN Android-added: App compat. Forbid non-hex chars after '%'.
private static boolean isValidHexChar(char c) {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
}
- // END Android-changed
+ // END Android-added: App compat. Forbid non-hex chars after '%'.
}
diff --git a/java/security/SecureRandom.java b/java/security/SecureRandom.java
index 0852cbd2..1e8707a0 100644
--- a/java/security/SecureRandom.java
+++ b/java/security/SecureRandom.java
@@ -300,41 +300,6 @@ public class SecureRandom extends java.util.Random {
instance.provider, algorithm);
}
- // BEGIN Android-added: Support for Crypto provider workaround
- /**
- * Maximum SDK version for which the workaround for the Crypto provider is in place.
- *
- * <p> We provide instances from the Crypto provider (although the provider is not installed) to
- * apps targeting M or earlier versions of the SDK.
- *
- * <p> Default is 23 (M). We have it as a field for testability and it shouldn't be changed.
- *
- * @hide
- */
- public static final int DEFAULT_SDK_TARGET_FOR_CRYPTO_PROVIDER_WORKAROUND = 23;
-
- private static int sdkTargetForCryptoProviderWorkaround =
- DEFAULT_SDK_TARGET_FOR_CRYPTO_PROVIDER_WORKAROUND;
-
- /**
- * Only for testing.
- *
- * @hide
- */
- public static void setSdkTargetForCryptoProviderWorkaround(int sdkTargetVersion) {
- sdkTargetForCryptoProviderWorkaround = sdkTargetVersion;
- }
-
- /**
- * Only for testing.
- *
- * @hide
- */
- public static int getSdkTargetForCryptoProviderWorkaround() {
- return sdkTargetForCryptoProviderWorkaround;
- }
- // END Android-added: Support for Crypto provider workaround
-
/**
* Returns a SecureRandom object that implements the specified
* Random Number Generator (RNG) algorithm.
@@ -380,54 +345,11 @@ public class SecureRandom extends java.util.Random {
*/
public static SecureRandom getInstance(String algorithm, String provider)
throws NoSuchAlgorithmException, NoSuchProviderException {
- try {
- Instance instance = GetInstance.getInstance("SecureRandom",
- SecureRandomSpi.class, algorithm, provider);
- return new SecureRandom((SecureRandomSpi) instance.impl,
- instance.provider, algorithm);
- // BEGIN Android-added: Crypto provider deprecation
- } catch (NoSuchProviderException nspe) {
- if ("Crypto".equals(provider)) {
- System.logE(" ********** PLEASE READ ************ ");
- System.logE(" * ");
- System.logE(" * New versions of the Android SDK no longer support the Crypto provider.");
- System.logE(" * If your app was relying on setSeed() to derive keys from strings, you");
- System.logE(" * should switch to using SecretKeySpec to load raw key bytes directly OR");
- System.logE(" * use a real key derivation function (KDF). See advice here : ");
- System.logE(" * http://android-developers.blogspot.com/2016/06/security-crypto-provider-deprecated-in.html ");
- System.logE(" *********************************** ");
- if (VMRuntime.getRuntime().getTargetSdkVersion()
- <= sdkTargetForCryptoProviderWorkaround) {
- System.logE(" Returning an instance of SecureRandom from the Crypto provider");
- System.logE(" as a temporary measure so that the apps targeting earlier SDKs");
- System.logE(" keep working. Please do not rely on the presence of the Crypto");
- System.logE(" provider in the codebase, as our plan is to delete it");
- System.logE(" completely in the future.");
- return getInstanceFromCryptoProvider(algorithm);
- }
- }
-
- throw nspe;
- }
- }
-
- private static SecureRandom getInstanceFromCryptoProvider(String algorithm)
- throws NoSuchAlgorithmException {
- Provider cryptoProvider;
- try {
- cryptoProvider = (Provider) SecureRandom.class.getClassLoader()
- .loadClass(
- "org.apache.harmony.security.provider.crypto.CryptoProvider")
- .newInstance();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- Service service = cryptoProvider.getService("SecureRandom", algorithm);
- Instance instance = GetInstance.getInstance(service, SecureRandomSpi.class);
- return new SecureRandom(
- (SecureRandomSpi) instance.impl, instance.provider, algorithm);
+ Instance instance = GetInstance.getInstance("SecureRandom",
+ SecureRandomSpi.class, algorithm, provider);
+ return new SecureRandom((SecureRandomSpi)instance.impl,
+ instance.provider, algorithm);
}
- // END Android-added: Crypto provider deprecation
/**
* Returns a SecureRandom object that implements the specified
@@ -679,7 +601,7 @@ public class SecureRandom extends java.util.Random {
/**
* Returns a {@code SecureRandom} object.
*
- * In Android this is equivalent to get a SHA1PRNG from OpenSSLProvider.
+ * In Android this is equivalent to get a SHA1PRNG from AndroidOpenSSL.
*
* Some situations require strong random values, such as when
* creating high-value/long-lived secrets like RSA public/private
diff --git a/java/text/Bidi.java b/java/text/Bidi.java
index 21085f23..70e29aae 100644
--- a/java/text/Bidi.java
+++ b/java/text/Bidi.java
@@ -81,6 +81,13 @@ public final class Bidi {
*/
public static final int DIRECTION_DEFAULT_RIGHT_TO_LEFT = -1;
+ // Android-note: Upstream this class delegates to an internal implementation class BidiBase.
+ // For Android that is replaced with android.icu.text.Bidi. BidiBase and ICU Bidi work very
+ // similarly, but differ in some details like level of argument validation and how how exactly
+ // runs are counted. The majority of the changes in this file exist to allow for backwards
+ // compatibility with an earlier ICU4C based Bidi implementation.
+
+ // BEGIN Android-added: translateConstToIcu(int).
private static int translateConstToIcu(int javaInt) {
switch (javaInt) {
case DIRECTION_DEFAULT_LEFT_TO_RIGHT:
@@ -96,8 +103,10 @@ public final class Bidi {
return android.icu.text.Bidi.DIRECTION_LEFT_TO_RIGHT;
}
}
+ // END Android-added: translateConstToIcu(int).
- private android.icu.text.Bidi bidiBase;
+ // Android-changed: use ICU Bidi class instead of BidiBase.
+ private final android.icu.text.Bidi bidiBase;
/**
* Create Bidi from the given paragraph of text and base direction.
@@ -108,8 +117,13 @@ public final class Bidi {
* Other values are reserved.
*/
public Bidi(String paragraph, int flags) {
- this((paragraph == null ? null : paragraph.toCharArray()), 0, null, 0,
- (paragraph == null ? 0 : paragraph.length()), flags);
+ if (paragraph == null) {
+ throw new IllegalArgumentException("paragraph is null");
+ }
+
+ // Android-changed: use ICU Bidi class instead of BidiBase.
+ bidiBase = new android.icu.text.Bidi(paragraph.toCharArray(), 0, null, 0,
+ paragraph.length(), translateConstToIcu(flags));
}
/**
@@ -142,6 +156,7 @@ public final class Bidi {
throw new IllegalArgumentException("paragraph is null");
}
+ // Android-changed: change from BidiBase to ICU Bidi class.
this.bidiBase = new android.icu.text.Bidi(paragraph);
}
@@ -180,10 +195,12 @@ public final class Bidi {
" for embeddings of length: " + text.length);
}
+ // Android-changed: use ICU Bidi class instead of BidiBase.
bidiBase = new android.icu.text.Bidi(text, textStart, embeddings, embStart,
paragraphLength, translateConstToIcu(flags));
}
+ // Android-added: private constructor based on ICU Bidi object.
private Bidi(android.icu.text.Bidi bidiBase) {
this.bidiBase = bidiBase;
}
@@ -198,6 +215,7 @@ public final class Bidi {
* @return a {@code Bidi} object
*/
public Bidi createLineBidi(int lineStart, int lineLimit) {
+ // BEGIN Android-changed: add explict argument checks and use ICU Bidi class.
if (lineStart < 0 || lineLimit < 0 || lineStart > lineLimit || lineLimit > getLength()) {
throw new IllegalArgumentException("Invalid ranges (start=" + lineStart + ", " +
"limit=" + lineLimit + ", length=" + getLength() + ")");
@@ -214,6 +232,7 @@ public final class Bidi {
}
return new Bidi(bidiBase.createLineBidi(lineStart, lineLimit));
+ // END Android-changed: add explict argument checks and use ICU Bidi class.
}
/**
@@ -276,11 +295,13 @@ public final class Bidi {
* @return the resolved level of the character at offset
*/
public int getLevelAt(int offset) {
+ // BEGIN Android-changed: return base level on out of range offset argument.
try {
return bidiBase.getLevelAt(offset);
} catch (IllegalArgumentException e) {
return getBaseLevel();
}
+ // END Android-changed: return base level on out of range offset argument.
}
/**
@@ -288,6 +309,7 @@ public final class Bidi {
* @return the number of level runs
*/
public int getRunCount() {
+ // Android-changed: ICU treats the empty string as having 0 runs, we see it as 1 empty run.
int runCount = bidiBase.countRuns();
return (runCount == 0 ? 1 : runCount);
}
@@ -298,11 +320,11 @@ public final class Bidi {
* @return the level of the run
*/
public int getRunLevel(int run) {
- // Paper over a the ICU4J behaviour of strictly enforcing run must be strictly less than
- // the number of runs. Done to maintain compatibility with previous C implementation.
+ // Android-added: Tolerate calls with run == getRunCount() for backwards compatibility.
if (run == getRunCount()) {
return getBaseLevel();
}
+ // Android-changed: ICU treats the empty string as having 0 runs, we see it as 1 empty run.
return (bidiBase.countRuns() == 0 ? bidiBase.getBaseLevel() : bidiBase.getRunLevel(run));
}
@@ -313,11 +335,11 @@ public final class Bidi {
* @return the start of the run
*/
public int getRunStart(int run) {
- // Paper over a the ICU4J behaviour of strictly enforcing run must be strictly less than
- // the number of runs. Done to maintain compatibility with previous C implementation.
+ // Android-added: Tolerate calls with run == getRunCount() for backwards compatibility.
if (run == getRunCount()) {
return getBaseLevel();
}
+ // Android-changed: ICU treats the empty string as having 0 runs, we see it as 1 empty run.
return (bidiBase.countRuns() == 0 ? 0 : bidiBase.getRunStart(run));
}
@@ -329,11 +351,11 @@ public final class Bidi {
* @return limit the limit of the run
*/
public int getRunLimit(int run) {
- // Paper over a the ICU4J behaviour of strictly enforcing run must be strictly less than
- // the number of runs. Done to maintain compatibility with previous C implementation.
+ // Android-added: Tolerate calls with run == getRunCount() for backwards compatibility.
if (run == getRunCount()) {
return getBaseLevel();
}
+ // Android-changed: ICU treats the empty string as having 0 runs, we see it as 1 empty run.
return (bidiBase.countRuns() == 0 ? bidiBase.getLength() : bidiBase.getRunLimit(run));
}
@@ -349,6 +371,7 @@ public final class Bidi {
* @return true if the range of characters requires bidi analysis
*/
public static boolean requiresBidi(char[] text, int start, int limit) {
+ // Android-added: Check arguments to throw correct exception.
if (0 > start || start > limit || limit > text.length) {
throw new IllegalArgumentException("Value start " + start +
" is out of range 0 to " + limit);
@@ -373,6 +396,7 @@ public final class Bidi {
* @param count the number of objects to reorder
*/
public static void reorderVisually(byte[] levels, int levelStart, Object[] objects, int objectStart, int count) {
+ // BEGIN Android-added: Check arguments to throw correct exception.
if (0 > levelStart || levels.length <= levelStart) {
throw new IllegalArgumentException("Value levelStart " +
levelStart + " is out of range 0 to " +
@@ -388,6 +412,9 @@ public final class Bidi {
levelStart + " is out of range 0 to " +
(objects.length - objectStart));
}
+ // END Android-added: Check arguments to throw correct exception.
+
+ // Android-changed: use ICU Bidi class instead of BidiBase.
android.icu.text.Bidi.reorderVisually(levels, levelStart, objects, objectStart, count);
}
@@ -395,8 +422,10 @@ public final class Bidi {
* Display the bidi internal state, used in debugging.
*/
public String toString() {
+ // Android-changed: construct String representation from ICU Bidi object values.
return getClass().getName()
+ "[direction: " + bidiBase.getDirection() + " baseLevel: " + bidiBase.getBaseLevel()
+ " length: " + bidiBase.getLength() + " runs: " + bidiBase.getRunCount() + "]";
}
+
}
diff --git a/java/text/BreakIterator.java b/java/text/BreakIterator.java
index 6e7e0531..e9adafaf 100644
--- a/java/text/BreakIterator.java
+++ b/java/text/BreakIterator.java
@@ -424,6 +424,8 @@ public abstract class BreakIterator implements Cloneable
*/
public abstract void setText(CharacterIterator newText);
+ // Android-removed: Removed code related to BreakIteratorProvider support.
+
/**
* Returns a new <code>BreakIterator</code> instance
* for <a href="BreakIterator.html#word">word breaks</a>
@@ -528,7 +530,9 @@ public abstract class BreakIterator implements Cloneable
android.icu.text.BreakIterator.getSentenceInstance(locale));
}
- // Android-changed: Removed references to BreakIteratorProvider.
+ // Android-removed: Removed code related to BreakIteratorProvider support.
+
+ // Android-changed: Removed references to BreakIteratorProvider from JavaDoc.
/**
* Returns an array of all locales for which the
* <code>get*Instance</code> methods of this class can return
diff --git a/java/text/ChoiceFormat.java b/java/text/ChoiceFormat.java
index 1ac86279..3baaa90e 100644
--- a/java/text/ChoiceFormat.java
+++ b/java/text/ChoiceFormat.java
@@ -208,6 +208,7 @@ public class ChoiceFormat extends NumberFormat {
} else if (tempBuffer.equals("-\u221E")) {
startValue = Double.NEGATIVE_INFINITY;
} else {
+ // Android-changed: avoid object instantiation followed by unboxing.
startValue = Double.parseDouble(segments[0].toString());
}
} catch (Exception e) {
@@ -349,6 +350,7 @@ public class ChoiceFormat extends NumberFormat {
choiceFormats = Arrays.copyOf(formats, formats.length);
}
+ // Android-changed: Clarify that calling setChoices() changes what is returned here.
/**
* @return a copy of the {@code double[]} array supplied to the constructor or the most recent
* call to {@link #setChoices(double[], String[])}.
@@ -358,6 +360,7 @@ public class ChoiceFormat extends NumberFormat {
return newLimits;
}
+ // Android-changed: Clarify that calling setChoices() changes what is returned here.
/**
* @return a copy of the {@code String[]} array supplied to the constructor or the most recent
* call to {@link #setChoices(double[], String[])}.
diff --git a/java/text/CollationElementIterator.java b/java/text/CollationElementIterator.java
index ad31f4a4..fede747d 100644
--- a/java/text/CollationElementIterator.java
+++ b/java/text/CollationElementIterator.java
@@ -107,10 +107,15 @@ public final class CollationElementIterator
* Null order which indicates the end of string is reached by the
* cursor.
*/
+ // Android-changed: use ICU CollationElementIterator constant.
public final static int NULLORDER = android.icu.text.CollationElementIterator.NULLORDER;
+ // Android-removed: internal constructors.
+
+ // Android-added: ICU iterator to delegate to.
private android.icu.text.CollationElementIterator icuIterator;
+ // Android-added: internal constructor taking an ICU CollationElementIterator.
CollationElementIterator(android.icu.text.CollationElementIterator iterator) {
icuIterator = iterator;
}
@@ -121,6 +126,7 @@ public final class CollationElementIterator
*/
public void reset()
{
+ // Android-changed: delegate to ICU CollationElementIterator.
icuIterator.reset();
}
@@ -142,6 +148,7 @@ public final class CollationElementIterator
*/
public int next()
{
+ // Android-changed: delegate to ICU CollationElementIterator.
return icuIterator.next();
}
@@ -164,6 +171,7 @@ public final class CollationElementIterator
*/
public int previous()
{
+ // Android-changed: delegate to ICU CollationElementIterator.
return icuIterator.previous();
}
@@ -174,6 +182,7 @@ public final class CollationElementIterator
*/
public final static int primaryOrder(int order)
{
+ // Android-changed: delegate to ICU CollationElementIterator.
return android.icu.text.CollationElementIterator.primaryOrder(order);
}
/**
@@ -183,6 +192,7 @@ public final class CollationElementIterator
*/
public final static short secondaryOrder(int order)
{
+ // Android-changed: delegate to ICU CollationElementIterator.
return (short) android.icu.text.CollationElementIterator.secondaryOrder(order);
}
/**
@@ -192,6 +202,7 @@ public final class CollationElementIterator
*/
public final static short tertiaryOrder(int order)
{
+ // Android-changed: delegate to ICU CollationElementIterator.
return (short) android.icu.text.CollationElementIterator.tertiaryOrder(order);
}
@@ -213,6 +224,7 @@ public final class CollationElementIterator
@SuppressWarnings("deprecation") // getBeginIndex, getEndIndex and setIndex are deprecated
public void setOffset(int newOffset)
{
+ // Android-changed: delegate to ICU CollationElementIterator.
icuIterator.setOffset(newOffset);
}
@@ -232,9 +244,11 @@ public final class CollationElementIterator
*/
public int getOffset()
{
+ // Android-changed: delegate to ICU CollationElementIterator.
return icuIterator.getOffset();
}
+
/**
* Return the maximum length of any expansion sequences that end
* with the specified comparison order.
@@ -245,6 +259,7 @@ public final class CollationElementIterator
*/
public int getMaxExpansion(int order)
{
+ // Android-changed: delegate to ICU CollationElementIterator.
return icuIterator.getMaxExpansion(order);
}
@@ -256,6 +271,7 @@ public final class CollationElementIterator
*/
public void setText(String source)
{
+ // Android-changed: delegate to ICU CollationElementIterator.
icuIterator.setText(source);
}
@@ -267,6 +283,9 @@ public final class CollationElementIterator
*/
public void setText(CharacterIterator source)
{
+ // Android-changed: delegate to ICU CollationElementIterator.
icuIterator.setText(source);
}
+
+ // Android-removed: private helper methods and fields.
}
diff --git a/java/text/Collator.java b/java/text/Collator.java
index 01e7dc9c..ca3a220d 100644
--- a/java/text/Collator.java
+++ b/java/text/Collator.java
@@ -226,10 +226,9 @@ public abstract class Collator
* @see java.util.Locale
* @see java.util.ResourceBundle
*/
- public static synchronized
- Collator getInstance(Locale desiredLocale)
+ // Android-changed: Switched to ICU.
+ public static synchronized Collator getInstance(Locale desiredLocale)
{
- // Android-changed: Switched to ICU.
if (desiredLocale == null) {
throw new NullPointerException("locale == null");
}
@@ -302,6 +301,7 @@ public abstract class Collator
*/
public boolean equals(String source, String target)
{
+ // Android-changed: remove use of unnecessary EQUAL constant.
return (compare(source, target) == 0);
}
@@ -400,6 +400,7 @@ public abstract class Collator
return ICU.getAvailableCollatorLocales();
}
+ // BEGIN Android-added: conversion method for decompositionMode constants.
private int decompositionMode_Java_ICU(int mode) {
switch (mode) {
case Collator.CANONICAL_DECOMPOSITION:
@@ -422,7 +423,9 @@ public abstract class Collator
}
return javaMode;
}
+ // END Android-added: conversion method for decompositionMode constants.
+ // Android-changed: improve documentation.
/**
* Returns a new collator with the same decomposition mode and
* strength value as this collator.
@@ -484,9 +487,13 @@ public abstract class Collator
icuColl = android.icu.text.RuleBasedCollator.getInstance(Locale.getDefault());
}
+ // Android-added: ICU Collator this delegates to.
android.icu.text.Collator icuColl;
+ // Android-added: protected constructor taking a Collator.
Collator(android.icu.text.Collator icuColl) {
this.icuColl = icuColl;
}
+
+ // Android-removed: Fields and constants.
}
diff --git a/java/text/DateFormatSymbols.java b/java/text/DateFormatSymbols.java
index 96a966cd..305f6f2a 100644
--- a/java/text/DateFormatSymbols.java
+++ b/java/text/DateFormatSymbols.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -47,7 +47,6 @@ import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
-import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -142,6 +141,8 @@ public class DateFormatSymbols implements Serializable, Cloneable {
initializeData(locale);
}
+ // Android-removed: unused private DateFormatSymbols(boolean) constructor.
+
/**
* Era strings. For example: "AD" and "BC". An array of 2 strings,
* indexed by <code>Calendar.BC</code> and <code>Calendar.AD</code>.
@@ -227,10 +228,8 @@ public class DateFormatSymbols implements Serializable, Cloneable {
/**
* Unlocalized date-time pattern characters. For example: 'y', 'd', etc.
* All locales use the same these unlocalized pattern characters.
- *
- * Pretend to support 'L' and 'c' for now. It's meant for standalone weekday and
- * month names, but we just use the non-standalone versions for now.
*/
+ // Android-changed: Add 'c' (standalone day of week).
static final String patternChars = "GyMdkHmsSEDFwWahKzZYuXLc";
static final int PATTERN_ERA = 0; // G
@@ -256,6 +255,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
static final int PATTERN_ISO_DAY_OF_WEEK = 20; // u
static final int PATTERN_ISO_ZONE = 21; // X
static final int PATTERN_MONTH_STANDALONE = 22; // L
+ // Android-added: Constant for standalone day of week.
static final int PATTERN_STANDALONE_DAY_OF_WEEK = 23; // c
/**
@@ -281,6 +281,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
/* use serialVersionUID from JDK 1.1.4 for interoperability */
static final long serialVersionUID = -5987973545549424702L;
+ // BEGIN Android-added: Android specific serialization code.
// the internal serial version which says which version was written
// - 0 (default) for version up to JDK 1.1.4
// - 1 Android version that contains a whole bunch of new fields.
@@ -299,7 +300,9 @@ public class DateFormatSymbols implements Serializable, Cloneable {
* @since JDK1.1.4
*/
private int serialVersionOnStream = currentSerialVersion;
+ // END Android-added: Android specific serialization code.
+ // BEGIN Android-added: Support for tiny and standalone field names.
/**
* Tiny month strings; "J", "F", "M" etc.
*
@@ -355,6 +358,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
* @serial
*/
private String[] tinyStandAloneWeekdays;
+ // END Android-added: Support for tiny and standalone field names.
// Android-changed: Removed reference to DateFormatSymbolsProvider.
/**
@@ -411,6 +415,8 @@ public class DateFormatSymbols implements Serializable, Cloneable {
return getCachedInstance(locale);
}
+ // BEGIN Android-changed: Replace getProviderInstance() with getCachedInstance().
+ // Android removed support for DateFormatSymbolsProviders, but still caches DFS.
/**
* Returns a cached DateFormatSymbols if it's found in the
* cache. Otherwise, this method returns a newly cached instance
@@ -418,10 +424,10 @@ public class DateFormatSymbols implements Serializable, Cloneable {
*/
private static DateFormatSymbols getCachedInstance(Locale locale) {
SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale);
- DateFormatSymbols dfs = null;
+ DateFormatSymbols dfs;
if (ref == null || (dfs = ref.get()) == null) {
dfs = new DateFormatSymbols(locale);
- ref = new SoftReference<DateFormatSymbols>(dfs);
+ ref = new SoftReference<>(dfs);
SoftReference<DateFormatSymbols> x = cachedInstances.putIfAbsent(locale, ref);
if (x != null) {
DateFormatSymbols y = x.get();
@@ -435,6 +441,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
}
return dfs;
}
+ // END Android-changed: Replace getProviderInstance() with getCachedInstance().
/**
* Gets era strings. For example: "AD" and "BC".
@@ -455,6 +462,17 @@ public class DateFormatSymbols implements Serializable, Cloneable {
/**
* Gets month strings. For example: "January", "February", etc.
+ *
+ * <p>If the language requires different forms for formatting and
+ * stand-alone usages, this method returns month names in the
+ * formatting form. For example, the preferred month name for
+ * January in the Czech language is <em>ledna</em> in the
+ * formatting form, while it is <em>leden</em> in the stand-alone
+ * form. This method returns {@code "ledna"} in this case. Refer
+ * to the <a href="http://unicode.org/reports/tr35/#Calendar_Elements">
+ * Calendar Elements in the Unicode Locale Data Markup Language
+ * (LDML) specification</a> for more details.
+ *
* @return the month strings.
*/
public String[] getMonths() {
@@ -472,6 +490,17 @@ public class DateFormatSymbols implements Serializable, Cloneable {
/**
* Gets short month strings. For example: "Jan", "Feb", etc.
+ *
+ * <p>If the language requires different forms for formatting and
+ * stand-alone usages, This method returns short month names in
+ * the formatting form. For example, the preferred abbreviation
+ * for January in the Catalan language is <em>de gen.</em> in the
+ * formatting form, while it is <em>gen.</em> in the stand-alone
+ * form. This method returns {@code "de gen."} in this case. Refer
+ * to the <a href="http://unicode.org/reports/tr35/#Calendar_Elements">
+ * Calendar Elements in the Unicode Locale Data Markup Language
+ * (LDML) specification</a> for more details.
+ *
* @return the short month strings.
*/
public String[] getShortMonths() {
@@ -648,6 +677,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
cachedHashCode = 0;
}
+ // BEGIN Android-added: Support for tiny and standalone field names.
String[] getTinyMonths() {
return tinyMonths;
}
@@ -679,6 +709,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
String[] getTinyStandAloneWeekdays() {
return tinyStandAloneWeekdays;
}
+ // END Android-added: Support for tiny and standalone field names.
/**
* Overrides Cloneable
@@ -727,6 +758,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
DateFormatSymbols that = (DateFormatSymbols) obj;
+ // BEGIN Android-changed: Avoid populating zoneStrings just for the comparison, add fields.
if (!(Arrays.equals(eras, that.eras)
&& Arrays.equals(months, that.months)
&& Arrays.equals(shortMonths, that.shortMonths)
@@ -747,11 +779,11 @@ public class DateFormatSymbols implements Serializable, Cloneable {
&& that.localPatternChars == null)))) {
return false;
}
- // Android-changed: Avoid populating zoneStrings just for the comparison.
if (!isZoneStringsSet && !that.isZoneStringsSet && Objects.equals(locale, that.locale)) {
return true;
}
return Arrays.deepEquals(getZoneStringsWrapper(), that.getZoneStringsWrapper());
+ // END Android-changed: Avoid populating zoneStrings just for the comparison.
}
// =======================privates===============================
@@ -765,7 +797,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
* Cache to hold DateFormatSymbols instances per Locale.
*/
private static final ConcurrentMap<Locale, SoftReference<DateFormatSymbols>> cachedInstances
- = new ConcurrentHashMap<Locale, SoftReference<DateFormatSymbols>>(3);
+ = new ConcurrentHashMap<>(3);
private transient int lastZoneIndex = 0;
@@ -774,35 +806,43 @@ public class DateFormatSymbols implements Serializable, Cloneable {
*/
transient volatile int cachedHashCode = 0;
- private void initializeData(Locale desiredLocale) {
- locale = desiredLocale;
-
- // Copy values of a cached instance if any.
+ // Android-changed: update comment to describe local modification.
+ /**
+ * Initializes this DateFormatSymbols with the locale data. This method uses
+ * a cached DateFormatSymbols instance for the given locale if available. If
+ * there's no cached one, this method populates this objects fields from an
+ * appropriate LocaleData object. Note: zoneStrings isn't initialized in this method.
+ */
+ private void initializeData(Locale locale) {
SoftReference<DateFormatSymbols> ref = cachedInstances.get(locale);
DateFormatSymbols dfs;
+ // Android-changed: invert cache presence check to simplify code flow.
if (ref != null && (dfs = ref.get()) != null) {
copyMembers(dfs, this);
return;
}
+
+ // BEGIN Android-changed: Use ICU data and move cache handling to getCachedInstance().
locale = LocaleData.mapInvalidAndNullLocales(locale);
LocaleData localeData = LocaleData.get(locale);
+ this.locale = locale;
eras = localeData.eras;
-
- // Month names.
months = localeData.longMonthNames;
shortMonths = localeData.shortMonthNames;
-
ampms = localeData.amPm;
localPatternChars = patternChars;
- // Weekdays.
weekdays = localeData.longWeekdayNames;
shortWeekdays = localeData.shortWeekdayNames;
initializeSupplementaryData(localeData);
+ // END Android-changed: Use ICU data and move cache handling to getCachedInstance().
}
+ // Android-removed: toOneBasedArray(String[])
+
+ // BEGIN Android-added: initializeSupplementaryData(LocaleData) for tiny and standalone fields.
private void initializeSupplementaryData(LocaleData localeData) {
// Tiny weekdays and months.
tinyMonths = localeData.tinyMonthNames;
@@ -818,6 +858,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
shortStandAloneWeekdays = localeData.shortStandAloneWeekdayNames;
tinyStandAloneWeekdays = localeData.tinyStandAloneWeekdayNames;
}
+ // END Android-added: initializeSupplementaryData(LocaleData) for tiny and standalone fields.
/**
* Package private: used by SimpleDateFormat
@@ -866,14 +907,14 @@ public class DateFormatSymbols implements Serializable, Cloneable {
}
// BEGIN Android-changed: extract initialization of zoneStrings to separate method.
- private final synchronized String[][] internalZoneStrings() {
+ private synchronized String[][] internalZoneStrings() {
if (zoneStrings == null) {
zoneStrings = TimeZoneNames.getZoneStrings(locale);
}
return zoneStrings;
}
- private final String[][] getZoneStringsImpl(boolean needsCopy) {
+ private String[][] getZoneStringsImpl(boolean needsCopy) {
String[][] zoneStrings = internalZoneStrings();
// END Android-changed: extract initialization of zoneStrings to separate method.
@@ -895,12 +936,14 @@ public class DateFormatSymbols implements Serializable, Cloneable {
/**
* Clones all the data members from the source DateFormatSymbols to
- * the target DateFormatSymbols. This is only for subclasses.
+ * the target DateFormatSymbols.
+ *
* @param src the source DateFormatSymbols.
* @param dst the target DateFormatSymbols.
*/
private void copyMembers(DateFormatSymbols src, DateFormatSymbols dst)
{
+ dst.locale = src.locale;
dst.eras = Arrays.copyOf(src.eras, src.eras.length);
dst.months = Arrays.copyOf(src.months, src.months.length);
dst.shortMonths = Arrays.copyOf(src.shortMonths, src.shortMonths.length);
@@ -915,6 +958,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
dst.localPatternChars = src.localPatternChars;
dst.cachedHashCode = 0;
+ // BEGIN Android-added: Support for tiny and standalone field names.
dst.tinyMonths = src.tinyMonths;
dst.tinyWeekdays = src.tinyWeekdays;
@@ -925,8 +969,10 @@ public class DateFormatSymbols implements Serializable, Cloneable {
dst.standAloneWeekdays = src.standAloneWeekdays;
dst.shortStandAloneWeekdays = src.shortStandAloneWeekdays;
dst.tinyStandAloneWeekdays = src.tinyStandAloneWeekdays;
+ // END Android-added: Support for tiny and standalone field names.
}
+ // BEGIN Android-added: support reading non-Android serialized DFS.
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
@@ -937,6 +983,7 @@ public class DateFormatSymbols implements Serializable, Cloneable {
serialVersionOnStream = currentSerialVersion;
}
+ // END Android-added: support reading non-Android serialized DFS.
/**
* Write out the default serializable data, after ensuring the
diff --git a/java/text/DecimalFormat.java b/java/text/DecimalFormat.java
index 17d5d418..d2e8530c 100644
--- a/java/text/DecimalFormat.java
+++ b/java/text/DecimalFormat.java
@@ -381,6 +381,12 @@ import android.icu.math.MathContext;
*/
public class DecimalFormat extends NumberFormat {
+ // Android-note: This class is heavily modified from upstream OpenJDK.
+ // Android's version delegates most of its work to android.icu.text.DecimalFormat. This is done
+ // to avoid code duplication and to stay compatible with earlier releases that used ICU4C/ICU4J
+ // to implement DecimalFormat.
+
+ // Android-added: ICU DecimalFormat to delegate to.
private transient android.icu.text.DecimalFormat icuDecimalFormat;
/**
@@ -402,6 +408,7 @@ public class DecimalFormat extends NumberFormat {
public DecimalFormat() {
// Get the pattern for the default locale.
Locale def = Locale.getDefault(Locale.Category.FORMAT);
+ // BEGIN Android-changed: Use ICU LocaleData.
// try to get the pattern from the cache
String pattern = cachedLocaleData.get(def);
if (pattern == null) { /* cache miss */
@@ -410,8 +417,11 @@ public class DecimalFormat extends NumberFormat {
/* update cache */
cachedLocaleData.putIfAbsent(def, pattern);
}
- this.symbols = new DecimalFormatSymbols(def);
- init(pattern);
+ // END Android-changed: Use ICU LocaleData.
+ // Always applyPattern after the symbols are set
+ this.symbols = DecimalFormatSymbols.getInstance(def);
+ // Android-changed: use initPattern() instead of removed applyPattern(String, boolean).
+ initPattern(pattern);
}
@@ -435,8 +445,10 @@ public class DecimalFormat extends NumberFormat {
* @see java.text.NumberFormat#getPercentInstance
*/
public DecimalFormat(String pattern) {
- this.symbols = new DecimalFormatSymbols(Locale.getDefault(Locale.Category.FORMAT));
- init(pattern);
+ // Always applyPattern after the symbols are set
+ this.symbols = DecimalFormatSymbols.getInstance(Locale.getDefault(Locale.Category.FORMAT));
+ // Android-changed: use initPattern() instead of removed applyPattern(String, boolean).
+ initPattern(pattern);
}
@@ -464,45 +476,69 @@ public class DecimalFormat extends NumberFormat {
public DecimalFormat (String pattern, DecimalFormatSymbols symbols) {
// Always applyPattern after the symbols are set
this.symbols = (DecimalFormatSymbols)symbols.clone();
- init(pattern);
+ // Android-changed: use initPattern() instead of removed applyPattern(String, boolean).
+ initPattern(pattern);
}
- private void init(String pattern) {
+ // BEGIN Android-added: initPattern() and conversion methods between ICU and Java values.
+ /**
+ * Applies the pattern similarly to {@link #applyPattern(String)}, except it initializes
+ * {@link #icuDecimalFormat} in the process. This should only be called from constructors.
+ */
+ private void initPattern(String pattern) {
this.icuDecimalFormat = new android.icu.text.DecimalFormat(pattern,
symbols.getIcuDecimalFormatSymbols());
updateFieldsFromIcu();
}
/**
+ * Update local fields indicating maximum/minimum integer/fraction digit count from the ICU
+ * DecimalFormat. This needs to be called whenever a new pattern is applied.
+ */
+ private void updateFieldsFromIcu() {
+ // Imitate behaviour of ICU4C NumberFormat that Android used up to M.
+ // If the pattern doesn't enforce a different value (some exponential
+ // patterns do), then set the maximum integer digits to 2 billion.
+ if (icuDecimalFormat.getMaximumIntegerDigits() == DOUBLE_INTEGER_DIGITS) {
+ icuDecimalFormat.setMaximumIntegerDigits(2000000000);
+ }
+ maximumIntegerDigits = icuDecimalFormat.getMaximumIntegerDigits();
+ minimumIntegerDigits = icuDecimalFormat.getMinimumIntegerDigits();
+ maximumFractionDigits = icuDecimalFormat.getMaximumFractionDigits();
+ minimumFractionDigits = icuDecimalFormat.getMinimumFractionDigits();
+ }
+
+ /**
* Converts between field positions used by Java/ICU.
* @param fp The java.text.NumberFormat.Field field position
* @return The android.icu.text.NumberFormat.Field field position
*/
private static FieldPosition getIcuFieldPosition(FieldPosition fp) {
- if (fp.getFieldAttribute() == null) return fp;
+ Format.Field fieldAttribute = fp.getFieldAttribute();
+ if (fieldAttribute == null) return fp;
android.icu.text.NumberFormat.Field attribute;
- if (fp.getFieldAttribute() == Field.INTEGER) {
+ if (fieldAttribute == Field.INTEGER) {
attribute = android.icu.text.NumberFormat.Field.INTEGER;
- } else if (fp.getFieldAttribute() == Field.FRACTION) {
+ } else if (fieldAttribute == Field.FRACTION) {
attribute = android.icu.text.NumberFormat.Field.FRACTION;
- } else if (fp.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
+ } else if (fieldAttribute == Field.DECIMAL_SEPARATOR) {
attribute = android.icu.text.NumberFormat.Field.DECIMAL_SEPARATOR;
- } else if (fp.getFieldAttribute() == Field.EXPONENT_SYMBOL) {
+ } else if (fieldAttribute == Field.EXPONENT_SYMBOL) {
attribute = android.icu.text.NumberFormat.Field.EXPONENT_SYMBOL;
- } else if (fp.getFieldAttribute() == Field.EXPONENT_SIGN) {
+ } else if (fieldAttribute == Field.EXPONENT_SIGN) {
attribute = android.icu.text.NumberFormat.Field.EXPONENT_SIGN;
- } else if (fp.getFieldAttribute() == Field.EXPONENT) {
+ } else if (fieldAttribute == Field.EXPONENT) {
attribute = android.icu.text.NumberFormat.Field.EXPONENT;
- } else if (fp.getFieldAttribute() == Field.GROUPING_SEPARATOR) {
+ } else if (fieldAttribute == Field.GROUPING_SEPARATOR) {
attribute = android.icu.text.NumberFormat.Field.GROUPING_SEPARATOR;
- } else if (fp.getFieldAttribute() == Field.CURRENCY) {
+ } else if (fieldAttribute == Field.CURRENCY) {
attribute = android.icu.text.NumberFormat.Field.CURRENCY;
- } else if (fp.getFieldAttribute() == Field.PERCENT) {
+ } else if (fieldAttribute == Field.PERCENT) {
attribute = android.icu.text.NumberFormat.Field.PERCENT;
- } else if (fp.getFieldAttribute() == Field.PERMILLE) {
+ } else if (fieldAttribute == Field.PERMILLE) {
attribute = android.icu.text.NumberFormat.Field.PERMILLE;
- } else if (fp.getFieldAttribute() == Field.SIGN) {
+ } else if (fieldAttribute == Field.SIGN) {
attribute = android.icu.text.NumberFormat.Field.SIGN;
} else {
throw new IllegalArgumentException("Unexpected field position attribute type.");
@@ -521,41 +557,43 @@ public class DecimalFormat extends NumberFormat {
* @return Field converted to a java.text.NumberFormat.Field field.
*/
private static Field toJavaFieldAttribute(AttributedCharacterIterator.Attribute icuAttribute) {
- if (icuAttribute.getName().equals(Field.INTEGER.getName())) {
+ String name = icuAttribute.getName();
+ if (name.equals(Field.INTEGER.getName())) {
return Field.INTEGER;
}
- if (icuAttribute.getName().equals(Field.CURRENCY.getName())) {
+ if (name.equals(Field.CURRENCY.getName())) {
return Field.CURRENCY;
}
- if (icuAttribute.getName().equals(Field.DECIMAL_SEPARATOR.getName())) {
+ if (name.equals(Field.DECIMAL_SEPARATOR.getName())) {
return Field.DECIMAL_SEPARATOR;
}
- if (icuAttribute.getName().equals(Field.EXPONENT.getName())) {
+ if (name.equals(Field.EXPONENT.getName())) {
return Field.EXPONENT;
}
- if (icuAttribute.getName().equals(Field.EXPONENT_SIGN.getName())) {
+ if (name.equals(Field.EXPONENT_SIGN.getName())) {
return Field.EXPONENT_SIGN;
}
- if (icuAttribute.getName().equals(Field.EXPONENT_SYMBOL.getName())) {
+ if (name.equals(Field.EXPONENT_SYMBOL.getName())) {
return Field.EXPONENT_SYMBOL;
}
- if (icuAttribute.getName().equals(Field.FRACTION.getName())) {
+ if (name.equals(Field.FRACTION.getName())) {
return Field.FRACTION;
}
- if (icuAttribute.getName().equals(Field.GROUPING_SEPARATOR.getName())) {
+ if (name.equals(Field.GROUPING_SEPARATOR.getName())) {
return Field.GROUPING_SEPARATOR;
}
- if (icuAttribute.getName().equals(Field.SIGN.getName())) {
+ if (name.equals(Field.SIGN.getName())) {
return Field.SIGN;
}
- if (icuAttribute.getName().equals(Field.PERCENT.getName())) {
+ if (name.equals(Field.PERCENT.getName())) {
return Field.PERCENT;
}
- if (icuAttribute.getName().equals(Field.PERMILLE.getName())) {
+ if (name.equals(Field.PERMILLE.getName())) {
return Field.PERMILLE;
}
- throw new IllegalArgumentException("Unrecognized attribute: " + icuAttribute.getName());
- }
+ throw new IllegalArgumentException("Unrecognized attribute: " + name);
+ }
+ // END Android-added: initPattern() and conversion methods between ICU and Java values.
// Overrides
/**
@@ -614,13 +652,17 @@ public class DecimalFormat extends NumberFormat {
@Override
public StringBuffer format(double number, StringBuffer result,
FieldPosition fieldPosition) {
+ // BEGIN Android-changed: Use ICU.
FieldPosition icuFieldPosition = getIcuFieldPosition(fieldPosition);
icuDecimalFormat.format(number, result, icuFieldPosition);
fieldPosition.setBeginIndex(icuFieldPosition.getBeginIndex());
fieldPosition.setEndIndex(icuFieldPosition.getEndIndex());
return result;
+ // END Android-changed: Use ICU.
}
+ // Android-removed: private StringBuffer format(double, StringBuffer, FieldDelegate).
+
/**
* Format a long to produce a string.
* @param number The long to format
@@ -635,13 +677,17 @@ public class DecimalFormat extends NumberFormat {
@Override
public StringBuffer format(long number, StringBuffer result,
FieldPosition fieldPosition) {
+ // BEGIN Android-changed: Use ICU.
FieldPosition icuFieldPosition = getIcuFieldPosition(fieldPosition);
icuDecimalFormat.format(number, result, icuFieldPosition);
fieldPosition.setBeginIndex(icuFieldPosition.getBeginIndex());
fieldPosition.setEndIndex(icuFieldPosition.getEndIndex());
return result;
+ // END Android-changed: Use ICU.
}
+ // Android-removed: private StringBuffer format(long, StringBuffer, FieldDelegate).
+
/**
* Formats a BigDecimal to produce a string.
* @param number The BigDecimal to format
@@ -655,13 +701,17 @@ public class DecimalFormat extends NumberFormat {
*/
private StringBuffer format(BigDecimal number, StringBuffer result,
FieldPosition fieldPosition) {
+ // BEGIN Android-changed: Use ICU.
FieldPosition icuFieldPosition = getIcuFieldPosition(fieldPosition);
icuDecimalFormat.format(number, result, fieldPosition);
fieldPosition.setBeginIndex(icuFieldPosition.getBeginIndex());
fieldPosition.setEndIndex(icuFieldPosition.getEndIndex());
return result;
+ // END Android-changed: Use ICU.
}
+ // Android-removed: private StringBuffer format(BigDecimal, StringBuffer, FieldDelegate).
+
/**
* Format a BigInteger to produce a string.
* @param number The BigInteger to format
@@ -675,13 +725,17 @@ public class DecimalFormat extends NumberFormat {
*/
private StringBuffer format(BigInteger number, StringBuffer result,
FieldPosition fieldPosition) {
+ // BEGIN Android-changed: Use ICU.
FieldPosition icuFieldPosition = getIcuFieldPosition(fieldPosition);
icuDecimalFormat.format(number, result, fieldPosition);
fieldPosition.setBeginIndex(icuFieldPosition.getBeginIndex());
fieldPosition.setEndIndex(icuFieldPosition.getEndIndex());
return result;
+ // END Android-changed: Use ICU.
}
+ // Android-removed: private StringBuffer format(BigInteger, StringBuffer, FieldDelegate).
+
/**
* Formats an Object producing an <code>AttributedCharacterIterator</code>.
* You can use the returned <code>AttributedCharacterIterator</code>
@@ -703,6 +757,7 @@ public class DecimalFormat extends NumberFormat {
*/
@Override
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
+ // BEGIN Android-changed: Use ICU.
if (obj == null) {
throw new NullPointerException("object == null");
}
@@ -736,8 +791,13 @@ public class DecimalFormat extends NumberFormat {
}
return result.getIterator();
+ // END Android-changed: Use ICU.
}
+ // Android-removed: "fast-path formating logic for double" (sic).
+
+ // Android-removed: subformat(), append().
+
/**
* Parses text from a string to produce a <code>Number</code>.
* <p>
@@ -796,6 +856,7 @@ public class DecimalFormat extends NumberFormat {
*/
@Override
public Number parse(String text, ParsePosition pos) {
+ // BEGIN Android-changed: Use ICU.
// Return early if the parse position is bogus.
if (pos.index < 0 || pos.index >= text.length()) {
return null;
@@ -829,8 +890,11 @@ public class DecimalFormat extends NumberFormat {
return 0L;
}
return number;
+ // END Android-changed: Use ICU.
}
+ // Android-removed: STATUS_* constants, multiplier fields and methods and subparse(String, ...).
+
/**
* Returns a copy of the decimal format symbols, which is generally not
* changed by the programmer or user.
@@ -838,6 +902,7 @@ public class DecimalFormat extends NumberFormat {
* @see java.text.DecimalFormatSymbols
*/
public DecimalFormatSymbols getDecimalFormatSymbols() {
+ // Android-changed: Use ICU.
return DecimalFormatSymbols.fromIcuInstance(icuDecimalFormat.getDecimalFormatSymbols());
}
@@ -852,6 +917,7 @@ public class DecimalFormat extends NumberFormat {
try {
// don't allow multiple references
symbols = (DecimalFormatSymbols) newSymbols.clone();
+ // Android-changed: Use ICU.
icuDecimalFormat.setDecimalFormatSymbols(symbols.getIcuDecimalFormatSymbols());
} catch (Exception foo) {
// should never happen
@@ -865,6 +931,7 @@ public class DecimalFormat extends NumberFormat {
* @return the positive prefix
*/
public String getPositivePrefix () {
+ // Android-changed: Use ICU.
return icuDecimalFormat.getPositivePrefix();
}
@@ -875,9 +942,12 @@ public class DecimalFormat extends NumberFormat {
* @param newValue the new positive prefix
*/
public void setPositivePrefix (String newValue) {
+ // Android-changed: Use ICU.
icuDecimalFormat.setPositivePrefix(newValue);
}
+ // Android-removed: private helper getPositivePrefixFieldPositions().
+
/**
* Get the prefix.
* <P>Examples: -123, ($123) (with negative suffix), sFr-123
@@ -885,6 +955,7 @@ public class DecimalFormat extends NumberFormat {
* @return the negative prefix
*/
public String getNegativePrefix () {
+ // Android-changed: Use ICU.
return icuDecimalFormat.getNegativePrefix();
}
@@ -895,9 +966,12 @@ public class DecimalFormat extends NumberFormat {
* @param newValue the new negative prefix
*/
public void setNegativePrefix (String newValue) {
+ // Android-changed: Use ICU.
icuDecimalFormat.setNegativePrefix(newValue);
}
+ // Android-removed: private helper getNegativePrefixFieldPositions().
+
/**
* Get the positive suffix.
* <P>Example: 123%
@@ -905,6 +979,7 @@ public class DecimalFormat extends NumberFormat {
* @return the positive suffix
*/
public String getPositiveSuffix () {
+ // Android-changed: Use ICU.
return icuDecimalFormat.getPositiveSuffix();
}
@@ -915,9 +990,12 @@ public class DecimalFormat extends NumberFormat {
* @param newValue the new positive suffix
*/
public void setPositiveSuffix (String newValue) {
+ // Android-changed: Use ICU.
icuDecimalFormat.setPositiveSuffix(newValue);
}
+ // Android-removed: private helper getPositiveSuffixFieldPositions().
+
/**
* Get the negative suffix.
* <P>Examples: -123%, ($123) (with positive suffixes)
@@ -925,6 +1003,7 @@ public class DecimalFormat extends NumberFormat {
* @return the negative suffix
*/
public String getNegativeSuffix () {
+ // Android-changed: Use ICU.
return icuDecimalFormat.getNegativeSuffix();
}
@@ -935,9 +1014,12 @@ public class DecimalFormat extends NumberFormat {
* @param newValue the new negative suffix
*/
public void setNegativeSuffix (String newValue) {
+ // Android-changed: Use ICU.
icuDecimalFormat.setNegativeSuffix(newValue);
}
+ // Android-removed: private helper getNegativeSuffixFieldPositions().
+
/**
* Gets the multiplier for use in percent, per mille, and similar
* formats.
@@ -946,6 +1028,7 @@ public class DecimalFormat extends NumberFormat {
* @see #setMultiplier(int)
*/
public int getMultiplier () {
+ // Android-changed: Use ICU.
return icuDecimalFormat.getMultiplier();
}
@@ -968,6 +1051,27 @@ public class DecimalFormat extends NumberFormat {
}
/**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setGroupingUsed(boolean newValue) {
+ // Android-changed: Use ICU.
+ icuDecimalFormat.setGroupingUsed(newValue);
+ // Android-removed: fast path related code.
+ // fastPathCheckNeeded = true;
+ }
+
+ // BEGIN Android-added: isGroupingUsed() override delegating to ICU.
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isGroupingUsed() {
+ return icuDecimalFormat.isGroupingUsed();
+ }
+ // END Android-added: isGroupingUsed() override delegating to ICU.
+
+ /**
* Return the grouping size. Grouping size is the number of digits between
* grouping separators in the integer portion of a number. For example,
* in the number "123,456.78", the grouping size is 3.
@@ -978,6 +1082,7 @@ public class DecimalFormat extends NumberFormat {
* @see java.text.DecimalFormatSymbols#getGroupingSeparator
*/
public int getGroupingSize () {
+ // Android-changed: Use ICU.
return icuDecimalFormat.getGroupingSize();
}
@@ -994,26 +1099,10 @@ public class DecimalFormat extends NumberFormat {
* @see java.text.DecimalFormatSymbols#setGroupingSeparator
*/
public void setGroupingSize (int newValue) {
+ // Android-changed: Use ICU.
icuDecimalFormat.setGroupingSize(newValue);
- }
-
- /**
- * Returns true if grouping is used in this format. For example, in the
- * English locale, with grouping on, the number 1234567 might be formatted
- * as "1,234,567". The grouping separator as well as the size of each group
- * is locale dependant and is determined by sub-classes of NumberFormat.
- * @see #setGroupingUsed
- */
- public boolean isGroupingUsed() {
- return icuDecimalFormat.isGroupingUsed();
- }
-
- /**
- * Set whether or not grouping will be used in this format.
- * @see #isGroupingUsed
- */
- public void setGroupingUsed(boolean newValue) {
- icuDecimalFormat.setGroupingUsed(newValue);
+ // Android-removed: fast path related code.
+ // fastPathCheckNeeded = true;
}
/**
@@ -1025,6 +1114,7 @@ public class DecimalFormat extends NumberFormat {
* {@code false} otherwise
*/
public boolean isDecimalSeparatorAlwaysShown() {
+ // Android-changed: Use ICU.
return icuDecimalFormat.isDecimalSeparatorAlwaysShown();
}
@@ -1037,6 +1127,7 @@ public class DecimalFormat extends NumberFormat {
* {@code false} otherwise
*/
public void setDecimalSeparatorAlwaysShown(boolean newValue) {
+ // Android-changed: Use ICU.
icuDecimalFormat.setDecimalSeparatorAlwaysShown(newValue);
}
@@ -1050,6 +1141,7 @@ public class DecimalFormat extends NumberFormat {
* @since 1.5
*/
public boolean isParseBigDecimal() {
+ // Android-changed: Use ICU.
return icuDecimalFormat.isParseBigDecimal();
}
@@ -1063,35 +1155,35 @@ public class DecimalFormat extends NumberFormat {
* @since 1.5
*/
public void setParseBigDecimal(boolean newValue) {
+ // Android-changed: Use ICU.
icuDecimalFormat.setParseBigDecimal(newValue);
}
+ // BEGIN Android-added: setParseIntegerOnly()/isParseIntegerOnly() overrides delegating to ICU.
/**
- * Sets whether or not numbers should be parsed as integers only.
- * @see #isParseIntegerOnly
+ * {@inheritDoc}
*/
- public void setParseIntegerOnly(boolean value) {
- super.setParseIntegerOnly(value);
- icuDecimalFormat.setParseIntegerOnly(value);
+ @Override
+ public boolean isParseIntegerOnly() {
+ return icuDecimalFormat.isParseIntegerOnly();
}
/**
- * Returns true if this format will parse numbers as integers only.
- * For example in the English locale, with ParseIntegerOnly true, the
- * string "1234." would be parsed as the integer value 1234 and parsing
- * would stop at the "." character. Of course, the exact format accepted
- * by the parse operation is locale dependant and determined by sub-classes
- * of NumberFormat.
+ * {@inheritDoc}
*/
- public boolean isParseIntegerOnly() {
- return icuDecimalFormat.isParseIntegerOnly();
+ @Override
+ public void setParseIntegerOnly(boolean value) {
+ super.setParseIntegerOnly(value);
+ icuDecimalFormat.setParseIntegerOnly(value);
}
+ // END Android-added: setParseIntegerOnly()/isParseIntegerOnly() overrides delegating to ICU.
/**
* Standard override; no change in semantics.
*/
@Override
public Object clone() {
+ // BEGIN Android-changed: Use ICU, remove fast path related code.
try {
DecimalFormat other = (DecimalFormat) super.clone();
other.icuDecimalFormat = (android.icu.text.DecimalFormat) icuDecimalFormat.clone();
@@ -1100,8 +1192,10 @@ public class DecimalFormat extends NumberFormat {
} catch (Exception e) {
throw new InternalError();
}
+ // END Android-changed: Use ICU, remove fast path related code.
}
+ // BEGIN Android-changed: re-implement equals() using ICU fields.
/**
* Overrides equals
*/
@@ -1130,12 +1224,14 @@ public class DecimalFormat extends NumberFormat {
}
return other.getRoundingIncrement() == null;
}
+ // END Android-changed: re-implement equals() using ICU fields.
/**
* Overrides hashCode
*/
@Override
public int hashCode() {
+ // Android-changed: use getPositivePrefix() instead of positivePrefix field.
return super.hashCode() * 37 + getPositivePrefix().hashCode();
// just enough fields for a reasonable distribution
}
@@ -1148,6 +1244,7 @@ public class DecimalFormat extends NumberFormat {
* @see #applyPattern
*/
public String toPattern() {
+ // Android-changed: use ICU.
return icuDecimalFormat.toPattern();
}
@@ -1159,9 +1256,12 @@ public class DecimalFormat extends NumberFormat {
* @see #applyPattern
*/
public String toLocalizedPattern() {
+ // Android-changed: use ICU.
return icuDecimalFormat.toLocalizedPattern();
}
+ // Android-removed: private helper methods expandAffixes(), expandAffix(), toPattern(boolean).
+
/**
* Apply the given pattern to this Format object. A pattern is a
* short-hand specification for the various formatting properties.
@@ -1185,11 +1285,11 @@ public class DecimalFormat extends NumberFormat {
* @exception IllegalArgumentException if the given pattern is invalid.
*/
public void applyPattern(String pattern) {
+ // Android-changed: use ICU.
icuDecimalFormat.applyPattern(pattern);
updateFieldsFromIcu();
}
-
/**
* Apply the given pattern to this Format object. The pattern
* is assumed to be in a localized notation. A pattern is a
@@ -1214,22 +1314,12 @@ public class DecimalFormat extends NumberFormat {
* @exception IllegalArgumentException if the given pattern is invalid.
*/
public void applyLocalizedPattern(String pattern) {
+ // Android-changed: use ICU.
icuDecimalFormat.applyLocalizedPattern(pattern);
updateFieldsFromIcu();
}
- private void updateFieldsFromIcu() {
- // Imitate behaviour of ICU4C NumberFormat that Android used up to M.
- // If the pattern doesn't enforce a different value (some exponential
- // patterns do), then set the maximum integer digits to 2 billion.
- if (icuDecimalFormat.getMaximumIntegerDigits() == DOUBLE_INTEGER_DIGITS) {
- icuDecimalFormat.setMaximumIntegerDigits(2000000000);
- }
- maximumIntegerDigits = icuDecimalFormat.getMaximumIntegerDigits();
- minimumIntegerDigits = icuDecimalFormat.getMinimumIntegerDigits();
- maximumFractionDigits = icuDecimalFormat.getMaximumFractionDigits();
- minimumFractionDigits = icuDecimalFormat.getMinimumFractionDigits();
- }
+ // Android-removed: applyPattern(String, boolean) as apply[Localized]Pattern calls ICU directly.
/**
* Sets the maximum number of digits allowed in the integer portion of a
@@ -1249,7 +1339,10 @@ public class DecimalFormat extends NumberFormat {
super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ?
DOUBLE_INTEGER_DIGITS : minimumIntegerDigits);
}
+ // Android-changed: use ICU.
icuDecimalFormat.setMaximumIntegerDigits(getMaximumIntegerDigits());
+ // Android-removed: fast path related code.
+ // fastPathCheckNeeded = true;
}
/**
@@ -1270,7 +1363,10 @@ public class DecimalFormat extends NumberFormat {
super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ?
DOUBLE_INTEGER_DIGITS : maximumIntegerDigits);
}
+ // Android-changed: use ICU.
icuDecimalFormat.setMinimumIntegerDigits(getMinimumIntegerDigits());
+ // Android-removed: fast path related code.
+ // fastPathCheckNeeded = true;
}
/**
@@ -1291,7 +1387,10 @@ public class DecimalFormat extends NumberFormat {
super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ?
DOUBLE_FRACTION_DIGITS : minimumFractionDigits);
}
+ // Android-changed: use ICU.
icuDecimalFormat.setMaximumFractionDigits(getMaximumFractionDigits());
+ // Android-removed: fast path related code.
+ // fastPathCheckNeeded = true;
}
/**
@@ -1312,7 +1411,10 @@ public class DecimalFormat extends NumberFormat {
super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ?
DOUBLE_FRACTION_DIGITS : maximumFractionDigits);
}
+ // Android-changed: use ICU.
icuDecimalFormat.setMinimumFractionDigits(getMinimumFractionDigits());
+ // Android-removed: fast path related code.
+ // fastPathCheckNeeded = true;
}
/**
@@ -1396,6 +1498,7 @@ public class DecimalFormat extends NumberFormat {
*/
@Override
public void setCurrency(Currency currency) {
+ // BEGIN Android-changed: use ICU.
// Set the international currency symbol, and currency symbol on the DecimalFormatSymbols
// object and tell ICU to use that.
if (currency != symbols.getCurrency()
@@ -1407,6 +1510,9 @@ public class DecimalFormat extends NumberFormat {
icuDecimalFormat.setMinimumFractionDigits(minimumFractionDigits);
icuDecimalFormat.setMaximumFractionDigits(maximumFractionDigits);
}
+ // END Android-changed: use ICU.
+ // Android-removed: fast path related code.
+ // fastPathCheckNeeded = true;
}
/**
@@ -1421,6 +1527,7 @@ public class DecimalFormat extends NumberFormat {
return roundingMode;
}
+ // Android-added: convertRoundingMode() to convert between Java and ICU RoundingMode enums.
private static int convertRoundingMode(RoundingMode rm) {
switch (rm) {
case UP:
@@ -1458,10 +1565,16 @@ public class DecimalFormat extends NumberFormat {
}
this.roundingMode = roundingMode;
-
+ // Android-changed: use ICU.
icuDecimalFormat.setRoundingMode(convertRoundingMode(roundingMode));
+ // Android-removed: fast path related code.
+ // fastPathCheckNeeded = true;
}
+ // BEGIN Android-added: 7u40 version of adjustForCurrencyDefaultFractionDigits().
+ // This method was removed in OpenJDK 8 in favor of doing equivalent work in the provider. Since
+ // Android removed support for providers for NumberFormat we keep this method around as an
+ // "Android addition".
/**
* Adjusts the minimum and maximum fraction digits to values that
* are reasonable for the currency's default fraction digits.
@@ -1490,9 +1603,9 @@ public class DecimalFormat extends NumberFormat {
}
}
}
+ // END Android-added: Upstream code from earlier OpenJDK release.
- private static final int currentSerialVersion = 4;
-
+ // BEGIN Android-added: Custom serialization code for compatibility with RI serialization.
// the fields list to be serialized
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("positivePrefix", String.class),
@@ -1545,6 +1658,7 @@ public class DecimalFormat extends NumberFormat {
fields.put("serialVersionOnStream", currentSerialVersion);
stream.writeFields();
}
+ // BEGIN Android-added: Custom serialization code for compatibility with RI serialization.
/**
* Reads the default serializable fields from the stream and performs
@@ -1589,12 +1703,13 @@ public class DecimalFormat extends NumberFormat {
* literal values. This is exactly what we want, since that corresponds to
* the pre-version-2 behavior.
*/
+ // BEGIN Android-added: Custom serialization code for compatibility with RI serialization.
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = stream.readFields();
this.symbols = (DecimalFormatSymbols) fields.get("symbols", null);
- init("#");
+ initPattern("#");
// Calling a setter method on an ICU DecimalFormat object will change the object's internal
// state, even if the value set is the same as the default value (ICU Ticket #13266).
@@ -1683,11 +1798,14 @@ public class DecimalFormat extends NumberFormat {
setMinimumFractionDigits(super.getMinimumFractionDigits());
}
}
+ // END Android-added: Custom serialization code for compatibility with RI serialization.
//----------------------------------------------------------------------
// INSTANCE VARIABLES
//----------------------------------------------------------------------
+ // Android-removed: various fields now stored in icuDecimalFormat.
+
/**
* The <code>DecimalFormatSymbols</code> object used by this format.
* It contains the symbols used to format numbers, e.g. the grouping separator,
@@ -1697,7 +1815,9 @@ public class DecimalFormat extends NumberFormat {
* @see #setDecimalFormatSymbols
* @see java.text.DecimalFormatSymbols
*/
- private DecimalFormatSymbols symbols;
+ private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols();
+
+ // Android-removed: useExponentialNotation, *FieldPositions, minExponentDigits.
/**
* The maximum number of digits allowed in the integer portion of a
@@ -1709,7 +1829,8 @@ public class DecimalFormat extends NumberFormat {
* @see #getMaximumIntegerDigits
* @since 1.5
*/
- private int maximumIntegerDigits;
+ // Android-changed: removed initialisation.
+ private int maximumIntegerDigits /* = super.getMaximumIntegerDigits() */;
/**
* The minimum number of digits allowed in the integer portion of a
@@ -1721,7 +1842,8 @@ public class DecimalFormat extends NumberFormat {
* @see #getMinimumIntegerDigits
* @since 1.5
*/
- private int minimumIntegerDigits;
+ // Android-changed: removed initialisation.
+ private int minimumIntegerDigits /* = super.getMinimumIntegerDigits() */;
/**
* The maximum number of digits allowed in the fractional portion of a
@@ -1733,7 +1855,8 @@ public class DecimalFormat extends NumberFormat {
* @see #getMaximumFractionDigits
* @since 1.5
*/
- private int maximumFractionDigits;
+ // Android-changed: removed initialisation.
+ private int maximumFractionDigits /* = super.getMaximumFractionDigits() */;
/**
* The minimum number of digits allowed in the fractional portion of a
@@ -1745,7 +1868,8 @@ public class DecimalFormat extends NumberFormat {
* @see #getMinimumFractionDigits
* @since 1.5
*/
- private int minimumFractionDigits;
+ // Android-changed: removed initialisation.
+ private int minimumFractionDigits /* = super.getMinimumFractionDigits() */;
/**
* The {@link java.math.RoundingMode} used in this DecimalFormat.
@@ -1755,12 +1879,20 @@ public class DecimalFormat extends NumberFormat {
*/
private RoundingMode roundingMode = RoundingMode.HALF_EVEN;
+ // Android-removed: FastPathData, isFastPath, fastPathCheckNeeded and fastPathData.
+
+ //----------------------------------------------------------------------
+ static final int currentSerialVersion = 4;
+
+ // Android-removed: serialVersionOnStream.
//----------------------------------------------------------------------
// CONSTANTS
//----------------------------------------------------------------------
+ // Android-removed: Fast-Path for double Constants, various constants.
+
// Upper limit on integer and fraction digits for a Java double
static final int DOUBLE_INTEGER_DIGITS = 309;
static final int DOUBLE_FRACTION_DIGITS = 340;
@@ -1772,9 +1904,10 @@ public class DecimalFormat extends NumberFormat {
// Proclaim JDK 1.1 serial compatibility.
static final long serialVersionUID = 864413376551465018L;
+ // Android-added: cachedLocaleData for caching default number format pattern per locale.
/**
* Cache to hold the NumberPattern of a Locale.
*/
private static final ConcurrentMap<Locale, String> cachedLocaleData
- = new ConcurrentHashMap<Locale, String>(3);
+ = new ConcurrentHashMap<>(3);
}
diff --git a/java/text/DecimalFormatSymbols.java b/java/text/DecimalFormatSymbols.java
index 2acb128b..a9f11c8c 100644
--- a/java/text/DecimalFormatSymbols.java
+++ b/java/text/DecimalFormatSymbols.java
@@ -66,8 +66,7 @@ import libcore.icu.LocaleData;
public class DecimalFormatSymbols implements Cloneable, Serializable {
- // Android-changed: Removed reference to DecimalFormatSymbolsProvider but suggested
- // getInstance() be used instead in case Android supports it in future.
+ // Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
/**
* Create a DecimalFormatSymbols object for the default
* {@link java.util.Locale.Category#FORMAT FORMAT} locale.
@@ -83,8 +82,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
initialize( Locale.getDefault(Locale.Category.FORMAT) );
}
- // Android-changed: Removed reference to DecimalFormatSymbolsProvider but suggested
- // getInstance() be used instead in case Android supports it in future.
+ // Android-changed: Removed reference to DecimalFormatSymbolsProvider, suggested getInstance().
/**
* Create a DecimalFormatSymbols object for the given locale.
* It is recommended that the {@link #getInstance(Locale) getInstance} method is used
@@ -175,6 +173,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setZeroDigit(char zeroDigit) {
this.zeroDigit = zeroDigit;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -194,6 +193,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setGroupingSeparator(char groupingSeparator) {
this.groupingSeparator = groupingSeparator;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -213,6 +213,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setDecimalSeparator(char decimalSeparator) {
this.decimalSeparator = decimalSeparator;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -232,6 +233,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setPerMill(char perMill) {
this.perMill = perMill;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -244,6 +246,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
return percent;
}
+ // Android-added: getPercentString() for percent signs longer than one char.
/**
* Gets the string used for percent sign. Different for Arabic, etc.
*
@@ -260,6 +263,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setPercent(char percent) {
this.percent = percent;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -279,6 +283,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setDigit(char digit) {
this.digit = digit;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -300,6 +305,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setPatternSeparator(char patternSeparator) {
this.patternSeparator = patternSeparator;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -321,6 +327,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setInfinity(String infinity) {
this.infinity = infinity;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -342,6 +349,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setNaN(String NaN) {
this.NaN = NaN;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -357,6 +365,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
}
+ // Android-added: getPercentString() for percent signs longer than one char.
/**
* Gets the string used to represent minus sign. If no explicit
* negative format is specified, one is formed by prefixing
@@ -377,6 +386,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
public void setMinusSign(char minusSign) {
this.minusSign = minusSign;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -402,6 +412,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
public void setCurrencySymbol(String currency)
{
currencySymbol = currency;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -440,10 +451,12 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
if (currencyCode != null) {
try {
currency = Currency.getInstance(currencyCode);
+ // Android-changed: get currencySymbol for locale.
currencySymbol = currency.getSymbol(locale);
} catch (IllegalArgumentException e) {
}
}
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -478,6 +491,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
this.currency = currency;
intlCurrencySymbol = currency.getCurrencyCode();
currencySymbol = currency.getSymbol(locale);
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -502,6 +516,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
public void setMonetaryDecimalSeparator(char sep)
{
monetarySeparator = sep;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -535,6 +550,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
void setExponentialSymbol(char exp)
{
exponential = exp;
+ // Android-added: reset cachedIcuDFS.
cachedIcuDFS = null;
}
@@ -608,6 +624,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
int result = zeroDigit;
result = result * 37 + groupingSeparator;
result = result * 37 + decimalSeparator;
+ // BEGIN Android-added: more fields in hashcode calculation.
result = result * 37 + percent;
result = result * 37 + perMill;
result = result * 37 + digit;
@@ -621,6 +638,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
result = result * 37 + monetarySeparator;
result = result * 37 + exponentialSeparator.hashCode();
result = result * 37 + locale.hashCode();
+ // END Android-added: more fields in hashcode calculation.
return result;
}
@@ -666,7 +684,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
minusSign = maybeStripMarkers(numberElements[6], '-');
exponential = numberElements[7].charAt(0);
exponentialSeparator = numberElements[7]; //string representation new since 1.6
- perMill = numberElements[8].charAt(0);
+ perMill = maybeStripMarkers(numberElements[8], '\u2030');
infinity = numberElements[9];
NaN = numberElements[10];
@@ -674,7 +692,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
// Check for empty country string separately because it's a valid
// country ID for Locale (and used for the C locale), but not a valid
// ISO 3166 country code, and exceptions are expensive.
- if (!"".equals(locale.getCountry())) {
+ if (locale.getCountry().length() > 0) {
try {
currency = Currency.getInstance(locale);
} catch (IllegalArgumentException e) {
@@ -689,6 +707,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
currencySymbol = currency.getSymbol(locale);
data[1] = intlCurrencySymbol;
data[2] = currencySymbol;
+ // Android-added: update cache when necessary.
needCacheUpdate = true;
}
} else {
@@ -705,6 +724,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
// If that changes, add a new entry to NumberElements.
monetarySeparator = decimalSeparator;
+ // Android-added: update cache when necessary.
if (needCacheUpdate) {
cachedLocaleData.putIfAbsent(locale, data);
}
@@ -743,6 +763,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
return fallback;
}
+ // BEGIN Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
/**
* Convert an instance of this class to the ICU version so that it can be used with ICU4J.
* @hide
@@ -753,6 +774,9 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
}
cachedIcuDFS = new android.icu.text.DecimalFormatSymbols(this.locale);
+ // Do not localize plus sign. See "Special Pattern Characters" section in DecimalFormat.
+ // http://b/67034519
+ cachedIcuDFS.setPlusSign('+');
cachedIcuDFS.setZeroDigit(zeroDigit);
cachedIcuDFS.setDigit(digit);
cachedIcuDFS.setDecimalSeparator(decimalSeparator);
@@ -763,6 +787,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
cachedIcuDFS.setMonetaryGroupingSeparator(groupingSeparator);
cachedIcuDFS.setPatternSeparator(patternSeparator);
cachedIcuDFS.setPercent(percent);
+ cachedIcuDFS.setPerMill(perMill);
cachedIcuDFS.setMonetaryDecimalSeparator(monetarySeparator);
cachedIcuDFS.setMinusSign(minusSign);
cachedIcuDFS.setInfinity(infinity);
@@ -816,8 +841,9 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
result.setCurrencySymbol(dfs.getCurrencySymbol());
return result;
}
+ // END Android-added: getIcuDecimalFormatSymbols() and fromIcuInstance().
-
+ // BEGIN Android-added: Android specific serialization code.
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("currencySymbol", String.class),
new ObjectStreamField("decimalSeparator", char.class),
@@ -867,6 +893,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
fields.put("percentStr", getPercentString());
stream.writeFields();
}
+ // END Android-added: Android specific serialization code.
/**
* Reads the default serializable fields, provides default values for objects
@@ -885,7 +912,9 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*
* @since JDK 1.1.6
*/
- private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+ private void readObject(ObjectInputStream stream)
+ throws IOException, ClassNotFoundException {
+ // BEGIN Android-changed: Android specific serialization code.
ObjectInputStream.GetField fields = stream.readFields();
final int serialVersionOnStream = fields.get("serialVersionOnStream", 0);
currencySymbol = (String) fields.get("currencySymbol", "");
@@ -939,6 +968,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
} catch (IllegalArgumentException e) {
currency = null;
}
+ // END Android-changed: Android specific serialization code.
}
/**
@@ -1109,11 +1139,18 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
*/
private int serialVersionOnStream = currentSerialVersion;
+ // BEGIN Android-added: cache for locale data and cachedIcuDFS.
/**
* cache to hold the NumberElements and the Currency
* of a Locale.
*/
- private static final ConcurrentHashMap<Locale, Object[]> cachedLocaleData = new ConcurrentHashMap<Locale, Object[]>(3);
+ private static final ConcurrentHashMap<Locale, Object[]> cachedLocaleData = new ConcurrentHashMap<>(3);
+ /**
+ * Lazily created cached instance of an ICU DecimalFormatSymbols that's equivalent to this one.
+ * This field is reset to null whenever any of the relevant fields of this class are modified
+ * and will be re-created by {@link #getIcuDecimalFormatSymbols()} as necessary.
+ */
private transient android.icu.text.DecimalFormatSymbols cachedIcuDFS = null;
+ // END Android-added: cache for locale data and cachedIcuDFS.
}
diff --git a/java/text/MessageFormat.java b/java/text/MessageFormat.java
index d7431ef0..b9008247 100644
--- a/java/text/MessageFormat.java
+++ b/java/text/MessageFormat.java
@@ -690,6 +690,7 @@ public class MessageFormat extends Format {
* larger than the number of format elements in the pattern string
*/
public void setFormat(int formatElementIndex, Format newFormat) {
+ // Android-added: prevent setting unused formatters.
if (formatElementIndex > maxOffset) {
throw new ArrayIndexOutOfBoundsException(maxOffset, formatElementIndex);
}
diff --git a/java/text/Normalizer.java b/java/text/Normalizer.java
index 4c551c31..8c22547f 100644
--- a/java/text/Normalizer.java
+++ b/java/text/Normalizer.java
@@ -106,8 +106,7 @@ package java.text;
*/
public final class Normalizer {
- private Normalizer() {
- }
+ private Normalizer() {};
/**
* This enum provides constants of the four Unicode normalization forms
@@ -118,6 +117,7 @@ public final class Normalizer {
*
* @since 1.6
*/
+ // BEGIN Android-changed: remove static modifier and add mapping to equivalent ICU values.
public enum Form {
/**
@@ -146,41 +146,42 @@ public final class Normalizer {
this.icuMode = icuMode;
}
}
+ // END Android-changed: remove static modifier and add mapping to equivalent ICU values.
/**
* Normalize a sequence of char values.
* The sequence will be normalized according to the specified normalization
* from.
- *
- * @param src The sequence of char values to normalize.
- * @param form The normalization form; one of
- * {@link java.text.Normalizer.Form#NFC},
- * {@link java.text.Normalizer.Form#NFD},
- * {@link java.text.Normalizer.Form#NFKC},
- * {@link java.text.Normalizer.Form#NFKD}
+ * @param src The sequence of char values to normalize.
+ * @param form The normalization form; one of
+ * {@link java.text.Normalizer.Form#NFC},
+ * {@link java.text.Normalizer.Form#NFD},
+ * {@link java.text.Normalizer.Form#NFKC},
+ * {@link java.text.Normalizer.Form#NFKD}
* @return The normalized String
* @throws NullPointerException If <code>src</code> or <code>form</code>
- * is null.
+ * is null.
*/
public static String normalize(CharSequence src, Form form) {
+ // Android-changed: Switched to ICU.
return android.icu.text.Normalizer.normalize(src.toString(), form.icuMode);
}
/**
* Determines if the given sequence of char values is normalized.
- *
- * @param src The sequence of char values to be checked.
- * @param form The normalization form; one of
- * {@link java.text.Normalizer.Form#NFC},
- * {@link java.text.Normalizer.Form#NFD},
- * {@link java.text.Normalizer.Form#NFKC},
- * {@link java.text.Normalizer.Form#NFKD}
+ * @param src The sequence of char values to be checked.
+ * @param form The normalization form; one of
+ * {@link java.text.Normalizer.Form#NFC},
+ * {@link java.text.Normalizer.Form#NFD},
+ * {@link java.text.Normalizer.Form#NFKC},
+ * {@link java.text.Normalizer.Form#NFKD}
* @return true if the sequence of char values is normalized;
* false otherwise.
* @throws NullPointerException If <code>src</code> or <code>form</code>
- * is null.
+ * is null.
*/
public static boolean isNormalized(CharSequence src, Form form) {
+ // Android-changed: Switched to ICU.
return android.icu.text.Normalizer.isNormalized(src.toString(), form.icuMode, 0);
}
}
diff --git a/java/text/NumberFormat.java b/java/text/NumberFormat.java
index 218a6683..70a0aa96 100644
--- a/java/text/NumberFormat.java
+++ b/java/text/NumberFormat.java
@@ -286,10 +286,13 @@ public abstract class NumberFormat extends Format {
* @see java.text.Format#format
*/
public final String format(double number) {
+ // Android-removed: fast-path code.
return format(number, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
}
+ // Android-removed: fastFormat method.
+
/**
* Specialization of format.
*
@@ -545,6 +548,8 @@ public abstract class NumberFormat extends Format {
return getInstance(inLocale, PERCENTSTYLE);
}
+ // Android-removed: non-API methods getScientificInstance([Locale]).
+
// Android-changed: Removed reference to NumberFormatProvider.
/**
* Returns an array of all locales for which the
@@ -912,6 +917,7 @@ public abstract class NumberFormat extends Format {
stream.defaultWriteObject();
}
+ // Android-added: cachedLocaleData.
/**
* Cache to hold the NumberPatterns of a Locale.
*/
@@ -921,6 +927,8 @@ public abstract class NumberFormat extends Format {
private static final int NUMBERSTYLE = 0;
private static final int CURRENCYSTYLE = 1;
private static final int PERCENTSTYLE = 2;
+ // Android-changed: changed: removed SCIENTIFICSTYLE and pull down INTEGERSTYLE value.
+ //private static final int SCIENTIFICSTYLE = 3;
private static final int INTEGERSTYLE = 3;
/**
diff --git a/java/text/RuleBasedCollator.java b/java/text/RuleBasedCollator.java
index a82080b1..3f3c5bc0 100644
--- a/java/text/RuleBasedCollator.java
+++ b/java/text/RuleBasedCollator.java
@@ -242,10 +242,12 @@ import libcore.icu.CollationKeyICU;
* @see CollationElementIterator
* @author Helena Shih, Laura Werner, Richard Gillam
*/
-public class RuleBasedCollator extends Collator {
+public class RuleBasedCollator extends Collator{
+ // Android-added: protected constructor taking an ICU RuleBasedCollator.
RuleBasedCollator(android.icu.text.RuleBasedCollator wrapper) {
super(wrapper);
}
+
// IMPLEMENTATION NOTES: The implementation of the collation algorithm is
// divided across three classes: RuleBasedCollator, RBCollationTables, and
// CollationElementIterator. RuleBasedCollator contains the collator's
@@ -280,6 +282,7 @@ public class RuleBasedCollator extends Collator {
* throw the ParseException because the '?' is not quoted.
*/
public RuleBasedCollator(String rules) throws ParseException {
+ // BEGIN Android-changed: Switched to ICU.
if (rules == null) {
throw new NullPointerException("rules == null");
}
@@ -295,8 +298,12 @@ public class RuleBasedCollator extends Collator {
*/
throw new ParseException(e.getMessage(), -1);
}
+ // BEGIN Android-changed: Switched to ICU.
}
+ // Android-removed: (String rules, int decomp) constructor and copy constructor.
+
+ // Android-changed: document that getRules() won't return rules in common case.
/**
* Gets the table-based rules for the collation object.
*
@@ -308,6 +315,7 @@ public class RuleBasedCollator extends Collator {
*/
public String getRules()
{
+ // Android-changed: Switched to ICU.
return collAsICU().getRules();
}
@@ -319,6 +327,7 @@ public class RuleBasedCollator extends Collator {
* @see java.text.CollationElementIterator
*/
public CollationElementIterator getCollationElementIterator(String source) {
+ // Android-changed: Switch to ICU and check for null value.
if (source == null) {
throw new NullPointerException("source == null");
}
@@ -335,6 +344,7 @@ public class RuleBasedCollator extends Collator {
*/
public CollationElementIterator getCollationElementIterator(
CharacterIterator source) {
+ // Android-changed: Switch to ICU and check for null value.
if (source == null) {
throw new NullPointerException("source == null");
}
@@ -354,6 +364,7 @@ public class RuleBasedCollator extends Collator {
if (source == null || target == null) {
throw new NullPointerException();
}
+ // Android-changed: Switched to ICU.
return icuColl.compare(source, target);
}
@@ -364,6 +375,7 @@ public class RuleBasedCollator extends Collator {
*/
public synchronized CollationKey getCollationKey(String source)
{
+ // Android-changed: Switched to ICU.
if (source == null) {
return null;
}
@@ -374,6 +386,7 @@ public class RuleBasedCollator extends Collator {
* Standard override; no change in semantics.
*/
public Object clone() {
+ // Android-changed: remove special case for cloning.
return super.clone();
}
@@ -385,6 +398,7 @@ public class RuleBasedCollator extends Collator {
*/
public boolean equals(Object obj) {
if (obj == null) return false;
+ // Android-changed: delegate to super class, as that already compares icuColl.
return super.equals(obj);
}
@@ -392,10 +406,14 @@ public class RuleBasedCollator extends Collator {
* Generates the hash code for the table-based collation object
*/
public int hashCode() {
+ // Android-changed: Switched to ICU.
return icuColl.hashCode();
}
+ // Android-added: collAsIcu helper method.
private android.icu.text.RuleBasedCollator collAsICU() {
return (android.icu.text.RuleBasedCollator) icuColl;
}
+
+ // Android-removed: private constants and fields.
}
diff --git a/java/text/SimpleDateFormat.java b/java/text/SimpleDateFormat.java
index 6ae90575..c294e5a2 100644
--- a/java/text/SimpleDateFormat.java
+++ b/java/text/SimpleDateFormat.java
@@ -128,7 +128,7 @@ import static java.text.DateFormatSymbols.*;
* <td>Week year
* <td><a href="#year">Year</a>
* <td><code>2009</code>; <code>09</code>
- * <td>1+</td>
+ * <td>24+</td>
* <tr style="background-color: rgb(238, 238, 255);">
* <td><code>M</code>
* <td>Month in year (context sensitive)
@@ -242,7 +242,7 @@ import static java.text.DateFormatSymbols.*;
* <td>Time zone
* <td><a href="#iso8601timezone">ISO 8601 time zone</a>
* <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code>
- * <td>1+</td>
+ * <td>24+</td>
* </table>
* </blockquote>
* Pattern letters are usually repeated, as their number determines the
@@ -559,6 +559,7 @@ public class SimpleDateFormat extends DateFormat {
*/
transient boolean useDateFormatSymbols;
+ // Android-added: ICU TimeZoneNames field.
/**
* ICU TimeZoneNames used to format and parse time zone names.
*/
@@ -656,6 +657,7 @@ public class SimpleDateFormat extends DateFormat {
// initialize calendar and related fields
initializeCalendar(loc);
+ // BEGIN Android-changed: Use ICU for locale data.
formatData = DateFormatSymbols.getInstanceRef(loc);
LocaleData localeData = LocaleData.get(loc);
if ((timeStyle >= 0) && (dateStyle >= 0)) {
@@ -674,6 +676,7 @@ public class SimpleDateFormat extends DateFormat {
else {
throw new IllegalArgumentException("No date or time style specified");
}
+ // END Android-changed: Use ICU for locale data.
initialize(loc);
}
@@ -1072,8 +1075,8 @@ public class SimpleDateFormat extends DateFormat {
CalendarBuilder.WEEK_YEAR, // Pseudo Calendar field
CalendarBuilder.ISO_DAY_OF_WEEK, // Pseudo Calendar field
Calendar.ZONE_OFFSET,
- // 'L' and 'c',
Calendar.MONTH,
+ // Android-added: 'c' for standalone day of week.
Calendar.DAY_OF_WEEK
};
@@ -1101,8 +1104,8 @@ public class SimpleDateFormat extends DateFormat {
DateFormat.YEAR_FIELD,
DateFormat.DAY_OF_WEEK_FIELD,
DateFormat.TIMEZONE_FIELD,
- // 'L' and 'c'
DateFormat.MONTH_FIELD,
+ // Android-added: 'c' for standalone day of week.
DateFormat.DAY_OF_WEEK_FIELD
};
@@ -1130,11 +1133,12 @@ public class SimpleDateFormat extends DateFormat {
Field.YEAR,
Field.DAY_OF_WEEK,
Field.TIME_ZONE,
- // 'L' and 'c'
Field.MONTH,
+ // Android-added: 'c' for standalone day of week.
Field.DAY_OF_WEEK
};
+ // BEGIN Android-added: Special handling for UTC time zone.
private static final String UTC = "UTC";
/**
@@ -1144,6 +1148,7 @@ public class SimpleDateFormat extends DateFormat {
private static final Set<String> UTC_ZONE_IDS = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "UCT", "UTC",
"Universal", "Zulu")));
+ // END Android-added: Special handling for UTC time zone.
/**
* Private member function that does the real date/time formatting.
@@ -1238,15 +1243,19 @@ public class SimpleDateFormat extends DateFormat {
case PATTERN_DAY_OF_WEEK: // 'E'
if (current == null) {
+ // Android-changed: extract formatWeekday() method.
current = formatWeekday(count, value, useDateFormatSymbols, false /* standalone */);
}
break;
+ // BEGIN Android-added: support for 'c' (standalone day of week).
case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
if (current == null) {
+ // Android-changed: extract formatWeekday() method.
current = formatWeekday(count, value, useDateFormatSymbols, true /* standalone */);
}
break;
+ // END Android-added: support for 'c' (standalone day of week).
case PATTERN_AM_PM: // 'a'
if (useDateFormatSymbols) {
@@ -1268,6 +1277,7 @@ public class SimpleDateFormat extends DateFormat {
case PATTERN_ZONE_NAME: // 'z'
if (current == null) {
+ // BEGIN Android-changed: format time zone name using ICU.
TimeZone tz = calendar.getTimeZone();
boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
String zoneString;
@@ -1279,7 +1289,8 @@ public class SimpleDateFormat extends DateFormat {
formatData.getZoneStringsWrapper(), tz.getID(), daylight, tzstyle);
} else {
if (UTC_ZONE_IDS.contains(tz.getID())) {
- // ICU doesn't have name strings for UTC, explicitly print it as "UTC".
+ // ICU used to not have name strings UTC, explicitly print it as "UTC".
+ // TODO: remove special case now that ICU has that data (http://b/36337342).
zoneString = UTC;
} else {
TimeZoneNames.NameType nameType;
@@ -1304,10 +1315,12 @@ public class SimpleDateFormat extends DateFormat {
calendar.get(Calendar.DST_OFFSET);
buffer.append(TimeZone.createGmtOffsetString(true, true, offsetMillis));
}
+ // END Android-changed: format time zone name using ICU.
}
break;
case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)
+ // BEGIN Android-Changed: use shared code in TimeZone for zone offset string.
{
value = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
final boolean includeSeparator = (count >= 4);
@@ -1316,6 +1329,7 @@ public class SimpleDateFormat extends DateFormat {
break;
}
+ // END Android-Changed: use shared code in TimeZone for zone offset string.
case PATTERN_ISO_ZONE: // 'X'
value = calendar.get(Calendar.ZONE_OFFSET)
@@ -1344,6 +1358,7 @@ public class SimpleDateFormat extends DateFormat {
}
CalendarUtils.sprintf0d(buffer, value % 60, 2);
break;
+ // BEGIN Android-added: Better UTS#35 conformity for fractional seconds.
case PATTERN_MILLISECOND: // 'S'
// Fractional seconds must be treated specially. We must always convert the parsed
// value into a fractional second [0, 1) and then widen it out to the appropriate
@@ -1355,12 +1370,15 @@ public class SimpleDateFormat extends DateFormat {
zeroPaddingNumber(value, count, count, buffer);
}
break;
+ // END Android-added: Better UTS#35 conformity for fractional seconds.
default:
// case PATTERN_DAY_OF_MONTH: // 'd'
// case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59
// case PATTERN_MINUTE: // 'm'
// case PATTERN_SECOND: // 's'
+ // Android-removed: PATTERN_MILLISECONDS handled in an explicit case above.
+ //// case PATTERN_MILLISECOND: // 'S'
// case PATTERN_DAY_OF_YEAR: // 'D'
// case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
// case PATTERN_WEEK_OF_YEAR: // 'w'
@@ -1383,6 +1401,7 @@ public class SimpleDateFormat extends DateFormat {
delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
}
+ // BEGIN Android-added: formatWeekday and formatMonth methods to format using ICU data.
private String formatWeekday(int count, int value, boolean useDateFormatSymbols,
boolean standalone) {
if (useDateFormatSymbols) {
@@ -1433,6 +1452,7 @@ public class SimpleDateFormat extends DateFormat {
return current;
}
+ // END Android-added: formatWeekday and formatMonth methods to format using ICU data.
/**
* Formats a number with the specified minimum and maximum number of digits.
@@ -1521,6 +1541,7 @@ public class SimpleDateFormat extends DateFormat {
*/
@Override
public Date parse(String text, ParsePosition pos) {
+ // BEGIN Android-changed: extract parseInternal() and avoid modifying timezone during parse.
// Make sure the timezone associated with this dateformat instance (set via
// {@code setTimeZone} isn't change as a side-effect of parsing a date.
final TimeZone tz = getTimeZone();
@@ -1533,6 +1554,7 @@ public class SimpleDateFormat extends DateFormat {
private Date parseInternal(String text, ParsePosition pos)
{
+ // END Android-changed: extract parseInternal() and avoid modifying timezone during parse.
checkNegativeNumberExpression();
int start = pos.index;
@@ -1685,6 +1707,7 @@ public class SimpleDateFormat extends DateFormat {
bestMatchLength = length;
}
+ // BEGIN Android-changed: Handle abbreviated fields that end with a '.'.
// When the input option ends with a period (usually an abbreviated form), attempt
// to match all chars up to that period.
if ((data[i].charAt(length - 1) == '.') &&
@@ -1693,6 +1716,7 @@ public class SimpleDateFormat extends DateFormat {
bestMatch = i;
bestMatchLength = (length - 1);
}
+ // END Android-changed: Handle abbreviated fields that end with a '.'.
}
if (bestMatch >= 0)
{
@@ -1742,6 +1766,12 @@ public class SimpleDateFormat extends DateFormat {
return -1;
}
+ // Android-removed: unused private method matchDSTString.
+
+ // BEGIN Android-changed: Parse time zone strings using ICU TimeZoneNames.
+ // Note that this change falls back to the upstream zone names parsing code if the zoneStrings
+ // for the formatData field has been set by the user. The original code of subParseZoneString
+ // can be found in subParseZoneStringFromSymbols().
/**
* Parses the string in {@code text} (starting at {@code start}), interpreting it as a time zone
* name. If a time zone is found, the internal calendar is set to that timezone and the index of
@@ -1863,6 +1893,7 @@ public class SimpleDateFormat extends DateFormat {
* Parses the time zone string using the information in {@link #formatData}.
*/
private int subParseZoneStringFromSymbols(String text, int start, CalendarBuilder calb) {
+ // END Android-changed: Parse time zone strings using ICU TimeZoneNames.
boolean useSameName = false; // true if standard and daylight time use the same abbreviation.
TimeZone currentTimeZone = getTimeZone();
@@ -1967,9 +1998,9 @@ public class SimpleDateFormat extends DateFormat {
if (count != 1) {
// Proceed with parsing mm
c = text.charAt(index++);
- // Intentional change in behavior from OpenJDK. OpenJDK will return an error code
- // if a : is found and colonRequired is false, this will return an error code if
- // a : is not found and colonRequired is true.
+ // BEGIN Android-changed: Intentional change in behavior from OpenJDK.
+ // OpenJDK will return an error code if a : is found and colonRequired is false,
+ // this will return an error code if a : is not found and colonRequired is true.
//
// colonRequired | c == ':' | OpenJDK | this
// false | false | ok | ok
@@ -1981,6 +2012,7 @@ public class SimpleDateFormat extends DateFormat {
} else if (colonRequired) {
break parse;
}
+ // END Android-changed: Intentional change in behavior from OpenJDK.
if (!isDigit(c)) {
break parse;
}
@@ -2152,6 +2184,7 @@ public class SimpleDateFormat extends DateFormat {
return pos.index;
case PATTERN_MONTH: // 'M'
+ // BEGIN Android-changed: extract parseMonth method.
{
final int idx = parseMonth(text, count, value, start, field, pos,
useDateFormatSymbols, false /* isStandalone */, calb);
@@ -2171,6 +2204,7 @@ public class SimpleDateFormat extends DateFormat {
}
break parsing;
}
+ // END Android-changed: extract parseMonth method.
case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
if (!isLenient()) {
@@ -2187,6 +2221,7 @@ public class SimpleDateFormat extends DateFormat {
return pos.index;
case PATTERN_DAY_OF_WEEK: // 'E'
+ // BEGIN Android-changed: extract parseWeekday method.
{
final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
false /* standalone */, calb);
@@ -2195,7 +2230,9 @@ public class SimpleDateFormat extends DateFormat {
}
break parsing;
}
+ // END Android-changed: extract parseWeekday method.
+ // BEGIN Android-added: support for 'c' (standalone day of week).
case PATTERN_STANDALONE_DAY_OF_WEEK: // 'c'
{
final int idx = parseWeekday(text, start, field, useDateFormatSymbols,
@@ -2206,6 +2243,7 @@ public class SimpleDateFormat extends DateFormat {
break parsing;
}
+ // END Android-added: support for 'c' (standalone day of week).
case PATTERN_AM_PM: // 'a'
if (useDateFormatSymbols) {
@@ -2267,7 +2305,7 @@ public class SimpleDateFormat extends DateFormat {
.set(Calendar.DST_OFFSET, 0);
return pos.index;
}
-
+ // Android-changed: tolerate colon in zone offset.
// Parse the rest as "hh[:]?mm"
int i = subParseNumericZone(text, ++pos.index, sign, 0,
false, calb);
@@ -2285,6 +2323,7 @@ public class SimpleDateFormat extends DateFormat {
pos.index = -i;
}
} else {
+ // Android-changed: tolerate colon in zone offset.
// Parse the rest as "hh[:]?mm" (RFC 822)
int i = subParseNumericZone(text, ++pos.index, sign, 0,
false, calb);
@@ -2353,6 +2392,7 @@ public class SimpleDateFormat extends DateFormat {
number = numberFormat.parse(text, pos);
}
if (number != null) {
+ // BEGIN Android-changed: Better UTS#35 conformity for fractional seconds.
if (patternCharIndex == PATTERN_MILLISECOND) {
// Fractional seconds must be treated specially. We must always
// normalize them to their fractional second value [0, 1) before we attempt
@@ -2367,6 +2407,7 @@ public class SimpleDateFormat extends DateFormat {
} else {
value = number.intValue();
}
+ // END Android-changed: Better UTS#35 conformity for fractional seconds.
if (useFollowingMinusSignAsDelimiter && (value < 0) &&
(((pos.index < text.length()) &&
@@ -2389,6 +2430,7 @@ public class SimpleDateFormat extends DateFormat {
return -1;
}
+ // BEGIN Android-added: parseMonth and parseWeekday methods to parse using ICU data.
private int parseMonth(String text, int count, int value, int start,
int field, ParsePosition pos, boolean useDateFormatSymbols,
boolean standalone,
@@ -2464,7 +2506,7 @@ public class SimpleDateFormat extends DateFormat {
return index;
}
-
+ // END Android-added: parseMonth and parseWeekday methods to parse using ICU data.
private final String getCalendarName() {
return calendar.getClass().getName();
diff --git a/java/time/format/DateTimeFormatterBuilder.java b/java/time/format/DateTimeFormatterBuilder.java
index f9cff0a3..69e35d63 100644
--- a/java/time/format/DateTimeFormatterBuilder.java
+++ b/java/time/format/DateTimeFormatterBuilder.java
@@ -3641,7 +3641,7 @@ public final class DateTimeFormatterBuilder {
private static final int DST = 1;
private static final int GENERIC = 2;
- // Android-changed: List of types used by getDisplayName().
+ // BEGIN Android-added: Lists of types used by getDisplayName().
private static final TimeZoneNames.NameType[] TYPES = new TimeZoneNames.NameType[] {
TimeZoneNames.NameType.LONG_STANDARD,
TimeZoneNames.NameType.SHORT_STANDARD,
@@ -3662,6 +3662,7 @@ public final class DateTimeFormatterBuilder {
TimeZoneNames.NameType.SHORT_DAYLIGHT,
TimeZoneNames.NameType.SHORT_GENERIC,
};
+ // END Android-added: Lists of types used by getDisplayName().
private static final Map<String, SoftReference<Map<Locale, String[]>>> cache =
new ConcurrentHashMap<>();
@@ -3675,7 +3676,7 @@ public final class DateTimeFormatterBuilder {
Map<Locale, String[]> perLocale = null;
if (ref == null || (perLocale = ref.get()) == null ||
(names = perLocale.get(locale)) == null) {
- // Android-changed: use ICU TimeZoneNames instead of TimeZoneNameUtility.
+ // BEGIN Android-changed: use ICU TimeZoneNames instead of TimeZoneNameUtility.
TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
names = new String[TYPES.length + 1];
// Zeroth index used for id, other indexes based on NameType constant + 1.
@@ -3703,6 +3704,7 @@ public final class DateTimeFormatterBuilder {
if (names[5] == null) {
names[5] = names[0]; // use the id
}
+ // END Android-changed: use ICU TimeZoneNames instead of TimeZoneNameUtility.
if (names[6] == null) {
names[6] = names[0];
}
@@ -3763,12 +3765,12 @@ public final class DateTimeFormatterBuilder {
isCaseSensitive ? cachedTree : cachedTreeCI;
Entry<Integer, SoftReference<PrefixTree>> entry;
+ // BEGIN Android-changed: use ICU TimeZoneNames to get Zone names.
PrefixTree tree;
if ((entry = cached.get(locale)) == null ||
(entry.getKey() != regionIdsSize ||
(tree = entry.getValue().get()) == null)) {
tree = PrefixTree.newTree(context);
- // Android-changed: use ICU TimeZoneNames to get Zone names.
TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale);
long now = System.currentTimeMillis();
TimeZoneNames.NameType[] types =
@@ -3798,6 +3800,7 @@ public final class DateTimeFormatterBuilder {
tree.add(names[i], zid);
}
}
+ // END Android-changed: use ICU TimeZoneNames to get Zone names.
}
}
cached.put(locale, new SimpleImmutableEntry<>(regionIdsSize, new SoftReference<>(tree)));
@@ -3923,7 +3926,7 @@ public final class DateTimeFormatterBuilder {
return position;
}
- // Android-changed: "GMT0" is considered a valid ZoneId.
+ // Android-added: "GMT0" is considered a valid ZoneId.
if (text.charAt(position) == '0' && prefix.equals("GMT")) {
context.setParsed(ZoneId.of("GMT0"));
return position + 1;
diff --git a/java/time/format/DateTimeTextProvider.java b/java/time/format/DateTimeTextProvider.java
index 99ab9b07..2ef9df8d 100644
--- a/java/time/format/DateTimeTextProvider.java
+++ b/java/time/format/DateTimeTextProvider.java
@@ -448,6 +448,7 @@ class DateTimeTextProvider {
return ""; // null marker for map
}
+ // Android-added: extractQuarters to extract Map of quarter names from ICU resource bundle.
private static Map<Long, String> extractQuarters(ICUResourceBundle rb, String key) {
String[] names = rb.getWithFallback(key).getStringArray();
Map<Long, String> map = new HashMap<>();
@@ -468,7 +469,7 @@ class DateTimeTextProvider {
return new SimpleImmutableEntry<>(text, field);
}
- // Android-changed: removed getLocalizedResource.
+ // Android-removed: unused helper method getLocalizedResource.
/**
* Stores the text for a single locale.
diff --git a/java/time/format/ZoneName.java b/java/time/format/ZoneName.java
index c3eb20ab..fe4a95a9 100644
--- a/java/time/format/ZoneName.java
+++ b/java/time/format/ZoneName.java
@@ -38,10 +38,10 @@ import java.util.Map;
* The zid<->metazone mappings are based on CLDR metaZones.xml.
* The alias mappings are based on Link entries in tzdb data files.
*/
-// Android-changed: delegate to ICU.
class ZoneName {
public static String toZid(String zid, Locale locale) {
+ // Android-changed: delegate to ICU.
TimeZoneNames tzNames = TimeZoneNames.getInstance(locale);
if (tzNames.getAvailableMetaZoneIDs().contains(zid)) {
// Compare TimeZoneFormat#getTargetRegion.
@@ -64,4 +64,7 @@ class ZoneName {
}
return zid;
}
+
+ // Android-removed: zidMap and aliasMap containing zone id data.
+ // Android-removed: zidToMzone, mzoneToZid, mzoneToZidL, aliases and their initialization code.
}
diff --git a/java/util/zip/ZipFile.java b/java/util/zip/ZipFile.java
index fbe366e4..797f2c6d 100644
--- a/java/util/zip/ZipFile.java
+++ b/java/util/zip/ZipFile.java
@@ -396,7 +396,9 @@ class ZipFile implements ZipConstants, Closeable {
case DEFLATED:
// MORE: Compute good size for inflater stream:
long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack
- if (size > 65536) size = 8192;
+ // Android-changed: Use 64k buffer size, performs better than 8k.
+ // if (size > 65536) size = 8192;
+ if (size > 65536) size = 65536;
if (size <= 0) size = 4096;
Inflater inf = getInflater();
InputStream is =
diff --git a/org/apache/harmony/security/PrivateKeyImpl.java b/org/apache/harmony/security/PrivateKeyImpl.java
deleted file mode 100644
index 47aceb34..00000000
--- a/org/apache/harmony/security/PrivateKeyImpl.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.apache.harmony.security;
-
-import java.security.PrivateKey;
-
-/**
- * PrivateKeyImpl
- */
-public class PrivateKeyImpl implements PrivateKey {
-
- /*
- * @serial
- */
- private static final long serialVersionUID = 7776497482533790279L;
-
- private String algorithm;
-
- private byte[] encoding;
-
- public PrivateKeyImpl(String algorithm) {
- this.algorithm = algorithm;
- }
-
- public String getAlgorithm() {
- return algorithm;
- }
-
- public String getFormat() {
- return "PKCS#8";
- }
-
- public byte[] getEncoded() {
-
- byte[] toReturn = new byte[encoding.length];
- System.arraycopy(encoding, 0, toReturn, 0, encoding.length);
-
- return toReturn;
- }
-
- public void setAlgorithm(String algorithm) {
- this.algorithm = algorithm;
- }
-
- public void setEncoding(byte[] encoding) {
- this.encoding = new byte[encoding.length];
- System.arraycopy(encoding, 0, this.encoding, 0, encoding.length);
- }
-
-}
diff --git a/org/apache/harmony/security/PublicKeyImpl.java b/org/apache/harmony/security/PublicKeyImpl.java
deleted file mode 100644
index dccc72db..00000000
--- a/org/apache/harmony/security/PublicKeyImpl.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.apache.harmony.security;
-
-import java.security.PublicKey;
-
-
-/**
- * PublicKeyImpl
- */
-public class PublicKeyImpl implements PublicKey {
-
- /**
- * @serial
- */
- private static final long serialVersionUID = 7179022516819534075L;
-
-
- private byte[] encoding;
-
- private String algorithm;
-
-
- public PublicKeyImpl(String algorithm) {
- this.algorithm = algorithm;
- }
-
-
- public String getAlgorithm() {
- return algorithm;
- }
-
-
- public String getFormat() {
- return "X.509";
- }
-
-
- public byte[] getEncoded() {
- byte[] result = new byte[encoding.length];
- System.arraycopy(encoding, 0, result, 0, encoding.length);
- return result;
- }
-
-
- public void setAlgorithm(String algorithm) {
- this.algorithm = algorithm;
- }
-
-
- public void setEncoding(byte[] encoding) {
- this.encoding = new byte[encoding.length];
- System.arraycopy(encoding, 0, this.encoding, 0, encoding.length);
- }
-}
-
diff --git a/org/apache/harmony/security/provider/crypto/CryptoProvider.java b/org/apache/harmony/security/provider/crypto/CryptoProvider.java
deleted file mode 100644
index ad5ac7de..00000000
--- a/org/apache/harmony/security/provider/crypto/CryptoProvider.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.harmony.security.provider.crypto;
-
-import java.security.Provider;
-
-/**
- * Implementation of Provider for SecureRandom. The implementation supports the
- * "SHA1PRNG" algorithm described in JavaTM Cryptography Architecture, API
- * Specification & Reference
- */
-
-public final class CryptoProvider extends Provider {
-
- private static final long serialVersionUID = 7991202868423459598L;
-
- /**
- * Creates a Provider and puts parameters
- */
- public CryptoProvider() {
- super("Crypto", 1.0, "HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature)");
-
- put("SecureRandom.SHA1PRNG",
- "org.apache.harmony.security.provider.crypto.SHA1PRNG_SecureRandomImpl");
- put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
- }
-}
diff --git a/org/apache/harmony/security/provider/crypto/SHA1Constants.java b/org/apache/harmony/security/provider/crypto/SHA1Constants.java
deleted file mode 100644
index fc6a847b..00000000
--- a/org/apache/harmony/security/provider/crypto/SHA1Constants.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/**
-* @author Yuri A. Kropachev
-* @version $Revision$
-*/
-
-
-package org.apache.harmony.security.provider.crypto;
-
-
-/**
- * This interface contains : <BR>
- * - a set of constant values, H0-H4, defined in "SECURE HASH STANDARD", FIPS PUB 180-2 ;<BR>
- * - implementation constant values to use in classes using SHA-1 algorithm. <BR>
- */
-public final class SHA1Constants {
- private SHA1Constants() {
- }
-
- /**
- * constant defined in "SECURE HASH STANDARD"
- */
- public static final int H0 = 0x67452301;
-
-
- /**
- * constant defined in "SECURE HASH STANDARD"
- */
- public static final int H1 = 0xEFCDAB89;
-
-
- /**
- * constant defined in "SECURE HASH STANDARD"
- */
- public static final int H2 = 0x98BADCFE;
-
-
- /**
- * constant defined in "SECURE HASH STANDARD"
- */
- public static final int H3 = 0x10325476;
-
-
- /**
- * constant defined in "SECURE HASH STANDARD"
- */
- public static final int H4 = 0xC3D2E1F0;
-
-
- /**
- * offset in buffer to store number of bytes in 0-15 word frame
- */
- public static final int BYTES_OFFSET = 81;
-
-
- /**
- * offset in buffer to store current hash value
- */
- public static final int HASH_OFFSET = 82;
-
-
- /**
- * # of bytes in H0-H4 words; <BR>
- * in this implementation # is set to 20 (in general # varies from 1 to 20)
- */
- public static final int DIGEST_LENGTH = 20;
-}
diff --git a/org/apache/harmony/security/provider/crypto/SHA1Impl.java b/org/apache/harmony/security/provider/crypto/SHA1Impl.java
deleted file mode 100644
index 57b90059..00000000
--- a/org/apache/harmony/security/provider/crypto/SHA1Impl.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/**
-* @author Yuri A. Kropachev
-* @version $Revision$
-*/
-
-
-package org.apache.harmony.security.provider.crypto;
-
-import static org.apache.harmony.security.provider.crypto.SHA1Constants.*;
-
-/**
- * This class contains methods providing SHA-1 functionality to use in classes. <BR>
- * The methods support the algorithm described in "SECURE HASH STANDARD", FIPS PUB 180-2, <BR>
- * "http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf" <BR>
- * <BR>
- * The class contains two package level access methods, -
- * "void updateHash(int[], byte[], int, int)" and "void computeHash(int[])", -
- * performing the following operations. <BR>
- * <BR>
- * The "updateHash(..)" method appends new bytes to existing ones
- * within limit of a frame of 64 bytes (16 words).
- * Once a length of accumulated bytes reaches the limit
- * the "computeHash(int[])" method is invoked on the frame to compute updated hash,
- * and the number of bytes in the frame is set to 0.
- * Thus, after appending all bytes, the frame contain only those bytes
- * that were not used in computing final hash value yet. <BR>
- * <BR>
- * The "computeHash(..)" method generates a 160 bit hash value using
- * a 512 bit message stored in first 16 words of int[] array argument and
- * current hash value stored in five words, beginning HASH_OFFSET, of the array argument.
- * Computation is done according to SHA-1 algorithm. <BR>
- * <BR>
- * The resulting hash value replaces the previous hash value in the array;
- * original bits of the message are not preserved.
- */
-public class SHA1Impl {
-
-
- /**
- * The method generates a 160 bit hash value using
- * a 512 bit message stored in first 16 words of int[] array argument and
- * current hash value stored in five words, beginning OFFSET+1, of the array argument.
- * Computation is done according to SHA-1 algorithm.
- *
- * The resulting hash value replaces the previous hash value in the array;
- * original bits of the message are not preserved.
- *
- * No checks on argument supplied, that is,
- * a calling method is responsible for such checks.
- * In case of incorrect array passed to the method
- * either NPE or IndexOutOfBoundException gets thrown by JVM.
- *
- * @params
- * arrW - integer array; arrW.length >= (BYTES_OFFSET+6); <BR>
- * only first (BYTES_OFFSET+6) words are used
- */
- static void computeHash(int[] arrW) {
-
- int a = arrW[HASH_OFFSET ];
- int b = arrW[HASH_OFFSET +1];
- int c = arrW[HASH_OFFSET +2];
- int d = arrW[HASH_OFFSET +3];
- int e = arrW[HASH_OFFSET +4];
-
- int temp;
-
- // In this implementation the "d. For t = 0 to 79 do" loop
- // is split into four loops. The following constants:
- // K = 5A827999 0 <= t <= 19
- // K = 6ED9EBA1 20 <= t <= 39
- // K = 8F1BBCDC 40 <= t <= 59
- // K = CA62C1D6 60 <= t <= 79
- // are hex literals in the loops.
-
- for ( int t = 16; t < 80 ; t++ ) {
-
- temp = arrW[t-3] ^ arrW[t-8] ^ arrW[t-14] ^ arrW[t-16];
- arrW[t] = ( temp<<1 ) | ( temp>>>31 );
- }
-
- for ( int t = 0 ; t < 20 ; t++ ) {
-
- temp = ( ( a<<5 ) | ( a>>>27 ) ) +
- ( ( b & c) | ((~b) & d) ) +
- ( e + arrW[t] + 0x5A827999 ) ;
- e = d;
- d = c;
- c = ( b<<30 ) | ( b>>>2 ) ;
- b = a;
- a = temp;
- }
- for ( int t = 20 ; t < 40 ; t++ ) {
-
- temp = ((( a<<5 ) | ( a>>>27 ))) + (b ^ c ^ d) + (e + arrW[t] + 0x6ED9EBA1) ;
- e = d;
- d = c;
- c = ( b<<30 ) | ( b>>>2 ) ;
- b = a;
- a = temp;
- }
- for ( int t = 40 ; t < 60 ; t++ ) {
-
- temp = (( a<<5 ) | ( a>>>27 )) + ((b & c) | (b & d) | (c & d)) +
- (e + arrW[t] + 0x8F1BBCDC) ;
- e = d;
- d = c;
- c = ( b<<30 ) | ( b>>>2 ) ;
- b = a;
- a = temp;
- }
- for ( int t = 60 ; t < 80 ; t++ ) {
-
- temp = ((( a<<5 ) | ( a>>>27 ))) + (b ^ c ^ d) + (e + arrW[t] + 0xCA62C1D6) ;
- e = d;
- d = c;
- c = ( b<<30 ) | ( b>>>2 ) ;
- b = a;
- a = temp;
- }
-
- arrW[HASH_OFFSET ] += a;
- arrW[HASH_OFFSET +1] += b;
- arrW[HASH_OFFSET +2] += c;
- arrW[HASH_OFFSET +3] += d;
- arrW[HASH_OFFSET +4] += e;
- }
-
- /**
- * The method appends new bytes to existing ones
- * within limit of a frame of 64 bytes (16 words).
- *
- * Once a length of accumulated bytes reaches the limit
- * the "computeHash(int[])" method is invoked on the array to compute updated hash,
- * and the number of bytes in the frame is set to 0.
- * Thus, after appending all bytes, the array contain only those bytes
- * that were not used in computing final hash value yet.
- *
- * No checks on arguments passed to the method, that is,
- * a calling method is responsible for such checks.
- *
- * @params
- * intArray - int array containing bytes to which to append;
- * intArray.length >= (BYTES_OFFSET+6)
- * @params
- * byteInput - array of bytes to use for the update
- * @params
- * from - the offset to start in the "byteInput" array
- * @params
- * to - a number of the last byte in the input array to use,
- * that is, for first byte "to"==0, for last byte "to"==input.length-1
- */
- static void updateHash(int[] intArray, byte[] byteInput, int fromByte, int toByte) {
-
- // As intArray contains a packed bytes
- // the buffer's index is in the intArray[BYTES_OFFSET] element
-
- int index = intArray[BYTES_OFFSET];
- int i = fromByte;
- int maxWord;
- int nBytes;
-
- int wordIndex = index >>2;
- int byteIndex = index & 0x03;
-
- intArray[BYTES_OFFSET] = ( index + toByte - fromByte + 1 ) & 077 ;
-
- // In general case there are 3 stages :
- // - appending bytes to non-full word,
- // - writing 4 bytes into empty words,
- // - writing less than 4 bytes in last word
-
- if ( byteIndex != 0 ) { // appending bytes in non-full word (as if)
-
- for ( ; ( i <= toByte ) && ( byteIndex < 4 ) ; i++ ) {
- intArray[wordIndex] |= ( byteInput[i] & 0xFF ) << ((3 - byteIndex)<<3) ;
- byteIndex++;
- }
- if ( byteIndex == 4 ) {
- wordIndex++;
- if ( wordIndex == 16 ) { // intArray is full, computing hash
-
- computeHash(intArray);
- wordIndex = 0;
- }
- }
- if ( i > toByte ) { // all input bytes appended
- return ;
- }
- }
-
- // writing full words
-
- maxWord = (toByte - i + 1) >> 2; // # of remaining full words, may be "0"
- for ( int k = 0; k < maxWord ; k++ ) {
-
- intArray[wordIndex] = ( ((int) byteInput[i ] & 0xFF) <<24 ) |
- ( ((int) byteInput[i +1] & 0xFF) <<16 ) |
- ( ((int) byteInput[i +2] & 0xFF) <<8 ) |
- ( ((int) byteInput[i +3] & 0xFF) ) ;
- i += 4;
- wordIndex++;
-
- if ( wordIndex < 16 ) { // buffer is not full yet
- continue;
- }
- computeHash(intArray); // buffer is full, computing hash
- wordIndex = 0;
- }
-
- // writing last incomplete word
- // after writing free byte positions are set to "0"s
-
- nBytes = toByte - i +1;
- if ( nBytes != 0 ) {
-
- int w = ((int) byteInput[i] & 0xFF) <<24 ;
-
- if ( nBytes != 1 ) {
- w |= ((int) byteInput[i +1] & 0xFF) <<16 ;
- if ( nBytes != 2) {
- w |= ((int) byteInput[i +2] & 0xFF) <<8 ;
- }
- }
- intArray[wordIndex] = w;
- }
-
- return ;
- }
-
-}
diff --git a/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java b/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java
deleted file mode 100644
index 5c0e3284..00000000
--- a/org/apache/harmony/security/provider/crypto/SHA1PRNG_SecureRandomImpl.java
+++ /dev/null
@@ -1,564 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.apache.harmony.security.provider.crypto;
-
-import dalvik.system.BlockGuard;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.security.InvalidParameterException;
-import java.security.ProviderException;
-import java.security.SecureRandomSpi;
-import libcore.io.Streams;
-import libcore.util.EmptyArray;
-
-import static org.apache.harmony.security.provider.crypto.SHA1Constants.*;
-
-/**
- * This class extends the SecureRandomSpi class implementing all its abstract methods.
- *
- * <p>To generate pseudo-random bits, the implementation uses technique described in
- * the "Random Number Generator (RNG) algorithms" section, Appendix A,
- * JavaTM Cryptography Architecture, API Specification & Reference.
- */
-public class SHA1PRNG_SecureRandomImpl extends SecureRandomSpi implements Serializable {
-
- private static final long serialVersionUID = 283736797212159675L;
-
- private static FileInputStream devURandom;
- static {
- try {
- devURandom = new FileInputStream(new File("/dev/urandom"));
- } catch (IOException ex) {
- throw new RuntimeException(ex);
- }
- }
-
- // constants to use in expressions operating on bytes in int and long variables:
- // END_FLAGS - final bytes in words to append to message;
- // see "ch.5.1 Padding the Message, FIPS 180-2"
- // RIGHT1 - shifts to right for left half of long
- // RIGHT2 - shifts to right for right half of long
- // LEFT - shifts to left for bytes
- // MASK - mask to select counter's bytes after shift to right
-
- private static final int[] END_FLAGS = { 0x80000000, 0x800000, 0x8000, 0x80 };
-
- private static final int[] RIGHT1 = { 0, 40, 48, 56 };
-
- private static final int[] RIGHT2 = { 0, 8, 16, 24 };
-
- private static final int[] LEFT = { 0, 24, 16, 8 };
-
- private static final int[] MASK = { 0xFFFFFFFF, 0x00FFFFFF, 0x0000FFFF,
- 0x000000FF };
-
- // HASHBYTES_TO_USE defines # of bytes returned by "computeHash(byte[])"
- // to use to form byte array returning by the "nextBytes(byte[])" method
- // Note, that this implementation uses more bytes than it is defined
- // in the above specification.
- private static final int HASHBYTES_TO_USE = 20;
-
- // value of 16 defined in the "SECURE HASH STANDARD", FIPS PUB 180-2
- private static final int FRAME_LENGTH = 16;
-
- // miscellaneous constants defined in this implementation:
- // COUNTER_BASE - initial value to set to "counter" before computing "nextBytes(..)";
- // note, that the exact value is not defined in STANDARD
- // HASHCOPY_OFFSET - offset for copy of current hash in "copies" array
- // EXTRAFRAME_OFFSET - offset for extra frame in "copies" array;
- // as the extra frame follows the current hash frame,
- // EXTRAFRAME_OFFSET is equal to length of current hash frame
- // FRAME_OFFSET - offset for frame in "copies" array
- // MAX_BYTES - maximum # of seed bytes processing which doesn't require extra frame
- // see (1) comments on usage of "seed" array below and
- // (2) comments in "engineNextBytes(byte[])" method
- //
- // UNDEFINED - three states of engine; initially its state is "UNDEFINED"
- // SET_SEED call to "engineSetSeed" sets up "SET_SEED" state,
- // NEXT_BYTES call to "engineNextByte" sets up "NEXT_BYTES" state
-
- private static final int COUNTER_BASE = 0;
-
- private static final int HASHCOPY_OFFSET = 0;
-
- private static final int EXTRAFRAME_OFFSET = 5;
-
- private static final int FRAME_OFFSET = 21;
-
- private static final int MAX_BYTES = 48;
-
- private static final int UNDEFINED = 0;
-
- private static final int SET_SEED = 1;
-
- private static final int NEXT_BYTES = 2;
-
- private static SHA1PRNG_SecureRandomImpl myRandom;
-
- // Structure of "seed" array:
- // - 0-79 - words for computing hash
- // - 80 - unused
- // - 81 - # of seed bytes in current seed frame
- // - 82-86 - 5 words, current seed hash
- private transient int[] seed;
-
- // total length of seed bytes, including all processed
- private transient long seedLength;
-
- // Structure of "copies" array
- // - 0-4 - 5 words, copy of current seed hash
- // - 5-20 - extra 16 words frame;
- // is used if final padding exceeds 512-bit length
- // - 21-36 - 16 word frame to store a copy of remaining bytes
- private transient int[] copies;
-
- // ready "next" bytes; needed because words are returned
- private transient byte[] nextBytes;
-
- // index of used bytes in "nextBytes" array
- private transient int nextBIndex;
-
- // variable required according to "SECURE HASH STANDARD"
- private transient long counter;
-
- // contains int value corresponding to engine's current state
- private transient int state;
-
- // The "seed" array is used to compute both "current seed hash" and "next bytes".
- //
- // As the "SHA1" algorithm computes a hash of entire seed by splitting it into
- // a number of the 512-bit length frames (512 bits = 64 bytes = 16 words),
- // "current seed hash" is a hash (5 words, 20 bytes) for all previous full frames;
- // remaining bytes are stored in the 0-15 word frame of the "seed" array.
- //
- // As for calculating "next bytes",
- // both remaining bytes and "current seed hash" are used,
- // to preserve the latter for following "setSeed(..)" commands,
- // the following technique is used:
- // - upon getting "nextBytes(byte[])" invoked, single or first in row,
- // which requires computing new hash, that is,
- // there is no more bytes remaining from previous "next bytes" computation,
- // remaining bytes are copied into the 21-36 word frame of the "copies" array;
- // - upon getting "setSeed(byte[])" invoked, single or first in row,
- // remaining bytes are copied back.
-
- /**
- * Creates object and sets implementation variables to their initial values
- */
- public SHA1PRNG_SecureRandomImpl() {
-
- seed = new int[HASH_OFFSET + EXTRAFRAME_OFFSET];
- seed[HASH_OFFSET] = H0;
- seed[HASH_OFFSET + 1] = H1;
- seed[HASH_OFFSET + 2] = H2;
- seed[HASH_OFFSET + 3] = H3;
- seed[HASH_OFFSET + 4] = H4;
-
- seedLength = 0;
- copies = new int[2 * FRAME_LENGTH + EXTRAFRAME_OFFSET];
- nextBytes = new byte[DIGEST_LENGTH];
- nextBIndex = HASHBYTES_TO_USE;
- counter = COUNTER_BASE;
- state = UNDEFINED;
- }
-
- /*
- * The method invokes the SHA1Impl's "updateHash(..)" method
- * to update current seed frame and
- * to compute new intermediate hash value if the frame is full.
- *
- * After that it computes a length of whole seed.
- */
- private void updateSeed(byte[] bytes) {
-
- // on call: "seed" contains current bytes and current hash;
- // on return: "seed" contains new current bytes and possibly new current hash
- // if after adding, seed bytes overfill its buffer
- SHA1Impl.updateHash(seed, bytes, 0, bytes.length - 1);
-
- seedLength += bytes.length;
- }
-
- /**
- * Changes current seed by supplementing a seed argument to the current seed,
- * if this already set;
- * the argument is used as first seed otherwise. <BR>
- *
- * The method overrides "engineSetSeed(byte[])" in class SecureRandomSpi.
- *
- * @param
- * seed - byte array
- * @throws
- * NullPointerException - if null is passed to the "seed" argument
- */
- protected synchronized void engineSetSeed(byte[] seed) {
-
- if (seed == null) {
- throw new NullPointerException("seed == null");
- }
-
- if (state == NEXT_BYTES) { // first setSeed after NextBytes; restoring hash
- System.arraycopy(copies, HASHCOPY_OFFSET, this.seed, HASH_OFFSET,
- EXTRAFRAME_OFFSET);
- }
- state = SET_SEED;
-
- if (seed.length != 0) {
- updateSeed(seed);
- }
- }
-
- /**
- * Returns a required number of random bytes. <BR>
- *
- * The method overrides "engineGenerateSeed (int)" in class SecureRandomSpi. <BR>
- *
- * @param
- * numBytes - number of bytes to return; should be >= 0.
- * @return
- * byte array containing bits in order from left to right
- * @throws
- * InvalidParameterException - if numBytes < 0
- */
- protected synchronized byte[] engineGenerateSeed(int numBytes) {
-
- byte[] myBytes; // byte[] for bytes returned by "nextBytes()"
-
- if (numBytes < 0) {
- throw new NegativeArraySizeException(Integer.toString(numBytes));
- }
- if (numBytes == 0) {
- return EmptyArray.BYTE;
- }
-
- if (myRandom == null) {
- myRandom = new SHA1PRNG_SecureRandomImpl();
- myRandom.engineSetSeed(getRandomBytes(DIGEST_LENGTH));
- }
-
- myBytes = new byte[numBytes];
- myRandom.engineNextBytes(myBytes);
-
- return myBytes;
- }
-
- /**
- * Writes random bytes into an array supplied.
- * Bits in a byte are from left to right. <BR>
- *
- * To generate random bytes, the "expansion of source bits" method is used,
- * that is,
- * the current seed with a 64-bit counter appended is used to compute new bits.
- * The counter is incremented by 1 for each 20-byte output. <BR>
- *
- * The method overrides engineNextBytes in class SecureRandomSpi.
- *
- * @param
- * bytes - byte array to be filled in with bytes
- * @throws
- * NullPointerException - if null is passed to the "bytes" argument
- */
- protected synchronized void engineNextBytes(byte[] bytes) {
-
- int i, n;
-
- long bits; // number of bits required by Secure Hash Standard
- int nextByteToReturn; // index of ready bytes in "bytes" array
- int lastWord; // index of last word in frame containing bytes
- final int extrabytes = 7;// # of bytes to add in order to computer # of 8 byte words
-
- if (bytes == null) {
- throw new NullPointerException("bytes == null");
- }
-
- lastWord = seed[BYTES_OFFSET] == 0 ? 0
- : (seed[BYTES_OFFSET] + extrabytes) >> 3 - 1;
-
- if (state == UNDEFINED) {
-
- // no seed supplied by user, hence it is generated thus randomizing internal state
- updateSeed(getRandomBytes(DIGEST_LENGTH));
- nextBIndex = HASHBYTES_TO_USE;
-
- // updateSeed(...) updates where the last word of the seed is, so we
- // have to read it again.
- lastWord = seed[BYTES_OFFSET] == 0 ? 0
- : (seed[BYTES_OFFSET] + extrabytes) >> 3 - 1;
-
- } else if (state == SET_SEED) {
-
- System.arraycopy(seed, HASH_OFFSET, copies, HASHCOPY_OFFSET,
- EXTRAFRAME_OFFSET);
-
- // possible cases for 64-byte frame:
- //
- // seed bytes < 48 - remaining bytes are enough for all, 8 counter bytes,
- // 0x80, and 8 seedLength bytes; no extra frame required
- // 48 < seed bytes < 56 - remaining 9 bytes are for 0x80 and 8 counter bytes
- // extra frame contains only seedLength value at the end
- // seed bytes > 55 - extra frame contains both counter's bytes
- // at the beginning and seedLength value at the end;
- // note, that beginning extra bytes are not more than 8,
- // that is, only 2 extra words may be used
-
- // no need to set to "0" 3 words after "lastWord" and
- // more than two words behind frame
- for (i = lastWord + 3; i < FRAME_LENGTH + 2; i++) {
- seed[i] = 0;
- }
-
- bits = (seedLength << 3) + 64; // transforming # of bytes into # of bits
-
- // putting # of bits into two last words (14,15) of 16 word frame in
- // seed or copies array depending on total length after padding
- if (seed[BYTES_OFFSET] < MAX_BYTES) {
- seed[14] = (int) (bits >>> 32);
- seed[15] = (int) (bits & 0xFFFFFFFF);
- } else {
- copies[EXTRAFRAME_OFFSET + 14] = (int) (bits >>> 32);
- copies[EXTRAFRAME_OFFSET + 15] = (int) (bits & 0xFFFFFFFF);
- }
-
- nextBIndex = HASHBYTES_TO_USE; // skipping remaining random bits
- }
- state = NEXT_BYTES;
-
- if (bytes.length == 0) {
- return;
- }
-
- nextByteToReturn = 0;
-
- // possibly not all of HASHBYTES_TO_USE bytes were used previous time
- n = (HASHBYTES_TO_USE - nextBIndex) < (bytes.length - nextByteToReturn) ? HASHBYTES_TO_USE
- - nextBIndex
- : bytes.length - nextByteToReturn;
- if (n > 0) {
- System.arraycopy(nextBytes, nextBIndex, bytes, nextByteToReturn, n);
- nextBIndex += n;
- nextByteToReturn += n;
- }
-
- if (nextByteToReturn >= bytes.length) {
- return; // return because "bytes[]" are filled in
- }
-
- n = seed[BYTES_OFFSET] & 0x03;
- for (;;) {
- if (n == 0) {
-
- seed[lastWord] = (int) (counter >>> 32);
- seed[lastWord + 1] = (int) (counter & 0xFFFFFFFF);
- seed[lastWord + 2] = END_FLAGS[0];
-
- } else {
-
- seed[lastWord] |= (int) ((counter >>> RIGHT1[n]) & MASK[n]);
- seed[lastWord + 1] = (int) ((counter >>> RIGHT2[n]) & 0xFFFFFFFF);
- seed[lastWord + 2] = (int) ((counter << LEFT[n]) | END_FLAGS[n]);
- }
- if (seed[BYTES_OFFSET] > MAX_BYTES) {
- copies[EXTRAFRAME_OFFSET] = seed[FRAME_LENGTH];
- copies[EXTRAFRAME_OFFSET + 1] = seed[FRAME_LENGTH + 1];
- }
-
- SHA1Impl.computeHash(seed);
-
- if (seed[BYTES_OFFSET] > MAX_BYTES) {
-
- System.arraycopy(seed, 0, copies, FRAME_OFFSET, FRAME_LENGTH);
- System.arraycopy(copies, EXTRAFRAME_OFFSET, seed, 0,
- FRAME_LENGTH);
-
- SHA1Impl.computeHash(seed);
- System.arraycopy(copies, FRAME_OFFSET, seed, 0, FRAME_LENGTH);
- }
- counter++;
-
- int j = 0;
- for (i = 0; i < EXTRAFRAME_OFFSET; i++) {
- int k = seed[HASH_OFFSET + i];
- nextBytes[j] = (byte) (k >>> 24); // getting first byte from left
- nextBytes[j + 1] = (byte) (k >>> 16); // getting second byte from left
- nextBytes[j + 2] = (byte) (k >>> 8); // getting third byte from left
- nextBytes[j + 3] = (byte) (k); // getting fourth byte from left
- j += 4;
- }
-
- nextBIndex = 0;
- j = HASHBYTES_TO_USE < (bytes.length - nextByteToReturn) ? HASHBYTES_TO_USE
- : bytes.length - nextByteToReturn;
-
- if (j > 0) {
- System.arraycopy(nextBytes, 0, bytes, nextByteToReturn, j);
- nextByteToReturn += j;
- nextBIndex += j;
- }
-
- if (nextByteToReturn >= bytes.length) {
- break;
- }
- }
- }
-
- private void writeObject(ObjectOutputStream oos) throws IOException {
-
- int[] intData = null;
-
- final int only_hash = EXTRAFRAME_OFFSET;
- final int hashes_and_frame = EXTRAFRAME_OFFSET * 2 + FRAME_LENGTH;
- final int hashes_and_frame_extra = EXTRAFRAME_OFFSET * 2 + FRAME_LENGTH
- * 2;
-
- oos.writeLong(seedLength);
- oos.writeLong(counter);
- oos.writeInt(state);
- oos.writeInt(seed[BYTES_OFFSET]);
-
- int nRemaining = (seed[BYTES_OFFSET] + 3) >> 2; // converting bytes in words
- // result may be 0
- if (state != NEXT_BYTES) {
-
- // either the state is UNDEFINED or previous method was "setSeed(..)"
- // so in "seed[]" to serialize are remaining bytes (seed[0-nRemaining]) and
- // current hash (seed[82-86])
-
- intData = new int[only_hash + nRemaining];
-
- System.arraycopy(seed, 0, intData, 0, nRemaining);
- System.arraycopy(seed, HASH_OFFSET, intData, nRemaining,
- EXTRAFRAME_OFFSET);
-
- } else {
- // previous method was "nextBytes(..)"
- // so, data to serialize are all the above (two first are in "copies" array)
- // and current words in both frame and extra frame (as if)
-
- int offset = 0;
- if (seed[BYTES_OFFSET] < MAX_BYTES) { // no extra frame
-
- intData = new int[hashes_and_frame + nRemaining];
-
- } else { // extra frame is used
-
- intData = new int[hashes_and_frame_extra + nRemaining];
-
- intData[offset] = seed[FRAME_LENGTH];
- intData[offset + 1] = seed[FRAME_LENGTH + 1];
- intData[offset + 2] = seed[FRAME_LENGTH + 14];
- intData[offset + 3] = seed[FRAME_LENGTH + 15];
- offset += 4;
- }
-
- System.arraycopy(seed, 0, intData, offset, FRAME_LENGTH);
- offset += FRAME_LENGTH;
-
- System.arraycopy(copies, FRAME_LENGTH + EXTRAFRAME_OFFSET, intData,
- offset, nRemaining);
- offset += nRemaining;
-
- System.arraycopy(copies, 0, intData, offset, EXTRAFRAME_OFFSET);
- offset += EXTRAFRAME_OFFSET;
-
- System.arraycopy(seed, HASH_OFFSET, intData, offset,
- EXTRAFRAME_OFFSET);
- }
- for (int i = 0; i < intData.length; i++) {
- oos.writeInt(intData[i]);
- }
-
- oos.writeInt(nextBIndex);
- oos.write(nextBytes, nextBIndex, HASHBYTES_TO_USE - nextBIndex);
- }
-
- private void readObject(ObjectInputStream ois) throws IOException,
- ClassNotFoundException {
-
- seed = new int[HASH_OFFSET + EXTRAFRAME_OFFSET];
- copies = new int[2 * FRAME_LENGTH + EXTRAFRAME_OFFSET];
- nextBytes = new byte[DIGEST_LENGTH];
-
- seedLength = ois.readLong();
- counter = ois.readLong();
- state = ois.readInt();
- seed[BYTES_OFFSET] = ois.readInt();
-
- int nRemaining = (seed[BYTES_OFFSET] + 3) >> 2; // converting bytes in words
-
- if (state != NEXT_BYTES) {
-
- for (int i = 0; i < nRemaining; i++) {
- seed[i] = ois.readInt();
- }
- for (int i = 0; i < EXTRAFRAME_OFFSET; i++) {
- seed[HASH_OFFSET + i] = ois.readInt();
- }
- } else {
- if (seed[BYTES_OFFSET] >= MAX_BYTES) {
-
- // reading next bytes in seed extra frame
- seed[FRAME_LENGTH] = ois.readInt();
- seed[FRAME_LENGTH + 1] = ois.readInt();
- seed[FRAME_LENGTH + 14] = ois.readInt();
- seed[FRAME_LENGTH + 15] = ois.readInt();
- }
- // reading next bytes in seed frame
- for (int i = 0; i < FRAME_LENGTH; i++) {
- seed[i] = ois.readInt();
- }
- // reading remaining seed bytes
- for (int i = 0; i < nRemaining; i++) {
- copies[FRAME_LENGTH + EXTRAFRAME_OFFSET + i] = ois.readInt();
- }
- // reading copy of current hash
- for (int i = 0; i < EXTRAFRAME_OFFSET; i++) {
- copies[i] = ois.readInt();
- }
- // reading current hash
- for (int i = 0; i < EXTRAFRAME_OFFSET; i++) {
- seed[HASH_OFFSET + i] = ois.readInt();
- }
- }
-
- nextBIndex = ois.readInt();
- Streams.readFully(ois, nextBytes, nextBIndex, HASHBYTES_TO_USE - nextBIndex);
- }
-
- private static byte[] getRandomBytes(int byteCount) {
- if (byteCount <= 0) {
- throw new IllegalArgumentException("Too few bytes requested: " + byteCount);
- }
-
- BlockGuard.Policy originalPolicy = BlockGuard.getThreadPolicy();
- try {
- BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);
- byte[] result = new byte[byteCount];
- Streams.readFully(devURandom, result, 0, byteCount);
- return result;
- } catch (Exception ex) {
- throw new ProviderException("Couldn't read " + byteCount + " random bytes", ex);
- } finally {
- BlockGuard.setThreadPolicy(originalPolicy);
- }
- }
-}